/* LIBPIKA - The PIKA Library * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball * * 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 * . */ #include "config.h" #include #include #include "libpikawidgets/pikawidgets.h" #include "pika.h" #include "pikauitypes.h" #include "pikaresourcechooser.h" #include "pikauimarshal.h" #include "libpika-intl.h" /** * SECTION: pikaresourcechooser * @title: PikaResourceChooser * @short_description: Base class for buttons that popup a resource * selection dialog. * * A button which pops up a resource selection dialog. * * Responsibilities: * * - implementing outer container widget, * - managing clicks and popping up a remote chooser, * - having a resource property, * - signaling when user selects resource * - receiving drag, * - triggering draws of the button interior (by subclass) and draws of remote popup chooser. * * Collaborations: * * - owned by PikaProcedureDialog via PikaPropWidget * - resource property usually bound to a PikaConfig for a PikaPluginProcedure. * - communicates using PikaResourceSelect with remote PikaPDBDialog, * to choose an installed PikaResource owned by core. * * Subclass responsibilities: * * - creating interior widgets * - drawing the interior (a preview of the chosen resource) * - declaring which interior widgets are drag destinations * - declaring which interior widgets are clickable (generate "clicked" signal) * - generate "clicked" (delegating to GtkButton or implementing from mouse events) * * Since: 3.0 **/ enum { RESOURCE_SET, LAST_SIGNAL }; enum { PROP_0, PROP_TITLE, PROP_LABEL, PROP_RESOURCE, N_PROPS }; typedef struct { PikaResource *resource; gchar *title; gchar *label; gchar *callback; GtkWidget *label_widget; } PikaResourceChooserPrivate; /* local function prototypes */ static void pika_resource_chooser_constructed (GObject *object); static void pika_resource_chooser_dispose (GObject *object); static void pika_resource_chooser_finalize (GObject *object); static void pika_resource_chooser_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void pika_resource_chooser_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void pika_resource_chooser_clicked (PikaResourceChooser *self); static void pika_resource_chooser_callback (PikaResource *resource, gboolean dialog_closing, gpointer user_data); static void pika_resource_select_drag_data_received (PikaResourceChooser *self, GdkDragContext *context, gint x, gint y, GtkSelectionData *selection, guint info, guint time); static void pika_resource_chooser_set_remote_dialog (PikaResourceChooser *self, PikaResource *resource); static guint resource_button_signals[LAST_SIGNAL] = { 0 }; static GParamSpec *resource_button_props[N_PROPS] = { NULL, }; G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (PikaResourceChooser, pika_resource_chooser, GTK_TYPE_BOX) static void pika_resource_chooser_class_init (PikaResourceChooserClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->constructed = pika_resource_chooser_constructed; object_class->dispose = pika_resource_chooser_dispose; object_class->finalize = pika_resource_chooser_finalize; object_class->set_property = pika_resource_chooser_set_property; object_class->get_property = pika_resource_chooser_get_property; klass->resource_set = NULL; /** * PikaResourceChooser:title: * * The title to be used for the resource selection popup dialog. * * Since: 3.0 */ resource_button_props[PROP_TITLE] = g_param_spec_string ("title", "Title", "The title to be used for the resource selection popup dialog", "Resource Selection", PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); /** * PikaResourceChooser:label: * * Label text with mnemonic. * * Since: 3.0 */ resource_button_props[PROP_LABEL] = g_param_spec_string ("label", "Label", "The label to be used next to the button", NULL, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); /** * PikaResourceChooser:resource: * * The currently selected resource. * * Since: 3.0 */ resource_button_props[PROP_RESOURCE] = pika_param_spec_resource ("resource", "Resource", "The currently selected resource", TRUE, /* none_ok */ PIKA_PARAM_READWRITE); g_object_class_install_properties (object_class, N_PROPS, resource_button_props); /** * PikaResourceChooser::resource-set: * @widget: the object which received the signal. * @resource: the currently selected resource. * @dialog_closing: whether the dialog was closed or not. * * The ::resource-set signal is emitted when the user selects a resource. * * Since: 3.0 */ resource_button_signals[RESOURCE_SET] = g_signal_new ("resource-set", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaResourceChooserClass, resource_set), NULL, NULL, _pikaui_marshal_VOID__POINTER_BOOLEAN, G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_BOOLEAN); } static void pika_resource_chooser_init (PikaResourceChooser *self) { PikaResourceChooserPrivate *priv; priv = pika_resource_chooser_get_instance_private (PIKA_RESOURCE_CHOOSER (self)); gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_HORIZONTAL); gtk_box_set_spacing (GTK_BOX (self), 6); priv->label_widget = gtk_label_new (NULL); gtk_box_pack_start (GTK_BOX (self), priv->label_widget, FALSE, FALSE, 0); } static void pika_resource_chooser_constructed (GObject *object) { PikaResourceChooserPrivate *priv; priv = pika_resource_chooser_get_instance_private (PIKA_RESOURCE_CHOOSER (object)); gtk_label_set_text_with_mnemonic (GTK_LABEL (priv->label_widget), priv->label); gtk_widget_show (GTK_WIDGET (priv->label_widget)); G_OBJECT_CLASS (pika_resource_chooser_parent_class)->constructed (object); } static void pika_resource_chooser_dispose (GObject *self) { PikaResourceChooserPrivate *priv; PikaResourceChooserClass *klass; priv = pika_resource_chooser_get_instance_private (PIKA_RESOURCE_CHOOSER (self)); klass = PIKA_RESOURCE_CHOOSER_GET_CLASS (self); if (priv->callback) { GType resource_type = klass->resource_type; if (resource_type == PIKA_TYPE_FONT) pika_fonts_close_popup (priv->callback); else if (resource_type == PIKA_TYPE_GRADIENT) pika_gradients_close_popup (priv->callback); else if (resource_type == PIKA_TYPE_BRUSH) pika_brushes_close_popup (priv->callback); else if (resource_type == PIKA_TYPE_PALETTE) pika_palettes_close_popup (priv->callback); else if (resource_type == PIKA_TYPE_PATTERN) pika_patterns_close_popup (priv->callback); else g_warning ("%s: unhandled resource type", G_STRFUNC); pika_plug_in_remove_temp_procedure (pika_get_plug_in (), priv->callback); g_clear_pointer (&priv->callback, g_free); } G_OBJECT_CLASS (pika_resource_chooser_parent_class)->dispose (self); } static void pika_resource_chooser_finalize (GObject *object) { PikaResourceChooser *self = PIKA_RESOURCE_CHOOSER (object); PikaResourceChooserPrivate *priv = pika_resource_chooser_get_instance_private (self); g_clear_pointer (&priv->title, g_free); g_clear_pointer (&priv->label, g_free); G_OBJECT_CLASS (pika_resource_chooser_parent_class)->finalize (object); } /** * pika_resource_chooser_set_drag_target: * @chooser: A [class@ResourceChooser] * @drag_region_widget: An interior widget to be a droppable region * and emit "drag-data-received" signal * @drag_target: The drag target to accept * * Called by a subclass init to specialize the instance. * * Subclass knows its interior widget whose region is a drop zone. * Subclass knows what things can be dropped (target.) * Self (super) handles the drop. * * Since: 3.0 **/ void pika_resource_chooser_set_drag_target (PikaResourceChooser *chooser, GtkWidget *drag_region_widget, const GtkTargetEntry *drag_target) { g_return_if_fail (PIKA_IS_RESOURCE_CHOOSER (chooser)); g_return_if_fail (drag_target != NULL); g_return_if_fail (drag_region_widget != NULL); gtk_drag_dest_set (drag_region_widget, GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP, drag_target, 1, /* Pass array of size 1 */ GDK_ACTION_COPY); /* connect drag_region_widget's drag_received signal to chooser's callback. */ g_signal_connect_swapped (drag_region_widget, "drag-data-received", G_CALLBACK (pika_resource_select_drag_data_received), chooser); } /** * pika_resource_chooser_set_clickable: * @chooser: A [class@ResourceChooser] * @widget: An interior widget that emits "clicked" signal * * Called by a subclass init to specialize the instance. * * Subclass knows its interior widget whose region when clicked * should popup remote chooser. * Self handles the click event. * * Since: 3.0 **/ void pika_resource_chooser_set_clickable (PikaResourceChooser *chooser, GtkWidget *widget) { g_return_if_fail (PIKA_IS_RESOURCE_CHOOSER (chooser)); g_return_if_fail (widget != NULL); g_return_if_fail (GTK_IS_WIDGET (widget)); /* Require the widget have a signal "clicked", usually a button. */ g_signal_connect_swapped (widget, "clicked", G_CALLBACK (pika_resource_chooser_clicked), chooser); } /** * pika_resource_chooser_get_resource: * @chooser: A #PikaResourceChooser * * Gets the currently selected resource. * * Returns: (transfer none): an internal copy of the resource which must not be freed. * * Since: 3.0 */ PikaResource * pika_resource_chooser_get_resource (PikaResourceChooser *chooser) { PikaResourceChooserPrivate *priv; g_return_val_if_fail (PIKA_IS_RESOURCE_CHOOSER (chooser), NULL); priv = pika_resource_chooser_get_instance_private (chooser); return priv->resource; } /** * pika_resource_chooser_set_resource: * @chooser: A #PikaResourceChooser * @resource: Resource to set. * * Sets the currently selected resource. * This will select the resource in both the button and any chooser popup. * * Since: 3.0 */ void pika_resource_chooser_set_resource (PikaResourceChooser *chooser, PikaResource *resource) { PikaResourceChooserPrivate *priv; g_return_if_fail (PIKA_IS_RESOURCE_CHOOSER (chooser)); g_return_if_fail (resource != NULL); priv = pika_resource_chooser_get_instance_private (chooser); if (priv->callback) { /* A popup chooser dialog is already shown. * Call its setter to change the selection there * (since all views of the resource must be consistent.) * That will call back, which will change our own view of the resource. */ pika_resource_chooser_set_remote_dialog (chooser, resource); } else { /* Call our own setter. */ pika_resource_chooser_callback (resource, FALSE, chooser); } } /** * pika_resource_chooser_get_label: * @widget: A [class@ResourceChooser]. * * Returns the label widget. * * Returns: (transfer none): the [class@Gtk.Widget] showing the label text. * Since: 3.0 */ GtkWidget * pika_resource_chooser_get_label (PikaResourceChooser *widget) { PikaResourceChooserPrivate *priv; g_return_val_if_fail (PIKA_IS_RESOURCE_CHOOSER (widget), NULL); priv = pika_resource_chooser_get_instance_private (widget); return priv->label_widget; } /* private functions */ static void pika_resource_chooser_set_remote_dialog (PikaResourceChooser *self, PikaResource *resource) { PikaResourceChooserPrivate *priv; PikaResourceChooserClass *klass; g_return_if_fail (PIKA_IS_RESOURCE_CHOOSER (self)); g_return_if_fail (resource != NULL); priv = pika_resource_chooser_get_instance_private (self); klass = PIKA_RESOURCE_CHOOSER_GET_CLASS (self); g_return_if_fail (klass->resource_type != G_TYPE_INVALID); g_return_if_fail (klass->resource_type == G_TYPE_FROM_INSTANCE (resource)); pika_resource_select_set (priv->callback, resource); } static void pika_resource_chooser_set_property (GObject *object, guint property_id, const GValue *gvalue, GParamSpec *pspec) { PikaResourceChooser *self = PIKA_RESOURCE_CHOOSER (object); PikaResourceChooserPrivate *priv = pika_resource_chooser_get_instance_private (self); switch (property_id) { case PROP_TITLE: priv->title = g_value_dup_string (gvalue); break; case PROP_LABEL: priv->label = g_value_dup_string (gvalue); break; case PROP_RESOURCE: priv->resource = g_value_get_object (gvalue); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void pika_resource_chooser_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PikaResourceChooser *self = PIKA_RESOURCE_CHOOSER (object); PikaResourceChooserPrivate *priv = pika_resource_chooser_get_instance_private (self); switch (property_id) { case PROP_TITLE: g_value_set_string (value, priv->title); break; case PROP_LABEL: g_value_set_string (value, priv->label); break; case PROP_RESOURCE: g_value_set_object (value, priv->resource); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } /* A callback from the remote resource select popup. * When user chooses a resource. * Via a temporary PDB procedure. * * Set self's model (priv->resource) * Notify any parent widget subscribed on the "resource" property * typically a prop widget. * Update the view, since model changed. */ static void pika_resource_chooser_callback (PikaResource *resource, gboolean dialog_closing, gpointer user_data) { PikaResourceChooser *self = PIKA_RESOURCE_CHOOSER (user_data); PikaResourceChooserPrivate *priv = pika_resource_chooser_get_instance_private (self); priv->resource = resource; PIKA_RESOURCE_CHOOSER_GET_CLASS (self)->draw_interior (self); if (dialog_closing) g_clear_pointer (&priv->callback, g_free); g_signal_emit (self, resource_button_signals[RESOURCE_SET], 0, resource, dialog_closing); g_object_notify_by_pspec (G_OBJECT (self), resource_button_props[PROP_RESOURCE]); } static void pika_resource_chooser_clicked (PikaResourceChooser *self) { PikaResourceChooserPrivate *priv = pika_resource_chooser_get_instance_private (self); PikaResourceChooserClass *klass = PIKA_RESOURCE_CHOOSER_GET_CLASS (self); if (priv->callback) { /* Popup already created. Calling setter raises the popup. */ pika_resource_chooser_set_remote_dialog (self, priv->resource); } else { GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self)); GBytes *handle = NULL; if (PIKA_IS_DIALOG (toplevel)) handle = pika_dialog_get_native_handle (PIKA_DIALOG (toplevel)); priv->callback = g_strdup (pika_resource_select_new (priv->title, handle, priv->resource, klass->resource_type, pika_resource_chooser_callback, self, NULL)); pika_resource_chooser_set_remote_dialog (self, priv->resource); } } /* Drag methods. */ static void pika_resource_select_drag_data_received (PikaResourceChooser *self, GdkDragContext *context, gint x, gint y, GtkSelectionData *selection, guint info, guint time) { gint length = gtk_selection_data_get_length (selection); gchar *str; PikaResourceChooserClass *klass; klass = PIKA_RESOURCE_CHOOSER_GET_CLASS (self); /* Require class resource_type was initialized. */ g_assert (klass->resource_type != 0); /* Drag data is a string that is the ID of the resource. */ if (gtk_selection_data_get_format (selection) != 8 || length < 1) { g_warning ("%s: received invalid resource data", G_STRFUNC); return; } str = g_strndup ((const gchar *) gtk_selection_data_get_data (selection), length); if (g_utf8_validate (str, -1, NULL)) { gint pid; gpointer unused; gint name_offset = 0; if (sscanf (str, "%i:%p:%n", &pid, &unused, &name_offset) >= 2 && pid == pika_getpid () && name_offset > 0) { gchar *name = str + name_offset; PikaResource *resource; resource = pika_resource_get_by_name (klass->resource_type, name); pika_resource_chooser_set_resource (self, resource); } } g_free (str); }