/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * gimpcomponenteditor.c
 * Copyright (C) 2003-2005 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 <gegl.h>
#include <gtk/gtk.h>

#include "libgimpbase/gimpbase.h"
#include "libgimpwidgets/gimpwidgets.h"

#include "widgets-types.h"

#include "core/gimpchannel.h"
#include "core/gimpimage.h"

#include "gimpcellrendererviewable.h"
#include "gimpcomponenteditor.h"
#include "gimpdnd.h"
#include "gimpdocked.h"
#include "gimpmenufactory.h"
#include "gimpviewrendererimage.h"
#include "gimpwidgets-utils.h"

#include "gimp-intl.h"


enum
{
  COLUMN_CHANNEL,
  COLUMN_VISIBLE,
  COLUMN_RENDERER,
  COLUMN_NAME,
  N_COLUMNS
};


static void gimp_component_editor_docked_iface_init (GimpDockedInterface *iface);

static void gimp_component_editor_set_context       (GimpDocked          *docked,
                                                     GimpContext         *context);

static void gimp_component_editor_set_image         (GimpImageEditor     *editor,
                                                     GimpImage           *image);

static void gimp_component_editor_create_components (GimpComponentEditor *editor);
static void gimp_component_editor_clear_components  (GimpComponentEditor *editor);
static void gimp_component_editor_clicked         (GtkCellRendererToggle *cellrenderertoggle,
                                                   gchar                 *path,
                                                   GdkModifierType        state,
                                                   GimpComponentEditor   *editor);
static gboolean gimp_component_editor_select        (GtkTreeSelection    *selection,
                                                     GtkTreeModel        *model,
                                                     GtkTreePath         *path,
                                                     gboolean             path_currently_selected,
                                                     gpointer             data);
static gboolean gimp_component_editor_button_press  (GtkWidget           *widget,
                                                     GdkEventButton      *bevent,
                                                     GimpComponentEditor *editor);
static void gimp_component_editor_renderer_update   (GimpViewRenderer    *renderer,
                                                     GimpComponentEditor *editor);
static void gimp_component_editor_mode_changed      (GimpImage           *image,
                                                     GimpComponentEditor *editor);
static void gimp_component_editor_alpha_changed     (GimpImage           *image,
                                                     GimpComponentEditor *editor);
static void gimp_component_editor_visibility_changed(GimpImage           *image,
                                                     GimpChannelType      channel,
                                                     GimpComponentEditor *editor);
static void gimp_component_editor_active_changed    (GimpImage           *image,
                                                     GimpChannelType      channel,
                                                     GimpComponentEditor *editor);
static GimpImage * gimp_component_editor_drag_component (GtkWidget       *widget,
                                                         GimpContext    **context,
                                                         GimpChannelType *channel,
                                                         gpointer         data);


G_DEFINE_TYPE_WITH_CODE (GimpComponentEditor, gimp_component_editor,
                         GIMP_TYPE_IMAGE_EDITOR,
                         G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED,
                                                gimp_component_editor_docked_iface_init))

#define parent_class gimp_component_editor_parent_class

static GimpDockedInterface *parent_docked_iface = NULL;


static void
gimp_component_editor_class_init (GimpComponentEditorClass *klass)
{
  GimpImageEditorClass *image_editor_class = GIMP_IMAGE_EDITOR_CLASS (klass);

  image_editor_class->set_image = gimp_component_editor_set_image;
}

static void
gimp_component_editor_init (GimpComponentEditor *editor)
{
  GtkWidget    *frame;
  GtkListStore *list;

  frame = gtk_frame_new (NULL);
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
  gtk_box_pack_start (GTK_BOX (editor), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  list = gtk_list_store_new (N_COLUMNS,
                             G_TYPE_INT,
                             G_TYPE_BOOLEAN,
                             GIMP_TYPE_VIEW_RENDERER,
                             G_TYPE_STRING);
  editor->model = GTK_TREE_MODEL (list);

  editor->view = GTK_TREE_VIEW (gtk_tree_view_new_with_model (editor->model));
  g_object_unref (list);

  gtk_tree_view_set_headers_visible (editor->view, FALSE);
  gtk_tree_view_set_enable_search (editor->view, FALSE);

  editor->eye_column = gtk_tree_view_column_new ();
  gtk_tree_view_append_column (editor->view, editor->eye_column);

  editor->eye_cell = gimp_cell_renderer_toggle_new (GIMP_ICON_VISIBLE);
  gtk_tree_view_column_pack_start (editor->eye_column, editor->eye_cell,
                                   FALSE);
  gtk_tree_view_column_set_attributes (editor->eye_column, editor->eye_cell,
                                       "active", COLUMN_VISIBLE,
                                       NULL);

  g_signal_connect (editor->eye_cell, "clicked",
                    G_CALLBACK (gimp_component_editor_clicked),
                    editor);

  editor->renderer_cell = gimp_cell_renderer_viewable_new ();
  gtk_tree_view_insert_column_with_attributes (editor->view,
                                               -1, NULL,
                                               editor->renderer_cell,
                                               "renderer", COLUMN_RENDERER,
                                               NULL);

  gtk_tree_view_insert_column_with_attributes (editor->view,
                                               -1, NULL,
                                               gtk_cell_renderer_text_new (),
                                               "text", COLUMN_NAME,
                                               NULL);

  gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (editor->view));
  gtk_widget_show (GTK_WIDGET (editor->view));

  g_signal_connect (editor->view, "button-press-event",
                    G_CALLBACK (gimp_component_editor_button_press),
                    editor);

  editor->selection = gtk_tree_view_get_selection (editor->view);
  gtk_tree_selection_set_mode (editor->selection, GTK_SELECTION_MULTIPLE);

  gtk_tree_selection_set_select_function (editor->selection,
                                          gimp_component_editor_select,
                                          editor, NULL);

  gimp_dnd_component_source_add (GTK_WIDGET (editor->view),
                                 gimp_component_editor_drag_component,
                                 editor);
}

static void
gimp_component_editor_docked_iface_init (GimpDockedInterface *iface)
{
  parent_docked_iface = g_type_interface_peek_parent (iface);

  if (! parent_docked_iface)
    parent_docked_iface = g_type_default_interface_peek (GIMP_TYPE_DOCKED);

  iface->set_context = gimp_component_editor_set_context;
}

static void
gimp_component_editor_set_context (GimpDocked  *docked,
                                   GimpContext *context)
{
  GimpComponentEditor *editor = GIMP_COMPONENT_EDITOR (docked);
  GtkTreeIter          iter;
  gboolean             iter_valid;

  parent_docked_iface->set_context (docked, context);

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

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

      gimp_view_renderer_set_context (renderer, context);
      g_object_unref (renderer);
    }
}

static void
gimp_component_editor_set_image (GimpImageEditor *editor,
                                 GimpImage       *image)
{
  GimpComponentEditor *component_editor = GIMP_COMPONENT_EDITOR (editor);

  if (editor->image)
    {
      gimp_component_editor_clear_components (component_editor);

      g_signal_handlers_disconnect_by_func (editor->image,
                                            gimp_component_editor_mode_changed,
                                            component_editor);
      g_signal_handlers_disconnect_by_func (editor->image,
                                            gimp_component_editor_alpha_changed,
                                            component_editor);
      g_signal_handlers_disconnect_by_func (editor->image,
                                            gimp_component_editor_visibility_changed,
                                            component_editor);
      g_signal_handlers_disconnect_by_func (editor->image,
                                            gimp_component_editor_active_changed,
                                            component_editor);
    }

  GIMP_IMAGE_EDITOR_CLASS (parent_class)->set_image (editor, image);

  if (editor->image)
    {
      gimp_component_editor_create_components (component_editor);

      g_signal_connect (editor->image, "mode-changed",
                        G_CALLBACK (gimp_component_editor_mode_changed),
                        component_editor);
      g_signal_connect (editor->image, "alpha-changed",
                        G_CALLBACK (gimp_component_editor_alpha_changed),
                        component_editor);
      g_signal_connect (editor->image, "component-visibility-changed",
                        G_CALLBACK (gimp_component_editor_visibility_changed),
                        component_editor);
      g_signal_connect (editor->image, "component-active-changed",
                        G_CALLBACK (gimp_component_editor_active_changed),
                        component_editor);
    }
}

GtkWidget *
gimp_component_editor_new (gint             view_size,
                           GimpMenuFactory *menu_factory)
{
  GimpComponentEditor *editor;

  g_return_val_if_fail (view_size > 0 &&
                        view_size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL);
  g_return_val_if_fail (GIMP_IS_MENU_FACTORY (menu_factory), NULL);

  editor = g_object_new (GIMP_TYPE_COMPONENT_EDITOR,
                         "menu-factory",    menu_factory,
                         "menu-identifier", "<Channels>",
                         "ui-path",         "/channels-popup",
                         NULL);

  gimp_component_editor_set_view_size (editor, view_size);

  return GTK_WIDGET (editor);
}

void
gimp_component_editor_set_view_size (GimpComponentEditor *editor,
                                     gint                 view_size)
{
  GtkWidget       *tree_widget;
  GtkStyleContext *tree_style;
  GtkBorder        border;
  GtkTreeIter      iter;
  gboolean         iter_valid;
  gint             icon_size;

  g_return_if_fail (GIMP_IS_COMPONENT_EDITOR (editor));
  g_return_if_fail (view_size >  0 &&
                    view_size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE);

  tree_widget = GTK_WIDGET (editor->view);
  tree_style  = gtk_widget_get_style_context (tree_widget);

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

  g_object_get (editor->eye_cell, "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 (editor->eye_cell, "icon-size", icon_size, NULL);

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

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

      gimp_view_renderer_set_size (renderer, view_size, 1);
      g_object_unref (renderer);
    }

  editor->view_size = view_size;

  gtk_tree_view_columns_autosize (editor->view);
}

static void
gimp_component_editor_create_components (GimpComponentEditor *editor)
{
  GimpImage       *image        = GIMP_IMAGE_EDITOR (editor)->image;
  gint             n_components = 0;
  GimpChannelType  components[MAX_CHANNELS];
  GEnumClass      *enum_class;
  gint             i;

  switch (gimp_image_get_base_type (image))
    {
    case GIMP_RGB:
      n_components  = 3;
      components[0] = GIMP_CHANNEL_RED;
      components[1] = GIMP_CHANNEL_GREEN;
      components[2] = GIMP_CHANNEL_BLUE;
      break;

    case GIMP_GRAY:
      n_components  = 1;
      components[0] = GIMP_CHANNEL_GRAY;
      break;

    case GIMP_INDEXED:
      n_components  = 1;
      components[0] = GIMP_CHANNEL_INDEXED;
      break;
    }

  if (gimp_image_has_alpha (image))
    components[n_components++] = GIMP_CHANNEL_ALPHA;

  enum_class = g_type_class_ref (GIMP_TYPE_CHANNEL_TYPE);

  for (i = 0; i < n_components; i++)
    {
      GimpViewRenderer *renderer;
      GtkTreeIter       iter;
      GEnumValue       *enum_value;
      const gchar      *desc;
      gboolean          visible;

      visible = gimp_image_get_component_visible (image, components[i]);

      renderer = gimp_view_renderer_new (GIMP_IMAGE_EDITOR (editor)->context,
                                         G_TYPE_FROM_INSTANCE (image),
                                         editor->view_size, 1, FALSE);
      gimp_view_renderer_set_viewable (renderer, GIMP_VIEWABLE (image));
      gimp_view_renderer_remove_idle (renderer);

      GIMP_VIEW_RENDERER_IMAGE (renderer)->channel = components[i];

      g_signal_connect (renderer, "update",
                        G_CALLBACK (gimp_component_editor_renderer_update),
                        editor);

      enum_value = g_enum_get_value (enum_class, components[i]);
      desc = gimp_enum_value_get_desc (enum_class, enum_value);

      gtk_list_store_append (GTK_LIST_STORE (editor->model), &iter);

      gtk_list_store_set (GTK_LIST_STORE (editor->model), &iter,
                          COLUMN_CHANNEL,  components[i],
                          COLUMN_VISIBLE,  visible,
                          COLUMN_RENDERER, renderer,
                          COLUMN_NAME,     desc,
                          -1);

      g_object_unref (renderer);

      if (gimp_image_get_component_active (image, components[i]))
        gtk_tree_selection_select_iter (editor->selection, &iter);
    }

  g_type_class_unref (enum_class);
}

static void
gimp_component_editor_clear_components (GimpComponentEditor *editor)
{
  gtk_list_store_clear (GTK_LIST_STORE (editor->model));

  /*  Clear the renderer so that it don't reference the viewable.
   *  See bug #149906.
   */
  g_object_set (editor->renderer_cell, "renderer", NULL, NULL);
}

static void
gimp_component_editor_clicked (GtkCellRendererToggle *cellrenderertoggle,
                               gchar                 *path_str,
                               GdkModifierType        state,
                               GimpComponentEditor   *editor)
{
  GtkTreePath *path;
  GtkTreeIter  iter;

  path = gtk_tree_path_new_from_string (path_str);

  if (gtk_tree_model_get_iter (editor->model, &iter, path))
    {
      GimpImage       *image = GIMP_IMAGE_EDITOR (editor)->image;
      GimpChannelType  channel;
      gboolean         active;

      gtk_tree_model_get (editor->model, &iter,
                          COLUMN_CHANNEL, &channel,
                          -1);
      g_object_get (cellrenderertoggle,
                    "active", &active,
                    NULL);

      gimp_image_set_component_visible (image, channel, !active);
      gimp_image_flush (image);
    }

  gtk_tree_path_free (path);
}

static gboolean
gimp_component_editor_select (GtkTreeSelection *selection,
                              GtkTreeModel     *model,
                              GtkTreePath      *path,
                              gboolean          path_currently_selected,
                              gpointer          data)
{
  GimpComponentEditor *editor = GIMP_COMPONENT_EDITOR (data);
  GtkTreeIter          iter;
  GimpChannelType      channel;
  gboolean             active;

  gtk_tree_model_get_iter (editor->model, &iter, path);
  gtk_tree_model_get (editor->model, &iter,
                      COLUMN_CHANNEL, &channel,
                      -1);

  active = gimp_image_get_component_active (GIMP_IMAGE_EDITOR (editor)->image,
                                            channel);

  return active != path_currently_selected;
}

static gboolean
gimp_component_editor_button_press (GtkWidget           *widget,
                                    GdkEventButton      *bevent,
                                    GimpComponentEditor *editor)
{
  GtkTreeViewColumn *column;
  GtkTreePath       *path;

  editor->clicked_component = -1;

  if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
                                     bevent->x,
                                     bevent->y,
                                     &path, &column, NULL, NULL))
    {
      GtkTreeIter     iter;
      GimpChannelType channel;
      gboolean        active;

      active = gtk_tree_selection_path_is_selected (editor->selection, path);

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

      gtk_tree_path_free (path);

      gtk_tree_model_get (editor->model, &iter,
                          COLUMN_CHANNEL, &channel,
                          -1);

      editor->clicked_component = channel;

      if (gdk_event_triggers_context_menu ((GdkEvent *) bevent))
        {
          gimp_editor_popup_menu_at_pointer (GIMP_EDITOR (editor), (GdkEvent *) bevent);
        }
      else if (bevent->type == GDK_BUTTON_PRESS && bevent->button == 1 &&
               column != editor->eye_column)
        {
          GimpImage *image = GIMP_IMAGE_EDITOR (editor)->image;

          gimp_image_set_component_active (image, channel, ! active);
          gimp_image_flush (image);
        }
    }

  return FALSE;
}

static gboolean
gimp_component_editor_get_iter (GimpComponentEditor *editor,
                                GimpChannelType      channel,
                                GtkTreeIter         *iter)
{
  gint index;

  index = gimp_image_get_component_index (GIMP_IMAGE_EDITOR (editor)->image,
                                          channel);

  if (index != -1)
    return gtk_tree_model_iter_nth_child (editor->model, iter, NULL, index);

  return FALSE;
}

static void
gimp_component_editor_renderer_update (GimpViewRenderer    *renderer,
                                       GimpComponentEditor *editor)
{
  GimpChannelType channel = GIMP_VIEW_RENDERER_IMAGE (renderer)->channel;
  GtkTreeIter     iter;

  if (gimp_component_editor_get_iter (editor, channel, &iter))
    {
      GtkTreePath *path;

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

static void
gimp_component_editor_mode_changed (GimpImage           *image,
                                    GimpComponentEditor *editor)
{
  gimp_component_editor_clear_components (editor);
  gimp_component_editor_create_components (editor);
}

static void
gimp_component_editor_alpha_changed (GimpImage           *image,
                                     GimpComponentEditor *editor)
{
  gimp_component_editor_clear_components (editor);
  gimp_component_editor_create_components (editor);
}

static void
gimp_component_editor_visibility_changed (GimpImage           *image,
                                          GimpChannelType      channel,
                                          GimpComponentEditor *editor)
{
  GtkTreeIter iter;

  if (gimp_component_editor_get_iter (editor, channel, &iter))
    {
      gboolean visible = gimp_image_get_component_visible (image, channel);

      gtk_list_store_set (GTK_LIST_STORE (editor->model), &iter,
                          COLUMN_VISIBLE, visible,
                          -1);
    }
}

static void
gimp_component_editor_active_changed (GimpImage           *image,
                                      GimpChannelType      channel,
                                      GimpComponentEditor *editor)
{
  GtkTreeIter iter;

  if (gimp_component_editor_get_iter (editor, channel, &iter))
    {
      gboolean active = gimp_image_get_component_active (image, channel);

      if (gtk_tree_selection_iter_is_selected (editor->selection, &iter) !=
          active)
        {
          if (active)
            gtk_tree_selection_select_iter (editor->selection, &iter);
          else
            gtk_tree_selection_unselect_iter (editor->selection, &iter);
        }
    }
}

static GimpImage *
gimp_component_editor_drag_component (GtkWidget        *widget,
                                      GimpContext     **context,
                                      GimpChannelType  *channel,
                                      gpointer          data)
{
  GimpComponentEditor *editor = GIMP_COMPONENT_EDITOR (data);

  if (GIMP_IMAGE_EDITOR (editor)->image &&
      editor->clicked_component != -1)
    {
      if (channel)
        *channel = editor->clicked_component;

      if (context)
        *context = GIMP_IMAGE_EDITOR (editor)->context;

      return GIMP_IMAGE_EDITOR (editor)->image;
    }

  return NULL;
}
