Index: thunar/thunar-renamer-dialog.c =================================================================== --- thunar/thunar-renamer-dialog.c (revision 28959) +++ thunar/thunar-renamer-dialog.c (working copy) @@ -56,6 +56,7 @@ /* Identifiers for DnD target types */ enum { + TARGET_GTK_TREE_MODEL_ROW, TARGET_TEXT_URI_LIST, }; @@ -113,6 +114,12 @@ gint y, guint time, ThunarRenamerDialog *renamer_dialog); +static gboolean thunar_renamer_dialog_drag_drop (GtkWidget *tree_view, + GdkDragContext *context, + gint x, + gint y, + guint time, + ThunarRenamerDialog *renamer_dialog); static void thunar_renamer_dialog_notify_page (GtkNotebook *notebook, GParamSpec *pspec, ThunarRenamerDialog *renamer_dialog); @@ -182,11 +189,17 @@ /* Target types for dropping to the tree view */ static const GtkTargetEntry drop_targets[] = { - { "text/uri-list", 0, TARGET_TEXT_URI_LIST, }, + { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, TARGET_GTK_TREE_MODEL_ROW }, + { "text/uri-list", 0, TARGET_TEXT_URI_LIST } }; +/* Target types for dragging files in the tree view */ +static const GtkTargetEntry drag_targets[] = { + { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, TARGET_GTK_TREE_MODEL_ROW } +}; + static GObjectClass *thunar_renamer_dialog_parent_class; @@ -409,10 +422,12 @@ gtk_widget_show (swin); /* create the tree view */ - renamer_dialog->tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (renamer_dialog->model)); + renamer_dialog->tree_view = exo_tree_view_new (); + gtk_tree_view_set_model (GTK_TREE_VIEW (renamer_dialog->tree_view), GTK_TREE_MODEL (renamer_dialog->model)); gtk_tree_view_set_search_column (GTK_TREE_VIEW (renamer_dialog->tree_view), THUNAR_RENAMER_MODEL_COLUMN_OLDNAME); gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW (renamer_dialog->tree_view), TRUE); gtk_tree_view_set_enable_search (GTK_TREE_VIEW (renamer_dialog->tree_view), TRUE); + gtk_tree_view_set_rubber_banding (GTK_TREE_VIEW (renamer_dialog->tree_view), TRUE); gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (renamer_dialog->tree_view), TRUE); g_signal_connect (G_OBJECT (renamer_dialog->tree_view), "button-press-event", G_CALLBACK (thunar_renamer_dialog_button_press_event), renamer_dialog); g_signal_connect (G_OBJECT (renamer_dialog->tree_view), "popup-menu", G_CALLBACK (thunar_renamer_dialog_popup_menu), renamer_dialog); @@ -581,12 +596,14 @@ /* close the config handle */ xfce_rc_close (rc); - + /* setup drop support for the tree view */ - gtk_drag_dest_set (renamer_dialog->tree_view, GTK_DEST_DEFAULT_DROP, drop_targets, G_N_ELEMENTS (drop_targets), GDK_ACTION_COPY); + gtk_drag_dest_set (renamer_dialog->tree_view, 0, drop_targets, G_N_ELEMENTS (drop_targets), GDK_ACTION_COPY | GDK_ACTION_MOVE); + gtk_drag_source_set (renamer_dialog->tree_view, GDK_BUTTON1_MASK, drag_targets, G_N_ELEMENTS (drag_targets), GDK_ACTION_MOVE); g_signal_connect (G_OBJECT (renamer_dialog->tree_view), "drag-data-received", G_CALLBACK (thunar_renamer_dialog_drag_data_received), renamer_dialog); g_signal_connect (G_OBJECT (renamer_dialog->tree_view), "drag-leave", G_CALLBACK (thunar_renamer_dialog_drag_leave), renamer_dialog); g_signal_connect (G_OBJECT (renamer_dialog->tree_view), "drag-motion", G_CALLBACK (thunar_renamer_dialog_drag_motion), renamer_dialog); + g_signal_connect (G_OBJECT (renamer_dialog->tree_view), "drag-drop", G_CALLBACK (thunar_renamer_dialog_drag_drop), renamer_dialog); /* setup the tree selection */ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (renamer_dialog->tree_view)); @@ -1311,9 +1328,13 @@ guint time, ThunarRenamerDialog *renamer_dialog) { - ThunarFile *file; - GList *path_list; - GList *lp; + ThunarFile *file; + GList *path_list; + GList *lp; + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeViewDropPosition drop_pos; + gint position = -1; _thunar_return_if_fail (THUNAR_IS_RENAMER_DIALOG (renamer_dialog)); _thunar_return_if_fail (GTK_IS_TREE_VIEW (tree_view)); @@ -1321,6 +1342,25 @@ /* we only accept text/uri-list drops with format 8 and atleast one byte of data */ if (info == TARGET_TEXT_URI_LIST && selection_data->format == 8 && selection_data->length > 0) { + /* determine the renamer model */ + model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view)); + + /* get the drop position, if no path is foud we append the files */ + if (gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (tree_view), x, y, &path, &drop_pos)) + { + /* get the position of the path */ + position = gtk_tree_path_get_indices (path)[0]; + gtk_tree_path_free (path); + + /* advance the offset if we drop after the item */ + if (drop_pos == GTK_TREE_VIEW_DROP_AFTER || drop_pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER) + position++; + + /* append if we've reached the end of the model */ + if (position >= gtk_tree_model_iter_n_children (model, NULL)) + position = -1; + } + /* determine the path list from the selection_data */ path_list = thunar_vfs_path_list_from_string ((const gchar *) selection_data->data, NULL); @@ -1331,9 +1371,13 @@ file = thunar_file_get_for_path (lp->data, NULL); if (G_LIKELY (file != NULL)) { - /* append the file to the model */ - thunar_renamer_model_append (renamer_dialog->model, file); + /* insert the file in the model */ + thunar_renamer_model_insert (renamer_dialog->model, file, position); + /* advance the offset if we do not append, so the drop order is consistent */ + if (position != -1) + position++; + /* release the file */ g_object_unref (G_OBJECT (file)); } @@ -1342,6 +1386,9 @@ thunar_vfs_path_unref (lp->data); } + /* finish the drag */ + gtk_drag_finish (context, (path_list != NULL), FALSE, time); + /* release the list */ g_list_free (path_list); } @@ -1382,16 +1429,30 @@ guint time, ThunarRenamerDialog *renamer_dialog) { - GdkAtom target; + GdkAtom target; + GtkTreeViewDropPosition position; + GtkTreePath *path; + GdkDragAction action; + GtkTreeModel *model; + gint n_rows; _thunar_return_val_if_fail (THUNAR_IS_RENAMER_DIALOG (renamer_dialog), FALSE); _thunar_return_val_if_fail (GTK_IS_TREE_VIEW (tree_view), FALSE); /* determine the drop target */ target = gtk_drag_dest_find_target (tree_view, context, NULL); - if (G_UNLIKELY (target != gdk_atom_intern ("text/uri-list", FALSE))) + if (target == gdk_atom_intern ("text/uri-list", FALSE)) { - /* unhighlight the widget if highlighted */ + /* set the drag action for uris */ + action = GDK_ACTION_COPY; + } + else if (target == gdk_atom_intern ("GTK_TREE_MODEL_ROW", FALSE)) + { + /* set the drag action for tree rows */ + action = GDK_ACTION_MOVE; + } + else + { if (renamer_dialog->drag_highlighted) { /* we use the tree view parent (the scrolled window), @@ -1404,23 +1465,111 @@ /* we cannot handle the drop */ return FALSE; } + + /* compute the drop position */ + if (gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (tree_view), x, y, &path, &position)) + { + /* we can only drop before or after an item */ + if (position == GTK_TREE_VIEW_DROP_BEFORE || position == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE) + position = GTK_TREE_VIEW_DROP_BEFORE; + else + position = GTK_TREE_VIEW_DROP_AFTER; + } else { - /* highlight the widget */ - if (!renamer_dialog->drag_highlighted) + /* append the item if there are rows in the model */ + model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view)); + n_rows = gtk_tree_model_iter_n_children (model, NULL); + + if (G_LIKELY (n_rows > 0)) { - /* we use the tree view parent (the scrolled window), - * as the drag_highlight doesn't work for tree views. - */ - gtk_drag_highlight (tree_view->parent); - renamer_dialog->drag_highlighted = TRUE; + /* get the last tree path in the model */ + path = gtk_tree_path_new_from_indices (n_rows - 1, -1); + position = GTK_TREE_VIEW_DROP_AFTER; } + } - /* we can handle the drop */ - gdk_drag_status (context, GDK_ACTION_COPY, time); - return TRUE; + if (G_LIKELY (path != NULL)) + { + /* highlight the appropriate row */ + gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (tree_view), path, position); + gtk_tree_path_free (path); } + else if (!renamer_dialog->drag_highlighted) + { + /* highlight the parent */ + gtk_drag_highlight (tree_view->parent); + renamer_dialog->drag_highlighted = TRUE; + } + + /* we can handle the drop */ + gdk_drag_status (context, action, time); + return TRUE; } + + + +static gboolean +thunar_renamer_dialog_drag_drop (GtkWidget *tree_view, + GdkDragContext *context, + gint x, + gint y, + guint time, + ThunarRenamerDialog *renamer_dialog) +{ + GdkAtom target; + GtkTreeSelection *selection; + GtkTreePath *dst_path; + GtkTreeModel *model; + GtkTreeViewDropPosition drop_pos; + gint position = -1; + GList *rows; + + _thunar_return_val_if_fail (THUNAR_IS_RENAMER_DIALOG (renamer_dialog), FALSE); + _thunar_return_val_if_fail (GTK_IS_TREE_VIEW (tree_view), FALSE); + + /* determine the drop target */ + target = gtk_drag_dest_find_target (tree_view, context, NULL); + if (G_LIKELY (target == gdk_atom_intern ("text/uri-list", FALSE))) + { + /* request the data, we call gtk_drag_finish() later */ + gtk_drag_get_data (tree_view, context, target, time); + } + else if (target == gdk_atom_intern ("GTK_TREE_MODEL_ROW", FALSE)) + { + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view)); + rows = gtk_tree_selection_get_selected_rows (selection, &model); + if (G_LIKELY (rows != NULL)) + { + /* get the drop position, if no path is found we append the file */ + if (gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (tree_view), x, y, &dst_path, &drop_pos)) + { + /* get the position of the path */ + position = gtk_tree_path_get_indices (dst_path)[0]; + gtk_tree_path_free (dst_path); + + /* advance the offset if we drop after the item */ + if (drop_pos == GTK_TREE_VIEW_DROP_AFTER || drop_pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER) + position++; + } + + /* perform the move */ + thunar_renamer_model_reorder (renamer_dialog->model, rows, position); + g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL); + g_list_free (rows); + } + + /* finish the dnd operation */ + gtk_drag_finish (context, TRUE, FALSE, time); + } + else + { + /* we cannot handle the drop */ + return FALSE; + } + + return TRUE; +} Index: thunar/thunar-renamer-model.c =================================================================== --- thunar/thunar-renamer-model.c (revision 28959) +++ thunar/thunar-renamer-model.c (working copy) @@ -49,8 +49,15 @@ PROP_RENAMER, }; +typedef struct +{ + gint offset; + gint position; + GList *item; +} SortTuple; + typedef struct _ThunarRenamerModelItem ThunarRenamerModelItem; @@ -114,6 +121,9 @@ static void thunar_renamer_model_update_idle_destroy (gpointer user_data); static ThunarRenamerModelItem *thunar_renamer_model_item_new (ThunarFile *file) G_GNUC_MALLOC; static void thunar_renamer_model_item_free (ThunarRenamerModelItem *item); +static gint thunar_renamer_model_cmp_array (gconstpointer pointer_a, + gconstpointer pointer_b, + gpointer user_data); @@ -1014,6 +1024,24 @@ +static gint +thunar_renamer_model_cmp_array (gconstpointer pointer_a, + gconstpointer pointer_b, + gpointer user_data) +{ + const SortTuple *a = pointer_a; + const SortTuple *b = pointer_b; + + if (G_UNLIKELY (a->position == b->position)) + /* return the sort order for the 'moved' items in the list */ + return a->offset - b->offset; + else + /* return the sort order for all other items in the lis */ + return a->position - b->position; +} + + + /** * thunar_renamer_model_new: * @@ -1249,15 +1277,17 @@ /** - * thunar_renamer_model_append: + * thunar_renamer_model_insert: * @renamer_model : a #ThunarRenamerModel. - * @file : the #ThunarFile to add to @renamer_model. + * @file : the #ThunarFile to add to @renamer_model. + * @position : the position in the model. 0 is prepend, -1 is append. * - * Adds the @file to the @renamer_model. + * Inserts the @file to the @renamer_model at @position. **/ void -thunar_renamer_model_append (ThunarRenamerModel *renamer_model, - ThunarFile *file) +thunar_renamer_model_insert (ThunarRenamerModel *renamer_model, + ThunarFile *file, + gint position) { ThunarRenamerModelItem *item; GtkTreePath *path; @@ -1276,10 +1306,10 @@ item = thunar_renamer_model_item_new (file); /* append the item to the model */ - renamer_model->items = g_list_append (renamer_model->items, item); + renamer_model->items = g_list_insert (renamer_model->items, item, position); /* determine the iterator for the new item */ - GTK_TREE_ITER_INIT (iter, renamer_model->stamp, g_list_last (renamer_model->items)); + GTK_TREE_ITER_INIT (iter, renamer_model->stamp, g_list_find (renamer_model->items, item)); /* emit the "row-inserted" signal */ path = gtk_tree_model_get_path (GTK_TREE_MODEL (renamer_model), &iter); @@ -1293,6 +1323,102 @@ /** + * thunar_renamer_model_reorder: + * @renamer_model : a #ThunarRenamerModel. + * @tree_paths : the list of #GtkTreePath that need to be moved to + * @position. + * @position : the new position for the list of paths. + * + * Reorder the treepaths in the model to their new postion in the list + * and sends an update to the treeview to reorder the list. + **/ +void +thunar_renamer_model_reorder (ThunarRenamerModel *renamer_model, + GList *tree_paths, + gint position) +{ + GList *lp, *lprev; + gint n_items; + gint *new_order; + GtkTreePath *path; + gint k, n, m; + SortTuple *sort_array; + + /* leave when there is nothing to sort */ + n_items = g_list_length (renamer_model->items); + if (G_UNLIKELY (n_items <= 1)) + return; + + /* correct the sort position to match the list items */ + if (position == -1 || position > n_items) + position = n_items; + + /* be sure to not overuse the stack */ + if (G_LIKELY (n_items < 500)) + sort_array = g_newa (SortTuple, n_items); + else + sort_array = g_new (SortTuple, n_items); + + /* generate the sort array of tuples */ + for (lp = renamer_model->items, m = 0, n = 0; lp != NULL; lp = lp->next, ++n, ++m) + { + /* leave a hole in the sort position for the drop items */ + if (G_UNLIKELY (m == position)) + m++; + + sort_array[n].offset = n; + sort_array[n].item = lp; + sort_array[n].position = m; + } + + /* set the requested sort position for the dragged paths */ + for (lp = tree_paths; lp != NULL; lp = lp->next) + { + k = CLAMP (gtk_tree_path_get_indices (lp->data)[0], 0, n_items - 1); + sort_array[k].position = position; + } + + /* sort the array using QuickSort */ + g_qsort_with_data (sort_array, n_items, sizeof (SortTuple), thunar_renamer_model_cmp_array, NULL); + + /* update our internals and generate the new order */ + new_order = g_newa (gint, n_items); + for (n = 0, lprev = NULL; n < n_items; ++n) + { + /* set the new order in the sort list */ + new_order[n] = sort_array[n].offset; + + /* reinsert the item in the list */ + lp = sort_array[n].item; + lp->next = NULL; + lp->prev = lprev; + + /* hookup the item in the list */ + if (G_LIKELY (lprev != NULL)) + lprev->next = lp; + else + renamer_model->items = lp; + + /* advance the offset */ + lprev = lp; + } + + /* tell the view about the new item order */ + path = gtk_tree_path_new (); + gtk_tree_model_rows_reordered (GTK_TREE_MODEL (renamer_model), path, NULL, new_order); + gtk_tree_path_free (path); + + /* invalidate all items */ + thunar_renamer_model_invalidate_all (renamer_model); + + /* cleanup if we used the heap */ + if (G_UNLIKELY (n_items >= 500)) + g_free (sort_array); +} + + + +/** * thunar_renamer_model_clear: * @renamer_model : a #ThunarRenamerModel. * Index: thunar/thunar-renamer-model.h =================================================================== --- thunar/thunar-renamer-model.h (revision 28959) +++ thunar/thunar-renamer-model.h (working copy) @@ -71,12 +71,26 @@ void thunar_renamer_model_set_renamer (ThunarRenamerModel *renamer_model, ThunarxRenamer *renamer); -void thunar_renamer_model_append (ThunarRenamerModel *renamer_model, - ThunarFile *file); +void thunar_renamer_model_insert (ThunarRenamerModel *renamer_model, + ThunarFile *file, + gint position); +void thunar_renamer_model_reorder (ThunarRenamerModel *renamer_model, + GList *tree_paths, + gint position); void thunar_renamer_model_clear (ThunarRenamerModel *renamer_model); void thunar_renamer_model_remove (ThunarRenamerModel *renamer_model, GtkTreePath *path); + +/** + * thunar_renamer_model_append: + * @model : a #ThunarRenamerModel. + * @file : a #ThunarFile instance. + * + * Appends the @file to the @renamer_model. + **/ +#define thunar_renamer_model_append(model,file) thunar_renamer_model_insert (model, file, -1) + G_END_DECLS; #endif /* !__THUNAR_RENAMER_MODEL_H__ */