/* The GIMP -- an image manipulation program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * gimpcontainertreeview.c
 * Copyright (C) 2003-2004 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 2 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "config.h"

#include <string.h>

#include <gtk/gtk.h>

#include "libgimpwidgets/gimpwidgets.h"

#include "widgets-types.h"

#include "base/temp-buf.h"

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

#include "gimpcellrendererviewable.h"
#include "gimpcontainertreeview.h"
#include "gimpcontainertreeview-dnd.h"
#include "gimpcontainerview.h"
#include "gimpdnd.h"
#include "gimpviewrenderer.h"
#include "gimpwidgets-utils.h"


enum
{
  COLUMN_RENDERER,
  COLUMN_NAME,
  COLUMN_NAME_ATTRIBUTES,
  NUM_COLUMNS
};


static void     gimp_container_tree_view_class_init   (GimpContainerTreeViewClass *klass);
static void     gimp_container_tree_view_init         (GimpContainerTreeView      *view);

static void  gimp_container_tree_view_view_iface_init (GimpContainerViewInterface *view_iface);

static GObject *gimp_container_tree_view_constructor  (GType                   type,
                                                       guint                   n_params,
                                                       GObjectConstructParam  *params);

static void    gimp_container_tree_view_unrealize     (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 gpointer gimp_container_tree_view_insert_item  (GimpContainerView      *view,
                                                       GimpViewable           *viewable,
                                                       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 gboolean  gimp_container_tree_view_select_item (GimpContainerView      *view,
                                                       GimpViewable           *viewable,
                                                       gpointer                insert_data);
static void     gimp_container_tree_view_clear_items  (GimpContainerView      *view);
static void gimp_container_tree_view_set_preview_size (GimpContainerView      *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_press (GtkWidget              *widget,
                                                       GdkEventButton         *bevent,
                                                       GimpContainerTreeView  *tree_view);
static void gimp_container_tree_view_renderer_update  (GimpViewRenderer       *renderer,
                                                       GimpContainerTreeView  *tree_view);

static GimpViewable * gimp_container_tree_view_drag_viewable (GtkWidget       *widget,
                                                              gpointer         data);


static GimpContainerBoxClass      *parent_class      = NULL;
static GimpContainerViewInterface *parent_view_iface = NULL;


GType
gimp_container_tree_view_get_type (void)
{
  static GType view_type = 0;

  if (! view_type)
    {
      static const GTypeInfo view_info =
      {
        sizeof (GimpContainerTreeViewClass),
        NULL,           /* base_init */
        NULL,           /* base_finalize */
        (GClassInitFunc) gimp_container_tree_view_class_init,
        NULL,           /* class_finalize */
        NULL,           /* class_data */
        sizeof (GimpContainerTreeView),
        0,              /* n_preallocs */
        (GInstanceInitFunc) gimp_container_tree_view_init,
      };

      static const GInterfaceInfo view_iface_info =
      {
        (GInterfaceInitFunc) gimp_container_tree_view_view_iface_init,
        NULL,           /* iface_finalize */
        NULL            /* iface_data     */
      };

      view_type = g_type_register_static (GIMP_TYPE_CONTAINER_BOX,
                                          "GimpContainerTreeView",
                                          &view_info, 0);

      g_type_add_interface_static (view_type, GIMP_TYPE_CONTAINER_VIEW,
                                   &view_iface_info);
    }

  return view_type;
}

static void
gimp_container_tree_view_class_init (GimpContainerTreeViewClass *klass)
{
  GObjectClass   *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

  parent_class = g_type_class_peek_parent (klass);

  object_class->constructor = gimp_container_tree_view_constructor;

  widget_class->unrealize   = gimp_container_tree_view_unrealize;
  widget_class->unmap       = gimp_container_tree_view_unmap;
  widget_class->popup_menu  = gimp_container_tree_view_popup_menu;

  klass->drop_possible      = gimp_container_tree_view_real_drop_possible;
  klass->drop_viewable      = gimp_container_tree_view_real_drop_viewable;
  klass->drop_color         = NULL;
  klass->drop_uri_list      = NULL;
  klass->drop_svg           = NULL;
}

static void
gimp_container_tree_view_init (GimpContainerTreeView *tree_view)
{
  GimpContainerBox *box = GIMP_CONTAINER_BOX (tree_view);

  tree_view->n_model_columns = NUM_COLUMNS;

  tree_view->model_columns[COLUMN_RENDERER]        = GIMP_TYPE_VIEW_RENDERER;
  tree_view->model_columns[COLUMN_NAME]            = G_TYPE_STRING;
  tree_view->model_columns[COLUMN_NAME_ATTRIBUTES] = PANGO_TYPE_ATTR_LIST;

  tree_view->model_column_renderer        = COLUMN_RENDERER;
  tree_view->model_column_name            = COLUMN_NAME;
  tree_view->model_column_name_attributes = COLUMN_NAME_ATTRIBUTES;

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

static void
gimp_container_tree_view_view_iface_init (GimpContainerViewInterface *view_iface)
{
  parent_view_iface = g_type_interface_peek_parent (view_iface);

  view_iface->set_container    = gimp_container_tree_view_set_container;
  view_iface->insert_item      = gimp_container_tree_view_insert_item;
  view_iface->remove_item      = gimp_container_tree_view_remove_item;
  view_iface->reorder_item     = gimp_container_tree_view_reorder_item;
  view_iface->rename_item      = gimp_container_tree_view_rename_item;
  view_iface->select_item      = gimp_container_tree_view_select_item;
  view_iface->clear_items      = gimp_container_tree_view_clear_items;
  view_iface->set_preview_size = gimp_container_tree_view_set_preview_size;

  view_iface->insert_data_free = (GDestroyNotify) g_free;
}

static GObject *
gimp_container_tree_view_constructor (GType                  type,
                                      guint                  n_params,
                                      GObjectConstructParam *params)
{
  GimpContainerTreeView *tree_view;
  GimpContainerView     *view;
  GimpContainerBox      *box;
  GtkListStore          *list;
  GObject               *object;

  object = G_OBJECT_CLASS (parent_class)->constructor (type, n_params, params);

  tree_view = GIMP_CONTAINER_TREE_VIEW (object);
  view      = GIMP_CONTAINER_VIEW (object);
  box       = GIMP_CONTAINER_BOX (object);

  list = gtk_list_store_newv (tree_view->n_model_columns,
                              tree_view->model_columns);
  tree_view->model = GTK_TREE_MODEL (list);

  tree_view->view = g_object_new (GTK_TYPE_TREE_VIEW,
                                  "model",           list,
                                  "search-column",   COLUMN_NAME,
                                  "enable-search",   FALSE,
                                  "headers-visible", FALSE,
                                  NULL);
  g_object_unref (list);

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

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

  tree_view->name_cell = gtk_cell_renderer_text_new ();
  g_object_set (tree_view->name_cell, "xalign", 0.0, NULL);
  gtk_tree_view_column_pack_end (tree_view->main_column,
                                   tree_view->name_cell,
                                   FALSE);

  gtk_tree_view_column_set_attributes (tree_view->main_column,
                                       tree_view->name_cell,
                                       "text",       COLUMN_NAME,
                                       "attributes", COLUMN_NAME_ATTRIBUTES,
                                       NULL);

  g_signal_connect (tree_view->name_cell, "editing-canceled",
                    G_CALLBACK (gimp_container_tree_view_name_canceled),
                    tree_view);

  tree_view->renderer_cells = g_list_prepend (tree_view->renderer_cells,
                                              tree_view->renderer_cell);

  tree_view->selection = gtk_tree_view_get_selection (tree_view->view);

  g_signal_connect (tree_view->selection, "changed",
                    G_CALLBACK (gimp_container_tree_view_selection_changed),
                    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);

  return object;
}

static void
gimp_container_tree_view_unrealize (GtkWidget *widget)
{
  GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (widget);
  GtkTreeIter            iter;
  gboolean               iter_valid;

  for (iter_valid = gtk_tree_model_get_iter_first (tree_view->model, &iter);
       iter_valid;
       iter_valid = gtk_tree_model_iter_next (tree_view->model, &iter))
    {
      GimpViewRenderer *renderer;

      gtk_tree_model_get (tree_view->model, &iter,
                          COLUMN_RENDERER, &renderer,
                          -1);

      gimp_view_renderer_unrealize (renderer);
      g_object_unref (renderer);
    }

  GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
}

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

  if (tree_view->scroll_timeout_id)
    {
      g_source_remove (tree_view->scroll_timeout_id);
      tree_view->scroll_timeout_id = 0;
    }

  GTK_WIDGET_CLASS (parent_class)->unmap (widget);
}

static void
gimp_container_tree_view_menu_position (GtkMenu  *menu,
                                        gint     *x,
                                        gint     *y,
                                        gpointer  data)
{
  GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (data);
  GtkWidget             *widget    = GTK_WIDGET (tree_view->view);
  GtkTreeIter            selected_iter;

  gdk_window_get_origin (widget->window, x, y);

  if (GTK_WIDGET_NO_WINDOW (widget))
    {
      *x += widget->allocation.x;
      *y += widget->allocation.y;
    }

  if (gtk_tree_selection_get_selected (tree_view->selection, NULL,
                                       &selected_iter))
    {
      GtkTreePath  *path;
      GdkRectangle  cell_rect;
      gint          center;

      path = gtk_tree_model_get_path (tree_view->model, &selected_iter);
      gtk_tree_view_get_cell_area (tree_view->view, path,
                                   tree_view->main_column, &cell_rect);
      gtk_tree_path_free (path);

      center = cell_rect.y + cell_rect.height / 2;
      center = CLAMP (center, 0, widget->allocation.height);

      *x += widget->allocation.width / 2;
      *y += center;
    }
  else
    {
      *x += widget->style->xthickness;
      *y += widget->style->ythickness;
    }

  gimp_menu_position (menu, x, y);
}

static gboolean
gimp_container_tree_view_popup_menu (GtkWidget *widget)
{
  GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (widget);

  if (gtk_tree_selection_get_selected (tree_view->selection, NULL, NULL))
    {
      return gimp_editor_popup_menu (GIMP_EDITOR (tree_view),
                                     gimp_container_tree_view_menu_position,
                                     tree_view);
    }

  return FALSE;
}

GtkWidget *
gimp_container_tree_view_new (GimpContainer *container,
                              GimpContext   *context,
                              gint           preview_size,
                              gint           preview_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 (preview_size > 0 &&
                        preview_size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL);
  g_return_val_if_fail (preview_border_width >= 0 &&
                        preview_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_preview_size (view, preview_size,
                                        preview_border_width);

  if (container)
    gimp_container_view_set_container (view, container);

  if (context)
    gimp_container_view_set_context (view, context);

  return GTK_WIDGET (tree_view);
}

static void
gimp_container_tree_view_set (GimpContainerTreeView *tree_view,
                              GtkTreeIter           *iter,
                              GimpViewable          *viewable)
{
  GimpContainerView *view = GIMP_CONTAINER_VIEW (tree_view);
  GimpViewRenderer  *renderer;
  gchar             *name;
  gint               preview_size;
  gint               border_width;

  preview_size = gimp_container_view_get_preview_size (view, &border_width);

  renderer = gimp_view_renderer_new (G_TYPE_FROM_INSTANCE (viewable),
                                     preview_size, border_width,
                                     FALSE);
  gimp_view_renderer_set_viewable (renderer, viewable);
  gimp_view_renderer_remove_idle (renderer);

  g_signal_connect (renderer, "update",
                    G_CALLBACK (gimp_container_tree_view_renderer_update),
                    tree_view);

  name = gimp_viewable_get_description (viewable, NULL);

  gtk_list_store_set (GTK_LIST_STORE (tree_view->model), iter,
                      COLUMN_RENDERER, renderer,
                      COLUMN_NAME,     name,
                      -1);

  g_free (name);
  g_object_unref (renderer);
}

/*  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)
    {
      if (! container)
        {
          if (gimp_dnd_viewable_source_remove (GTK_WIDGET (tree_view->view),
                                               old_container->children_type))
            {
              gtk_drag_source_unset (GTK_WIDGET (tree_view->view));
            }

          g_signal_handlers_disconnect_by_func (tree_view->view,
                                                gimp_container_tree_view_button_press,
                                                tree_view);
        }
    }
  else if (container)
    {
      if (gimp_dnd_drag_source_set_by_type (GTK_WIDGET (tree_view->view),
                                            GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
                                            container->children_type,
                                            GDK_ACTION_COPY))
        {
          gimp_dnd_viewable_source_add (GTK_WIDGET (tree_view->view),
                                        container->children_type,
                                        gimp_container_tree_view_drag_viewable,
                                        tree_view);
        }

      /*  connect button_press_event after DND so we can keep the list from
       *  selecting the item on button2
       */
      g_signal_connect (tree_view->view, "button_press_event",
                        G_CALLBACK (gimp_container_tree_view_button_press),
                        tree_view);
    }

  parent_view_iface->set_container (view, container);
}

static gpointer
gimp_container_tree_view_insert_item (GimpContainerView *view,
                                      GimpViewable      *viewable,
                                      gint               index)
{
  GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view);
  GtkTreeIter           *iter;

  iter = g_new0 (GtkTreeIter, 1);

  if (index == -1)
    gtk_list_store_append (GTK_LIST_STORE (tree_view->model), iter);
  else
    gtk_list_store_insert (GTK_LIST_STORE (tree_view->model), iter, index);

  gimp_container_tree_view_set (tree_view, iter, viewable);

  return (gpointer) 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;

  if (iter)
    {
      gtk_list_store_remove (GTK_LIST_STORE (tree_view->model), iter);

      /*  if the store is empty after this remove, clear out renderers
       *  from all cells so they don't keep refing the viewables
       */
      if (! gtk_tree_model_iter_n_children (tree_view->model, NULL))
        {
          GList *list;

          for (list = tree_view->renderer_cells; list; list = g_list_next (list))
            g_object_set (list->data, "renderer", NULL, NULL);
        }
    }
}

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;

  if (iter)
    {
      GimpContainer *container;
      GtkTreePath   *path;
      GtkTreeIter    selected_iter;
      gboolean       selected;

      container = gimp_container_view_get_container (view);

      selected = gtk_tree_selection_get_selected (tree_view->selection,
                                                  NULL, &selected_iter);

      if (selected)
        {
          GimpViewRenderer *renderer;

          gtk_tree_model_get (tree_view->model, &selected_iter,
                              COLUMN_RENDERER, &renderer,
                              -1);

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

          g_object_unref (renderer);
        }

      if (new_index == -1 || new_index == container->num_children - 1)
        {
          gtk_list_store_move_before (GTK_LIST_STORE (tree_view->model),
                                      iter, NULL);
        }
      else if (new_index == 0)
        {
          gtk_list_store_move_after (GTK_LIST_STORE (tree_view->model),
                                     iter, NULL);
        }
      else
        {
          GtkTreeIter place_iter;
          gint        old_index;

          path = gtk_tree_model_get_path (tree_view->model, iter);
          old_index = gtk_tree_path_get_indices (path)[0];
          gtk_tree_path_free (path);

          if (new_index != old_index)
            {
              path = gtk_tree_path_new_from_indices (new_index, -1);
              gtk_tree_model_get_iter (tree_view->model, &place_iter, path);
              gtk_tree_path_free (path);

              if (new_index > old_index)
                gtk_list_store_move_after (GTK_LIST_STORE (tree_view->model),
                                           iter, &place_iter);
              else
                gtk_list_store_move_before (GTK_LIST_STORE (tree_view->model),
                                            iter, &place_iter);
            }
        }

      if (selected)
        gimp_container_view_select_item (view, viewable);
    }
}

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 (iter)
    {
      gchar *name = gimp_viewable_get_description (viewable, NULL);

      gtk_list_store_set (GTK_LIST_STORE (tree_view->model), iter,
                          COLUMN_NAME, name,
                          -1);

      g_free (name);
    }
}

static gboolean
gimp_container_tree_view_select_item (GimpContainerView *view,
                                      GimpViewable      *viewable,
                                      gpointer           insert_data)
{
  GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view);
  GtkTreeIter           *iter      = (GtkTreeIter *) insert_data;

  if (iter)
    {
      GtkTreePath *path;

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

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

      gtk_tree_view_set_cursor (tree_view->view, path, NULL, FALSE);

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

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

      gtk_tree_path_free (path);
    }
  else
    {
      gtk_tree_selection_unselect_all (tree_view->selection);
    }

  return TRUE;
}

static void
gimp_container_tree_view_clear_items (GimpContainerView *view)
{
  GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view);

  gtk_list_store_clear (GTK_LIST_STORE (tree_view->model));

  /*  clear out renderers from all cells so they don't keep refing the
   *  viewables
   */
  if (! gtk_tree_model_iter_n_children (tree_view->model, NULL))
    {
      GList *list;

      for (list = tree_view->renderer_cells; list; list = g_list_next (list))
        g_object_set (list->data, "renderer", NULL, NULL);
    }

  parent_view_iface->clear_items (view);
}

static void
gimp_container_tree_view_set_preview_size (GimpContainerView *view)
{
  GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view);
  GtkWidget             *tree_widget;
  GtkTreeIter            iter;
  gboolean               iter_valid;
  GList                 *list;
  gint                   preview_size;
  gint                   border_width;

  preview_size = gimp_container_view_get_preview_size (view, &border_width);

  tree_widget = GTK_WIDGET (tree_view->view);

  for (list = tree_view->toggle_cells; list; list = g_list_next (list))
    {
      gchar       *stock_id;
      GtkIconSize  icon_size;

      g_object_get (list->data, "stock-id", &stock_id, NULL);

      if (stock_id)
        {
          icon_size = gimp_get_icon_size (tree_widget,
                                          stock_id,
                                          GTK_ICON_SIZE_BUTTON,
                                          preview_size -
                                          2 * tree_widget->style->xthickness,
                                          preview_size -
                                          2 * tree_widget->style->ythickness);

          g_object_set (list->data, "stock-size", icon_size, NULL);

          g_free (stock_id);
        }
    }

  for (iter_valid = gtk_tree_model_get_iter_first (tree_view->model, &iter);
       iter_valid;
       iter_valid = gtk_tree_model_iter_next (tree_view->model, &iter))
    {
      GimpViewRenderer *renderer;

      gtk_tree_model_get (tree_view->model, &iter,
                          COLUMN_RENDERER, &renderer,
                          -1);

      gimp_view_renderer_set_size (renderer, preview_size, border_width);
      g_object_unref (renderer);
    }

  gtk_tree_view_columns_autosize (tree_view->view);
}


/*  callbacks  */

static void
gimp_container_tree_view_name_canceled (GtkCellRendererText   *cell,
                                        GimpContainerTreeView *tree_view)
{
  GtkTreeIter iter;

  if (gtk_tree_selection_get_selected (tree_view->selection, NULL, &iter))
    {
      GimpViewRenderer *renderer;
      gchar            *name;

      gtk_tree_model_get (tree_view->model, &iter,
                          tree_view->model_column_renderer, &renderer,
                          -1);

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

      gtk_list_store_set (GTK_LIST_STORE (tree_view->model), &iter,
                          tree_view->model_column_name, name,
                          -1);

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

static void
gimp_container_tree_view_selection_changed (GtkTreeSelection      *selection,
                                            GimpContainerTreeView *tree_view)
{
  GtkTreeIter iter;

  if (gtk_tree_selection_get_selected (selection, NULL, &iter))
    {
      GimpViewRenderer *renderer;

      gtk_tree_model_get (tree_view->model, &iter,
                          COLUMN_RENDERER, &renderer,
                          -1);

      gimp_container_view_item_selected (GIMP_CONTAINER_VIEW (tree_view),
                                         renderer->viewable);
      g_object_unref (renderer);
    }
}

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 (renderer->visible &&
          gtk_tree_view_column_cell_get_position (column, renderer,
                                                  &start, &width))
        {
          gint x;

          if (rtl)
            x = column_area->x + column_area->width - start - width;
          else
            x = start + column_area->x;

          if (tree_x >= x + renderer->xpad &&
              tree_x < x + width - renderer->xpad)
            {
              return renderer;
            }
        }
    }

  return NULL;
}

static gboolean
gimp_container_tree_view_button_press (GtkWidget             *widget,
                                       GdkEventButton        *bevent,
                                       GimpContainerTreeView *tree_view)
{
  GimpContainerView *container_view = GIMP_CONTAINER_VIEW (tree_view);
  GtkTreeViewColumn *column;
  GtkTreePath       *path;
  gint               x;

  tree_view->dnd_viewable = NULL;

  if (! GTK_WIDGET_HAS_FOCUS (widget))
    gtk_widget_grab_focus (widget);

  /*  Actually gtk_tree_view_get_path_at_pos() should take care of the
   *  render direction, but unfortunately it doesn't seem to do that.
   */
  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
    x = MAX (widget->requisition.width, widget->allocation.width) - bevent->x;
  else
    x = bevent->x;

  if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
                                     x, bevent->y,
                                     &path, &column, NULL, NULL))
    {
      GimpViewRenderer         *renderer;
      GimpCellRendererToggle   *toggled_cell = NULL;
      GimpCellRendererViewable *clicked_cell = NULL;
      GtkCellRenderer          *edit_cell    = NULL;
      GdkRectangle              column_area;
      GtkTreeIter               iter;

      gtk_tree_model_get_iter (tree_view->model, &iter, path);

      gtk_tree_model_get (tree_view->model, &iter,
                          COLUMN_RENDERER, &renderer,
                          -1);

      tree_view->dnd_viewable = renderer->viewable;

      gtk_tree_view_get_background_area (tree_view->view, path,
                                         column, &column_area);

      gtk_tree_view_column_cell_set_cell_data (column,
                                               tree_view->model,
                                               &iter,
                                               FALSE, FALSE);

      toggled_cell = (GimpCellRendererToggle *)
        gimp_container_tree_view_find_click_cell (widget,
                                                  tree_view->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->renderer_cells,
                                                    column, &column_area,
                                                    bevent->x, bevent->y);

      if (! toggled_cell && ! clicked_cell)
        edit_cell =
          gimp_container_tree_view_find_click_cell (widget,
                                                    tree_view->editable_cells,
                                                    column, &column_area,
                                                    bevent->x, bevent->y);

      g_object_ref (tree_view);

      switch (bevent->button)
        {
        case 1:
          if (bevent->type == GDK_BUTTON_PRESS)
            {
              gboolean success = TRUE;

              /*  don't select item if a toggle was clicked */
              if (! toggled_cell)
                success = gimp_container_view_item_selected (container_view,
                                                             renderer->viewable);

              /*  a callback invoked by selecting the item may have
               *  destroyed us, so check if the container is still there
               */
              if (gimp_container_view_get_container (container_view))
                {
                  /*  another row may have been set by selecting  */
                  gtk_tree_view_column_cell_set_cell_data (column,
                                                           tree_view->model,
                                                           &iter,
                                                           FALSE, FALSE);

                  if (toggled_cell || clicked_cell)
                    {
                      gchar *path_str = gtk_tree_path_to_string (path);

                      if (toggled_cell)
                        {
                          gimp_cell_renderer_toggle_clicked (toggled_cell,
                                                             path_str,
                                                             bevent->state);
                        }
                      else if (clicked_cell)
                        {
                          gimp_cell_renderer_viewable_clicked (clicked_cell,
                                                               path_str,
                                                               bevent->state);
                        }

                      g_free (path_str);
                    }
                }
            }
          else if (bevent->type == GDK_2BUTTON_PRESS)
            {
              gboolean success = TRUE;

              /*  don't select item if a toggle was clicked */
              if (! toggled_cell)
                success = gimp_container_view_item_selected (container_view,
                                                             renderer->viewable);

              if (success)
                {
                  if (edit_cell)
                    {
                      if (edit_cell == tree_view->name_cell)
                        {
                          const gchar *real_name;

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

                          gtk_list_store_set (GTK_LIST_STORE (tree_view->model),
                                              &iter,
                                              tree_view->model_column_name,
                                              real_name,
                                              -1);
                        }

                      gtk_tree_view_set_cursor_on_cell (tree_view->view, path,
                                                        column, edit_cell, TRUE);
                    }
                  else if (! toggled_cell) /* ignore double click on toggles */
                    {
                      gimp_container_view_item_activated (container_view,
                                                          renderer->viewable);
                    }
                }
            }
          break;

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

        case 3:
          if (gimp_container_view_item_selected (container_view,
                                                 renderer->viewable))
            {
              if (gimp_container_view_get_container (container_view))
                gimp_container_view_item_context (container_view,
                                                  renderer->viewable);
            }
          break;

        default:
          break;
        }

      g_object_unref (tree_view);

      gtk_tree_path_free (path);
      g_object_unref (renderer);
    }

  return TRUE;
}

static void
gimp_container_tree_view_renderer_update (GimpViewRenderer      *renderer,
                                          GimpContainerTreeView *tree_view)
{
  GimpContainerView *view = GIMP_CONTAINER_VIEW (tree_view);
  GtkTreeIter       *iter;

  iter = gimp_container_view_lookup (view, renderer->viewable);

  if (iter)
    {
      GtkTreePath *path;

      path = gtk_tree_model_get_path (tree_view->model, iter);
      gtk_tree_model_row_changed (tree_view->model, path, iter);
      gtk_tree_path_free (path);
    }
}

static GimpViewable *
gimp_container_tree_view_drag_viewable (GtkWidget *widget,
                                        gpointer   data)
{
  GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (data);

  return tree_view->dnd_viewable;
}
