diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:30:19 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:30:19 +0000 |
commit | 5c1676dfe6d2f3c837a5e074117b45613fd29a72 (patch) | |
tree | cbffb45144febf451e54061db2b21395faf94bfe /libgimpwidgets/gimppageselector.c | |
parent | Initial commit. (diff) | |
download | gimp-5c1676dfe6d2f3c837a5e074117b45613fd29a72.tar.xz gimp-5c1676dfe6d2f3c837a5e074117b45613fd29a72.zip |
Adding upstream version 2.10.34.upstream/2.10.34upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | libgimpwidgets/gimppageselector.c | 1327 |
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; +} |