/* vi:set et ai sw=2 sts=2 ts=2: */
/*-
 * Copyright (c) 2005-2006 Benedikt Meurer <benny@xfce.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
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif

#include "thunar/thunar-application.h"
#include "thunar/thunar-emblem-chooser.h"
#include "thunar/thunar-gobject-extensions.h"
#include "thunar/thunar-io-jobs.h"
#include "thunar/thunar-private.h"
#include "thunar/thunar-util.h"
#include "thunar/thunar-window.h"

#include <libxfce4util/libxfce4util.h>


/* Property identifiers */
enum
{
  PROP_0,
  PROP_FILES,
};



static void
thunar_emblem_chooser_dispose (GObject *object);
static void
thunar_emblem_chooser_get_property (GObject    *object,
                                    guint       prop_id,
                                    GValue     *value,
                                    GParamSpec *pspec);
static void
thunar_emblem_chooser_set_property (GObject      *object,
                                    guint         prop_id,
                                    const GValue *value,
                                    GParamSpec   *pspec);
static void
thunar_emblem_chooser_scale_changed (GObject    *object,
                                     GParamSpec *pspec,
                                     gpointer    user_data);
static void
thunar_emblem_chooser_realize (GtkWidget *widget);
static void
thunar_emblem_chooser_unrealize (GtkWidget *widget);
static void
thunar_emblem_chooser_button_toggled (GtkToggleButton     *button,
                                      ThunarEmblemChooser *chooser);
static void
thunar_emblem_chooser_file_changed (ThunarEmblemChooser *chooser);
static void
thunar_emblem_chooser_theme_changed (GtkIconTheme        *icon_theme,
                                     ThunarEmblemChooser *chooser);
static void
thunar_emblem_chooser_create_buttons (ThunarEmblemChooser *chooser);
static GtkWidget *
thunar_emblem_chooser_create_button (ThunarEmblemChooser *chooser,
                                     const gchar         *emblem);
static GList *
thunar_emblem_chooser_get_files (const ThunarEmblemChooser *chooser);
static void
thunar_emblem_chooser_set_files (ThunarEmblemChooser *chooser,
                                 GList               *files);



struct _ThunarEmblemChooserClass
{
  GtkScrolledWindowClass __parent__;
};

struct _ThunarEmblemChooser
{
  GtkScrolledWindow __parent__;

  GtkIconTheme *icon_theme;
  GList        *files;
  GtkWidget    *table;

  ThunarJob *emblem_change_job;
  gulong     emblem_change_job_finish_signal;
};



G_DEFINE_TYPE (ThunarEmblemChooser, thunar_emblem_chooser, GTK_TYPE_SCROLLED_WINDOW)



static void
thunar_emblem_chooser_class_init (ThunarEmblemChooserClass *klass)
{
  GtkWidgetClass *gtkwidget_class;
  GObjectClass   *gobject_class;

  gobject_class = G_OBJECT_CLASS (klass);
  gobject_class->dispose = thunar_emblem_chooser_dispose;
  gobject_class->get_property = thunar_emblem_chooser_get_property;
  gobject_class->set_property = thunar_emblem_chooser_set_property;

  gtkwidget_class = GTK_WIDGET_CLASS (klass);
  gtkwidget_class->realize = thunar_emblem_chooser_realize;
  gtkwidget_class->unrealize = thunar_emblem_chooser_unrealize;

  /**
   * ThunarEmblemChooser::file:
   *
   * The file for which emblems should be choosen.
   **/
  g_object_class_install_property (gobject_class,
                                   PROP_FILES,
                                   g_param_spec_boxed ("files", "files", "files",
                                                       THUNARX_TYPE_FILE_INFO_LIST,
                                                       EXO_PARAM_READWRITE));
}



static void
thunar_emblem_chooser_init (ThunarEmblemChooser *chooser)
{
  GtkWidget *viewport;

  /* setup the scrolled window instance */
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (chooser), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (chooser), NULL);
  gtk_scrolled_window_set_vadjustment (GTK_SCROLLED_WINDOW (chooser), NULL);

  /* setup the viewport */
  viewport = gtk_viewport_new (gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (chooser)),
                               gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser)));
  gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE);
  gtk_container_add (GTK_CONTAINER (chooser), viewport);
  gtk_widget_show (viewport);

  /* setup the wrap table */
  chooser->table = g_object_new (GTK_TYPE_FLOW_BOX,
                                 "homogeneous", TRUE,
                                 "column-spacing", 12,
                                 "row-spacing", 12,
                                 NULL);
  gtk_container_add (GTK_CONTAINER (viewport), chooser->table);
  gtk_widget_show (chooser->table);

  g_signal_connect (G_OBJECT (chooser), "notify::scale-factor", G_CALLBACK (thunar_emblem_chooser_scale_changed), NULL);

  chooser->emblem_change_job = NULL;
  chooser->emblem_change_job_finish_signal = 0;
}



static void
thunar_emblem_chooser_dispose (GObject *object)
{
  ThunarEmblemChooser *chooser = THUNAR_EMBLEM_CHOOSER (object);

  if (chooser->emblem_change_job != NULL && chooser->emblem_change_job_finish_signal != 0)
    g_signal_handler_disconnect (chooser->emblem_change_job, chooser->emblem_change_job_finish_signal);

  /* disconnect from the file */
  thunar_emblem_chooser_set_files (chooser, NULL);

  (*G_OBJECT_CLASS (thunar_emblem_chooser_parent_class)->dispose) (object);
}



static void
thunar_emblem_chooser_get_property (GObject    *object,
                                    guint       prop_id,
                                    GValue     *value,
                                    GParamSpec *pspec)
{
  ThunarEmblemChooser *chooser = THUNAR_EMBLEM_CHOOSER (object);

  switch (prop_id)
    {
    case PROP_FILES:
      g_value_set_boxed (value, thunar_emblem_chooser_get_files (chooser));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}



static void
thunar_emblem_chooser_set_property (GObject      *object,
                                    guint         prop_id,
                                    const GValue *value,
                                    GParamSpec   *pspec)
{
  ThunarEmblemChooser *chooser = THUNAR_EMBLEM_CHOOSER (object);

  switch (prop_id)
    {
    case PROP_FILES:
      thunar_emblem_chooser_set_files (chooser, g_value_get_boxed (value));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}



static void
thunar_emblem_chooser_scale_changed (GObject    *object,
                                     GParamSpec *pspec,
                                     gpointer    user_data)
{
  gtk_widget_queue_draw (GTK_WIDGET (object));
}



static void
thunar_emblem_chooser_realize (GtkWidget *widget)
{
  ThunarEmblemChooser *chooser = THUNAR_EMBLEM_CHOOSER (widget);

  /* let the GtkWidget class perform the realization */
  (*GTK_WIDGET_CLASS (thunar_emblem_chooser_parent_class)->realize) (widget);

  /* determine the icon theme for the new screen */
  chooser->icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget));
  g_signal_connect (G_OBJECT (chooser->icon_theme), "changed",
                    G_CALLBACK (thunar_emblem_chooser_theme_changed),
                    chooser);
  g_object_ref (G_OBJECT (chooser->icon_theme));

  /* create the emblem buttons */
  thunar_emblem_chooser_create_buttons (chooser);
}



static void
thunar_emblem_chooser_unrealize (GtkWidget *widget)
{
  ThunarEmblemChooser *chooser = THUNAR_EMBLEM_CHOOSER (widget);

  /* drop all check buttons */
  gtk_container_foreach (GTK_CONTAINER (chooser->table),
                         (GtkCallback) (void (*) (void)) gtk_widget_destroy,
                         NULL);

  /* release our reference on the icon theme */
  g_signal_handlers_disconnect_by_func (G_OBJECT (chooser->icon_theme), thunar_emblem_chooser_theme_changed, chooser);
  g_object_unref (G_OBJECT (chooser->icon_theme));
  chooser->icon_theme = NULL;

  /* let the GtkWidget class perform the unrealization */
  (*GTK_WIDGET_CLASS (thunar_emblem_chooser_parent_class)->unrealize) (widget);
}



static void
emblem_change_job_finished (gpointer data)
{
  ThunarEmblemChooser *chooser = THUNAR_EMBLEM_CHOOSER (data);
  GList               *lp;
  GList               *windows;
  ThunarApplication   *application;

  /* Re-enable listening to the "changed" signal of the files */
  for (lp = chooser->files; lp != NULL; lp = lp->next)
    g_signal_handlers_unblock_by_func (lp->data, thunar_emblem_chooser_file_changed, chooser);

  /* redraw all windows in order to show emblem changes */
  application = thunar_application_get ();
  windows = thunar_application_get_windows (application);
  for (lp = windows; lp != NULL; lp = lp->next)
    thunar_window_queue_redraw (lp->data);
  g_list_free (windows);

  g_object_unref (chooser->emblem_change_job);
  chooser->emblem_change_job = NULL;
  chooser->emblem_change_job_finish_signal = 0;
}



static void
thunar_emblem_chooser_button_toggled (GtkToggleButton     *button,
                                      ThunarEmblemChooser *chooser)
{
  const gchar *emblem_name;
  GList       *emblem_names = NULL;
  GList       *lp;
  GtkWidget   *box_child;
  GList       *children;
  GObject     *child;


  _thunar_return_if_fail (GTK_IS_TOGGLE_BUTTON (button));
  _thunar_return_if_fail (THUNAR_IS_EMBLEM_CHOOSER (chooser));

  /* we just ignore toggle events if no file is set */
  if (G_LIKELY (chooser->files == NULL))
    return;

  /* once clicked, it is active or inactive */
  gtk_toggle_button_set_inconsistent (button, FALSE);

  children = gtk_container_get_children (GTK_CONTAINER (chooser->table));
  for (lp = children; lp != NULL; lp = lp->next)
    {
      child = G_OBJECT (lp->data);
      if (GTK_IS_FLOW_BOX_CHILD (child))
        child = G_OBJECT (gtk_bin_get_child (GTK_BIN (child)));

      emblem_name = g_object_get_data (child, I_ ("thunar-emblem"));

      if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (child)))
        emblem_names = g_list_append (emblem_names, (gchar *) emblem_name);
    }
  g_list_free (children);

  /* limit the number of selectable emblems */
  if (g_list_length (emblem_names) > MAX_EMBLEMS_PER_FILE)
    {
      GtkWidget *toplevel;
      GtkWindow *window = NULL;

      /* determine the toplevel window for the chooser */
      toplevel = gtk_widget_get_toplevel (GTK_WIDGET (chooser));
      if (toplevel != NULL)
        window = GTK_WINDOW (toplevel);

      g_signal_handlers_block_by_func (G_OBJECT (button), thunar_emblem_chooser_button_toggled, chooser);
      gtk_toggle_button_set_active (button, FALSE);
      g_signal_handlers_unblock_by_func (G_OBJECT (button), thunar_emblem_chooser_button_toggled, chooser);

      gchar *message = g_strdup_printf (_("A maximum of %u emblems is supported per file."), MAX_EMBLEMS_PER_FILE);
      xfce_dialog_show_warning (window, message, _("Too many emblems selected"));
      g_free (message);
      g_list_free (emblem_names);
      return;
    }

  /* Disable listening to the "changed" signal of the files to prevent lag */
  for (lp = chooser->files; lp != NULL; lp = lp->next)
    g_signal_handlers_block_by_func (lp->data, thunar_emblem_chooser_file_changed, chooser);

  if (g_list_length (emblem_names) == 0)
    {
      chooser->emblem_change_job = thunar_io_jobs_clear_metadata_for_files (chooser->files,
                                                                            "emblems", NULL);
    }
  else
    {
      gchar *emblem_names_joined = NULL;
      emblem_names_joined = thunar_util_strjoin_list (emblem_names, THUNAR_METADATA_STRING_DELIMETER);
      chooser->emblem_change_job = thunar_io_jobs_set_metadata_for_files (chooser->files, THUNAR_GTYPE_STRINGV,
                                                                          "emblems", emblem_names_joined, NULL);
      g_free (emblem_names_joined);
    }

  chooser->emblem_change_job_finish_signal =
  g_signal_connect_swapped (chooser->emblem_change_job, "finished",
                            G_CALLBACK (emblem_change_job_finished), chooser);

  exo_job_launch (EXO_JOB (chooser->emblem_change_job));

  g_list_free (emblem_names);

  /* select and give focus to the GtkFlowBox child containing the button */
  box_child = gtk_widget_get_parent (GTK_WIDGET (button));
  if (GTK_IS_FLOW_BOX_CHILD (box_child))
    {
      gtk_flow_box_select_child (GTK_FLOW_BOX (chooser->table), GTK_FLOW_BOX_CHILD (box_child));
      gtk_widget_grab_focus (GTK_WIDGET (box_child));
    }
}



static void
thunar_emblem_chooser_file_changed (ThunarEmblemChooser *chooser)
{
  const gchar *emblem_name;
  GHashTable  *emblem_names;
  GList       *file_emblems;
  GList       *children;
  GList       *lp, *li;
  GObject     *child;
  guint       *count;
  guint        n_files = 0;
  guint        n_emblems = 0;

  _thunar_return_if_fail (THUNAR_IS_EMBLEM_CHOOSER (chooser));

  emblem_names = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);

  /* determine the emblems set for the files */
  for (lp = chooser->files; lp != NULL; lp = lp->next)
    {
      file_emblems = thunar_file_get_emblem_names (THUNAR_FILE (lp->data));
      for (n_emblems = 0, li = file_emblems; li != NULL; li = li->next, n_emblems++)
        {
          /* dont load more emblems than we can display */
          if (n_emblems >= MAX_EMBLEMS_PER_FILE)
            break;

          count = g_hash_table_lookup (emblem_names, li->data);
          if (count == NULL)
            {
              count = g_new0 (guint, 1);
              g_hash_table_insert (emblem_names, g_strdup (li->data), count);
            }

          *count = *count + 1;
        }
      g_list_free_full (file_emblems, g_free);

      n_files++;
    }

  /* toggle the button states appropriately */
  children = gtk_container_get_children (GTK_CONTAINER (chooser->table));
  for (lp = children; lp != NULL; lp = lp->next)
    {
      child = G_OBJECT (lp->data);
      if (GTK_IS_FLOW_BOX_CHILD (child))
        child = G_OBJECT (gtk_bin_get_child (GTK_BIN (child)));

      emblem_name = g_object_get_data (child, I_ ("thunar-emblem"));
      count = g_hash_table_lookup (emblem_names, emblem_name);

      g_signal_handlers_block_by_func (child, thunar_emblem_chooser_button_toggled, chooser);

      if (count == NULL || *count == n_files)
        {
          gtk_toggle_button_set_inconsistent (GTK_TOGGLE_BUTTON (child), FALSE);
          gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (child), count != NULL);
        }
      else
        {
          gtk_toggle_button_set_inconsistent (GTK_TOGGLE_BUTTON (child), TRUE);
        }

      g_signal_handlers_unblock_by_func (child, thunar_emblem_chooser_button_toggled, chooser);
    }
  g_list_free (children);

  g_hash_table_destroy (emblem_names);
}



static void
thunar_emblem_chooser_theme_changed (GtkIconTheme        *icon_theme,
                                     ThunarEmblemChooser *chooser)
{
  _thunar_return_if_fail (GTK_IS_ICON_THEME (icon_theme));
  _thunar_return_if_fail (THUNAR_IS_EMBLEM_CHOOSER (chooser));
  _thunar_return_if_fail (chooser->icon_theme == icon_theme);

  /* drop the current buttons */
  gtk_container_foreach (GTK_CONTAINER (chooser->table),
                         (GtkCallback) (void (*) (void)) gtk_widget_destroy,
                         NULL);

  /* create buttons for the new theme */
  thunar_emblem_chooser_create_buttons (chooser);
}



static void
thunar_emblem_chooser_create_buttons (ThunarEmblemChooser *chooser)
{
  GtkWidget *button;
  GList     *emblems;
  GList     *lp;

  /* determine the emblems for the icon theme */
  emblems = gtk_icon_theme_list_icons (chooser->icon_theme, "Emblems");

  /* sort the emblem list */
  emblems = g_list_sort (emblems, (GCompareFunc) (void (*) (void)) g_ascii_strcasecmp);

  /* create buttons for the emblems */
  for (lp = emblems; lp != NULL; lp = lp->next)
    {
      /* skip special emblems, as they cannot be selected */
      if (strcmp (lp->data, THUNAR_FILE_EMBLEM_NAME_SYMBOLIC_LINK) != 0
          && strcmp (lp->data, THUNAR_FILE_EMBLEM_NAME_CANT_READ) != 0
          && strcmp (lp->data, THUNAR_FILE_EMBLEM_NAME_CANT_WRITE) != 0
          && strcmp (lp->data, THUNAR_FILE_EMBLEM_NAME_DESKTOP) != 0)
        {
          /* create a button and add it to the table */
          button = thunar_emblem_chooser_create_button (chooser, lp->data);
          if (G_LIKELY (button != NULL))
            {
              gtk_container_add (GTK_CONTAINER (chooser->table), button);
              gtk_widget_show (button);
            }
        }
      g_free (lp->data);
    }
  g_list_free (emblems);

  /* be sure to update the buttons according to the selected file */
  if (G_LIKELY (chooser->files != NULL))
    thunar_emblem_chooser_file_changed (chooser);
}



static GtkWidget *
thunar_emblem_chooser_create_button (ThunarEmblemChooser *chooser,
                                     const gchar         *emblem)
{
  GtkIconInfo     *info;
  const gchar     *name;
  GtkWidget       *button = NULL;
  GtkWidget       *image;
  GdkPixbuf       *icon;
  gint             scale_factor;
  cairo_surface_t *surface;

  /* lookup the icon info for the emblem */
  scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (chooser));
  info = gtk_icon_theme_lookup_icon_for_scale (chooser->icon_theme, emblem, 48, scale_factor, GTK_ICON_LOOKUP_FORCE_SIZE);
  if (G_UNLIKELY (info == NULL))
    return NULL;

  /* try to load the icon */
  icon = gtk_icon_info_load_icon (info, NULL);
  if (G_UNLIKELY (icon == NULL))
    goto done;

  /* determine the display name for the emblem */
  name = (strncmp (emblem, "emblem-", 7) == 0) ? emblem + 7 : emblem;

  /* allocate the button */
  button = gtk_check_button_new ();
  gtk_widget_set_can_focus (button, FALSE);
  g_object_set_data_full (G_OBJECT (button), I_ ("thunar-emblem"), g_strdup (emblem), g_free);
  g_signal_connect (G_OBJECT (button), "toggled", G_CALLBACK (thunar_emblem_chooser_button_toggled), chooser);

  /* allocate the image */
  surface = gdk_cairo_surface_create_from_pixbuf (icon, scale_factor, gtk_widget_get_window (GTK_WIDGET (chooser)));
  image = gtk_image_new_from_surface (surface);
  gtk_container_add (GTK_CONTAINER (button), image);
  gtk_widget_set_tooltip_text (image, name);
  gtk_widget_show (image);

  g_object_unref (G_OBJECT (icon));
  cairo_surface_destroy (surface);
done:
  g_object_unref (info);
  return button;
}



/**
 * thunar_emblem_chooser_new:
 *
 * Allocates a new #ThunarEmblemChooser.
 *
 * Return value: the newly allocated #ThunarEmblemChooser.
 **/
GtkWidget *
thunar_emblem_chooser_new (void)
{
  return g_object_new (THUNAR_TYPE_EMBLEM_CHOOSER, NULL);
}



/**
 * thunar_emblem_chooser_get_files:
 * @chooser : a #ThunarEmblemChooser.
 *
 * Returns the #ThunarFile's associated with
 * the @chooser or %NULL.
 *
 * Return value: the #ThunarFile associated
 *               with @chooser.
 **/
GList *
thunar_emblem_chooser_get_files (const ThunarEmblemChooser *chooser)
{
  _thunar_return_val_if_fail (THUNAR_IS_EMBLEM_CHOOSER (chooser), NULL);
  return chooser->files;
}



/**
 * thunar_emblem_chooser_set_file:
 * @chooser : a #ThunarEmblemChooser.
 * @file    : a list of #ThunarFile's or %NULL.
 *
 * Associates @chooser with @file.
 **/
void
thunar_emblem_chooser_set_files (ThunarEmblemChooser *chooser,
                                 GList               *files)
{
  GList *lp;

  _thunar_return_if_fail (THUNAR_IS_EMBLEM_CHOOSER (chooser));

  if (G_LIKELY (chooser->files != files))
    {
      /* disconnect from the previous file (if any) */
      for (lp = chooser->files; lp != NULL; lp = lp->next)
        {
          g_signal_handlers_disconnect_by_func (G_OBJECT (lp->data), thunar_emblem_chooser_file_changed, chooser);
          g_object_unref (G_OBJECT (lp->data));
        }
      g_list_free (chooser->files);

      /* activate the new file */
      chooser->files = g_list_copy (files);

      /* connect to the new file (if any) */
      for (lp = files; lp != NULL; lp = lp->next)
        {
          g_object_ref (G_OBJECT (lp->data));
          g_signal_connect_swapped (G_OBJECT (lp->data), "changed", G_CALLBACK (thunar_emblem_chooser_file_changed), chooser);
        }

      if (chooser->files != NULL)
        thunar_emblem_chooser_file_changed (chooser);
    }
}
