Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Gimp/app/widgets/   (Gimp Bildeditor Version 3.2.0©)  Datei vom 14.2.2026 mit Größe 73 kB image not shown  

Quelle  gimpcontainertreeview.c   Sprache: C

 

/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * gimpcontainertreeview.c
 * Copyright (C) 2003-2010 Michael Natterer <mitch@gimp.org>
 *
 * 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 <https://www.gnu.org/licenses/>.
 */


#include "config.h"

#include <string.h>

#include <gegl.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>

#include "libgimpwidgets/gimpwidgets.h"

#include "widgets-types.h"

#include "core/gimpcontainer.h"
#include "core/gimpcontext.h"
#include "core/gimpviewable.h"

#include "gimpaction.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 "gimpcontainerview-cruft.h"
#include "gimpdnd.h"
#include "gimpviewrenderer.h"
#include "gimpwidgets-utils.h"

#include "gimp-intl.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_updated     (GtkWidget                   *widget);
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 void          gimp_container_tree_view_set_view_size     (GimpContainerView           *view);
static gboolean      gimp_container_tree_view_set_selected      (GimpContainerView           *view,
                                                                 GList                       *items);
static gint          gimp_container_tree_view_get_selected      (GimpContainerView    *view,
                                                                 GList               **items);

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 void          gimp_container_tree_view_clear_items       (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_selection_changed (GtkTreeSelection            *sel,
                                                                 GimpContainerTreeView       *tree_view);
static gboolean      gimp_container_tree_view_button            (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 GList       * gimp_container_tree_view_drag_viewable_list (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_1_selected      (GimpContainerTreeView  *tree_view,
                                                                   GtkTreeIter            *iter);
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);


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_updated = gimp_container_tree_view_style_updated;
  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_viewables       = gimp_container_tree_view_real_drop_viewables;
  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, NULL,
                  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->set_view_size      = gimp_container_tree_view_set_view_size;
  iface->set_selected       = gimp_container_tree_view_set_selected;
  iface->get_selected       = gimp_container_tree_view_get_selected;

  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->clear_items        = gimp_container_tree_view_clear_items;

  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, 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_IFACE (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));

  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_column_set_clickable (tree_view->main_column, FALSE);
  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,
                                              GIMP_CONTAINER_TREE_STORE_COLUMN_RENDERER);

  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;
    }

  g_list_free (tree_view->priv->renderer_cells);

  if (tree_view->priv->editable_cells)
    {
      g_list_free (tree_view->priv->editable_cells);
      tree_view->priv->editable_cells = NULL;
    }

  g_clear_pointer (&tree_view->priv->editing_path, g_free);

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static gboolean
gimp_container_tree_view_style_updated_foreach (GtkTreeModel *model,
                                                GtkTreePath  *path,
                                                GtkTreeIter  *iter,
                                                gpointer      data)
{
  GimpViewRenderer *renderer;

  renderer = gimp_container_tree_store_get_renderer (GIMP_CONTAINER_TREE_STORE (model), iter);

  if (renderer)
    {
      gimp_view_renderer_invalidate (renderer);
      g_object_unref (renderer);
    }

  return FALSE;
}

static void
gimp_container_tree_view_style_updated (GtkWidget *widget)
{
  GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (widget);

  GTK_WIDGET_CLASS (parent_class)->style_updated (widget);

  if (tree_view->model)
    gtk_tree_model_foreach (tree_view->model,
                            gimp_container_tree_view_style_updated_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 gboolean
gimp_container_tree_view_popup_menu (GtkWidget *widget)
{
  GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (widget);
  GtkTreeIter            iter;
  GtkTreePath           *path;
  GdkRectangle           rect;

  if (! gimp_container_tree_view_get_1_selected (tree_view, &iter))
    return FALSE;

  path = gtk_tree_model_get_path (tree_view->model, &iter);
  gtk_tree_view_get_cell_area (tree_view->view, path,
                               tree_view->main_column, &rect);
  gtk_tree_view_convert_bin_window_to_widget_coords (tree_view->view,
                                                     rect.x, rect.y,
                                                     &rect.x, &rect.y);
  gtk_tree_path_free (path);

  return gimp_editor_popup_menu_at_rect (GIMP_EDITOR (widget),
                                         gtk_tree_view_get_bin_window (tree_view->view),
                                         &rect, GDK_GRAVITY_CENTER, GDK_GRAVITY_NORTH_WEST,
                                         NULL);
}

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 (context)
    gimp_container_view_set_context (view, context);

  if (container)
    gimp_container_view_set_container (view, container);

  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);
}

gint
gimp_container_tree_view_get_main_column_position (GimpContainerTreeView *tree_view)
{
  g_return_val_if_fail (GIMP_IS_CONTAINER_TREE_VIEW (tree_view), -1);

  for (gint i = 0; i < tree_view->n_model_columns; i++)
    {
      GtkTreeViewColumn *column;

      column = gtk_tree_view_get_column (tree_view->view, i);
      if (tree_view->main_column == column)
        return i;
    }

  return -1;
}

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,
                                            gint                   column_number)
{
  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, column_number);
}

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;

      renderer = gimp_container_tree_store_get_renderer (GIMP_CONTAINER_TREE_STORE (tree_view->model), &iter);

      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_list_source_remove (GTK_WIDGET (tree_view->view),
                                                    gimp_container_get_child_type (old_container)))
            {
              if (GIMP_VIEWABLE_CLASS (g_type_class_peek (gimp_container_get_child_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,
                                                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_child_type (container),
                                            GDK_ACTION_COPY))
        {
          gimp_dnd_viewable_list_source_add (GTK_WIDGET (tree_view->view),
                                             gimp_container_get_child_type (container),
                                             gimp_container_tree_view_drag_viewable_list,
                                             tree_view);
          gimp_dnd_viewable_source_add (GTK_WIDGET (tree_view->view),
                                        gimp_container_get_child_type (container),
                                        gimp_container_tree_view_drag_viewable,
                                        tree_view);

          if (GIMP_VIEWABLE_CLASS (g_type_class_peek (gimp_container_get_child_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),
                        tree_view);
      g_signal_connect (tree_view->view, "button-release-event",
                        G_CALLBACK (gimp_container_tree_view_button),
                        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 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;
      gint   icon_size;

      g_object_get (list->data, "icon-name", &icon_name, NULL);

      if (icon_name)
        {
          GtkStyleContext *style = gtk_widget_get_style_context (tree_widget);
          GtkBorder        border;

          gtk_style_context_save (style);
          gtk_style_context_add_class (style, GTK_STYLE_CLASS_BUTTON);
          gtk_style_context_get_border (style, 0, &border);
          gtk_style_context_restore (style);

          g_object_get (list->data, "icon-size", &icon_size, NULL);
          icon_size = MIN (icon_size, MAX (view_size - (border.left + border.right),
                                           view_size - (border.top + border.bottom)));
          g_object_set (list->data, "icon-size", icon_size, NULL);

          g_free (icon_name);
        }
    }

  gtk_tree_view_columns_autosize (tree_view->view);
}

static gboolean
gimp_container_tree_view_set_selected (GimpContainerView *view,
                                       GList             *items)
{
  GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view);
  GList                 *item;
  GList                 *paths = NULL;
  GList                 *path;
  gboolean               scroll_to_first = TRUE;
  GtkTreePath           *focused_path    = NULL;

  for (item = items; item; item = g_list_next (item))
    {
      GtkTreeIter *iter = _gimp_container_view_lookup (view, item->data);
      GtkTreePath *p;

      if (! iter)
        /* This may happen when the view only shows a subpart of the
         * whole items. We don't select what is not shown.
         */

        continue;

      p = gtk_tree_model_get_path (tree_view->model, iter);

      paths = g_list_prepend (paths, p);
    }

  paths = g_list_reverse (paths);

  g_signal_handlers_block_by_func (tree_view->priv->selection,
                                   gimp_container_tree_view_selection_changed,
                                   tree_view);

  gtk_tree_selection_unselect_all (tree_view->priv->selection);
  gtk_tree_view_get_cursor (tree_view->view, &focused_path, NULL);

  if (focused_path != NULL)
    {
      GtkTreePath *closer_up   = NULL;
      GtkTreePath *closer_down = NULL;

      for (path = paths; path; path = g_list_next (path))
        {
          if (gtk_tree_path_compare (path->data, focused_path) == 0)
            {
              break;
            }
          else if (gtk_tree_path_compare (path->data, focused_path) == -1)
            {
              if (closer_up == NULL ||
                  gtk_tree_path_compare (path->data, closer_up) == 1)
                {
                  closer_up = path->data;
                }
            }
          else
            {
              if (closer_down == NULL ||
                  gtk_tree_path_compare (path->data, closer_down) == -1)
                {
                  closer_down = path->data;
                }
            }
        }

      if (path == NULL)
        {
          /* The current cursor is not part of the selection. This may
           * happen in particular with a ctrl-click interaction which
           * would deselect the item the cursor is now on.
           */

          g_clear_pointer (&focused_path, gtk_tree_path_free);

          if (closer_up != NULL || closer_down != NULL)
            {
              GtkTreePath *first = NULL;
              GtkTreePath *last  = NULL;

              if (gtk_tree_view_get_visible_range (tree_view->view,
                                                   &first, &last))
                {
                  if (closer_up != NULL                             &&
                      gtk_tree_path_compare (closer_up, first) >= 0 &&
                      gtk_tree_path_compare (closer_up, last) <= 0)
                    {
                      focused_path = gtk_tree_path_copy (closer_up);
                    }
                  else if (closer_down != NULL                             &&
                           gtk_tree_path_compare (closer_down, first) >= 0 &&
                           gtk_tree_path_compare (closer_down, last) <= 0)
                    {
                      focused_path = gtk_tree_path_copy (closer_down);
                    }
                }

              if (focused_path == NULL)
                {
                  if (closer_up != NULL)
                    focused_path = gtk_tree_path_copy (closer_up);
                  else
                    focused_path = gtk_tree_path_copy (closer_down);
                }

              gtk_tree_path_free (first);
              gtk_tree_path_free (last);
            }
          else if (paths != NULL)
            {
              focused_path = gtk_tree_path_copy (paths->data);
            }
        }
    }
  else if (paths != NULL)
    {
      focused_path = gtk_tree_path_copy (paths->data);
    }

  /* Setting a cursor will reset the selection, so we must do it first. We don't
   * want to change the cursor (which is likely the last clicked item), yet we
   * also want to make sure that the cursor cannot end up out of the selected
   * items, leading to discrepancy between pointer and keyboard navigation. This
   * is why we set the cursor with this priority:
   * 1. unchanged if it stays within selection;
   * 2. closer item above the current cursor, if visible;
   * 3. closer item below the current cursor, if visible;
   * 4. closer item above the current cursor (even though invisible, which will
   *    make the view scroll up);
   * 5. closer item below the current cursor if there are no items above (view
   *    will scroll down);
   * 6. top selected item if there was no current cursor;
   * 7. nothing if no selected items.
   */

  if (focused_path != NULL)
    {
      gtk_tree_view_set_cursor (tree_view->view, focused_path, NULL, FALSE);
      gtk_tree_path_free (focused_path);
    }

  for (path = paths; path; path = g_list_next (path))
    {
      GtkTreePath *parent_path = gtk_tree_path_copy (path->data);

      /* Expand the parent. */
      if (gtk_tree_path_up (parent_path))
        gtk_tree_view_expand_to_path (tree_view->view, parent_path);

      gtk_tree_path_free (parent_path);

      /* Add to the selection. */
      gtk_tree_selection_select_path (tree_view->priv->selection, path->data);
    }

  g_signal_handlers_unblock_by_func (tree_view->priv->selection,
                                     gimp_container_tree_view_selection_changed,
                                     tree_view);

  if (paths)
    {
      GtkTreePath *first;
      GtkTreePath *last;

      /* Scroll to the top item if and only if none of the selected
       * items are already visible. Do nothing otherwise.
       */

      if (gtk_tree_view_get_visible_range (tree_view->view, &first, &last))
        {
          for (path = paths; path; path = g_list_next (path))
            {
              if (gtk_tree_path_compare (first, path->data) <= 0 &&
                  gtk_tree_path_compare (path->data, last) <= 0)
                {
                  scroll_to_first = FALSE;
                  break;
                }
            }

          gtk_tree_path_free (first);
          gtk_tree_path_free (last);
        }

      if (scroll_to_first)
        gtk_tree_view_scroll_to_cell (tree_view->view, paths->data,
                                      NULL, FALSE, 0.0, 0.0);
    }

  g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);

  _gimp_container_view_selection_changed (view);

  return TRUE;
}

static gint
gimp_container_tree_view_get_selected (GimpContainerView  *view,
                                       GList             **items)
{
  GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view);
  GtkTreeSelection      *selection;
  gint                   n_selected;

  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view->view));
  n_selected = gtk_tree_selection_count_selected_rows (selection);

  if (items)
    {
      GList *paths;
      GList  *list;

      paths = gtk_tree_selection_get_selected_rows (selection, NULL);

      for (list = paths; list; list = g_list_next (list))
        {
          GimpViewRenderer *renderer;
          GtkTreeIter       iter;

          gtk_tree_model_get_iter (GTK_TREE_MODEL (tree_view->model), &iter,
                                   list->data);

          renderer = gimp_container_tree_store_get_renderer
            (GIMP_CONTAINER_TREE_STORE (tree_view->model), &iter);

          if (renderer->viewable)
            *items = g_list_prepend (*items, renderer->viewable);
          else
            n_selected--;

          g_object_unref (renderer);
        }

      *items = g_list_reverse (*items);
    }

  return n_selected;
}

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;

  gimp_container_tree_store_reorder_item (GIMP_CONTAINER_TREE_STORE (tree_view->model),
                                          viewable,
                                          new_index,
                                          iter);

  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;

  renderer = gimp_container_tree_store_get_renderer (GIMP_CONTAINER_TREE_STORE (tree_view->model), iter);

  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 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);
}


/*  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_1_selected (tree_view,
                                               &selected_iter))
    {
      GimpViewRenderer *renderer;

      renderer = gimp_container_tree_store_get_renderer (GIMP_CONTAINER_TREE_STORE (tree_view->model),
                                                         &selected_iter);

      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)
{
  GimpContainerTreeView *tree_view = user_data;

  /*  When focusing out of a tree view, we want its content to be
   *  updated as though it had been activated.
   */

  g_clear_pointer (&tree_view->priv->editing_path, g_free);
  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);

  tree_view->priv->editing_path = g_strdup (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;

      renderer = gimp_container_tree_store_get_renderer (GIMP_CONTAINER_TREE_STORE (tree_view->model), &iter);

      real_name = gimp_object_get_name (renderer->viewable);
      gimp_view_renderer_remove_idle (renderer);

      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_1_selected (tree_view, &iter))
    {
      GimpViewRenderer *renderer;
      gchar            *name;

      renderer = gimp_container_tree_store_get_renderer (GIMP_CONTAINER_TREE_STORE (tree_view->model), &iter);

      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_selection_changed (GtkTreeSelection      *selection,
                                            GimpContainerTreeView *tree_view)
{
  _gimp_container_view_selection_changed (GIMP_CONTAINER_VIEW (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 (GtkWidget             *widget,
                                 GdkEventButton        *bevent,
                                 GimpContainerTreeView *tree_view)
{
  GimpContainerView        *container_view = GIMP_CONTAINER_VIEW (tree_view);
  GtkTreeViewColumn        *column;
  GtkTreePath              *path;
  gboolean                  handled        = TRUE;
  GtkCellRenderer          *toggled_cell   = NULL;
  GimpCellRendererViewable *clicked_cell   = NULL;

  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  *edit_cell    = NULL;
      GdkRectangle      column_area;
      GtkTreeIter       iter;
      gboolean          multisel_mode;
      GdkModifierType   modifiers = (bevent->state & gimp_get_all_modifiers_mask ());

      handled       = TRUE;
      multisel_mode = (gtk_tree_selection_get_mode (tree_view->priv->selection)
                       == GTK_SELECTION_MULTIPLE);

      if (! modifiers ||
          (modifiers & ~(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 at button click, in order to make keyboard
       * navigation possible; yet the timing matters:
       * 1. For multi-selection, actual selection will happen in
       * gimp_container_tree_view_selection_changed() but the widget
       * must already be focused or the handler won't run. So we grab first.
       * 2. For toggled and clicked cells, we must also grab first (see code
       * below), and absolutely not in the end, because some toggle cells may
       * trigger a popup (and the grab on the source widget would close the
       * popup).
       * 3. Finally 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.
       * This is why we have a few separate calls to gtk_widget_grab_focus()
       * in this function.
       * See also commit 3e101922, MR !1128 and #10281.
       */

      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);

      renderer = gimp_container_tree_store_get_renderer
        (GIMP_CONTAINER_TREE_STORE (tree_view->model), &iter);

      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))
            {
              if (bevent->state & gimp_get_extend_selection_mask ()                       &&
                  bevent->type == GDK_BUTTON_PRESS                                        &&
                  bevent->window == gtk_tree_view_get_bin_window (GTK_TREE_VIEW (widget)) &&
                  ! gtk_tree_view_is_blank_at_pos (GTK_TREE_VIEW (widget),
                                                   bevent->x, bevent->y, NULL, NULL,
                                                   NULL, NULL))
                {
                  /* When shift-clicking an expander, let's have a
                   * behavior similar to shift-clicking the visibility
                   * or link toggles, i.e. expanding the group alone or
                   * together will all groups of same depth.
                   */

                  GtkTreePath *first_path;
                  GtkTreeIter  parent;
                  gboolean     expand_all = TRUE;

                  /* Get the first item at same depth. */
                  if (gtk_tree_model_iter_parent (tree_view->model, &parent, &iter))
                    gtk_tree_model_iter_nth_child (tree_view->model, &iter, &parent, 0);
                  else
                    gtk_tree_model_get_iter_first (tree_view->model, &iter);
                  first_path = gtk_tree_model_get_path (tree_view->model, &iter);

                  /* Check expansion state of other items at same depth. */
                  do
                    {
                      GtkTreePath *path2 = gtk_tree_model_get_path (tree_view->model, &iter);

                      if (gtk_tree_path_compare (path, path2) != 0 &&
                          gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path2))
                        expand_all = FALSE;

                      gtk_tree_path_free (path2);

                      if (! expand_all)
                        break;
                    }
                  while (gtk_tree_model_iter_next (tree_view->model, &iter));

                  /* Revert back to first item at same depth. */
                  gtk_tree_model_get_iter (tree_view->model, &iter, first_path);
                  gtk_tree_path_free (first_path);

                  /* Expand or collapse rows at this depth. */
                  do
                    {
                      GtkTreePath *path2 = gtk_tree_model_get_path (tree_view->model, &iter);

                      if (gtk_tree_path_compare (path, path2) == 0 || expand_all)
                        gtk_tree_view_expand_to_path (GTK_TREE_VIEW (widget), path2);
                      else
                        gtk_tree_view_collapse_row (GTK_TREE_VIEW (widget), path2);

                      gtk_tree_path_free (path2);
                    }
                  while (gtk_tree_model_iter_next (tree_view->model, &iter));

                  gtk_tree_view_scroll_to_cell (tree_view->view, path,
                                                NULL, FALSE, 0.0, 0.0);

                  handled = TRUE;
                }
              else
                {
                  /*  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.
                   */

                  handled = FALSE;
                }

              g_list_free (cells);
              gtk_tree_path_free (path);
              g_object_unref (renderer);

              return handled;
            }

          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) &&
          bevent->type == GDK_BUTTON_PRESS && ! gtk_widget_has_focus (widget))
        gtk_widget_grab_focus (widget);

      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);

          if (edit_cell && bevent->type == GDK_BUTTON_RELEASE)
            {
              gchar *editing_path;

              editing_path = gtk_tree_path_to_string (path);
              if (tree_view->priv->editing_path &&
                  g_strcmp0 (tree_view->priv->editing_path, editing_path) == 0)
                {
                  /* Bail out when releasing over an edit cell we are
                   * already editing.
                   * The reason is that GDK_2BUTTON_PRESS happens before
                   * the last GDK_BUTTON_RELEASE in a double click. So
                   * if we were to proceed, the edit-in-progress would
                   * end up being canceled (by updating the selection)
                   * nearly immediately.
                   */

                  g_free (editing_path);
                  gtk_tree_path_free (path);
                  g_object_unref (renderer);
                  return handled;
                }
              g_free (editing_path);
            }
        }

      g_object_ref (tree_view);

      if (gimp_event_triggers_context_menu ((GdkEvent *) bevent, TRUE))
        {
          /* If the clicked item is not selected, it becomes the new
           * selection. Otherwise, we use the current selection. This
           * allows to not break multiple selection when
           * right-clicking.
           */

          if (! gimp_container_view_is_item_selected (container_view,
                                                      renderer->viewable))
            {
              gimp_container_view_set_1_selected (container_view,
                                                  renderer->viewable);
            }

          /* Show the context menu. */
          if (gimp_container_view_get_container (container_view))
            {
              gimp_editor_popup_menu_at_pointer (GIMP_EDITOR (tree_view),
                                                 (GdkEvent *) bevent);
            }
        }
      else if (bevent->button == 1)
        {
          handled = TRUE;

          if (bevent->type == GDK_BUTTON_PRESS ||
              bevent->type == GDK_BUTTON_RELEASE)
            {
              /*  don't select item if a toggle was clicked */
              if (! toggled_cell)
                {
                  gchar *path_str = gtk_tree_path_to_string (path);

                  handled = FALSE;
                  if (bevent->type == GDK_BUTTON_RELEASE && clicked_cell)
                    handled =
                      gimp_cell_renderer_viewable_pre_clicked (clicked_cell,
                                                               path_str,
                                                               bevent->state);

                  if (! handled && ! multisel_mode && ! modifiers)
                    {
                      if (! tree_view->priv->editing_path &&
                          (bevent->type == GDK_BUTTON_RELEASE ||
                           ! gimp_container_view_is_item_selected (container_view, renderer->viewable)))
                        /* If we click on currently selected item,
                         * handle simple click on release only for it
                         * to not change a multi-selection in case this
                         * click becomes a drag'n drop action.
                         */

                        handled =
                          gimp_container_view_set_1_selected (container_view,
                                                              renderer->viewable);
                    }
                  /* Multi selection will be handled by gimp_container_tree_view_selection_changed() */

                  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 (bevent->type == GDK_BUTTON_PRESS &&
                      (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 (bevent->type == GDK_BUTTON_RELEASE && ! toggled_cell)
                {
                  success = gimp_container_view_set_1_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_at_pointer (GIMP_EDITOR (tree_view),
                                             (GdkEvent *) bevent);
        }

      handled = TRUE;
    }

  if (handled                          &&
      bevent->type == GDK_BUTTON_PRESS &&
      ! gtk_widget_has_focus (widget)  &&
      ! toggled_cell                   &&
      ! clicked_cell)
    {
      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;

  renderer = gimp_container_tree_store_get_renderer (GIMP_CONTAINER_TREE_STORE (tree_view->model), &iter);

  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 GList *
gimp_container_tree_view_drag_viewable_list (GtkWidget    *widget,
                                             GimpContext **context,
                                             gpointer      data)
{
  GList *items = NULL;

  if (context)
    *context = gimp_container_view_get_context (GIMP_CONTAINER_VIEW (data));

  gimp_container_tree_view_get_selected (GIMP_CONTAINER_VIEW (data), &items);

  return items;
}

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, NULL);

  return NULL;
}

static gboolean
gimp_container_tree_view_get_1_selected (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 void
gimp_container_tree_view_row_expanded (GtkTreeView           *tree_view,
                                       GtkTreeIter           *iter,
                                       GtkTreePath           *path,
                                       GimpContainerTreeView *view)
{
  GimpViewRenderer *renderer;

  renderer = gimp_container_tree_store_get_renderer (GIMP_CONTAINER_TREE_STORE (view->model),
                                                     iter);
  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;

          renderer = gimp_container_tree_store_get_renderer (GIMP_CONTAINER_TREE_STORE (model),
                                                             &iter);
          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;

  renderer = gimp_container_tree_store_get_renderer (GIMP_CONTAINER_TREE_STORE (model),
                                                     iter);

  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);
}

Messung
CHZG
81969891

¤ Dauer der Verarbeitung: 0.22 Sekunden  (vorverarbeitet am  2026-11-04) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.