/* GIMP - The GNU Image Manipulation Program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * gimpcontainertreeview.c * Copyright (C) 2003-2010 Michael Natterer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include #include "libgimpwidgets/gimpwidgets.h" #include "widgets-types.h" #include "core/gimpcontainer.h" #include "core/gimpcontext.h" #include "core/gimpmarshal.h" #include "core/gimpviewable.h" #include "gimpcellrendererbutton.h" #include "gimpcellrendererviewable.h" #include "gimpcontainertreestore.h" #include "gimpcontainertreeview.h" #include "gimpcontainertreeview-dnd.h" #include "gimpcontainertreeview.h" #include "gimpcontainertreeview-private.h" #include "gimpcontainerview.h" #include "gimpdnd.h" #include "gimpviewrenderer.h" #include "gimpwidgets-utils.h" enum { EDIT_NAME, LAST_SIGNAL }; static void gimp_container_tree_view_view_iface_init (GimpContainerViewInterface *iface); static void gimp_container_tree_view_constructed (GObject *object); static void gimp_container_tree_view_finalize (GObject *object); static void gimp_container_tree_view_style_set (GtkWidget *widget, GtkStyle *prev_style); static void gimp_container_tree_view_unmap (GtkWidget *widget); static gboolean gimp_container_tree_view_popup_menu (GtkWidget *widget); static void gimp_container_tree_view_set_container (GimpContainerView *view, GimpContainer *container); static void gimp_container_tree_view_set_context (GimpContainerView *view, GimpContext *context); static void gimp_container_tree_view_set_selection_mode(GimpContainerView *view, GtkSelectionMode mode); static gpointer gimp_container_tree_view_insert_item (GimpContainerView *view, GimpViewable *viewable, gpointer parent_insert_data, gint index); static void gimp_container_tree_view_remove_item (GimpContainerView *view, GimpViewable *viewable, gpointer insert_data); static void gimp_container_tree_view_reorder_item (GimpContainerView *view, GimpViewable *viewable, gint new_index, gpointer insert_data); static void gimp_container_tree_view_rename_item (GimpContainerView *view, GimpViewable *viewable, gpointer insert_data); static void gimp_container_tree_view_expand_item (GimpContainerView *view, GimpViewable *viewable, gpointer insert_data); static gboolean gimp_container_tree_view_select_item (GimpContainerView *view, GimpViewable *viewable, gpointer insert_data); static void gimp_container_tree_view_clear_items (GimpContainerView *view); static void gimp_container_tree_view_set_view_size (GimpContainerView *view); static void gimp_container_tree_view_real_edit_name (GimpContainerTreeView *tree_view); static gboolean gimp_container_tree_view_edit_focus_out (GtkWidget *widget, GdkEvent *event, gpointer user_data); static void gimp_container_tree_view_name_started (GtkCellRendererText *cell, GtkCellEditable *editable, const gchar *path_str, GimpContainerTreeView *tree_view); static void gimp_container_tree_view_name_canceled (GtkCellRendererText *cell, GimpContainerTreeView *tree_view); static void gimp_container_tree_view_cursor_changed (GtkTreeView *view, GimpContainerTreeView *tree_view); static void gimp_container_tree_view_selection_changed (GtkTreeSelection *sel, GimpContainerTreeView *tree_view); static gboolean gimp_container_tree_view_button_press (GtkWidget *widget, GdkEventButton *bevent, GimpContainerTreeView *tree_view); static gboolean gimp_container_tree_view_tooltip (GtkWidget *widget, gint x, gint y, gboolean keyboard_tip, GtkTooltip *tooltip, GimpContainerTreeView *tree_view); static GimpViewable *gimp_container_tree_view_drag_viewable (GtkWidget *widget, GimpContext **context, gpointer data); static GdkPixbuf *gimp_container_tree_view_drag_pixbuf (GtkWidget *widget, gpointer data); static gboolean gimp_container_tree_view_get_selected_single (GimpContainerTreeView *tree_view, GtkTreeIter *iter); static gint gimp_container_tree_view_get_selected (GimpContainerView *view, GList **items); static void gimp_container_tree_view_row_expanded (GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, GimpContainerTreeView *view); static void gimp_container_tree_view_expand_rows (GtkTreeModel *model, GtkTreeView *view, GtkTreeIter *parent); static void gimp_container_tree_view_monitor_changed (GimpContainerTreeView *view); static void gimp_container_tree_view_process_updates (GimpContainerTreeView *tree_view); G_DEFINE_TYPE_WITH_CODE (GimpContainerTreeView, gimp_container_tree_view, GIMP_TYPE_CONTAINER_BOX, G_ADD_PRIVATE (GimpContainerTreeView) G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONTAINER_VIEW, gimp_container_tree_view_view_iface_init)) #define parent_class gimp_container_tree_view_parent_class static GimpContainerViewInterface *parent_view_iface = NULL; static guint tree_view_signals[LAST_SIGNAL] = { 0 }; static void gimp_container_tree_view_class_init (GimpContainerTreeViewClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GtkBindingSet *binding_set; object_class->constructed = gimp_container_tree_view_constructed; object_class->finalize = gimp_container_tree_view_finalize; widget_class->style_set = gimp_container_tree_view_style_set; widget_class->unmap = gimp_container_tree_view_unmap; widget_class->popup_menu = gimp_container_tree_view_popup_menu; klass->edit_name = gimp_container_tree_view_real_edit_name; klass->drop_possible = gimp_container_tree_view_real_drop_possible; klass->drop_viewable = gimp_container_tree_view_real_drop_viewable; klass->drop_color = NULL; klass->drop_uri_list = NULL; klass->drop_svg = NULL; klass->drop_component = NULL; klass->drop_pixbuf = NULL; tree_view_signals[EDIT_NAME] = g_signal_new ("edit-name", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GimpContainerTreeViewClass, edit_name), NULL, NULL, gimp_marshal_VOID__VOID, G_TYPE_NONE, 0); binding_set = gtk_binding_set_by_class (klass); gtk_binding_entry_add_signal (binding_set, GDK_KEY_F2, 0, "edit-name", 0); } static void gimp_container_tree_view_view_iface_init (GimpContainerViewInterface *iface) { parent_view_iface = g_type_interface_peek_parent (iface); if (! parent_view_iface) parent_view_iface = g_type_default_interface_peek (GIMP_TYPE_CONTAINER_VIEW); iface->set_container = gimp_container_tree_view_set_container; iface->set_context = gimp_container_tree_view_set_context; iface->set_selection_mode = gimp_container_tree_view_set_selection_mode; iface->insert_item = gimp_container_tree_view_insert_item; iface->remove_item = gimp_container_tree_view_remove_item; iface->reorder_item = gimp_container_tree_view_reorder_item; iface->rename_item = gimp_container_tree_view_rename_item; iface->expand_item = gimp_container_tree_view_expand_item; iface->select_item = gimp_container_tree_view_select_item; iface->clear_items = gimp_container_tree_view_clear_items; iface->set_view_size = gimp_container_tree_view_set_view_size; iface->get_selected = gimp_container_tree_view_get_selected; iface->insert_data_free = (GDestroyNotify) gtk_tree_iter_free; } static void gimp_container_tree_view_init (GimpContainerTreeView *tree_view) { GimpContainerBox *box = GIMP_CONTAINER_BOX (tree_view); tree_view->priv = gimp_container_tree_view_get_instance_private (tree_view); gimp_container_tree_store_columns_init (tree_view->model_columns, &tree_view->n_model_columns); gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (box->scrolled_win), GTK_SHADOW_IN); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (box->scrolled_win), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gimp_widget_track_monitor (GTK_WIDGET (tree_view), G_CALLBACK (gimp_container_tree_view_monitor_changed), NULL); } static void gimp_container_tree_view_constructed (GObject *object) { GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (object); GimpContainerView *view = GIMP_CONTAINER_VIEW (object); GimpContainerBox *box = GIMP_CONTAINER_BOX (object); G_OBJECT_CLASS (parent_class)->constructed (object); tree_view->model = gimp_container_tree_store_new (view, tree_view->n_model_columns, tree_view->model_columns); tree_view->view = g_object_new (GTK_TYPE_TREE_VIEW, "model", tree_view->model, "search-column", GIMP_CONTAINER_TREE_STORE_COLUMN_NAME, "enable-search", FALSE, "headers-visible", FALSE, "has-tooltip", TRUE, "show-expanders", GIMP_CONTAINER_VIEW_GET_INTERFACE (view)->model_is_tree, NULL); gtk_container_add (GTK_CONTAINER (box->scrolled_win), GTK_WIDGET (tree_view->view)); gtk_widget_show (GTK_WIDGET (tree_view->view)); gimp_container_view_set_dnd_widget (view, GTK_WIDGET (tree_view->view)); g_signal_connect (tree_view->view, "cursor-changed", G_CALLBACK (gimp_container_tree_view_cursor_changed), tree_view); tree_view->main_column = gtk_tree_view_column_new (); gtk_tree_view_insert_column (tree_view->view, tree_view->main_column, 0); gtk_tree_view_set_expander_column (tree_view->view, tree_view->main_column); gtk_tree_view_set_enable_tree_lines (tree_view->view, TRUE); tree_view->renderer_cell = gimp_cell_renderer_viewable_new (); gtk_tree_view_column_pack_start (tree_view->main_column, tree_view->renderer_cell, FALSE); gtk_tree_view_column_set_attributes (tree_view->main_column, tree_view->renderer_cell, "renderer", GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, NULL); tree_view->priv->name_cell = gtk_cell_renderer_text_new (); g_object_set (tree_view->priv->name_cell, "xalign", 0.0, NULL); gtk_tree_view_column_pack_end (tree_view->main_column, tree_view->priv->name_cell, FALSE); gtk_tree_view_column_set_attributes (tree_view->main_column, tree_view->priv->name_cell, "text", GIMP_CONTAINER_TREE_STORE_COLUMN_NAME, "attributes", GIMP_CONTAINER_TREE_STORE_COLUMN_NAME_ATTRIBUTES, "sensitive", GIMP_CONTAINER_TREE_STORE_COLUMN_NAME_SENSITIVE, NULL); g_signal_connect (tree_view->priv->name_cell, "editing-started", G_CALLBACK (gimp_container_tree_view_name_started), tree_view); g_signal_connect (tree_view->priv->name_cell, "editing-canceled", G_CALLBACK (gimp_container_tree_view_name_canceled), tree_view); gimp_container_tree_view_add_renderer_cell (tree_view, tree_view->renderer_cell); tree_view->priv->selection = gtk_tree_view_get_selection (tree_view->view); g_signal_connect (tree_view->priv->selection, "changed", G_CALLBACK (gimp_container_tree_view_selection_changed), tree_view); g_signal_connect (tree_view->view, "drag-failed", G_CALLBACK (gimp_container_tree_view_drag_failed), tree_view); g_signal_connect (tree_view->view, "drag-leave", G_CALLBACK (gimp_container_tree_view_drag_leave), tree_view); g_signal_connect (tree_view->view, "drag-motion", G_CALLBACK (gimp_container_tree_view_drag_motion), tree_view); g_signal_connect (tree_view->view, "drag-drop", G_CALLBACK (gimp_container_tree_view_drag_drop), tree_view); g_signal_connect (tree_view->view, "drag-data-received", G_CALLBACK (gimp_container_tree_view_drag_data_received), tree_view); /* connect_after so external code can connect to "query-tooltip" too * and override the default tip */ g_signal_connect_after (tree_view->view, "query-tooltip", G_CALLBACK (gimp_container_tree_view_tooltip), tree_view); } static void gimp_container_tree_view_finalize (GObject *object) { GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (object); g_clear_object (&tree_view->model); if (tree_view->priv->toggle_cells) { g_list_free (tree_view->priv->toggle_cells); tree_view->priv->toggle_cells = NULL; } if (tree_view->priv->renderer_cells) { g_list_free (tree_view->priv->renderer_cells); tree_view->priv->renderer_cells = NULL; } if (tree_view->priv->editable_cells) { g_list_free (tree_view->priv->editable_cells); tree_view->priv->editable_cells = NULL; } G_OBJECT_CLASS (parent_class)->finalize (object); } static gboolean gimp_container_tree_view_style_set_foreach (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) { GimpViewRenderer *renderer; gtk_tree_model_get (model, iter, GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, -1); if (renderer) { gimp_view_renderer_invalidate (renderer); g_object_unref (renderer); } return FALSE; } static void gimp_container_tree_view_style_set (GtkWidget *widget, GtkStyle *prev_style) { GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (widget); GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); if (tree_view->model) gtk_tree_model_foreach (tree_view->model, gimp_container_tree_view_style_set_foreach, NULL); } static void gimp_container_tree_view_unmap (GtkWidget *widget) { GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (widget); if (tree_view->priv->scroll_timeout_id) { g_source_remove (tree_view->priv->scroll_timeout_id); tree_view->priv->scroll_timeout_id = 0; } GTK_WIDGET_CLASS (parent_class)->unmap (widget); } static void gimp_container_tree_view_menu_position (GtkMenu *menu, gint *x, gint *y, gpointer data) { GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (data); GtkWidget *widget = GTK_WIDGET (tree_view->view); GtkAllocation allocation; GtkTreeIter selected_iter; gtk_widget_get_allocation (widget, &allocation); gdk_window_get_origin (gtk_widget_get_window (widget), x, y); if (! gtk_widget_get_has_window (widget)) { *x += allocation.x; *y += allocation.y; } if (gimp_container_tree_view_get_selected_single (tree_view, &selected_iter)) { GtkTreePath *path; GdkRectangle cell_rect; gint center; path = gtk_tree_model_get_path (tree_view->model, &selected_iter); gtk_tree_view_get_cell_area (tree_view->view, path, tree_view->main_column, &cell_rect); gtk_tree_path_free (path); center = cell_rect.y + cell_rect.height / 2; center = CLAMP (center, 0, allocation.height); *x += allocation.width / 2; *y += center; } else { GtkStyle *style = gtk_widget_get_style (widget); *x += style->xthickness; *y += style->ythickness; } gimp_menu_position (menu, x, y); } static gboolean gimp_container_tree_view_popup_menu (GtkWidget *widget) { return gimp_editor_popup_menu (GIMP_EDITOR (widget), gimp_container_tree_view_menu_position, widget); } GtkWidget * gimp_container_tree_view_new (GimpContainer *container, GimpContext *context, gint view_size, gint view_border_width) { GimpContainerTreeView *tree_view; GimpContainerView *view; g_return_val_if_fail (container == NULL || GIMP_IS_CONTAINER (container), NULL); g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL); g_return_val_if_fail (view_size > 0 && view_size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL); g_return_val_if_fail (view_border_width >= 0 && view_border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, NULL); tree_view = g_object_new (GIMP_TYPE_CONTAINER_TREE_VIEW, NULL); view = GIMP_CONTAINER_VIEW (tree_view); gimp_container_view_set_view_size (view, view_size, view_border_width); if (container) gimp_container_view_set_container (view, container); if (context) gimp_container_view_set_context (view, context); return GTK_WIDGET (tree_view); } GtkCellRenderer * gimp_container_tree_view_get_name_cell (GimpContainerTreeView *tree_view) { g_return_val_if_fail (GIMP_IS_CONTAINER_TREE_VIEW (tree_view), NULL); return tree_view->priv->name_cell; } void gimp_container_tree_view_set_main_column_title (GimpContainerTreeView *tree_view, const gchar *title) { g_return_if_fail (GIMP_IS_CONTAINER_TREE_VIEW (tree_view)); gtk_tree_view_column_set_title (tree_view->main_column, title); } void gimp_container_tree_view_add_toggle_cell (GimpContainerTreeView *tree_view, GtkCellRenderer *cell) { g_return_if_fail (GIMP_IS_CONTAINER_TREE_VIEW (tree_view)); g_return_if_fail (GIMP_IS_CELL_RENDERER_TOGGLE (cell) || GIMP_IS_CELL_RENDERER_BUTTON (cell)); tree_view->priv->toggle_cells = g_list_prepend (tree_view->priv->toggle_cells, cell); } void gimp_container_tree_view_add_renderer_cell (GimpContainerTreeView *tree_view, GtkCellRenderer *cell) { g_return_if_fail (GIMP_IS_CONTAINER_TREE_VIEW (tree_view)); g_return_if_fail (GIMP_IS_CELL_RENDERER_VIEWABLE (cell)); tree_view->priv->renderer_cells = g_list_prepend (tree_view->priv->renderer_cells, cell); gimp_container_tree_store_add_renderer_cell (GIMP_CONTAINER_TREE_STORE (tree_view->model), cell); } void gimp_container_tree_view_set_dnd_drop_to_empty (GimpContainerTreeView *tree_view, gboolean dnd_drop_to_empty) { g_return_if_fail (GIMP_IS_CONTAINER_TREE_VIEW (tree_view)); tree_view->priv->dnd_drop_to_empty = dnd_drop_to_empty; } void gimp_container_tree_view_connect_name_edited (GimpContainerTreeView *tree_view, GCallback callback, gpointer data) { g_return_if_fail (GIMP_IS_CONTAINER_TREE_VIEW (tree_view)); g_return_if_fail (callback != NULL); g_object_set (tree_view->priv->name_cell, "mode", GTK_CELL_RENDERER_MODE_EDITABLE, "editable", TRUE, NULL); if (! g_list_find (tree_view->priv->editable_cells, tree_view->priv->name_cell)) tree_view->priv->editable_cells = g_list_prepend (tree_view->priv->editable_cells, tree_view->priv->name_cell); g_signal_connect (tree_view->priv->name_cell, "edited", callback, data); } gboolean gimp_container_tree_view_name_edited (GtkCellRendererText *cell, const gchar *path_str, const gchar *new_name, GimpContainerTreeView *tree_view) { GtkTreePath *path; GtkTreeIter iter; gboolean changed = FALSE; path = gtk_tree_path_new_from_string (path_str); if (gtk_tree_model_get_iter (tree_view->model, &iter, path)) { GimpViewRenderer *renderer; GimpObject *object; const gchar *old_name; gtk_tree_model_get (tree_view->model, &iter, GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, -1); object = GIMP_OBJECT (renderer->viewable); old_name = gimp_object_get_name (object); if (! old_name) old_name = ""; if (! new_name) new_name = ""; if (strcmp (old_name, new_name)) { gimp_object_set_name (object, new_name); changed = TRUE; } else { gchar *name = gimp_viewable_get_description (renderer->viewable, NULL); gtk_tree_store_set (GTK_TREE_STORE (tree_view->model), &iter, GIMP_CONTAINER_TREE_STORE_COLUMN_NAME, name, -1); g_free (name); } g_object_unref (renderer); } gtk_tree_path_free (path); return changed; } /* GimpContainerView methods */ static void gimp_container_tree_view_set_container (GimpContainerView *view, GimpContainer *container) { GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); GimpContainer *old_container; old_container = gimp_container_view_get_container (view); if (old_container) { tree_view->priv->dnd_renderer = NULL; g_signal_handlers_disconnect_by_func (tree_view->view, gimp_container_tree_view_row_expanded, tree_view); if (! container) { if (gimp_dnd_viewable_source_remove (GTK_WIDGET (tree_view->view), gimp_container_get_children_type (old_container))) { if (GIMP_VIEWABLE_CLASS (g_type_class_peek (gimp_container_get_children_type (old_container)))->get_size) gimp_dnd_pixbuf_source_remove (GTK_WIDGET (tree_view->view)); gtk_drag_source_unset (GTK_WIDGET (tree_view->view)); } g_signal_handlers_disconnect_by_func (tree_view->view, gimp_container_tree_view_button_press, tree_view); } } else if (container) { if (gimp_dnd_drag_source_set_by_type (GTK_WIDGET (tree_view->view), GDK_BUTTON1_MASK | GDK_BUTTON2_MASK, gimp_container_get_children_type (container), GDK_ACTION_COPY)) { gimp_dnd_viewable_source_add (GTK_WIDGET (tree_view->view), gimp_container_get_children_type (container), gimp_container_tree_view_drag_viewable, tree_view); if (GIMP_VIEWABLE_CLASS (g_type_class_peek (gimp_container_get_children_type (container)))->get_size) gimp_dnd_pixbuf_source_add (GTK_WIDGET (tree_view->view), gimp_container_tree_view_drag_pixbuf, tree_view); } /* connect button_press_event after DND so we can keep the list from * selecting the item on button2 */ g_signal_connect (tree_view->view, "button-press-event", G_CALLBACK (gimp_container_tree_view_button_press), tree_view); } parent_view_iface->set_container (view, container); if (container) { gimp_container_tree_view_expand_rows (tree_view->model, tree_view->view, NULL); g_signal_connect (tree_view->view, "row-collapsed", G_CALLBACK (gimp_container_tree_view_row_expanded), tree_view); g_signal_connect (tree_view->view, "row-expanded", G_CALLBACK (gimp_container_tree_view_row_expanded), tree_view); } gtk_tree_view_columns_autosize (tree_view->view); } static void gimp_container_tree_view_set_context (GimpContainerView *view, GimpContext *context) { GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); if (tree_view->model) gimp_container_tree_store_set_context (GIMP_CONTAINER_TREE_STORE (tree_view->model), context); parent_view_iface->set_context (view, context); } static void gimp_container_tree_view_set_selection_mode (GimpContainerView *view, GtkSelectionMode mode) { GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); gtk_tree_selection_set_mode (tree_view->priv->selection, mode); parent_view_iface->set_selection_mode (view, mode); } static gpointer gimp_container_tree_view_insert_item (GimpContainerView *view, GimpViewable *viewable, gpointer parent_insert_data, gint index) { GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); GtkTreeIter *parent_iter = parent_insert_data; GtkTreeIter *iter; iter = gimp_container_tree_store_insert_item (GIMP_CONTAINER_TREE_STORE (tree_view->model), viewable, parent_iter, index); if (parent_iter) gimp_container_tree_view_expand_item (view, viewable, parent_iter); return iter; } static void gimp_container_tree_view_remove_item (GimpContainerView *view, GimpViewable *viewable, gpointer insert_data) { GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); GtkTreeIter *iter = (GtkTreeIter *) insert_data; gimp_container_tree_store_remove_item (GIMP_CONTAINER_TREE_STORE (tree_view->model), viewable, iter); if (iter) gtk_tree_view_columns_autosize (tree_view->view); } static void gimp_container_tree_view_reorder_item (GimpContainerView *view, GimpViewable *viewable, gint new_index, gpointer insert_data) { GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); GtkTreeIter *iter = (GtkTreeIter *) insert_data; GtkTreeIter parent_iter; gboolean selected = FALSE; if (iter) { GtkTreeIter selected_iter; selected = gimp_container_tree_view_get_selected_single (tree_view, &selected_iter); if (selected) { GimpViewRenderer *renderer; gtk_tree_model_get (tree_view->model, &selected_iter, GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, -1); if (renderer->viewable != viewable) selected = FALSE; g_object_unref (renderer); } } gimp_container_tree_store_reorder_item (GIMP_CONTAINER_TREE_STORE (tree_view->model), viewable, new_index, iter); if (selected) gimp_container_view_select_item (view, viewable); if (gtk_tree_model_iter_parent (tree_view->model, &parent_iter, iter)) gimp_container_tree_view_expand_item (view, viewable, &parent_iter); } static void gimp_container_tree_view_rename_item (GimpContainerView *view, GimpViewable *viewable, gpointer insert_data) { GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); GtkTreeIter *iter = (GtkTreeIter *) insert_data; if (gimp_container_tree_store_rename_item (GIMP_CONTAINER_TREE_STORE (tree_view->model), viewable, iter)) { gtk_tree_view_columns_autosize (tree_view->view); } } static void gimp_container_tree_view_expand_item (GimpContainerView *view, GimpViewable *viewable, gpointer insert_data) { GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); GtkTreeIter *iter = (GtkTreeIter *) insert_data; GimpViewRenderer *renderer; gtk_tree_model_get (tree_view->model, iter, GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, -1); if (renderer) { GtkTreePath *path = gtk_tree_model_get_path (tree_view->model, iter); g_signal_handlers_block_by_func (tree_view, gimp_container_tree_view_row_expanded, view); if (gimp_viewable_get_expanded (renderer->viewable)) gtk_tree_view_expand_row (tree_view->view, path, FALSE); else gtk_tree_view_collapse_row (tree_view->view, path); g_signal_handlers_unblock_by_func (tree_view, gimp_container_tree_view_row_expanded, view); gtk_tree_path_free (path); g_object_unref (renderer); } } static gboolean gimp_container_tree_view_select_item (GimpContainerView *view, GimpViewable *viewable, gpointer insert_data) { GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); if (viewable && insert_data) { GtkTreePath *path; GtkTreePath *parent_path; GtkTreeIter *iter = (GtkTreeIter *) insert_data; path = gtk_tree_model_get_path (tree_view->model, iter); parent_path = gtk_tree_path_copy (path); if (gtk_tree_path_up (parent_path)) gtk_tree_view_expand_to_path (tree_view->view, parent_path); gtk_tree_path_free (parent_path); g_signal_handlers_block_by_func (tree_view->priv->selection, gimp_container_tree_view_selection_changed, tree_view); gtk_tree_view_set_cursor (tree_view->view, path, NULL, FALSE); g_signal_handlers_unblock_by_func (tree_view->priv->selection, gimp_container_tree_view_selection_changed, tree_view); gtk_tree_view_scroll_to_cell (tree_view->view, path, NULL, FALSE, 0.0, 0.0); gtk_tree_path_free (path); } else if (insert_data == NULL) { /* viewable == NULL && insert_data != NULL means multiple selection. * viewable == NULL && insert_data == NULL means no selection. */ gtk_tree_selection_unselect_all (tree_view->priv->selection); } return TRUE; } static void gimp_container_tree_view_clear_items (GimpContainerView *view) { GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); g_signal_handlers_block_by_func (tree_view->priv->selection, gimp_container_tree_view_selection_changed, tree_view); /* temporarily unset the tree-view's model, so that name editing is stopped * now, before clearing the tree store. otherwise, name editing would stop * when the corresponding item is removed from the store, leading us to * rename the wrong item. see issue #3284. */ gtk_tree_view_set_model (tree_view->view, NULL); gimp_container_tree_store_clear_items (GIMP_CONTAINER_TREE_STORE (tree_view->model)); gtk_tree_view_set_model (tree_view->view, tree_view->model); g_signal_handlers_unblock_by_func (tree_view->priv->selection, gimp_container_tree_view_selection_changed, tree_view); parent_view_iface->clear_items (view); } static void gimp_container_tree_view_set_view_size (GimpContainerView *view) { GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); GtkWidget *tree_widget; GList *list; gint view_size; gint border_width; view_size = gimp_container_view_get_view_size (view, &border_width); if (tree_view->model) gimp_container_tree_store_set_view_size (GIMP_CONTAINER_TREE_STORE (tree_view->model)); tree_widget = GTK_WIDGET (tree_view->view); if (! tree_widget) return; for (list = tree_view->priv->toggle_cells; list; list = g_list_next (list)) { gchar *icon_name; GtkIconSize icon_size; g_object_get (list->data, "icon-name", &icon_name, NULL); if (icon_name) { GtkStyle *style = gtk_widget_get_style (tree_widget); icon_size = gimp_get_icon_size (tree_widget, icon_name, GTK_ICON_SIZE_BUTTON, view_size - 2 * style->xthickness, view_size - 2 * style->ythickness); g_object_set (list->data, "stock-size", icon_size, NULL); g_free (icon_name); } } gtk_tree_view_columns_autosize (tree_view->view); } /* GimpContainerTreeView methods */ static void gimp_container_tree_view_real_edit_name (GimpContainerTreeView *tree_view) { GtkTreeIter selected_iter; gboolean success = FALSE; if (g_list_find (tree_view->priv->editable_cells, tree_view->priv->name_cell) && gimp_container_tree_view_get_selected_single (tree_view, &selected_iter)) { GimpViewRenderer *renderer; gtk_tree_model_get (tree_view->model, &selected_iter, GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, -1); if (gimp_viewable_is_name_editable (renderer->viewable)) { GtkTreePath *path; path = gtk_tree_model_get_path (tree_view->model, &selected_iter); gtk_tree_view_set_cursor_on_cell (tree_view->view, path, tree_view->main_column, tree_view->priv->name_cell, TRUE); gtk_tree_path_free (path); success = TRUE; } g_object_unref (renderer); } if (! success) gtk_widget_error_bell (GTK_WIDGET (tree_view)); } /* callbacks */ static gboolean gimp_container_tree_view_edit_focus_out (GtkWidget *widget, GdkEvent *event, gpointer user_data) { /* When focusing out of a tree view, we want its content to be * updated as though it had been activated. */ g_signal_emit_by_name (widget, "activate", 0); return TRUE; } static void gimp_container_tree_view_name_started (GtkCellRendererText *cell, GtkCellEditable *editable, const gchar *path_str, GimpContainerTreeView *tree_view) { GtkTreePath *path; GtkTreeIter iter; path = gtk_tree_path_new_from_string (path_str); g_signal_connect (GTK_ENTRY (editable), "focus-out-event", G_CALLBACK (gimp_container_tree_view_edit_focus_out), tree_view); if (gtk_tree_model_get_iter (tree_view->model, &iter, path)) { GimpViewRenderer *renderer; const gchar *real_name; gtk_tree_model_get (tree_view->model, &iter, GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, -1); real_name = gimp_object_get_name (renderer->viewable); g_object_unref (renderer); gtk_entry_set_text (GTK_ENTRY (editable), real_name); } gtk_tree_path_free (path); } static void gimp_container_tree_view_name_canceled (GtkCellRendererText *cell, GimpContainerTreeView *tree_view) { GtkTreeIter iter; if (gimp_container_tree_view_get_selected_single (tree_view, &iter)) { GimpViewRenderer *renderer; gchar *name; gtk_tree_model_get (tree_view->model, &iter, GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, -1); name = gimp_viewable_get_description (renderer->viewable, NULL); gtk_tree_store_set (GTK_TREE_STORE (tree_view->model), &iter, GIMP_CONTAINER_TREE_STORE_COLUMN_NAME, name, -1); g_free (name); g_object_unref (renderer); } } static void gimp_container_tree_view_cursor_changed (GtkTreeView *view, GimpContainerTreeView *tree_view) { gimp_container_tree_view_process_updates (tree_view); } static void gimp_container_tree_view_selection_changed (GtkTreeSelection *selection, GimpContainerTreeView *tree_view) { GimpContainerView *view = GIMP_CONTAINER_VIEW (tree_view); GList *items; gimp_container_tree_view_get_selected (view, &items); gimp_container_view_multi_selected (view, items); g_list_free (items); if (items) gimp_container_tree_view_process_updates (tree_view); } static GtkCellRenderer * gimp_container_tree_view_find_click_cell (GtkWidget *widget, GList *cells, GtkTreeViewColumn *column, GdkRectangle *column_area, gint tree_x, gint tree_y) { GList *list; gboolean rtl = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL); for (list = cells; list; list = g_list_next (list)) { GtkCellRenderer *renderer = list->data; gint start; gint width; if (gtk_cell_renderer_get_visible (renderer) && gtk_tree_view_column_cell_get_position (column, renderer, &start, &width)) { gint xpad, ypad; gint x; gtk_cell_renderer_get_padding (renderer, &xpad, &ypad); if (rtl) x = column_area->x + column_area->width - start - width; else x = start + column_area->x; if (tree_x >= x + xpad && tree_x < x + width - xpad) { return renderer; } } } return NULL; } static gboolean gimp_container_tree_view_button_press (GtkWidget *widget, GdkEventButton *bevent, GimpContainerTreeView *tree_view) { GimpContainerView *container_view = GIMP_CONTAINER_VIEW (tree_view); GtkTreeViewColumn *column; GtkTreePath *path; gboolean handled = TRUE; tree_view->priv->dnd_renderer = NULL; if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), bevent->x, bevent->y, &path, &column, NULL, NULL)) { GimpViewRenderer *renderer; GtkCellRenderer *toggled_cell = NULL; GimpCellRendererViewable *clicked_cell = NULL; GtkCellRenderer *edit_cell = NULL; GdkRectangle column_area; GtkTreeIter iter; gboolean multisel_mode; handled = TRUE; multisel_mode = (gtk_tree_selection_get_mode (tree_view->priv->selection) == GTK_SELECTION_MULTIPLE); if (! (bevent->state & (gimp_get_extend_selection_mask () | gimp_get_modify_selection_mask ()))) { /* don't chain up for multi-selection handling if none of * the participating modifiers is pressed, we implement * button_press completely ourselves for a reason and don't * want the default implementation mess up our state */ multisel_mode = FALSE; } /* We need to grab focus after a button click, in order to make keyboard * navigation possible. * For single selection, grab must happen after we changed * the selection (which will happen in this function) otherwise we end up * first scrolling to the current selection. So we have a separate * gtk_widget_grab_focus() at the end of the function. */ if (multisel_mode && bevent->type == GDK_BUTTON_PRESS && ! gtk_widget_has_focus (widget)) gtk_widget_grab_focus (widget); gtk_tree_model_get_iter (tree_view->model, &iter, path); gtk_tree_model_get (tree_view->model, &iter, GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, -1); tree_view->priv->dnd_renderer = renderer; gtk_tree_view_get_cell_area (tree_view->view, path, column, &column_area); gtk_tree_view_column_cell_set_cell_data (column, tree_view->model, &iter, FALSE, FALSE); if (bevent->button == 1 && gtk_tree_model_iter_has_child (tree_view->model, &iter) && column == gtk_tree_view_get_expander_column (tree_view->view)) { GList *cells; cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column)); if (! gimp_container_tree_view_find_click_cell (widget, cells, column, &column_area, bevent->x, bevent->y)) { /* we didn't click on any cell, but we clicked on empty * space in the expander column of a row that has * children; let GtkTreeView process the button press * to maybe handle a click on an expander. */ g_list_free (cells); gtk_tree_path_free (path); g_object_unref (renderer); return FALSE; } g_list_free (cells); } toggled_cell = gimp_container_tree_view_find_click_cell (widget, tree_view->priv->toggle_cells, column, &column_area, bevent->x, bevent->y); if (! toggled_cell) clicked_cell = (GimpCellRendererViewable *) gimp_container_tree_view_find_click_cell (widget, tree_view->priv->renderer_cells, column, &column_area, bevent->x, bevent->y); if (! toggled_cell && ! clicked_cell) edit_cell = gimp_container_tree_view_find_click_cell (widget, tree_view->priv->editable_cells, column, &column_area, bevent->x, bevent->y); g_object_ref (tree_view); if (gdk_event_triggers_context_menu ((GdkEvent *) bevent)) { if (gimp_container_view_item_selected (container_view, renderer->viewable)) { if (gimp_container_view_get_container (container_view)) gimp_container_view_item_context (container_view, renderer->viewable); } } else if (bevent->button == 1) { if (bevent->type == GDK_BUTTON_PRESS) { /* don't select item if a toggle was clicked */ if (! toggled_cell) { gchar *path_str = gtk_tree_path_to_string (path); handled = FALSE; if (clicked_cell) handled = gimp_cell_renderer_viewable_pre_clicked (clicked_cell, path_str, bevent->state); if (! handled) { if (multisel_mode) { /* let parent do the work */ } else { handled = gimp_container_view_item_selected (container_view, renderer->viewable); } } g_free (path_str); } /* a callback invoked by selecting the item may have * destroyed us, so check if the container is still there */ if (gimp_container_view_get_container (container_view)) { /* another row may have been set by selecting */ gtk_tree_view_column_cell_set_cell_data (column, tree_view->model, &iter, FALSE, FALSE); if (toggled_cell || clicked_cell) { gchar *path_str = gtk_tree_path_to_string (path); if (toggled_cell) { if (GIMP_IS_CELL_RENDERER_TOGGLE (toggled_cell)) { gimp_cell_renderer_toggle_clicked (GIMP_CELL_RENDERER_TOGGLE (toggled_cell), path_str, bevent->state); } else if (GIMP_IS_CELL_RENDERER_BUTTON (toggled_cell)) { gimp_cell_renderer_button_clicked (GIMP_CELL_RENDERER_BUTTON (toggled_cell), path_str, bevent->state); } } else if (clicked_cell) { gimp_cell_renderer_viewable_clicked (clicked_cell, path_str, bevent->state); } g_free (path_str); } } } else if (bevent->type == GDK_2BUTTON_PRESS) { gboolean success = TRUE; /* don't select item if a toggle was clicked */ if (! toggled_cell) success = gimp_container_view_item_selected (container_view, renderer->viewable); if (success) { if (edit_cell) { if (gimp_viewable_is_name_editable (renderer->viewable)) { gtk_tree_view_set_cursor_on_cell (tree_view->view, path, column, edit_cell, TRUE); } else { gtk_widget_error_bell (widget); } } else if (! toggled_cell && ! (bevent->state & gimp_get_all_modifiers_mask ())) { /* Only activate if we're not in a toggled cell * and no modifier keys are pressed */ gimp_container_view_item_activated (container_view, renderer->viewable); } } } } else if (bevent->button == 2) { if (bevent->type == GDK_BUTTON_PRESS) { if (clicked_cell) { gchar *path_str = gtk_tree_path_to_string (path); gimp_cell_renderer_viewable_clicked (clicked_cell, path_str, bevent->state); g_free (path_str); } } } g_object_unref (tree_view); gtk_tree_path_free (path); g_object_unref (renderer); handled = (multisel_mode ? handled : (bevent->type == GDK_BUTTON_RELEASE ? FALSE : TRUE)); } else { if (gdk_event_triggers_context_menu ((GdkEvent *) bevent)) { gimp_editor_popup_menu (GIMP_EDITOR (tree_view), NULL, NULL); } handled = TRUE; } if (handled && bevent->type == GDK_BUTTON_PRESS && ! gtk_widget_has_focus (widget)) gtk_widget_grab_focus (widget); return handled; } static gboolean gimp_container_tree_view_tooltip (GtkWidget *widget, gint x, gint y, gboolean keyboard_tip, GtkTooltip *tooltip, GimpContainerTreeView *tree_view) { GimpViewRenderer *renderer; GtkTreeIter iter; GtkTreePath *path; gboolean show_tip = FALSE; if (! gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (widget), &x, &y, keyboard_tip, NULL, &path, &iter)) return FALSE; gtk_tree_model_get (tree_view->model, &iter, GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, -1); if (renderer) { gchar *desc; gchar *tip; desc = gimp_viewable_get_description (renderer->viewable, &tip); if (tip) { gtk_tooltip_set_text (tooltip, tip); gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (widget), tooltip, path); show_tip = TRUE; g_free (tip); } g_free (desc); g_object_unref (renderer); } gtk_tree_path_free (path); return show_tip; } static GimpViewable * gimp_container_tree_view_drag_viewable (GtkWidget *widget, GimpContext **context, gpointer data) { GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (data); if (context) *context = gimp_container_view_get_context (GIMP_CONTAINER_VIEW (data)); if (tree_view->priv->dnd_renderer) return tree_view->priv->dnd_renderer->viewable; return NULL; } static GdkPixbuf * gimp_container_tree_view_drag_pixbuf (GtkWidget *widget, gpointer data) { GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (data); GimpViewRenderer *renderer = tree_view->priv->dnd_renderer; gint width; gint height; if (renderer && gimp_viewable_get_size (renderer->viewable, &width, &height)) return gimp_viewable_get_new_pixbuf (renderer->viewable, renderer->context, width, height); return NULL; } static gboolean gimp_container_tree_view_get_selected_single (GimpContainerTreeView *tree_view, GtkTreeIter *iter) { GtkTreeSelection *selection; selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view->view)); if (gtk_tree_selection_count_selected_rows (selection) == 1) { GList *selected_rows; selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL); gtk_tree_model_get_iter (GTK_TREE_MODEL (tree_view->model), iter, (GtkTreePath *) selected_rows->data); g_list_free_full (selected_rows, (GDestroyNotify) gtk_tree_path_free); return TRUE; } else { return FALSE; } } static gint gimp_container_tree_view_get_selected (GimpContainerView *view, GList **items) { GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view); GtkTreeSelection *selection; gint selected_count; GList *selected_rows; GList *current_row; GtkTreeIter iter; GimpViewRenderer *renderer; selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view->view)); selected_count = gtk_tree_selection_count_selected_rows (selection); if (items == NULL) { /* just provide selected count */ return selected_count; } selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL); *items = NULL; for (current_row = selected_rows; current_row; current_row = g_list_next (current_row)) { gtk_tree_model_get_iter (GTK_TREE_MODEL (tree_view->model), &iter, (GtkTreePath *) current_row->data); gtk_tree_model_get (tree_view->model, &iter, GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, -1); if (renderer->viewable) *items = g_list_prepend (*items, renderer->viewable); g_object_unref (renderer); } g_list_free_full (selected_rows, (GDestroyNotify) gtk_tree_path_free); *items = g_list_reverse (*items); return selected_count; } static void gimp_container_tree_view_row_expanded (GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, GimpContainerTreeView *view) { GimpViewRenderer *renderer; gtk_tree_model_get (view->model, iter, GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, -1); if (renderer) { gboolean expanded = gtk_tree_view_row_expanded (tree_view, path); gimp_viewable_set_expanded (renderer->viewable, expanded); if (expanded) { g_signal_handlers_block_by_func (tree_view, gimp_container_tree_view_row_expanded, view); gimp_container_tree_view_expand_rows (view->model, tree_view, iter); g_signal_handlers_unblock_by_func (tree_view, gimp_container_tree_view_row_expanded, view); } g_object_unref (renderer); } } static void gimp_container_tree_view_expand_rows (GtkTreeModel *model, GtkTreeView *view, GtkTreeIter *parent) { GtkTreeIter iter; if (gtk_tree_model_iter_children (model, &iter, parent)) do if (gtk_tree_model_iter_has_child (model, &iter)) { GimpViewRenderer *renderer; gtk_tree_model_get (model, &iter, GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, -1); if (renderer) { GtkTreePath *path = gtk_tree_model_get_path (model, &iter); if (gimp_viewable_get_expanded (renderer->viewable)) gtk_tree_view_expand_row (view, path, FALSE); else gtk_tree_view_collapse_row (view, path); gtk_tree_path_free (path); g_object_unref (renderer); } gimp_container_tree_view_expand_rows (model, view, &iter); } while (gtk_tree_model_iter_next (model, &iter)); } static gboolean gimp_container_tree_view_monitor_changed_foreach (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) { GimpViewRenderer *renderer; gtk_tree_model_get (model, iter, GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, -1); if (renderer) { gimp_view_renderer_free_color_transform (renderer); g_object_unref (renderer); } return FALSE; } static void gimp_container_tree_view_monitor_changed (GimpContainerTreeView *view) { gtk_tree_model_foreach (view->model, gimp_container_tree_view_monitor_changed_foreach, NULL); } static void gimp_container_tree_view_process_updates (GimpContainerTreeView *tree_view) { GdkWindow *window = gtk_tree_view_get_bin_window (tree_view->view); /* this is a hack, necessary to work around a gtk bug which can cause the * window containing the tree view to stop processing updates until * explicitly requested. */ if (window) gdk_window_process_updates (window, TRUE); }