/* PIKA - Photo and Image Kooker Application
 * a rebranding of The GNU Image Manipulation Program (created with heckimp)
 * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
 *
 * Original copyright, applying to most contents (license remains unchanged): 
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * pikacontainertreeview.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 "libpikawidgets/pikawidgets.h"

#include "widgets-types.h"

#include "core/pikacontainer.h"
#include "core/pikacontext.h"
#include "core/pikaviewable.h"
#include "core/pika-utils.h"

#include "pikacellrendererbutton.h"
#include "pikacellrendererviewable.h"
#include "pikacontainertreestore.h"
#include "pikacontainertreeview.h"
#include "pikacontainertreeview-dnd.h"
#include "pikacontainertreeview.h"
#include "pikacontainertreeview-private.h"
#include "pikacontainerview.h"
#include "pikadnd.h"
#include "pikaviewrenderer.h"
#include "pikawidgets-utils.h"

#include "pika-intl.h"


enum
{
  EDIT_NAME,
  LAST_SIGNAL
};


static void          pika_container_tree_view_view_iface_init   (PikaContainerViewInterface  *iface);

static void          pika_container_tree_view_constructed       (GObject                     *object);
static void          pika_container_tree_view_finalize          (GObject                     *object);

static void          pika_container_tree_view_style_updated     (GtkWidget                   *widget);
static void          pika_container_tree_view_unmap             (GtkWidget                   *widget);
static gboolean      pika_container_tree_view_popup_menu        (GtkWidget                   *widget);

static void          pika_container_tree_view_set_container     (PikaContainerView           *view,
                                                                 PikaContainer               *container);
static void          pika_container_tree_view_set_context       (PikaContainerView           *view,
                                                                 PikaContext                 *context);
static void          pika_container_tree_view_set_selection_mode(PikaContainerView           *view,
                                                                 GtkSelectionMode             mode);

static gpointer      pika_container_tree_view_insert_item       (PikaContainerView           *view,
                                                                 PikaViewable                *viewable,
                                                                 gpointer                     parent_insert_data,
                                                                 gint                         index);
static void          pika_container_tree_view_remove_item       (PikaContainerView           *view,
                                                                 PikaViewable                *viewable,
                                                                 gpointer                     insert_data);
static void          pika_container_tree_view_reorder_item      (PikaContainerView           *view,
                                                                 PikaViewable                *viewable,
                                                                 gint                         new_index,
                                                                 gpointer                     insert_data);
static void          pika_container_tree_view_rename_item       (PikaContainerView           *view,
                                                                 PikaViewable                *viewable,
                                                                 gpointer                     insert_data);
static void          pika_container_tree_view_expand_item       (PikaContainerView           *view,
                                                                 PikaViewable                *viewable,
                                                                 gpointer                     insert_data);
static gboolean      pika_container_tree_view_select_items      (PikaContainerView           *view,
                                                                 GList                       *viewables,
                                                                 GList                       *paths);
static void          pika_container_tree_view_clear_items       (PikaContainerView           *view);
static void          pika_container_tree_view_set_view_size     (PikaContainerView           *view);

static void          pika_container_tree_view_real_edit_name    (PikaContainerTreeView       *tree_view);

static void     pika_container_tree_view_selection_label_notify (GtkLabel                    *label,
                                                                 GParamSpec                  *pspec,
                                                                 PikaItemTreeView            *view);

static gboolean      pika_container_tree_view_edit_focus_out    (GtkWidget                   *widget,
                                                                 GdkEvent                    *event,
                                                                 gpointer                     user_data);
static void          pika_container_tree_view_name_started      (GtkCellRendererText         *cell,
                                                                 GtkCellEditable             *editable,
                                                                 const gchar                 *path_str,
                                                                 PikaContainerTreeView       *tree_view);
static void          pika_container_tree_view_name_canceled     (GtkCellRendererText         *cell,
                                                                 PikaContainerTreeView       *tree_view);

static void          pika_container_tree_view_selection_changed (GtkTreeSelection            *sel,
                                                                 PikaContainerTreeView       *tree_view);
static gboolean      pika_container_tree_view_button            (GtkWidget                   *widget,
                                                                 GdkEventButton              *bevent,
                                                                 PikaContainerTreeView       *tree_view);
static gboolean      pika_container_tree_view_scroll            (GtkWidget                   *widget,
                                                                 GdkEventScroll              *event,
                                                                 PikaContainerTreeView       *tree_view);
static gboolean      pika_container_tree_view_tooltip           (GtkWidget                   *widget,
                                                                 gint                         x,
                                                                 gint                         y,
                                                                 gboolean                     keyboard_tip,
                                                                 GtkTooltip                  *tooltip,
                                                                 PikaContainerTreeView       *tree_view);
static PikaViewable *pika_container_tree_view_drag_viewable     (GtkWidget                   *widget,
                                                                 PikaContext                **context,
                                                                 gpointer                     data);
static GList       * pika_container_tree_view_drag_viewable_list (GtkWidget    *widget,
                                                                   PikaContext **context,
                                                                   gpointer      data);
static GdkPixbuf    *pika_container_tree_view_drag_pixbuf       (GtkWidget                   *widget,
                                                                 gpointer                     data);
static void          pika_container_tree_view_zoom_gesture_begin  (GtkGestureZoom            *gesture,
                                                                   GdkEventSequence          *sequence,
                                                                   PikaContainerTreeView     *tree_view);
static void          pika_container_tree_view_zoom_gesture_update (GtkGestureZoom            *gesture,
                                                                   GdkEventSequence          *sequence,
                                                                   PikaContainerTreeView     *tree_view);

static gboolean      pika_container_tree_view_get_selected_single (PikaContainerTreeView  *tree_view,
                                                                   GtkTreeIter            *iter);
static gint          pika_container_tree_view_get_selected        (PikaContainerView    *view,
                                                                   GList               **items,
                                                                   GList               **paths);
static void          pika_container_tree_view_row_expanded        (GtkTreeView               *tree_view,
                                                                   GtkTreeIter               *iter,
                                                                   GtkTreePath               *path,
                                                                   PikaContainerTreeView     *view);
static void          pika_container_tree_view_expand_rows         (GtkTreeModel             *model,
                                                                   GtkTreeView              *view,
                                                                   GtkTreeIter              *parent);

static void          pika_container_tree_view_monitor_changed     (PikaContainerTreeView    *view);

static gboolean      pika_container_tree_view_search_path_foreach (GtkTreeModel             *model,
                                                                   GtkTreePath              *path,
                                                                   GtkTreeIter              *iter,
                                                                   gpointer                  data);
static GtkTreePath * pika_container_tree_view_get_path            (PikaContainerTreeView    *tree_view,
                                                                   PikaViewable             *viewable);



G_DEFINE_TYPE_WITH_CODE (PikaContainerTreeView, pika_container_tree_view,
                         PIKA_TYPE_CONTAINER_BOX,
                         G_ADD_PRIVATE (PikaContainerTreeView)
                         G_IMPLEMENT_INTERFACE (PIKA_TYPE_CONTAINER_VIEW,
                                                pika_container_tree_view_view_iface_init))

#define parent_class pika_container_tree_view_parent_class

static PikaContainerViewInterface *parent_view_iface = NULL;

static guint tree_view_signals[LAST_SIGNAL] = { 0 };


static void
pika_container_tree_view_class_init (PikaContainerTreeViewClass *klass)
{
  GObjectClass   *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
  GtkBindingSet  *binding_set;

  object_class->constructed   = pika_container_tree_view_constructed;
  object_class->finalize      = pika_container_tree_view_finalize;

  widget_class->style_updated = pika_container_tree_view_style_updated;
  widget_class->unmap         = pika_container_tree_view_unmap;
  widget_class->popup_menu    = pika_container_tree_view_popup_menu;

  klass->edit_name            = pika_container_tree_view_real_edit_name;
  klass->drop_possible        = pika_container_tree_view_real_drop_possible;
  klass->drop_viewables       = pika_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 (PikaContainerTreeViewClass, 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
pika_container_tree_view_view_iface_init (PikaContainerViewInterface *iface)
{
  parent_view_iface = g_type_interface_peek_parent (iface);

  if (! parent_view_iface)
    parent_view_iface = g_type_default_interface_peek (PIKA_TYPE_CONTAINER_VIEW);

  iface->set_container      = pika_container_tree_view_set_container;
  iface->set_context        = pika_container_tree_view_set_context;
  iface->set_selection_mode = pika_container_tree_view_set_selection_mode;
  iface->insert_item        = pika_container_tree_view_insert_item;
  iface->remove_item        = pika_container_tree_view_remove_item;
  iface->reorder_item       = pika_container_tree_view_reorder_item;
  iface->rename_item        = pika_container_tree_view_rename_item;
  iface->expand_item        = pika_container_tree_view_expand_item;
  iface->select_items       = pika_container_tree_view_select_items;
  iface->clear_items        = pika_container_tree_view_clear_items;
  iface->set_view_size      = pika_container_tree_view_set_view_size;
  iface->get_selected       = pika_container_tree_view_get_selected;

  iface->insert_data_free = (GDestroyNotify) gtk_tree_iter_free;
}

static void
pika_container_tree_view_init (PikaContainerTreeView *tree_view)
{
  PikaContainerBox *box = PIKA_CONTAINER_BOX (tree_view);

  tree_view->priv = pika_container_tree_view_get_instance_private (tree_view);

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

  pika_widget_track_monitor (GTK_WIDGET (tree_view),
                             G_CALLBACK (pika_container_tree_view_monitor_changed),
                             NULL, NULL);
}

static void
pika_container_tree_view_constructed (GObject *object)
{
  PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (object);
  PikaContainerView     *view      = PIKA_CONTAINER_VIEW (object);
  PikaContainerBox      *box       = PIKA_CONTAINER_BOX (object);

  G_OBJECT_CLASS (parent_class)->constructed (object);

  tree_view->model = pika_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",   PIKA_CONTAINER_TREE_STORE_COLUMN_NAME,
                                  "enable-search",   FALSE,
                                  "headers-visible", FALSE,
                                  "has-tooltip",     TRUE,
                                  "show-expanders",  PIKA_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));

  pika_container_view_set_dnd_widget (view, GTK_WIDGET (tree_view->view));

  tree_view->main_column = gtk_tree_view_column_new ();
  tree_view->priv->multi_selection_label = gtk_label_new (NULL);
  gtk_label_set_selectable (GTK_LABEL (tree_view->priv->multi_selection_label), TRUE);
  gtk_tree_view_column_set_widget (tree_view->main_column,
                                   tree_view->priv->multi_selection_label);
  g_signal_connect (tree_view->priv->multi_selection_label, "notify::label",
                    G_CALLBACK (pika_container_tree_view_selection_label_notify),
                    tree_view);
  g_signal_connect (tree_view->priv->multi_selection_label, "notify::selection-bound",
                    G_CALLBACK (pika_container_tree_view_selection_label_notify),
                    tree_view);
  gtk_widget_show (tree_view->priv->multi_selection_label);
  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 = pika_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", PIKA_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",       PIKA_CONTAINER_TREE_STORE_COLUMN_NAME,
                                       "attributes", PIKA_CONTAINER_TREE_STORE_COLUMN_NAME_ATTRIBUTES,
                                       "sensitive",  PIKA_CONTAINER_TREE_STORE_COLUMN_NAME_SENSITIVE,
                                       NULL);

  g_signal_connect (tree_view->priv->name_cell, "editing-started",
                    G_CALLBACK (pika_container_tree_view_name_started),
                    tree_view);
  g_signal_connect (tree_view->priv->name_cell, "editing-canceled",
                    G_CALLBACK (pika_container_tree_view_name_canceled),
                    tree_view);

  pika_container_tree_view_add_renderer_cell (tree_view,
                                              tree_view->renderer_cell,
                                              PIKA_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 (pika_container_tree_view_selection_changed),
                    tree_view);

  g_signal_connect (tree_view->view, "drag-failed",
                    G_CALLBACK (pika_container_tree_view_drag_failed),
                    tree_view);
  g_signal_connect (tree_view->view, "drag-leave",
                    G_CALLBACK (pika_container_tree_view_drag_leave),
                    tree_view);
  g_signal_connect (tree_view->view, "drag-motion",
                    G_CALLBACK (pika_container_tree_view_drag_motion),
                    tree_view);
  g_signal_connect (tree_view->view, "drag-drop",
                    G_CALLBACK (pika_container_tree_view_drag_drop),
                    tree_view);
  g_signal_connect (tree_view->view, "drag-data-received",
                    G_CALLBACK (pika_container_tree_view_drag_data_received),
                    tree_view);
  g_signal_connect (tree_view->view, "scroll-event",
                    G_CALLBACK (pika_container_tree_view_scroll),
                    tree_view);

  tree_view->priv->zoom_gesture = gtk_gesture_zoom_new (GTK_WIDGET (tree_view->view));
  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (tree_view->priv->zoom_gesture),
                                              GTK_PHASE_CAPTURE);

  /* The default signal handler needs to run first to setup scale delta */
  g_signal_connect_after (tree_view->priv->zoom_gesture, "begin",
                          G_CALLBACK (pika_container_tree_view_zoom_gesture_begin),
                          tree_view);
  g_signal_connect_after (tree_view->priv->zoom_gesture, "update",
                          G_CALLBACK (pika_container_tree_view_zoom_gesture_update),
                          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 (pika_container_tree_view_tooltip),
                          tree_view);
}

static void
pika_container_tree_view_finalize (GObject *object)
{
  PikaContainerTreeView *tree_view = PIKA_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_clear_object (&tree_view->priv->zoom_gesture);

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

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

  renderer = pika_container_tree_store_get_renderer (PIKA_CONTAINER_TREE_STORE (model), iter);

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

  return FALSE;
}

static void
pika_container_tree_view_style_updated (GtkWidget *widget)
{
  PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (widget);

  GTK_WIDGET_CLASS (parent_class)->style_updated (widget);

  if (tree_view->model)
    gtk_tree_model_foreach (tree_view->model,
                            pika_container_tree_view_style_updated_foreach,
                            NULL);
}

static void
pika_container_tree_view_unmap (GtkWidget *widget)
{
  PikaContainerTreeView *tree_view = PIKA_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
pika_container_tree_view_popup_menu (GtkWidget *widget)
{
  PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (widget);
  GtkTreeIter            iter;
  GtkTreePath           *path;
  GdkRectangle           rect;

  if (!pika_container_tree_view_get_selected_single (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 pika_editor_popup_menu_at_rect (PIKA_EDITOR (widget),
                                         gtk_tree_view_get_bin_window (tree_view->view),
                                         &rect, GDK_GRAVITY_CENTER, GDK_GRAVITY_NORTH_WEST,
                                         NULL);
}

GtkWidget *
pika_container_tree_view_new (PikaContainer *container,
                              PikaContext   *context,
                              gint           view_size,
                              gint           view_border_width)
{
  PikaContainerTreeView *tree_view;
  PikaContainerView     *view;

  g_return_val_if_fail (container == NULL || PIKA_IS_CONTAINER (container),
                        NULL);
  g_return_val_if_fail (context == NULL || PIKA_IS_CONTEXT (context), NULL);
  g_return_val_if_fail (view_size > 0 &&
                        view_size <= PIKA_VIEWABLE_MAX_PREVIEW_SIZE, NULL);
  g_return_val_if_fail (view_border_width >= 0 &&
                        view_border_width <= PIKA_VIEW_MAX_BORDER_WIDTH,
                        NULL);

  tree_view = g_object_new (PIKA_TYPE_CONTAINER_TREE_VIEW, NULL);

  view = PIKA_CONTAINER_VIEW (tree_view);

  pika_container_view_set_view_size (view, view_size, view_border_width);

  if (container)
    pika_container_view_set_container (view, container);

  if (context)
    pika_container_view_set_context (view, context);

  return GTK_WIDGET (tree_view);
}

GtkCellRenderer *
pika_container_tree_view_get_name_cell (PikaContainerTreeView *tree_view)
{
  g_return_val_if_fail (PIKA_IS_CONTAINER_TREE_VIEW (tree_view), NULL);

  return tree_view->priv->name_cell;
}

void
pika_container_tree_view_set_main_column_title (PikaContainerTreeView *tree_view,
                                                const gchar           *title)
{
  g_return_if_fail (PIKA_IS_CONTAINER_TREE_VIEW (tree_view));

  gtk_tree_view_column_set_title (tree_view->main_column,
                                  title);
}

void
pika_container_tree_view_add_toggle_cell (PikaContainerTreeView *tree_view,
                                          GtkCellRenderer       *cell)
{
  g_return_if_fail (PIKA_IS_CONTAINER_TREE_VIEW (tree_view));
  g_return_if_fail (PIKA_IS_CELL_RENDERER_TOGGLE (cell) ||
                    PIKA_IS_CELL_RENDERER_BUTTON (cell));

  tree_view->priv->toggle_cells = g_list_prepend (tree_view->priv->toggle_cells,
                                                  cell);
}

void
pika_container_tree_view_add_renderer_cell (PikaContainerTreeView *tree_view,
                                            GtkCellRenderer       *cell,
                                            gint                   column_number)
{
  g_return_if_fail (PIKA_IS_CONTAINER_TREE_VIEW (tree_view));
  g_return_if_fail (PIKA_IS_CELL_RENDERER_VIEWABLE (cell));

  tree_view->priv->renderer_cells = g_list_prepend (tree_view->priv->renderer_cells,
                                                    cell);

  pika_container_tree_store_add_renderer_cell (PIKA_CONTAINER_TREE_STORE (tree_view->model),
                                               cell, column_number);
}

void
pika_container_tree_view_set_dnd_drop_to_empty (PikaContainerTreeView *tree_view,
                                                gboolean               dnd_drop_to_empty)
{
  g_return_if_fail (PIKA_IS_CONTAINER_TREE_VIEW (tree_view));

  tree_view->priv->dnd_drop_to_empty = dnd_drop_to_empty;
}

void
pika_container_tree_view_connect_name_edited (PikaContainerTreeView *tree_view,
                                              GCallback              callback,
                                              gpointer               data)
{
  g_return_if_fail (PIKA_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
pika_container_tree_view_name_edited (GtkCellRendererText   *cell,
                                      const gchar           *path_str,
                                      const gchar           *new_name,
                                      PikaContainerTreeView *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))
    {
      PikaViewRenderer *renderer;
      PikaObject       *object;
      const gchar      *old_name;

      renderer = pika_container_tree_store_get_renderer (PIKA_CONTAINER_TREE_STORE (tree_view->model), &iter);

      object = PIKA_OBJECT (renderer->viewable);

      old_name = pika_object_get_name (object);

      if (! old_name) old_name = "";
      if (! new_name) new_name = "";

      if (strcmp (old_name, new_name))
        {
          pika_object_set_name (object, new_name);

          changed = TRUE;
        }
      else
        {
          gchar *name = pika_viewable_get_description (renderer->viewable,
                                                       NULL);

          gtk_tree_store_set (GTK_TREE_STORE (tree_view->model), &iter,
                              PIKA_CONTAINER_TREE_STORE_COLUMN_NAME, name,
                              -1);
          g_free (name);
        }

      g_object_unref (renderer);
    }

  gtk_tree_path_free (path);

  return changed;
}


/*  PikaContainerView methods  */

static void
pika_container_tree_view_set_container (PikaContainerView *view,
                                        PikaContainer     *container)
{
  PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (view);
  PikaContainer         *old_container;

  old_container = pika_container_view_get_container (view);

  if (old_container)
    {
      tree_view->priv->dnd_renderer = NULL;

      g_signal_handlers_disconnect_by_func (tree_view->view,
                                            pika_container_tree_view_row_expanded,
                                            tree_view);
      if (! container)
        {
          if (pika_dnd_viewable_list_source_remove (GTK_WIDGET (tree_view->view),
                                                    pika_container_get_children_type (old_container)))
            {
              if (PIKA_VIEWABLE_CLASS (g_type_class_peek (pika_container_get_children_type (old_container)))->get_size)
                pika_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,
                                                pika_container_tree_view_button,
                                                tree_view);
        }
    }
  else if (container)
    {
      if (pika_dnd_drag_source_set_by_type (GTK_WIDGET (tree_view->view),
                                            GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
                                            pika_container_get_children_type (container),
                                            GDK_ACTION_COPY))
        {
          pika_dnd_viewable_list_source_add (GTK_WIDGET (tree_view->view),
                                             pika_container_get_children_type (container),
                                             pika_container_tree_view_drag_viewable_list,
                                             tree_view);
          pika_dnd_viewable_source_add (GTK_WIDGET (tree_view->view),
                                        pika_container_get_children_type (container),
                                        pika_container_tree_view_drag_viewable,
                                        tree_view);

          if (PIKA_VIEWABLE_CLASS (g_type_class_peek (pika_container_get_children_type (container)))->get_size)
            pika_dnd_pixbuf_source_add (GTK_WIDGET (tree_view->view),
                                        pika_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 (pika_container_tree_view_button),
                        tree_view);
      g_signal_connect (tree_view->view, "button-release-event",
                        G_CALLBACK (pika_container_tree_view_button),
                        tree_view);
    }

  parent_view_iface->set_container (view, container);

  if (container)
    {
      pika_container_tree_view_expand_rows (tree_view->model,
                                            tree_view->view,
                                            NULL);

      g_signal_connect (tree_view->view,
                        "row-collapsed",
                        G_CALLBACK (pika_container_tree_view_row_expanded),
                        tree_view);
      g_signal_connect (tree_view->view,
                        "row-expanded",
                        G_CALLBACK (pika_container_tree_view_row_expanded),
                        tree_view);
    }

  gtk_tree_view_columns_autosize (tree_view->view);
}

static void
pika_container_tree_view_set_context (PikaContainerView *view,
                                      PikaContext       *context)
{
  PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (view);

  if (tree_view->model)
    pika_container_tree_store_set_context (PIKA_CONTAINER_TREE_STORE (tree_view->model),
                                           context);

  parent_view_iface->set_context (view, context);
}

static void
pika_container_tree_view_set_selection_mode (PikaContainerView *view,
                                             GtkSelectionMode   mode)
{
  PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (view);

  gtk_tree_selection_set_mode (tree_view->priv->selection, mode);

  parent_view_iface->set_selection_mode (view, mode);
}

static gpointer
pika_container_tree_view_insert_item (PikaContainerView *view,
                                      PikaViewable      *viewable,
                                      gpointer           parent_insert_data,
                                      gint               index)
{
  PikaContainerTreeView *tree_view   = PIKA_CONTAINER_TREE_VIEW (view);
  GtkTreeIter           *parent_iter = parent_insert_data;
  GtkTreeIter           *iter;

  iter = pika_container_tree_store_insert_item (PIKA_CONTAINER_TREE_STORE (tree_view->model),
                                                viewable,
                                                parent_iter,
                                                index);

  if (parent_iter)
    pika_container_tree_view_expand_item (view, viewable, parent_iter);

  return iter;
}

static void
pika_container_tree_view_remove_item (PikaContainerView *view,
                                      PikaViewable      *viewable,
                                      gpointer           insert_data)
{
  PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (view);
  GtkTreeIter           *iter      = (GtkTreeIter *) insert_data;

  pika_container_tree_store_remove_item (PIKA_CONTAINER_TREE_STORE (tree_view->model),
                                         viewable,
                                         iter);

  if (iter)
    gtk_tree_view_columns_autosize (tree_view->view);
}

static void
pika_container_tree_view_reorder_item (PikaContainerView *view,
                                       PikaViewable      *viewable,
                                       gint               new_index,
                                       gpointer           insert_data)
{
  PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (view);
  GtkTreeIter           *iter      = (GtkTreeIter *) insert_data;
  GtkTreeIter            parent_iter;
  gboolean               selected  = FALSE;

  if (iter)
    {
      GtkTreeIter selected_iter;

      selected = pika_container_tree_view_get_selected_single (tree_view,
                                                               &selected_iter);

      if (selected)
        {
          PikaViewRenderer *renderer;

          renderer = pika_container_tree_store_get_renderer (PIKA_CONTAINER_TREE_STORE (tree_view->model),
                                                             &selected_iter);

          if (renderer->viewable != viewable)
            selected = FALSE;

          g_object_unref (renderer);
        }
    }

  pika_container_tree_store_reorder_item (PIKA_CONTAINER_TREE_STORE (tree_view->model),
                                          viewable,
                                          new_index,
                                          iter);

  if (selected)
    pika_container_view_select_item (view, viewable);

  if (gtk_tree_model_iter_parent (tree_view->model, &parent_iter, iter))
    pika_container_tree_view_expand_item (view, viewable, &parent_iter);
}

static void
pika_container_tree_view_rename_item (PikaContainerView *view,
                                      PikaViewable      *viewable,
                                      gpointer           insert_data)
{
  PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (view);
  GtkTreeIter           *iter      = (GtkTreeIter *) insert_data;

  if (pika_container_tree_store_rename_item (PIKA_CONTAINER_TREE_STORE (tree_view->model),
                                             viewable,
                                             iter))
    {
      gtk_tree_view_columns_autosize (tree_view->view);
    }
}

static void
pika_container_tree_view_expand_item (PikaContainerView *view,
                                      PikaViewable      *viewable,
                                      gpointer           insert_data)
{
  PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (view);
  GtkTreeIter           *iter      = (GtkTreeIter *) insert_data;
  PikaViewRenderer      *renderer;

  renderer = pika_container_tree_store_get_renderer (PIKA_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,
                                       pika_container_tree_view_row_expanded,
                                       view);

      if (pika_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,
                                         pika_container_tree_view_row_expanded,
                                         view);

      gtk_tree_path_free (path);
      g_object_unref (renderer);
    }
}

static gboolean
pika_container_tree_view_select_items (PikaContainerView *view,
                                       GList             *items,
                                       GList             *paths)
{
  PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (view);
  GList                 *item;
  GList                 *path;
  gboolean               free_paths      = FALSE;
  gboolean               scroll_to_first = TRUE;
  GtkTreePath           *focused_path    = NULL;

  /* If @paths is not set, compute it ourselves. */
  if (g_list_length (items) != g_list_length (paths))
    {
      paths = NULL;
      for (item = items; item; item = item->next)
        {
          GtkTreePath *path;
          path = pika_container_tree_view_get_path (tree_view, item->data);
          if (path != NULL)
            /* It may happen that some items have no paths when a tree
             * view has some filtering logics (for instance Palette or
             * Fonts dockables). Then an item which was selected at
             * first might become unselected during filtering and has to
             * be removed from selection.
             */
            paths = g_list_prepend (paths, path);
        }

      paths = g_list_reverse (paths);
      free_paths = TRUE;
    }

  g_signal_handlers_block_by_func (tree_view->priv->selection,
                                   pika_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 = path->next)
        {
          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 (item = items, path = paths; item && path; item = item->next, path = path->next)
    {
      GtkTreePath *parent_path;

      /* Expand if necessary. */
      parent_path = gtk_tree_path_copy (path->data);
      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,
                                     pika_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 (item = items, path = paths; item && path; item = item->next, path = path->next)
            {
              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);
    }

  if (free_paths)
    g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);

  if (g_list_length (items) > 1)
    {
      gchar *str;

      str = g_strdup_printf (ngettext ("%d item selected", "%d items selected",
                                       g_list_length (items)),
                             g_list_length (items));
      gtk_label_set_text (GTK_LABEL (tree_view->priv->multi_selection_label), str);
      g_free (str);
      gtk_widget_show (tree_view->priv->multi_selection_label);
    }
  else
    {
      gtk_widget_hide (tree_view->priv->multi_selection_label);
    }

  return TRUE;
}

static void
pika_container_tree_view_clear_items (PikaContainerView *view)
{
  PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (view);

  g_signal_handlers_block_by_func (tree_view->priv->selection,
                                   pika_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);

  pika_container_tree_store_clear_items (PIKA_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,
                                     pika_container_tree_view_selection_changed,
                                     tree_view);

  parent_view_iface->clear_items (view);
}

static void
pika_container_tree_view_set_view_size (PikaContainerView *view)
{
  PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (view);
  GtkWidget             *tree_widget;
  GList                 *list;
  gint                   view_size;
  gint                   border_width;

  view_size = pika_container_view_get_view_size (view, &border_width);

  if (tree_view->model)
    pika_container_tree_store_set_view_size (PIKA_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);
}


/*  PikaContainerTreeView methods  */

static void
pika_container_tree_view_real_edit_name (PikaContainerTreeView *tree_view)
{
  GtkTreeIter selected_iter;
  gboolean    success = FALSE;

  if (g_list_find (tree_view->priv->editable_cells,
                   tree_view->priv->name_cell) &&
      pika_container_tree_view_get_selected_single (tree_view,
                                                    &selected_iter))
    {
      PikaViewRenderer *renderer;

      renderer = pika_container_tree_store_get_renderer (PIKA_CONTAINER_TREE_STORE (tree_view->model),
                                                         &selected_iter);

      if (pika_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 void
pika_container_tree_view_selection_label_notify (GtkLabel         *label,
                                                 GParamSpec       *pspec,
                                                 PikaItemTreeView *view)
{
  /* This is a weird trick to make the label follow the color scheme of
   * selected items in whatever theme is selected. It seems we cannot
   * link to the color of another widget whose theme we don't control.
   * Faking selection is the only way I found, though it is quite ugly
   * semantically.
   */
  gtk_label_select_region (label, 0, -1);
}

static gboolean
pika_container_tree_view_edit_focus_out (GtkWidget *widget,
                                         GdkEvent  *event,
                                         gpointer   user_data)
{
  PikaContainerTreeView *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
pika_container_tree_view_name_started (GtkCellRendererText   *cell,
                                       GtkCellEditable       *editable,
                                       const gchar           *path_str,
                                       PikaContainerTreeView *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 (pika_container_tree_view_edit_focus_out),
                    tree_view);

  if (gtk_tree_model_get_iter (tree_view->model, &iter, path))
    {
      PikaViewRenderer *renderer;
      const gchar      *real_name;

      renderer = pika_container_tree_store_get_renderer (PIKA_CONTAINER_TREE_STORE (tree_view->model), &iter);

      real_name = pika_object_get_name (renderer->viewable);
      pika_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
pika_container_tree_view_name_canceled (GtkCellRendererText   *cell,
                                        PikaContainerTreeView *tree_view)
{
  GtkTreeIter iter;

  if (pika_container_tree_view_get_selected_single (tree_view, &iter))
    {
      PikaViewRenderer *renderer;
      gchar            *name;

      renderer = pika_container_tree_store_get_renderer (PIKA_CONTAINER_TREE_STORE (tree_view->model), &iter);

      name = pika_viewable_get_description (renderer->viewable, NULL);

      gtk_tree_store_set (GTK_TREE_STORE (tree_view->model), &iter,
                          PIKA_CONTAINER_TREE_STORE_COLUMN_NAME, name,
                          -1);

      g_free (name);
      g_object_unref (renderer);
    }
}

static void
pika_container_tree_view_selection_changed (GtkTreeSelection      *selection,
                                            PikaContainerTreeView *tree_view)
{
  PikaContainerView *view = PIKA_CONTAINER_VIEW (tree_view);
  GList             *items;
  GList             *paths;

  pika_container_tree_view_get_selected (view, &items, &paths);
  pika_container_view_multi_selected (view, items, paths);
  g_list_free (items);
  g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
}

static GtkCellRenderer *
pika_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
pika_container_tree_view_button (GtkWidget             *widget,
                                 GdkEventButton        *bevent,
                                 PikaContainerTreeView *tree_view)
{
  PikaContainerView *container_view = PIKA_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))
    {
      PikaViewRenderer         *renderer;
      GtkCellRenderer          *toggled_cell = NULL;
      PikaCellRendererViewable *clicked_cell = NULL;
      GtkCellRenderer          *edit_cell    = NULL;
      GdkRectangle              column_area;
      GtkTreeIter               iter;
      gboolean                  multisel_mode;
      GdkModifierType           modifiers = (bevent->state & pika_get_all_modifiers_mask ());

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

      if (! modifiers ||
          (modifiers & ~(pika_get_extend_selection_mask () | pika_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 multi-selection though, actual selection will
       * happen in pika_container_tree_view_selection_changed() but the widget
       * must already be focused or the handler won't run.
       * Whereas 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.
       * See also commit 3e101922 and MR !1128.
       */
      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 = pika_container_tree_store_get_renderer (PIKA_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 (! pika_container_tree_view_find_click_cell (widget,
                                                          cells,
                                                          column, &column_area,
                                                          bevent->x, bevent->y))
            {
              if (bevent->state & pika_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 =
        pika_container_tree_view_find_click_cell (widget,
                                                  tree_view->priv->toggle_cells,
                                                  column, &column_area,
                                                  bevent->x, bevent->y);

      if (! toggled_cell)
        clicked_cell = (PikaCellRendererViewable *)
          pika_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 =
            pika_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 (pika_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 (! pika_container_view_is_item_selected (container_view, renderer->viewable))
            pika_container_view_item_selected (container_view, renderer->viewable);
          /* Show the context menu. */
          if (pika_container_view_get_container (container_view))
            pika_editor_popup_menu_at_pointer (PIKA_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 =
                      pika_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 ||
                           ! pika_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 =
                          pika_container_view_item_selected (container_view,
                                                             renderer->viewable);
                    }
                  /* Multi selection will be handled by pika_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 (pika_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 (PIKA_IS_CELL_RENDERER_TOGGLE (toggled_cell))
                            {
                              pika_cell_renderer_toggle_clicked (PIKA_CELL_RENDERER_TOGGLE (toggled_cell),
                                                                 path_str,
                                                                 bevent->state);
                            }
                          else if (PIKA_IS_CELL_RENDERER_BUTTON (toggled_cell))
                            {
                              pika_cell_renderer_button_clicked (PIKA_CELL_RENDERER_BUTTON (toggled_cell),
                                                                 path_str,
                                                                 bevent->state);
                            }
                        }
                      else if (clicked_cell)
                        {
                          pika_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 = pika_container_view_item_selected (container_view,
                                                             renderer->viewable);

              if (success)
                {
                  if (edit_cell)
                    {
                      if (pika_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 & pika_get_all_modifiers_mask ()))
                    {
                      /* Only activate if we're not in a toggled cell
                       * and no modifier keys are pressed
                       */
                      pika_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);

                  pika_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))
        {
          pika_editor_popup_menu_at_pointer (PIKA_EDITOR (tree_view), (GdkEvent *) bevent);
        }

      handled = TRUE;
    }

  if (handled && bevent->type == GDK_BUTTON_PRESS && ! gtk_widget_has_focus (widget))
    gtk_widget_grab_focus (widget);

  return handled;
}

/* We want to zoom on each 1/4 scroll events to roughly match zooming
 * behavior  of the main canvas. Each step of PIKA_VIEW_SIZE_* is
 * approximately  of sqrt(2) = 1.4 relative change. The main canvas
 * on the other hand has zoom step equal to ZOOM_MIN_STEP=1.1
 * (see pika_zoom_model_zoom_step).
 */
#define SCROLL_ZOOM_STEP_SIZE 0.25

static gboolean
pika_container_tree_view_scroll (GtkWidget             *widget,
                                 GdkEventScroll        *event,
                                 PikaContainerTreeView *tree_view)
{
  PikaContainerView *view;
  gint               view_border_width;
  gint               view_size;

  if ((event->state & GDK_CONTROL_MASK) == 0)
    return FALSE;

  if (event->direction == GDK_SCROLL_UP)
    tree_view->priv->zoom_accumulated_scroll_delta -= SCROLL_ZOOM_STEP_SIZE;
  else if (event->direction == GDK_SCROLL_DOWN)
    tree_view->priv->zoom_accumulated_scroll_delta += SCROLL_ZOOM_STEP_SIZE;
  else if (event->direction == GDK_SCROLL_SMOOTH)
    tree_view->priv->zoom_accumulated_scroll_delta += event->delta_y * SCROLL_ZOOM_STEP_SIZE;
  else
    return FALSE;

  view      = PIKA_CONTAINER_VIEW (tree_view);
  view_size = pika_container_view_get_view_size (view, &view_border_width);

  if (tree_view->priv->zoom_accumulated_scroll_delta > 1)
    {
      tree_view->priv->zoom_accumulated_scroll_delta -= 1;
      view_size = pika_view_size_get_smaller (view_size);
    }
  else if (tree_view->priv->zoom_accumulated_scroll_delta < -1)
    {
      tree_view->priv->zoom_accumulated_scroll_delta += 1;
      view_size = pika_view_size_get_larger (view_size);
    }
  else
    return TRUE;

  pika_container_view_set_view_size (view, view_size, view_border_width);
  return TRUE;
}

static gboolean
pika_container_tree_view_tooltip (GtkWidget             *widget,
                                  gint                   x,
                                  gint                   y,
                                  gboolean               keyboard_tip,
                                  GtkTooltip            *tooltip,
                                  PikaContainerTreeView *tree_view)
{
  PikaViewRenderer *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 = pika_container_tree_store_get_renderer (PIKA_CONTAINER_TREE_STORE (tree_view->model), &iter);

  if (renderer)
    {
      gchar *desc;
      gchar *tip;

      desc = pika_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 PikaViewable *
pika_container_tree_view_drag_viewable (GtkWidget    *widget,
                                        PikaContext **context,
                                        gpointer      data)
{
  PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (data);

  if (context)
    *context = pika_container_view_get_context (PIKA_CONTAINER_VIEW (data));

  if (tree_view->priv->dnd_renderer)
    return tree_view->priv->dnd_renderer->viewable;

  return NULL;
}

static GList *
pika_container_tree_view_drag_viewable_list (GtkWidget    *widget,
                                             PikaContext **context,
                                             gpointer      data)
{
  GList *items = NULL;

  if (context)
    *context = pika_container_view_get_context (PIKA_CONTAINER_VIEW (data));

  pika_container_tree_view_get_selected (PIKA_CONTAINER_VIEW (data), &items, NULL);

  return items;
}

static GdkPixbuf *
pika_container_tree_view_drag_pixbuf (GtkWidget *widget,
                                      gpointer   data)
{
  PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (data);
  PikaViewRenderer      *renderer  = tree_view->priv->dnd_renderer;
  gint                   width;
  gint                   height;

  if (renderer && pika_viewable_get_size (renderer->viewable, &width, &height))
    return pika_viewable_get_new_pixbuf (renderer->viewable,
                                         renderer->context,
                                         width, height);

  return NULL;
}

#define ZOOM_GESTURE_STEP_SIZE 1.2

static void
pika_container_tree_view_zoom_gesture_begin (GtkGestureZoom        *gesture,
                                             GdkEventSequence      *sequence,
                                             PikaContainerTreeView *tree_view)
{
  tree_view->priv->zoom_gesture_last_set_value = gtk_gesture_zoom_get_scale_delta (gesture);
}

static void
pika_container_tree_view_zoom_gesture_update (GtkGestureZoom        *gesture,
                                              GdkEventSequence      *sequence,
                                              PikaContainerTreeView *tree_view)
{
  gdouble current_scale;
  gdouble last_set_value         = tree_view->priv->zoom_gesture_last_set_value;
  gdouble min_value_for_increase = last_set_value * ZOOM_GESTURE_STEP_SIZE;
  gdouble max_value_for_decrease = last_set_value / ZOOM_GESTURE_STEP_SIZE;
  PikaContainerView *view;
  gint               view_border_width;
  gint               view_size;

  view      = PIKA_CONTAINER_VIEW (tree_view);
  view_size = pika_container_view_get_view_size (view, &view_border_width);

  current_scale = gtk_gesture_zoom_get_scale_delta (gesture);
  if (current_scale > min_value_for_increase)
    {
      last_set_value = min_value_for_increase;
      view_size      = pika_view_size_get_larger (view_size);
    }
  else if (current_scale < max_value_for_decrease)
    {
      last_set_value = max_value_for_decrease;
      view_size      = pika_view_size_get_smaller (view_size);
    }
  else
    return;

  tree_view->priv->zoom_gesture_last_set_value = last_set_value;
  pika_container_view_set_view_size (view, view_size, view_border_width);
}

static gboolean
pika_container_tree_view_get_selected_single (PikaContainerTreeView  *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
pika_container_tree_view_get_selected (PikaContainerView    *view,
                                       GList               **items,
                                       GList               **paths)
{
  PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (view);
  GtkTreeSelection      *selection;
  gint                   selected_count;
  GList                 *selected_paths;
  GList                 *removed_paths = NULL;
  GList                 *current_row;
  GtkTreeIter            iter;
  PikaViewRenderer      *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)
    {
      if (paths)
        *paths = NULL;

      /* just provide selected count */
      return selected_count;
    }

  selected_paths = gtk_tree_selection_get_selected_rows (selection, NULL);
  *items         = NULL;

  for (current_row = selected_paths;
       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);

      renderer = pika_container_tree_store_get_renderer (PIKA_CONTAINER_TREE_STORE (tree_view->model),
                                                         &iter);

      if (renderer->viewable)
        *items = g_list_prepend (*items, renderer->viewable);
      else
        /* Remove from the selected_paths list but at the end, in order not
         * to break the for loop.
         */
        removed_paths = g_list_prepend (removed_paths, current_row);

      g_object_unref (renderer);
    }
  *items = g_list_reverse (*items);

  for (current_row = removed_paths; current_row; current_row = current_row->next)
    {
      GList *remove_list = current_row->data;

      selected_paths = g_list_remove_link (selected_paths, remove_list);
      gtk_tree_path_free (remove_list->data);
    }
  g_list_free_full (removed_paths, (GDestroyNotify) g_list_free);

  if (paths)
    *paths = selected_paths;
  else
    g_list_free_full (selected_paths, (GDestroyNotify) gtk_tree_path_free);

  return selected_count;
}

static void
pika_container_tree_view_row_expanded (GtkTreeView           *tree_view,
                                       GtkTreeIter           *iter,
                                       GtkTreePath           *path,
                                       PikaContainerTreeView *view)
{
  PikaViewRenderer *renderer;

  renderer = pika_container_tree_store_get_renderer (PIKA_CONTAINER_TREE_STORE (view->model),
                                                     iter);
  if (renderer)
    {
      gboolean expanded = gtk_tree_view_row_expanded (tree_view, path);

      pika_viewable_set_expanded (renderer->viewable,
                                  expanded);
      if (expanded)
        {
          g_signal_handlers_block_by_func (tree_view,
                                           pika_container_tree_view_row_expanded,
                                           view);

          pika_container_tree_view_expand_rows (view->model, tree_view, iter);

          g_signal_handlers_unblock_by_func (tree_view,
                                             pika_container_tree_view_row_expanded,
                                             view);
        }

      g_object_unref (renderer);
    }
}

static void
pika_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))
        {
          PikaViewRenderer *renderer;

          renderer = pika_container_tree_store_get_renderer (PIKA_CONTAINER_TREE_STORE (model),
                                                             &iter);
          if (renderer)
            {
              GtkTreePath *path = gtk_tree_model_get_path (model, &iter);

              if (pika_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);
            }

          pika_container_tree_view_expand_rows (model, view, &iter);
        }
    while (gtk_tree_model_iter_next (model, &iter));
}

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

  renderer = pika_container_tree_store_get_renderer (PIKA_CONTAINER_TREE_STORE (model),
                                                     iter);

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

  return FALSE;
}

static void
pika_container_tree_view_monitor_changed (PikaContainerTreeView *view)
{
  gtk_tree_model_foreach (view->model,
                          pika_container_tree_view_monitor_changed_foreach,
                          NULL);
}

typedef struct
{
    PikaViewable *viewable;
    GtkTreePath  *path;
} SearchData;

static gboolean
pika_container_tree_view_search_path_foreach (GtkTreeModel *model,
                                              GtkTreePath  *path,
                                              GtkTreeIter  *iter,
                                              gpointer      data)
{
  SearchData       *search_data = data;
  PikaViewRenderer *renderer;

  renderer = pika_container_tree_store_get_renderer (PIKA_CONTAINER_TREE_STORE (model),
                                                     iter);

  if (renderer->viewable == search_data->viewable)
    search_data->path = gtk_tree_path_copy (path);

  g_object_unref (renderer);

  return (search_data->path != NULL);
}

static GtkTreePath *
pika_container_tree_view_get_path (PikaContainerTreeView *tree_view,
                                   PikaViewable          *viewable)
{
  SearchData search_data;

  search_data.viewable = viewable;
  search_data.path     = NULL;

  gtk_tree_model_foreach (GTK_TREE_MODEL (tree_view->model),
                          (GtkTreeModelForeachFunc) pika_container_tree_view_search_path_foreach,
                          &search_data);

  return search_data.path;
}
