/* Image Scaling
 * #Keywords: zoom, scale, filter, action, menu
 *
 * The custom widget we create here is similar to a GtkPicture,
 * but allows setting a zoom level and filtering mode for the
 * displayed paintable.
 *
 * It also demonstrates how to add a context menu to a custom
 * widget and connect it with widget actions.
 *
 * The context menu has items to change the zoom level.
 */

#include <gtk/gtk.h>
#include "imageview.h"


static GtkWidget *window = NULL;
static GCancellable *cancellable = NULL;

static void
load_texture (GTask        *task,
              gpointer      source_object,
              gpointer      task_data,
              GCancellable *cable)
{
  GFile *file = task_data;
  GdkTexture *texture;
  GError *error = NULL;

  texture = gdk_texture_new_from_file (file, &error);

  if (texture)
    g_task_return_pointer (task, texture, g_object_unref);
  else
    g_task_return_error (task, error);
}

static void
set_wait_cursor (GtkWidget *widget)
{
  gtk_widget_set_cursor_from_name (GTK_WIDGET (gtk_widget_get_root (widget)), "wait");
}

static void
unset_wait_cursor (GtkWidget *widget)
{
  gtk_widget_set_cursor (GTK_WIDGET (gtk_widget_get_root (widget)), NULL);
}

static void
texture_loaded (GObject      *source,
                GAsyncResult *result,
                gpointer      data)
{
  GdkTexture *texture;
  GError *error = NULL;

  texture = g_task_propagate_pointer (G_TASK (result), &error);

  if (!texture)
    {
      g_print ("%s\n", error->message);
      g_error_free (error);
      return;
    }

  if (!window)
    {
      g_object_unref (texture);
      return;
    }

  unset_wait_cursor (GTK_WIDGET (data));

  g_object_set (G_OBJECT (data), "texture", texture, NULL);
}

static void
open_file_async (GFile     *file,
                 GtkWidget *demo)
{
  GTask *task;

  set_wait_cursor (demo);

  task = g_task_new (demo, cancellable, texture_loaded, demo);
  g_task_set_task_data (task, g_object_ref (file), g_object_unref);
  g_task_run_in_thread (task, load_texture);
  g_object_unref (task);
}

static void
open_portland_rose (GtkWidget *button,
                    GtkWidget *demo)
{
  GFile *file;

  file = g_file_new_for_uri ("resource:///transparent/portland-rose.jpg");
  open_file_async (file, demo);
  g_object_unref (file);
}

static void
open_large_image (GtkWidget *button,
                  GtkWidget *demo)
{
  GFile *file;

  file = g_file_new_for_uri ("resource:///org/gtk/Demo4/large-image.png");
  open_file_async (file, demo);
  g_object_unref (file);
}

static void
file_opened (GObject      *source,
             GAsyncResult *result,
             void         *data)
{
  GFile *file;
  GError *error = NULL;

  file = gtk_file_dialog_open_finish (GTK_FILE_DIALOG (source), result, &error);

  if (!file)
    {
      g_print ("%s\n", error->message);
      g_error_free (error);
      return;
    }

  open_file_async (file, data);

  g_object_unref (file);
}

static void
open_file (GtkWidget *picker,
           GtkWidget *demo)
{
  GtkWindow *parent = GTK_WINDOW (gtk_widget_get_root (picker));
  GtkFileDialog *dialog;
  GtkFileFilter *filter;
  GListStore *filters;

  dialog = gtk_file_dialog_new ();

  filter = gtk_file_filter_new ();
  gtk_file_filter_set_name (filter, "Images");
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
  gtk_file_filter_add_pixbuf_formats (filter);
G_GNUC_END_IGNORE_DEPRECATIONS
  filters = g_list_store_new (GTK_TYPE_FILE_FILTER);
  g_list_store_append (filters, filter);
  g_object_unref (filter);

  gtk_file_dialog_set_filters (dialog, G_LIST_MODEL (filters));
  g_object_unref (filters);

  gtk_file_dialog_open (dialog, parent, NULL, file_opened, demo);

  g_object_unref (dialog);
}

static void
rotate (GtkWidget *button,
        GtkWidget *demo)
{
  float angle;

  g_object_get (demo, "angle", &angle, NULL);

  angle = fmodf (angle + 90.f, 360.f);

  g_object_set (demo, "angle", angle, NULL);
}

static gboolean
transform_to (GBinding     *binding,
              const GValue *src,
              GValue       *dest,
              gpointer      user_data)
{
  double from;
  float to;

  from = g_value_get_double (src);
  to = (float) pow (2., from);
  g_value_set_float (dest, to);

  return TRUE;
}

static gboolean
transform_from (GBinding     *binding,
                const GValue *src,
                GValue       *dest,
                gpointer      user_data)
{
  float to;
  double from;

  to = g_value_get_float (src);
  from = log2 (to);
  g_value_set_double (dest, from);

  return TRUE;
}

static void
free_cancellable (gpointer data)
{
  g_cancellable_cancel (cancellable);
  g_clear_object (&cancellable);
}

static gboolean
cancel_load (GtkWidget *widget,
             GVariant  *args,
             gpointer   data)
{
  unset_wait_cursor (widget);
  g_cancellable_cancel (G_CANCELLABLE (data));
  return TRUE;
}

GtkWidget *
do_image_scaling (GtkWidget *do_widget)
{
  if (!window)
    {
      GtkWidget *box;
      GtkWidget *box2;
      GtkWidget *sw;
      GtkWidget *vp;
      GtkWidget *widget;
      GtkWidget *scale;
      GtkWidget *dropdown;
      GtkWidget *button;
      GtkEventController *controller;

      window = gtk_window_new ();
      gtk_window_set_title (GTK_WINDOW (window), "Image Scaling");
      gtk_window_set_default_size (GTK_WINDOW (window), 600, 400);
      gtk_window_set_display (GTK_WINDOW (window),
                              gtk_widget_get_display (do_widget));
      g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);

      cancellable = g_cancellable_new ();
      g_object_set_data_full (G_OBJECT (window), "cancellable",
                              cancellable, free_cancellable);

      controller = gtk_shortcut_controller_new ();
      gtk_shortcut_controller_add_shortcut (GTK_SHORTCUT_CONTROLLER (controller),
                                            gtk_shortcut_new (
                                                gtk_keyval_trigger_new (GDK_KEY_Escape, 0),
                                                gtk_callback_action_new (cancel_load, cancellable, NULL)
                                                ));
      gtk_shortcut_controller_set_scope (GTK_SHORTCUT_CONTROLLER (controller),
                                         GTK_SHORTCUT_SCOPE_GLOBAL);
      gtk_widget_add_controller (window, controller);

      box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
      gtk_window_set_child (GTK_WINDOW (window), box);

      sw = gtk_scrolled_window_new ();
      gtk_widget_set_vexpand (sw, TRUE);
      gtk_box_append (GTK_BOX (box), sw);

      widget = image_view_new ("/transparent/portland-rose.jpg");
      vp = gtk_viewport_new (NULL, NULL);
      gtk_viewport_set_scroll_to_focus (GTK_VIEWPORT (vp), FALSE);

      gtk_viewport_set_child (GTK_VIEWPORT (vp), widget);
      gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), vp);

      box2 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
      gtk_box_append (GTK_BOX (box), box2);

      button = gtk_button_new_from_icon_name ("document-open-symbolic");
      gtk_widget_set_tooltip_text (button, "Open File");
      g_signal_connect (button, "clicked", G_CALLBACK (open_file), widget);
      gtk_box_append (GTK_BOX (box2), button);

      button = gtk_button_new ();
      gtk_button_set_child (GTK_BUTTON (button),
                            gtk_image_new_from_resource ("/org/gtk/Demo4/portland-rose-thumbnail.png"));
      gtk_widget_add_css_class (button, "image-button");
      gtk_widget_set_tooltip_text (button, "Portland Rose");
      g_signal_connect (button, "clicked", G_CALLBACK (open_portland_rose), widget);
      gtk_box_append (GTK_BOX (box2), button);

      button = gtk_button_new ();
      gtk_button_set_child (GTK_BUTTON (button),
                            gtk_image_new_from_resource ("/org/gtk/Demo4/large-image-thumbnail.png"));
      gtk_widget_add_css_class (button, "image-button");
      gtk_widget_set_tooltip_text (button, "Large image");
      g_signal_connect (button, "clicked", G_CALLBACK (open_large_image), widget);
      gtk_box_append (GTK_BOX (box2), button);

      button = gtk_button_new_from_icon_name ("object-rotate-right-symbolic");
      gtk_widget_set_tooltip_text (button, "Rotate");
      g_signal_connect (button, "clicked", G_CALLBACK (rotate), widget);
      gtk_box_append (GTK_BOX (box2), button);

      scale = gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL, -10., 10., 0.1);
      gtk_scale_add_mark (GTK_SCALE (scale), 0., GTK_POS_TOP, NULL);
      gtk_widget_set_tooltip_text (scale, "Zoom");
      gtk_accessible_update_property (GTK_ACCESSIBLE (scale),
                                      GTK_ACCESSIBLE_PROPERTY_LABEL, "Zoom",
                                      -1);
      gtk_range_set_value (GTK_RANGE (scale), 0.);
      gtk_widget_set_hexpand (scale, TRUE);
      gtk_box_append (GTK_BOX (box2), scale);

      dropdown = gtk_drop_down_new (G_LIST_MODEL (gtk_string_list_new ((const char *[]){ "Linear", "Nearest", "Trilinear", NULL })), NULL);
      gtk_widget_set_tooltip_text (dropdown, "Filter");
      gtk_accessible_update_property (GTK_ACCESSIBLE (dropdown),
                                      GTK_ACCESSIBLE_PROPERTY_LABEL, "Filter",
                                      -1);
      gtk_box_append (GTK_BOX (box2), dropdown);

      g_object_bind_property (dropdown, "selected", widget, "filter", G_BINDING_DEFAULT);

      g_object_bind_property_full (gtk_range_get_adjustment (GTK_RANGE (scale)), "value",
                                   widget, "scale",
                                   G_BINDING_BIDIRECTIONAL,
                                   transform_to,
                                   transform_from,
                                   NULL, NULL);
    }

  if (!gtk_widget_get_visible (window))
    gtk_widget_set_visible (window, TRUE);
  else
    {
      gtk_window_destroy (GTK_WINDOW (window));
    }

  return window;
}
