summaryrefslogtreecommitdiffstats
path: root/libgimpwidgets/gimppageselector.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:30:19 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:30:19 +0000
commit5c1676dfe6d2f3c837a5e074117b45613fd29a72 (patch)
treecbffb45144febf451e54061db2b21395faf94bfe /libgimpwidgets/gimppageselector.c
parentInitial commit. (diff)
downloadgimp-upstream.tar.xz
gimp-upstream.zip
Adding upstream version 2.10.34.upstream/2.10.34upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'libgimpwidgets/gimppageselector.c')
-rw-r--r--libgimpwidgets/gimppageselector.c1327
1 files changed, 1327 insertions, 0 deletions
diff --git a/libgimpwidgets/gimppageselector.c b/libgimpwidgets/gimppageselector.c
new file mode 100644
index 0000000..c882781
--- /dev/null
+++ b/libgimpwidgets/gimppageselector.c
@@ -0,0 +1,1327 @@
+/* LIBGIMP - The GIMP Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * gimppageselector.c
+ * Copyright (C) 2005 Michael Natterer <mitch@gimp.org>
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "gimpwidgetstypes.h"
+
+#include "gimpicons.h"
+#include "gimppageselector.h"
+#include "gimppropwidgets.h"
+#include "gimpwidgets.h"
+#include "gimp3migration.h"
+
+#include "libgimp/libgimp-intl.h"
+
+
+/**
+ * SECTION: gimppageselector
+ * @title: GimpPageSelector
+ * @short_description: A widget to select pages from multi-page things.
+ *
+ * Use this for example for specifying what pages to import from
+ * a PDF or PS document.
+ **/
+
+
+enum
+{
+ SELECTION_CHANGED,
+ ACTIVATE,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_N_PAGES,
+ PROP_TARGET
+};
+
+enum
+{
+ COLUMN_PAGE_NO,
+ COLUMN_THUMBNAIL,
+ COLUMN_LABEL,
+ COLUMN_LABEL_SET
+};
+
+
+typedef struct
+{
+ gint n_pages;
+ GimpPageSelectorTarget target;
+
+ GtkListStore *store;
+ GtkWidget *view;
+
+ GtkWidget *count_label;
+ GtkWidget *range_entry;
+
+ GdkPixbuf *default_thumbnail;
+} GimpPageSelectorPrivate;
+
+#define GIMP_PAGE_SELECTOR_GET_PRIVATE(obj) \
+ ((GimpPageSelectorPrivate *) ((GimpPageSelector *) (obj))->priv)
+
+
+static void gimp_page_selector_finalize (GObject *object);
+static void gimp_page_selector_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_page_selector_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_page_selector_selection_changed (GtkIconView *icon_view,
+ GimpPageSelector *selector);
+static void gimp_page_selector_item_activated (GtkIconView *icon_view,
+ GtkTreePath *path,
+ GimpPageSelector *selector);
+static gboolean gimp_page_selector_range_focus_out (GtkEntry *entry,
+ GdkEventFocus *fevent,
+ GimpPageSelector *selector);
+static void gimp_page_selector_range_activate (GtkEntry *entry,
+ GimpPageSelector *selector);
+static gint gimp_page_selector_int_compare (gconstpointer a,
+ gconstpointer b);
+static void gimp_page_selector_print_range (GString *string,
+ gint start,
+ gint end);
+
+static GdkPixbuf * gimp_page_selector_add_frame (GtkWidget *widget,
+ GdkPixbuf *pixbuf);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpPageSelector, gimp_page_selector, GTK_TYPE_BOX)
+
+#define parent_class gimp_page_selector_parent_class
+
+static guint selector_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_page_selector_class_init (GimpPageSelectorClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = gimp_page_selector_finalize;
+ object_class->get_property = gimp_page_selector_get_property;
+ object_class->set_property = gimp_page_selector_set_property;
+
+ klass->selection_changed = NULL;
+ klass->activate = NULL;
+
+ /**
+ * GimpPageSelector::selection-changed:
+ * @widget: the object which received the signal.
+ *
+ * This signal is emitted whenever the set of selected pages changes.
+ *
+ * Since: 2.4
+ **/
+ selector_signals[SELECTION_CHANGED] =
+ g_signal_new ("selection-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpPageSelectorClass, selection_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ /**
+ * GimpPageSelector::activate:
+ * @widget: the object which received the signal.
+ *
+ * The "activate" signal on GimpPageSelector is an action signal. It
+ * is emitted when a user double-clicks an item in the page selection.
+ *
+ * Since: 2.4
+ */
+ selector_signals[ACTIVATE] =
+ g_signal_new ("activate",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GimpPageSelectorClass, activate),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ widget_class->activate_signal = selector_signals[ACTIVATE];
+
+ /**
+ * GimpPageSelector:n-pages:
+ *
+ * The number of pages of the document to open.
+ *
+ * Since: 2.4
+ **/
+ g_object_class_install_property (object_class, PROP_N_PAGES,
+ g_param_spec_int ("n-pages",
+ "N Pages",
+ "The number of pages to open",
+ 0, G_MAXINT, 0,
+ GIMP_PARAM_READWRITE));
+
+ /**
+ * GimpPageSelector:target:
+ *
+ * The target to open the document to.
+ *
+ * Since: 2.4
+ **/
+ g_object_class_install_property (object_class, PROP_TARGET,
+ g_param_spec_enum ("target",
+ "Target",
+ "the target to open to",
+ GIMP_TYPE_PAGE_SELECTOR_TARGET,
+ GIMP_PAGE_SELECTOR_TARGET_LAYERS,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_page_selector_init (GimpPageSelector *selector)
+{
+ GimpPageSelectorPrivate *priv;
+ GtkWidget *vbox;
+ GtkWidget *sw;
+ GtkWidget *hbox;
+ GtkWidget *hbbox;
+ GtkWidget *button;
+ GtkWidget *label;
+ GtkWidget *combo;
+
+ selector->priv = gimp_page_selector_get_instance_private (selector);
+
+ priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector);
+
+ priv->n_pages = 0;
+ priv->target = GIMP_PAGE_SELECTOR_TARGET_LAYERS;
+
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (selector),
+ GTK_ORIENTATION_VERTICAL);
+
+ gtk_box_set_spacing (GTK_BOX (selector), 12);
+
+ /* Pages */
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+ gtk_box_pack_start (GTK_BOX (selector), vbox, TRUE, TRUE, 0);
+ gtk_widget_show (vbox);
+
+ sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+ gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0);
+ gtk_widget_show (sw);
+
+ priv->store = gtk_list_store_new (4,
+ G_TYPE_INT,
+ GDK_TYPE_PIXBUF,
+ G_TYPE_STRING,
+ G_TYPE_BOOLEAN);
+
+ priv->view = gtk_icon_view_new_with_model (GTK_TREE_MODEL (priv->store));
+ gtk_icon_view_set_text_column (GTK_ICON_VIEW (priv->view),
+ COLUMN_LABEL);
+ gtk_icon_view_set_pixbuf_column (GTK_ICON_VIEW (priv->view),
+ COLUMN_THUMBNAIL);
+ gtk_icon_view_set_selection_mode (GTK_ICON_VIEW (priv->view),
+ GTK_SELECTION_MULTIPLE);
+ gtk_container_add (GTK_CONTAINER (sw), priv->view);
+ gtk_widget_show (priv->view);
+
+ g_signal_connect (priv->view, "selection-changed",
+ G_CALLBACK (gimp_page_selector_selection_changed),
+ selector);
+ g_signal_connect (priv->view, "item-activated",
+ G_CALLBACK (gimp_page_selector_item_activated),
+ selector);
+
+ /* Count label */
+
+ priv->count_label = gtk_label_new (_("Nothing selected"));
+ gtk_label_set_xalign (GTK_LABEL (priv->count_label), 0.0);
+ gimp_label_set_attributes (GTK_LABEL (priv->count_label),
+ PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC,
+ -1);
+ gtk_box_pack_start (GTK_BOX (vbox), priv->count_label, FALSE, FALSE, 0);
+ gtk_widget_show (priv->count_label);
+
+ /* Select all button & range entry */
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_box_pack_start (GTK_BOX (selector), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ hbbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_box_pack_start (GTK_BOX (hbox), hbbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbbox);
+
+ button = gtk_button_new_with_mnemonic (_("Select _All"));
+ gtk_box_pack_start (GTK_BOX (hbbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ g_signal_connect_swapped (button, "clicked",
+ G_CALLBACK (gimp_page_selector_select_all),
+ selector);
+
+ priv->range_entry = gtk_entry_new ();
+ gtk_widget_set_size_request (priv->range_entry, 80, -1);
+ gtk_box_pack_end (GTK_BOX (hbox), priv->range_entry, TRUE, TRUE, 0);
+ gtk_widget_show (priv->range_entry);
+
+ g_signal_connect (priv->range_entry, "focus-out-event",
+ G_CALLBACK (gimp_page_selector_range_focus_out),
+ selector);
+ g_signal_connect (priv->range_entry, "activate",
+ G_CALLBACK (gimp_page_selector_range_activate),
+ selector);
+
+ label = gtk_label_new_with_mnemonic (_("Select _range:"));
+ gtk_box_pack_end (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), priv->range_entry);
+
+ /* Target combo */
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_box_pack_start (GTK_BOX (selector), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ label = gtk_label_new_with_mnemonic (_("Open _pages as"));
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ combo = gimp_prop_enum_combo_box_new (G_OBJECT (selector), "target", -1, -1);
+ gtk_box_pack_start (GTK_BOX (hbox), combo, FALSE, FALSE, 0);
+ gtk_widget_show (combo);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo);
+
+ priv->default_thumbnail =
+ gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
+ "text-x-generic", 32, 0, NULL);
+}
+
+static void
+gimp_page_selector_finalize (GObject *object)
+{
+ GimpPageSelectorPrivate *priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (object);
+
+ g_clear_object (&priv->default_thumbnail);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_page_selector_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpPageSelectorPrivate *priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_N_PAGES:
+ g_value_set_int (value, priv->n_pages);
+ break;
+ case PROP_TARGET:
+ g_value_set_enum (value, priv->target);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_page_selector_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpPageSelector *selector = GIMP_PAGE_SELECTOR (object);
+ GimpPageSelectorPrivate *priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_N_PAGES:
+ gimp_page_selector_set_n_pages (selector, g_value_get_int (value));
+ break;
+ case PROP_TARGET:
+ priv->target = g_value_get_enum (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+
+/* public functions */
+
+/**
+ * gimp_page_selector_new:
+ *
+ * Creates a new #GimpPageSelector widget.
+ *
+ * Returns: Pointer to the new #GimpPageSelector widget.
+ *
+ * Since: 2.4
+ **/
+GtkWidget *
+gimp_page_selector_new (void)
+{
+ return g_object_new (GIMP_TYPE_PAGE_SELECTOR, NULL);
+}
+
+/**
+ * gimp_page_selector_set_n_pages:
+ * @selector: Pointer to a #GimpPageSelector.
+ * @n_pages: The number of pages.
+ *
+ * Sets the number of pages in the document to open.
+ *
+ * Since: 2.4
+ **/
+void
+gimp_page_selector_set_n_pages (GimpPageSelector *selector,
+ gint n_pages)
+{
+ GimpPageSelectorPrivate *priv;
+
+ g_return_if_fail (GIMP_IS_PAGE_SELECTOR (selector));
+ g_return_if_fail (n_pages >= 0);
+
+ priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector);
+
+ if (n_pages != priv->n_pages)
+ {
+ GtkTreeIter iter;
+ gint i;
+
+ if (n_pages < priv->n_pages)
+ {
+ for (i = n_pages; i < priv->n_pages; i++)
+ {
+ gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (priv->store),
+ &iter, NULL, n_pages);
+ gtk_list_store_remove (priv->store, &iter);
+ }
+ }
+ else
+ {
+ for (i = priv->n_pages; i < n_pages; i++)
+ {
+ gchar *text;
+
+ text = g_strdup_printf (_("Page %d"), i + 1);
+
+ gtk_list_store_append (priv->store, &iter);
+ gtk_list_store_set (priv->store, &iter,
+ COLUMN_PAGE_NO, i,
+ COLUMN_THUMBNAIL, priv->default_thumbnail,
+ COLUMN_LABEL, text,
+ COLUMN_LABEL_SET, FALSE,
+ -1);
+
+ g_free (text);
+ }
+ }
+
+ priv->n_pages = n_pages;
+
+ g_object_notify (G_OBJECT (selector), "n-pages");
+ }
+}
+
+/**
+ * gimp_page_selector_get_n_pages:
+ * @selector: Pointer to a #GimpPageSelector.
+ *
+ * Returns: the number of pages in the document to open.
+ *
+ * Since: 2.4
+ **/
+gint
+gimp_page_selector_get_n_pages (GimpPageSelector *selector)
+{
+ GimpPageSelectorPrivate *priv;
+
+ g_return_val_if_fail (GIMP_IS_PAGE_SELECTOR (selector), 0);
+
+ priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector);
+
+ return priv->n_pages;
+}
+
+/**
+ * gimp_page_selector_set_target:
+ * @selector: Pointer to a #GimpPageSelector.
+ * @target: How to open the selected pages.
+ *
+ * Since: 2.4
+ **/
+void
+gimp_page_selector_set_target (GimpPageSelector *selector,
+ GimpPageSelectorTarget target)
+{
+ GimpPageSelectorPrivate *priv;
+
+ g_return_if_fail (GIMP_IS_PAGE_SELECTOR (selector));
+ g_return_if_fail (target <= GIMP_PAGE_SELECTOR_TARGET_IMAGES);
+
+ priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector);
+
+ if (target != priv->target)
+ {
+ priv->target = target;
+
+ g_object_notify (G_OBJECT (selector), "target");
+ }
+}
+
+/**
+ * gimp_page_selector_get_target:
+ * @selector: Pointer to a #GimpPageSelector.
+ *
+ * Returns: How the selected pages should be opened.
+ *
+ * Since: 2.4
+ **/
+GimpPageSelectorTarget
+gimp_page_selector_get_target (GimpPageSelector *selector)
+{
+ GimpPageSelectorPrivate *priv;
+
+ g_return_val_if_fail (GIMP_IS_PAGE_SELECTOR (selector),
+ GIMP_PAGE_SELECTOR_TARGET_LAYERS);
+
+ priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector);
+
+ return priv->target;
+}
+
+/**
+ * gimp_page_selector_set_page_thumbnail:
+ * @selector: Pointer to a #GimpPageSelector.
+ * @page_no: The number of the page to set the thumbnail for.
+ * @thumbnail: The thumbnail pixbuf.
+ *
+ * Sets the thumbnail for given @page_no. A default "page" icon will
+ * be used if no page thumbnail is set.
+ *
+ * Since: 2.4
+ **/
+void
+gimp_page_selector_set_page_thumbnail (GimpPageSelector *selector,
+ gint page_no,
+ GdkPixbuf *thumbnail)
+{
+ GimpPageSelectorPrivate *priv;
+ GtkTreeIter iter;
+
+ g_return_if_fail (GIMP_IS_PAGE_SELECTOR (selector));
+ g_return_if_fail (thumbnail == NULL || GDK_IS_PIXBUF (thumbnail));
+
+ priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector);
+
+ g_return_if_fail (page_no >= 0 && page_no < priv->n_pages);
+
+ gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (priv->store),
+ &iter, NULL, page_no);
+
+ if (! thumbnail)
+ {
+ thumbnail = g_object_ref (priv->default_thumbnail);
+ }
+ else
+ {
+ thumbnail = gimp_page_selector_add_frame (GTK_WIDGET (selector),
+ thumbnail);
+ }
+
+ gtk_list_store_set (priv->store, &iter,
+ COLUMN_THUMBNAIL, thumbnail,
+ -1);
+ g_clear_object (&thumbnail);
+}
+
+/**
+ * gimp_page_selector_get_page_thumbnail:
+ * @selector: Pointer to a #GimpPageSelector.
+ * @page_no: The number of the page to get the thumbnail for.
+ *
+ * Returns: The page's thumbnail, or %NULL if none is set. The returned
+ * pixbuf is owned by #GimpPageSelector and must not be
+ * unref'ed when no longer needed.
+ *
+ * Since: 2.4
+ **/
+GdkPixbuf *
+gimp_page_selector_get_page_thumbnail (GimpPageSelector *selector,
+ gint page_no)
+{
+ GimpPageSelectorPrivate *priv;
+ GdkPixbuf *thumbnail;
+ GtkTreeIter iter;
+
+ g_return_val_if_fail (GIMP_IS_PAGE_SELECTOR (selector), NULL);
+
+ priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector);
+
+ g_return_val_if_fail (page_no >= 0 && page_no < priv->n_pages, NULL);
+
+ gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (priv->store),
+ &iter, NULL, page_no);
+ gtk_tree_model_get (GTK_TREE_MODEL (priv->store), &iter,
+ COLUMN_THUMBNAIL, &thumbnail,
+ -1);
+
+ if (thumbnail)
+ g_object_unref (thumbnail);
+
+ if (thumbnail == priv->default_thumbnail)
+ return NULL;
+
+ return thumbnail;
+}
+
+/**
+ * gimp_page_selector_set_page_label:
+ * @selector: Pointer to a #GimpPageSelector.
+ * @page_no: The number of the page to set the label for.
+ * @label: The label.
+ *
+ * Sets the label of the specified page.
+ *
+ * Since: 2.4
+ **/
+void
+gimp_page_selector_set_page_label (GimpPageSelector *selector,
+ gint page_no,
+ const gchar *label)
+{
+ GimpPageSelectorPrivate *priv;
+ GtkTreeIter iter;
+ gchar *tmp;
+
+ g_return_if_fail (GIMP_IS_PAGE_SELECTOR (selector));
+
+ priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector);
+
+ g_return_if_fail (page_no >= 0 && page_no < priv->n_pages);
+
+ if (! label)
+ tmp = g_strdup_printf (_("Page %d"), page_no + 1);
+ else
+ tmp = (gchar *) label;
+
+ gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (priv->store),
+ &iter, NULL, page_no);
+ gtk_list_store_set (priv->store, &iter,
+ COLUMN_LABEL, tmp,
+ COLUMN_LABEL_SET, label != NULL,
+ -1);
+
+ if (! label)
+ g_free (tmp);
+}
+
+/**
+ * gimp_page_selector_get_page_label:
+ * @selector: Pointer to a #GimpPageSelector.
+ * @page_no: The number of the page to get the thumbnail for.
+ *
+ * Returns: The page's label, or %NULL if none is set. This is a newly
+ * allocated string that should be g_free()'d when no longer
+ * needed.
+ *
+ * Since: 2.4
+ **/
+gchar *
+gimp_page_selector_get_page_label (GimpPageSelector *selector,
+ gint page_no)
+{
+ GimpPageSelectorPrivate *priv;
+ GtkTreeIter iter;
+ gchar *label;
+ gboolean label_set;
+
+ g_return_val_if_fail (GIMP_IS_PAGE_SELECTOR (selector), NULL);
+
+ priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector);
+
+ g_return_val_if_fail (page_no >= 0 && page_no < priv->n_pages, NULL);
+
+ gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (priv->store),
+ &iter, NULL, page_no);
+ gtk_tree_model_get (GTK_TREE_MODEL (priv->store), &iter,
+ COLUMN_LABEL, &label,
+ COLUMN_LABEL_SET, &label_set,
+ -1);
+
+ if (! label_set)
+ {
+ g_free (label);
+ label = NULL;
+ }
+
+ return label;
+}
+
+/**
+ * gimp_page_selector_select_all:
+ * @selector: Pointer to a #GimpPageSelector.
+ *
+ * Selects all pages.
+ *
+ * Since: 2.4
+ **/
+void
+gimp_page_selector_select_all (GimpPageSelector *selector)
+{
+ GimpPageSelectorPrivate *priv;
+
+ g_return_if_fail (GIMP_IS_PAGE_SELECTOR (selector));
+
+ priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector);
+
+ gtk_icon_view_select_all (GTK_ICON_VIEW (priv->view));
+}
+
+/**
+ * gimp_page_selector_unselect_all:
+ * @selector: Pointer to a #GimpPageSelector.
+ *
+ * Unselects all pages.
+ *
+ * Since: 2.4
+ **/
+void
+gimp_page_selector_unselect_all (GimpPageSelector *selector)
+{
+ GimpPageSelectorPrivate *priv;
+
+ g_return_if_fail (GIMP_IS_PAGE_SELECTOR (selector));
+
+ priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector);
+
+ gtk_icon_view_unselect_all (GTK_ICON_VIEW (priv->view));
+}
+
+/**
+ * gimp_page_selector_select_page:
+ * @selector: Pointer to a #GimpPageSelector.
+ * @page_no: The number of the page to select.
+ *
+ * Adds a page to the selection.
+ *
+ * Since: 2.4
+ **/
+void
+gimp_page_selector_select_page (GimpPageSelector *selector,
+ gint page_no)
+{
+ GimpPageSelectorPrivate *priv;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+
+ g_return_if_fail (GIMP_IS_PAGE_SELECTOR (selector));
+
+ priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector);
+
+ g_return_if_fail (page_no >= 0 && page_no < priv->n_pages);
+
+ gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (priv->store),
+ &iter, NULL, page_no);
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (priv->store), &iter);
+
+ gtk_icon_view_select_path (GTK_ICON_VIEW (priv->view), path);
+
+ gtk_tree_path_free (path);
+}
+
+/**
+ * gimp_page_selector_unselect_page:
+ * @selector: Pointer to a #GimpPageSelector.
+ * @page_no: The number of the page to unselect.
+ *
+ * Removes a page from the selection.
+ *
+ * Since: 2.4
+ **/
+void
+gimp_page_selector_unselect_page (GimpPageSelector *selector,
+ gint page_no)
+{
+ GimpPageSelectorPrivate *priv;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+
+ g_return_if_fail (GIMP_IS_PAGE_SELECTOR (selector));
+
+ priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector);
+
+ g_return_if_fail (page_no >= 0 && page_no < priv->n_pages);
+
+ gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (priv->store),
+ &iter, NULL, page_no);
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (priv->store), &iter);
+
+ gtk_icon_view_unselect_path (GTK_ICON_VIEW (priv->view), path);
+
+ gtk_tree_path_free (path);
+}
+
+/**
+ * gimp_page_selector_page_is_selected:
+ * @selector: Pointer to a #GimpPageSelector.
+ * @page_no: The number of the page to check.
+ *
+ * Returns: %TRUE if the page is selected, %FALSE otherwise.
+ *
+ * Since: 2.4
+ **/
+gboolean
+gimp_page_selector_page_is_selected (GimpPageSelector *selector,
+ gint page_no)
+{
+ GimpPageSelectorPrivate *priv;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ gboolean selected;
+
+ g_return_val_if_fail (GIMP_IS_PAGE_SELECTOR (selector), FALSE);
+
+ priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector);
+
+ g_return_val_if_fail (page_no >= 0 && page_no < priv->n_pages, FALSE);
+
+ gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (priv->store),
+ &iter, NULL, page_no);
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (priv->store), &iter);
+
+ selected = gtk_icon_view_path_is_selected (GTK_ICON_VIEW (priv->view),
+ path);
+
+ gtk_tree_path_free (path);
+
+ return selected;
+}
+
+/**
+ * gimp_page_selector_get_selected_pages:
+ * @selector: Pointer to a #GimpPageSelector.
+ * @n_selected_pages: Returns the number of selected pages.
+ *
+ * Returns: A sorted array of page numbers of selected pages. Use g_free() if
+ * you don't need the array any longer.
+ *
+ * Since: 2.4
+ **/
+gint *
+gimp_page_selector_get_selected_pages (GimpPageSelector *selector,
+ gint *n_selected_pages)
+{
+ GimpPageSelectorPrivate *priv;
+ GList *selected;
+ GList *list;
+ gint *array;
+ gint i;
+
+ g_return_val_if_fail (GIMP_IS_PAGE_SELECTOR (selector), NULL);
+ g_return_val_if_fail (n_selected_pages != NULL, NULL);
+
+ priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector);
+
+ selected = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (priv->view));
+
+ *n_selected_pages = g_list_length (selected);
+ array = g_new0 (gint, *n_selected_pages);
+
+ for (list = selected, i = 0; list; list = g_list_next (list), i++)
+ {
+ gint *indices = gtk_tree_path_get_indices (list->data);
+
+ array[i] = indices[0];
+ }
+
+ qsort (array, *n_selected_pages, sizeof (gint),
+ gimp_page_selector_int_compare);
+
+ g_list_free_full (selected, (GDestroyNotify) gtk_tree_path_free);
+
+ return array;
+}
+
+/**
+ * gimp_page_selector_select_range:
+ * @selector: Pointer to a #GimpPageSelector.
+ * @range: A string representing the set of selected pages.
+ *
+ * Selects the pages described by @range. The range string is a
+ * user-editable list of pages and ranges, e.g. "1,3,5-7,9-12,14".
+ * Note that the page numbering in the range string starts with 1,
+ * not 0.
+ *
+ * Invalid pages and ranges will be silently ignored, duplicate and
+ * overlapping pages and ranges will be merged.
+ *
+ * Since: 2.4
+ **/
+void
+gimp_page_selector_select_range (GimpPageSelector *selector,
+ const gchar *range)
+{
+ GimpPageSelectorPrivate *priv;
+ gchar **ranges;
+
+ g_return_if_fail (GIMP_IS_PAGE_SELECTOR (selector));
+
+ priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector);
+
+ if (! range)
+ range = "";
+
+ g_signal_handlers_block_by_func (priv->view,
+ gimp_page_selector_selection_changed,
+ selector);
+
+ gimp_page_selector_unselect_all (selector);
+
+ ranges = g_strsplit (range, ",", -1);
+
+ if (ranges)
+ {
+ gint i;
+
+ for (i = 0; ranges[i] != NULL; i++)
+ {
+ gchar *range = g_strstrip (ranges[i]);
+ gchar *dash;
+
+ dash = strchr (range, '-');
+
+ if (dash)
+ {
+ gchar *from;
+ gchar *to;
+ gint page_from = -1;
+ gint page_to = -1;
+
+ *dash = '\0';
+
+ from = g_strstrip (range);
+ to = g_strstrip (dash + 1);
+
+ if (sscanf (from, "%i", &page_from) != 1 && strlen (from) == 0)
+ page_from = 1;
+
+ if (sscanf (to, "%i", &page_to) != 1 && strlen (to) == 0)
+ page_to = priv->n_pages;
+
+ if (page_from > 0 &&
+ page_to > 0 &&
+ page_from <= page_to &&
+ page_from <= priv->n_pages)
+ {
+ gint page_no;
+
+ page_from = MAX (page_from, 1) - 1;
+ page_to = MIN (page_to, priv->n_pages) - 1;
+
+ for (page_no = page_from; page_no <= page_to; page_no++)
+ gimp_page_selector_select_page (selector, page_no);
+ }
+ }
+ else
+ {
+ gint page_no;
+
+ if (sscanf (range, "%i", &page_no) == 1 &&
+ page_no >= 1 &&
+ page_no <= priv->n_pages)
+ {
+ gimp_page_selector_select_page (selector, page_no - 1);
+ }
+ }
+ }
+
+ g_strfreev (ranges);
+ }
+
+ g_signal_handlers_unblock_by_func (priv->view,
+ gimp_page_selector_selection_changed,
+ selector);
+
+ gimp_page_selector_selection_changed (GTK_ICON_VIEW (priv->view), selector);
+}
+
+/**
+ * gimp_page_selector_get_selected_range:
+ * @selector: Pointer to a #GimpPageSelector.
+ *
+ * Returns: A newly allocated string representing the set of selected
+ * pages. See gimp_page_selector_select_range() for the
+ * format of the string.
+ *
+ * Since: 2.4
+ **/
+gchar *
+gimp_page_selector_get_selected_range (GimpPageSelector *selector)
+{
+ gint *pages;
+ gint n_pages;
+ GString *string;
+
+ g_return_val_if_fail (GIMP_IS_PAGE_SELECTOR (selector), NULL);
+
+ string = g_string_new ("");
+
+ pages = gimp_page_selector_get_selected_pages (selector, &n_pages);
+
+ if (pages)
+ {
+ gint range_start, range_end;
+ gint last_printed;
+ gint i;
+
+ range_start = pages[0];
+ range_end = pages[0];
+ last_printed = -1;
+
+ for (i = 1; i < n_pages; i++)
+ {
+ if (pages[i] > range_end + 1)
+ {
+ gimp_page_selector_print_range (string,
+ range_start, range_end);
+
+ last_printed = range_end;
+ range_start = pages[i];
+ }
+
+ range_end = pages[i];
+ }
+
+ if (range_end != last_printed)
+ gimp_page_selector_print_range (string, range_start, range_end);
+
+ g_free (pages);
+ }
+
+ return g_string_free (string, FALSE);
+}
+
+
+/* private functions */
+
+static void
+gimp_page_selector_selection_changed (GtkIconView *icon_view,
+ GimpPageSelector *selector)
+{
+ GimpPageSelectorPrivate *priv = GIMP_PAGE_SELECTOR_GET_PRIVATE (selector);
+ GList *selected;
+ gint n_selected;
+ gchar *range;
+
+ selected = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (priv->view));
+ n_selected = g_list_length (selected);
+ g_list_free_full (selected, (GDestroyNotify) gtk_tree_path_free);
+
+ if (n_selected == 0)
+ {
+ gtk_label_set_text (GTK_LABEL (priv->count_label),
+ _("Nothing selected"));
+ }
+ else if (n_selected == 1)
+ {
+ gtk_label_set_text (GTK_LABEL (priv->count_label),
+ _("One page selected"));
+ }
+ else
+ {
+ gchar *text;
+
+ if (n_selected == priv->n_pages)
+ text = g_strdup_printf (ngettext ("%d page selected",
+ "All %d pages selected", n_selected),
+ n_selected);
+ else
+ text = g_strdup_printf (ngettext ("%d page selected",
+ "%d pages selected",
+ n_selected),
+ n_selected);
+
+ gtk_label_set_text (GTK_LABEL (priv->count_label), text);
+ g_free (text);
+ }
+
+ range = gimp_page_selector_get_selected_range (selector);
+ gtk_entry_set_text (GTK_ENTRY (priv->range_entry), range);
+ g_free (range);
+
+ gtk_editable_set_position (GTK_EDITABLE (priv->range_entry), -1);
+
+ g_signal_emit (selector, selector_signals[SELECTION_CHANGED], 0);
+}
+
+static void
+gimp_page_selector_item_activated (GtkIconView *icon_view,
+ GtkTreePath *path,
+ GimpPageSelector *selector)
+{
+ g_signal_emit (selector, selector_signals[ACTIVATE], 0);
+}
+
+static gboolean
+gimp_page_selector_range_focus_out (GtkEntry *entry,
+ GdkEventFocus *fevent,
+ GimpPageSelector *selector)
+{
+ gimp_page_selector_range_activate (entry, selector);
+
+ return FALSE;
+}
+
+static void
+gimp_page_selector_range_activate (GtkEntry *entry,
+ GimpPageSelector *selector)
+{
+ gimp_page_selector_select_range (selector, gtk_entry_get_text (entry));
+}
+
+static gint
+gimp_page_selector_int_compare (gconstpointer a,
+ gconstpointer b)
+{
+ return *(gint*)a - *(gint*)b;
+}
+
+static void
+gimp_page_selector_print_range (GString *string,
+ gint start,
+ gint end)
+{
+ if (string->len != 0)
+ g_string_append_c (string, ',');
+
+ if (start == end)
+ g_string_append_printf (string, "%d", start + 1);
+ else
+ g_string_append_printf (string, "%d-%d", start + 1, end + 1);
+}
+
+static void
+draw_frame_row (GdkPixbuf *frame_image,
+ gint target_width,
+ gint source_width,
+ gint source_v_position,
+ gint dest_v_position,
+ GdkPixbuf *result_pixbuf,
+ gint left_offset,
+ gint height)
+{
+ gint remaining_width = target_width;
+ gint h_offset = 0;
+
+ while (remaining_width > 0)
+ {
+ gint slab_width = (remaining_width > source_width ?
+ source_width : remaining_width);
+
+ gdk_pixbuf_copy_area (frame_image,
+ left_offset, source_v_position,
+ slab_width, height,
+ result_pixbuf,
+ left_offset + h_offset, dest_v_position);
+
+ remaining_width -= slab_width;
+ h_offset += slab_width;
+ }
+}
+
+/* utility to draw the middle section of the frame in a loop */
+static void
+draw_frame_column (GdkPixbuf *frame_image,
+ gint target_height,
+ gint source_height,
+ gint source_h_position,
+ gint dest_h_position,
+ GdkPixbuf *result_pixbuf,
+ gint top_offset, int width)
+{
+ gint remaining_height = target_height;
+ gint v_offset = 0;
+
+ while (remaining_height > 0)
+ {
+ gint slab_height = (remaining_height > source_height ?
+ source_height : remaining_height);
+
+ gdk_pixbuf_copy_area (frame_image,
+ source_h_position, top_offset,
+ width, slab_height,
+ result_pixbuf,
+ dest_h_position, top_offset + v_offset);
+
+ remaining_height -= slab_height;
+ v_offset += slab_height;
+ }
+}
+
+static GdkPixbuf *
+stretch_frame_image (GdkPixbuf *frame_image,
+ gint left_offset,
+ gint top_offset,
+ gint right_offset,
+ gint bottom_offset,
+ gint dest_width,
+ gint dest_height)
+{
+ GdkPixbuf *pixbuf;
+ gint frame_width, frame_height;
+ gint target_width, target_frame_width;
+ gint target_height, target_frame_height;
+
+ frame_width = gdk_pixbuf_get_width (frame_image);
+ frame_height = gdk_pixbuf_get_height (frame_image );
+
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
+ dest_width, dest_height);
+ gdk_pixbuf_fill (pixbuf, 0);
+
+ target_width = dest_width - left_offset - right_offset;
+ target_height = dest_height - top_offset - bottom_offset;
+ target_frame_width = frame_width - left_offset - right_offset;
+ target_frame_height = frame_height - top_offset - bottom_offset;
+
+ left_offset += MIN (target_width / 4, target_frame_width / 4);
+ right_offset += MIN (target_width / 4, target_frame_width / 4);
+ top_offset += MIN (target_height / 4, target_frame_height / 4);
+ bottom_offset += MIN (target_height / 4, target_frame_height / 4);
+
+ target_width = dest_width - left_offset - right_offset;
+ target_height = dest_height - top_offset - bottom_offset;
+ target_frame_width = frame_width - left_offset - right_offset;
+ target_frame_height = frame_height - top_offset - bottom_offset;
+
+ /* draw the left top corner and top row */
+ gdk_pixbuf_copy_area (frame_image,
+ 0, 0, left_offset, top_offset,
+ pixbuf, 0, 0);
+ draw_frame_row (frame_image, target_width, target_frame_width,
+ 0, 0,
+ pixbuf,
+ left_offset, top_offset);
+
+ /* draw the right top corner and left column */
+ gdk_pixbuf_copy_area (frame_image,
+ frame_width - right_offset, 0,
+ right_offset, top_offset,
+
+ pixbuf,
+ dest_width - right_offset, 0);
+ draw_frame_column (frame_image, target_height, target_frame_height, 0, 0,
+ pixbuf, top_offset, left_offset);
+
+ /* draw the bottom right corner and bottom row */
+ gdk_pixbuf_copy_area (frame_image,
+ frame_width - right_offset, frame_height - bottom_offset,
+ right_offset, bottom_offset,
+ pixbuf,
+ dest_width - right_offset, dest_height - bottom_offset);
+ draw_frame_row (frame_image, target_width, target_frame_width,
+ frame_height - bottom_offset, dest_height - bottom_offset,
+ pixbuf, left_offset, bottom_offset);
+
+ /* draw the bottom left corner and the right column */
+ gdk_pixbuf_copy_area (frame_image,
+ 0, frame_height - bottom_offset,
+ left_offset, bottom_offset,
+ pixbuf,
+ 0, dest_height - bottom_offset);
+ draw_frame_column (frame_image, target_height, target_frame_height,
+ frame_width - right_offset, dest_width - right_offset,
+ pixbuf, top_offset, right_offset);
+
+ return pixbuf;
+}
+
+#define FRAME_LEFT 2
+#define FRAME_TOP 2
+#define FRAME_RIGHT 4
+#define FRAME_BOTTOM 4
+
+static GdkPixbuf *
+gimp_page_selector_add_frame (GtkWidget *widget,
+ GdkPixbuf *pixbuf)
+{
+ GdkPixbuf *frame;
+ gint width, height;
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+
+ frame = g_object_get_data (G_OBJECT (widget), "frame");
+
+ if (! frame)
+ {
+ GError *error = NULL;
+
+ frame = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
+ GIMP_ICON_FRAME, 64, 0, &error);
+ if (error)
+ {
+ g_printerr ("%s: %s\n", G_STRFUNC, error->message);
+ g_error_free (error);
+ }
+ g_return_val_if_fail (frame, NULL);
+ g_object_set_data_full (G_OBJECT (widget), "frame", frame,
+ (GDestroyNotify) g_object_unref);
+ }
+
+ frame = stretch_frame_image (frame,
+ FRAME_LEFT,
+ FRAME_TOP,
+ FRAME_RIGHT,
+ FRAME_BOTTOM,
+ width + FRAME_LEFT + FRAME_RIGHT,
+ height + FRAME_TOP + FRAME_BOTTOM);
+
+ gdk_pixbuf_copy_area (pixbuf, 0, 0, width, height,
+ frame, FRAME_LEFT, FRAME_TOP);
+
+ return frame;
+}