/* The GIMP -- an image manipulation program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * 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 "libgimpbase/gimpbase.h"
#include "libgimpthumb/gimpthumb.h"
#include "libgimpwidgets/gimpwidgets.h"

#include "widgets-types.h"

#include "config/gimpcoreconfig.h"

#include "core/gimp.h"
#include "core/gimpimagefile.h"
#include "core/gimpprogress.h"

#include "file/file-utils.h"

#include "gimpfiledialog.h" /* eek */
#include "gimpthumbbox.h"
#include "gimpview.h"
#include "gimpviewrenderer-frame.h"
#include "gimpwidgets-utils.h"

#include "gimp-intl.h"


/*  local function prototypes  */

static void     gimp_thumb_box_class_init (GimpThumbBoxClass *klass);
static void     gimp_thumb_box_init       (GimpThumbBox      *box);
static void     gimp_thumb_box_progress_iface_init (GimpProgressInterface *progress_iface);

static void     gimp_thumb_box_dispose    (GObject           *object);
static void     gimp_thumb_box_finalize   (GObject           *object);

static void     gimp_thumb_box_style_set  (GtkWidget         *widget,
                                           GtkStyle          *prev_style);

static GimpProgress *
                gimp_thumb_box_progress_start     (GimpProgress      *progress,
                                                   const gchar       *message,
                                                   gboolean           cancelable);
static void     gimp_thumb_box_progress_end       (GimpProgress      *progress);
static gboolean gimp_thumb_box_progress_is_active (GimpProgress      *progress);
static void     gimp_thumb_box_progress_set_value (GimpProgress      *progress,
                                                   gdouble            percentage);
static gdouble  gimp_thumb_box_progress_get_value (GimpProgress      *progress);

static void     gimp_thumb_box_progress_message   (GimpProgress      *progress,
                                                   Gimp              *gimp,
                                                   const gchar       *domain,
                                                   const gchar       *message);

static gboolean gimp_thumb_box_ebox_button_press  (GtkWidget         *widget,
                                                   GdkEventButton    *bevent,
                                                   GimpThumbBox      *box);
static void gimp_thumb_box_thumbnail_clicked      (GtkWidget         *widget,
                                                   GdkModifierType    state,
                                                   GimpThumbBox      *box);
static void gimp_thumb_box_imagefile_info_changed (GimpImagefile     *imagefile,
                                                   GimpThumbBox      *box);
static void gimp_thumb_box_thumb_state_notify     (GimpThumbnail     *thumb,
                                                   GParamSpec        *pspec,
                                                   GimpThumbBox      *box);
static void gimp_thumb_box_create_thumbnails      (GimpThumbBox      *box,
                                                   gboolean           force);
static void gimp_thumb_box_create_thumbnail       (GimpThumbBox      *box,
                                                   const gchar       *uri,
                                                   GimpThumbnailSize  size,
                                                   gboolean           force);
static gboolean gimp_thumb_box_auto_thumbnail     (GimpThumbBox      *box);


/*  private variables  */

static GtkFrameClass *parent_class = NULL;


GType
gimp_thumb_box_get_type (void)
{
  static GType box_type = 0;

  if (! box_type)
    {
      static const GTypeInfo box_info =
      {
        sizeof (GimpThumbBoxClass),
        (GBaseInitFunc)     NULL,
        (GBaseFinalizeFunc) NULL,
	(GClassInitFunc)    gimp_thumb_box_class_init,
        NULL,           /* class_finalize */
        NULL,           /* class_data     */
        sizeof (GimpThumbBox),
        0,              /* n_preallocs    */
        (GInstanceInitFunc) gimp_thumb_box_init,
      };

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

      box_type = g_type_register_static (GTK_TYPE_FRAME,
                                         "GimpThumbBox",
                                         &box_info, 0);

      g_type_add_interface_static (box_type, GIMP_TYPE_PROGRESS,
                                   &progress_iface_info);
    }

  return box_type;
}

static void
gimp_thumb_box_class_init (GimpThumbBoxClass *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->dispose   = gimp_thumb_box_dispose;
  object_class->finalize  = gimp_thumb_box_finalize;

  widget_class->style_set = gimp_thumb_box_style_set;
}

static void
gimp_thumb_box_init (GimpThumbBox *box)
{
  gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_IN);

  box->idle_id = 0;
}

static void
gimp_thumb_box_progress_iface_init (GimpProgressInterface *progress_iface)
{
  progress_iface->start     = gimp_thumb_box_progress_start;
  progress_iface->end       = gimp_thumb_box_progress_end;
  progress_iface->is_active = gimp_thumb_box_progress_is_active;
  progress_iface->set_value = gimp_thumb_box_progress_set_value;
  progress_iface->get_value = gimp_thumb_box_progress_get_value;

  progress_iface->message   = gimp_thumb_box_progress_message;
}

static void
gimp_thumb_box_dispose (GObject *object)
{
  GimpThumbBox *box = GIMP_THUMB_BOX (object);

  if (box->idle_id)
    {
      g_source_remove (box->idle_id);
      box->idle_id = 0;
    }

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

static void
gimp_thumb_box_finalize (GObject *object)
{
  GimpThumbBox *box = GIMP_THUMB_BOX (object);

  gimp_thumb_box_take_uris (box, NULL);

  if (box->imagefile)
    {
      g_object_unref (box->imagefile);
      box->imagefile = NULL;
    }

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

static void
gimp_thumb_box_style_set (GtkWidget *widget,
                          GtkStyle  *prev_style)
{
  GimpThumbBox *box = GIMP_THUMB_BOX (widget);
  GtkWidget    *ebox;

  GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style);

  gtk_widget_modify_bg (box->preview, GTK_STATE_NORMAL,
                        &widget->style->base[GTK_STATE_NORMAL]);
  gtk_widget_modify_bg (box->preview, GTK_STATE_INSENSITIVE,
                        &widget->style->base[GTK_STATE_NORMAL]);

  ebox = gtk_bin_get_child (GTK_BIN (widget));

  gtk_widget_modify_bg (ebox, GTK_STATE_NORMAL,
                        &widget->style->base[GTK_STATE_NORMAL]);
  gtk_widget_modify_bg (ebox, GTK_STATE_INSENSITIVE,
                        &widget->style->base[GTK_STATE_NORMAL]);
}

static GimpProgress *
gimp_thumb_box_progress_start (GimpProgress *progress,
                               const gchar  *message,
                               gboolean      cancelable)
{
  GimpThumbBox *box = GIMP_THUMB_BOX (progress);

  if (! box->progress_active)
    {
      GtkProgressBar *bar = GTK_PROGRESS_BAR (box->progress);

      gtk_progress_bar_set_fraction (bar, 0.0);

      box->progress_active = TRUE;

      return progress;
    }

  return NULL;
}

static void
gimp_thumb_box_progress_end (GimpProgress *progress)
{
  GimpThumbBox *box = GIMP_THUMB_BOX (progress);

  if (box->progress_active)
    {
      GtkProgressBar *bar = GTK_PROGRESS_BAR (box->progress);

      gtk_progress_bar_set_fraction (bar, 0.0);

      box->progress_active = FALSE;
    }
}

static gboolean
gimp_thumb_box_progress_is_active (GimpProgress *progress)
{
  GimpThumbBox *box = GIMP_THUMB_BOX (progress);

  return box->progress_active;
}

static void
gimp_thumb_box_progress_set_value (GimpProgress *progress,
                                   gdouble       percentage)
{
  GimpThumbBox *box = GIMP_THUMB_BOX (progress);

  if (box->progress_active)
    {
      GtkProgressBar *bar = GTK_PROGRESS_BAR (box->progress);

      gtk_progress_bar_set_fraction (bar, percentage);
    }
}

static gdouble
gimp_thumb_box_progress_get_value (GimpProgress *progress)
{
  GimpThumbBox *box = GIMP_THUMB_BOX (progress);

  if (box->progress_active)
    {
      GtkProgressBar *bar = GTK_PROGRESS_BAR (box->progress);

      return gtk_progress_bar_get_fraction (bar);
    }

  return 0.0;
}

static void
gimp_thumb_box_progress_message (GimpProgress      *progress,
                                 Gimp              *gimp,
                                 const gchar       *domain,
                                 const gchar       *message)
{
  /*  GimpThumbBox never shows any messages  */
}


/*  public functions  */

GtkWidget *
gimp_thumb_box_new (Gimp *gimp)
{
  GimpThumbBox   *box;
  GtkWidget      *vbox;
  GtkWidget      *vbox2;
  GtkWidget      *ebox;
  GtkWidget      *hbox;
  GtkWidget      *button;
  GtkWidget      *label;
  gchar          *str;
  gint            h, v;
  GtkRequisition  info_requisition;
  GtkRequisition  thumb_progress_requisition;
  GtkRequisition  progress_requisition;

  g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);

  box = g_object_new (GIMP_TYPE_THUMB_BOX, NULL);

  ebox = gtk_event_box_new ();
  gtk_container_add (GTK_CONTAINER (box), ebox);
  gtk_widget_show (ebox);

  g_signal_connect (ebox, "button_press_event",
                    G_CALLBACK (gimp_thumb_box_ebox_button_press),
                    box);

  str = g_strdup_printf (_("Click to update preview\n"
                           "%s  Click to force update even "
                           "if preview is up-to-date"),
                         gimp_get_mod_string (GDK_CONTROL_MASK));

  gimp_help_set_help_data (ebox, str, NULL);

  g_free (str);

  vbox = gtk_vbox_new (FALSE, 0);
  gtk_container_add (GTK_CONTAINER (ebox), vbox);
  gtk_widget_show (vbox);

  button = gtk_button_new ();
  gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
  gtk_widget_show (button);

  label = gtk_label_new_with_mnemonic (_("_Preview"));
  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
  gtk_container_add (GTK_CONTAINER (button), label);
  gtk_widget_show (label);

  g_signal_connect (button, "button_press_event",
                    G_CALLBACK (gtk_true),
                    NULL);
  g_signal_connect (button, "button_release_event",
                    G_CALLBACK (gtk_true),
                    NULL);
  g_signal_connect (button, "enter_notify_event",
                    G_CALLBACK (gtk_true),
                    NULL);
  g_signal_connect (button, "leave_notify_event",
                    G_CALLBACK (gtk_true),
                    NULL);

  vbox2 = gtk_vbox_new (FALSE, 2);
  gtk_container_set_border_width (GTK_CONTAINER (vbox2), 2);
  gtk_container_add (GTK_CONTAINER (vbox), vbox2);
  gtk_widget_show (vbox2);

  hbox = gtk_hbox_new (TRUE, 0);
  gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, FALSE, 0);
  gtk_widget_show (hbox);

  box->imagefile = gimp_imagefile_new (gimp, NULL);

  g_signal_connect (box->imagefile, "info_changed",
                    G_CALLBACK (gimp_thumb_box_imagefile_info_changed),
                    box);

  g_signal_connect (box->imagefile->thumbnail, "notify::thumb-state",
                    G_CALLBACK (gimp_thumb_box_thumb_state_notify),
                    box);

  gimp_view_renderer_get_frame_size (&h, &v);

  box->preview = gimp_view_new (GIMP_VIEWABLE (box->imagefile),
                                /* add padding for the shadow frame */
                                gimp->config->thumbnail_size + MAX (h, v),
                                0, FALSE);

  gtk_box_pack_start (GTK_BOX (hbox), box->preview, TRUE, FALSE, 10);
  gtk_widget_show (box->preview);

  gtk_label_set_mnemonic_widget (GTK_LABEL (label), box->preview);

  g_signal_connect (box->preview, "clicked",
                    G_CALLBACK (gimp_thumb_box_thumbnail_clicked),
                    box);

  box->filename = gtk_label_new (_("No selection"));
  gtk_label_set_line_wrap (GTK_LABEL (box->filename), TRUE);
  gtk_label_set_justify (GTK_LABEL (box->filename), GTK_JUSTIFY_CENTER);
  gtk_box_pack_start (GTK_BOX (vbox2), box->filename, FALSE, FALSE, 0);
  gtk_widget_show (box->filename);

  box->info = gtk_label_new (" \n \n \n ");
  gtk_misc_set_alignment (GTK_MISC (box->info), 0.5, 0.0);
  gtk_label_set_justify (GTK_LABEL (box->info), GTK_JUSTIFY_CENTER);
  gtk_box_pack_start (GTK_BOX (vbox2), box->info, FALSE, FALSE, 0);
  gtk_widget_show (box->info);

  box->progress = gtk_progress_bar_new ();
  gtk_progress_bar_set_text (GTK_PROGRESS_BAR (box->progress), "Fog");
  gtk_box_pack_end (GTK_BOX (vbox2), box->progress, FALSE, FALSE, 0);
  gtk_widget_set_no_show_all (box->progress, TRUE);
  /* don't gtk_widget_show (box->progress); */

  box->thumb_progress = gtk_progress_bar_new ();
  gtk_progress_bar_set_text (GTK_PROGRESS_BAR (box->thumb_progress), "Fog");
  gtk_box_pack_end (GTK_BOX (vbox2), box->thumb_progress, FALSE, FALSE, 0);
  gtk_widget_set_no_show_all (box->thumb_progress, TRUE);
  /* don't gtk_widget_show (box->thumb_progress); */

  /* eek */
  gtk_widget_size_request (box->info,           &info_requisition);
  gtk_widget_size_request (box->thumb_progress, &thumb_progress_requisition);
  gtk_widget_size_request (box->progress,       &progress_requisition);

  gtk_widget_set_size_request (box->info,
                               progress_requisition.width,
                               info_requisition.height);
  gtk_widget_set_size_request (box->filename,
                               progress_requisition.width, -1);

  gtk_widget_set_size_request (box->thumb_progress, -1,
                               thumb_progress_requisition.height);
  gtk_progress_bar_set_text (GTK_PROGRESS_BAR (box->thumb_progress), "");

  gtk_widget_set_size_request (box->progress, -1,
                               progress_requisition.height);
  gtk_progress_bar_set_text (GTK_PROGRESS_BAR (box->progress), "");

  return GTK_WIDGET (box);
}

void
gimp_thumb_box_set_uri (GimpThumbBox *box,
                        const gchar  *uri)
{
  g_return_if_fail (GIMP_IS_THUMB_BOX (box));

  if (box->idle_id)
    {
      g_source_remove (box->idle_id);
      box->idle_id = 0;
    }

  gimp_object_set_name (GIMP_OBJECT (box->imagefile), uri);

  if (uri)
    {
      gchar *basename;

      basename = file_utils_uri_to_utf8_basename (uri);
      gtk_label_set_text (GTK_LABEL (box->filename), basename);
      g_free (basename);
    }
  else
    {
      gtk_label_set_text (GTK_LABEL (box->filename), _("No selection"));
    }

  gtk_widget_set_sensitive (GTK_WIDGET (box), uri != NULL);
  gimp_imagefile_update (box->imagefile);
}

void
gimp_thumb_box_take_uris (GimpThumbBox *box,
                          GSList       *uris)
{
  g_return_if_fail (GIMP_IS_THUMB_BOX (box));

  if (box->uris)
    {
      g_slist_foreach (box->uris, (GFunc) g_free, NULL);
      g_slist_free (box->uris);
      box->uris = NULL;
    }

  box->uris = uris;
}


/*  private functions  */

static gboolean
gimp_thumb_box_ebox_button_press (GtkWidget      *widget,
                                  GdkEventButton *bevent,
                                  GimpThumbBox   *box)
{
  gimp_thumb_box_thumbnail_clicked (widget, bevent->state, box);

  return TRUE;
}

static void
gimp_thumb_box_thumbnail_clicked (GtkWidget       *widget,
                                  GdkModifierType  state,
                                  GimpThumbBox    *box)
{
  gimp_thumb_box_create_thumbnails (box,
                                    (state & GDK_CONTROL_MASK) ? TRUE : FALSE);
}

static void
gimp_thumb_box_imagefile_info_changed (GimpImagefile *imagefile,
                                       GimpThumbBox  *box)
{
  gtk_label_set_text (GTK_LABEL (box->info),
                      gimp_imagefile_get_desc_string (imagefile));
}

static void
gimp_thumb_box_thumb_state_notify (GimpThumbnail *thumb,
                                   GParamSpec    *pspec,
                                   GimpThumbBox  *box)
{
  if (box->idle_id)
    return;

  switch (thumb->thumb_state)
    {
    case GIMP_THUMB_STATE_NOT_FOUND:
    case GIMP_THUMB_STATE_OLD:
      box->idle_id =
        g_idle_add_full (G_PRIORITY_LOW,
                         (GSourceFunc) gimp_thumb_box_auto_thumbnail,
                         box, NULL);
      break;

    default:
      break;
    }
}

static void
gimp_thumb_box_create_thumbnails (GimpThumbBox *box,
                                  gboolean      force)
{
  Gimp           *gimp   = box->imagefile->gimp;
  GimpFileDialog *dialog = NULL;
  GtkWidget      *toplevel;
  GSList         *list;
  gint            n_uris;
  gint            i;

  if (gimp->config->thumbnail_size == GIMP_THUMBNAIL_SIZE_NONE ||
      ! gimp->config->layer_previews)
    return;

  toplevel = gtk_widget_get_toplevel (GTK_WIDGET (box));

  if (GIMP_IS_FILE_DIALOG (toplevel))
    dialog = GIMP_FILE_DIALOG (toplevel);

  gimp_set_busy (gimp);

  if (dialog)
    gimp_file_dialog_set_sensitive (dialog, FALSE);
  else
    gtk_widget_set_sensitive (toplevel, FALSE);

  if (box->uris)
    {
      gtk_widget_hide (box->info);
      gtk_widget_show (box->progress);
    }

  n_uris = g_slist_length (box->uris);

  if (n_uris > 1)
    {
      gchar *str;

      gtk_progress_bar_set_text (GTK_PROGRESS_BAR (box->thumb_progress), NULL);
      gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (box->thumb_progress),
                                     0.0);

      gtk_widget_show (box->thumb_progress);

      for (list = box->uris->next, i = 1;
           list;
           list = g_slist_next (list), i++)
        {
          str = g_strdup_printf (_("Thumbnail %d of %d"), i, n_uris);
          gtk_progress_bar_set_text (GTK_PROGRESS_BAR (box->thumb_progress),
                                     str);
          g_free (str);

          while (g_main_context_pending (NULL))
            g_main_context_iteration (NULL, FALSE);

          gimp_thumb_box_create_thumbnail (box,
                                           list->data,
                                           gimp->config->thumbnail_size,
                                           force);

          if (dialog && dialog->canceled)
            goto canceled;

          gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (box->thumb_progress),
                                         (gdouble) i /
                                         (gdouble) n_uris);

          while (g_main_context_pending (NULL))
            g_main_context_iteration (NULL, FALSE);
        }

      str = g_strdup_printf (_("Thumbnail %d of %d"), n_uris, n_uris);
      gtk_progress_bar_set_text (GTK_PROGRESS_BAR (box->thumb_progress), str);
      g_free (str);

      while (g_main_context_pending (NULL))
        g_main_context_iteration (NULL, FALSE);
    }

  if (box->uris)
    {
      gimp_thumb_box_create_thumbnail (box,
                                       box->uris->data,
                                       gimp->config->thumbnail_size,
                                       force);

      if (n_uris > 1)
        {
          gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (box->thumb_progress),
                                         1.0);

          while (g_main_context_pending (NULL))
            g_main_context_iteration (NULL, FALSE);
        }
    }

 canceled:

  if (n_uris > 1)
    gtk_widget_hide (box->thumb_progress);

  if (box->uris)
    {
      gtk_widget_hide (box->progress);
      gtk_widget_show (box->info);
    }

  if (dialog)
    gimp_file_dialog_set_sensitive (dialog, TRUE);
  else
    gtk_widget_set_sensitive (toplevel, TRUE);

  gimp_unset_busy (gimp);
}

static void
gimp_thumb_box_create_thumbnail (GimpThumbBox      *box,
                                 const gchar       *uri,
                                 GimpThumbnailSize  size,
                                 gboolean           force)
{
  gchar *filename = file_utils_filename_from_uri (uri);

  if (filename && g_file_test (filename, G_FILE_TEST_IS_REGULAR))
    {
      GimpThumbnail *thumb = box->imagefile->thumbnail;
      gchar         *basename;

      basename = file_utils_uri_to_utf8_basename (uri);
      gtk_label_set_text (GTK_LABEL (box->filename), basename);
      g_free (basename);

      gimp_object_set_name (GIMP_OBJECT (box->imagefile), uri);

      if (force ||
          (gimp_thumbnail_peek_thumb (thumb, size) < GIMP_THUMB_STATE_FAILED &&
           ! gimp_thumbnail_has_failed (thumb)))
        {
          Gimp *gimp = box->imagefile->gimp;

          gimp_imagefile_create_thumbnail (box->imagefile,
                                           gimp_get_user_context (gimp),
                                           GIMP_PROGRESS (box),
                                           size,
                                           !force);
        }
    }

  g_free (filename);
}

static gboolean
gimp_thumb_box_auto_thumbnail (GimpThumbBox *box)
{
  Gimp          *gimp  = box->imagefile->gimp;
  GimpThumbnail *thumb = box->imagefile->thumbnail;
  const gchar   *uri   = gimp_object_get_name (GIMP_OBJECT (box->imagefile));

  box->idle_id = 0;

  if (thumb->image_state == GIMP_THUMB_STATE_NOT_FOUND)
    return FALSE;

  switch (thumb->thumb_state)
    {
    case GIMP_THUMB_STATE_NOT_FOUND:
    case GIMP_THUMB_STATE_OLD:
      if (thumb->image_filesize < gimp->config->thumbnail_filesize_limit &&
          ! gimp_thumbnail_has_failed (thumb)                            &&
          file_utils_find_proc_by_extension (gimp->load_procs, uri))
        {
          if (thumb->image_filesize > 0)
            {
              gchar *size;
              gchar *text;

              size = gimp_memsize_to_string (thumb->image_filesize);
              text = g_strdup_printf ("%s\n%s",
                                      size, _("Creating Preview ..."));

              gtk_label_set_text (GTK_LABEL (box->info), text);

              g_free (text);
              g_free (size);
            }
          else
            {
              gtk_label_set_text (GTK_LABEL (box->info),
                                  _("Creating Preview ..."));
            }

          gimp_imagefile_create_thumbnail_weak (box->imagefile,
                                                gimp_get_user_context (gimp),
                                                GIMP_PROGRESS (box),
                                                gimp->config->thumbnail_size,
                                                TRUE);
        }
      break;

    default:
      break;
    }

  return FALSE;
}
