/* 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 "pikaresourceselectbutton.h" #include "pikauimarshal.h" #include "libpika-intl.h" /** * SECTION: pikaresourceselectbutton * @title: PikaResourceSelectButton * @short_description: Base class for buttons that popup a resource * selection dialog. * * A button which pops up a resource selection dialog. * * Subclasses: PikaFontSelectButton is a minimal one. * A subclass provides button trait (clickable), * but possibly not a GtkButton and may have many sub widgets. * * 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) * * Class is abstract and cannot be instantiated: no new () method. * Instead, instantiate a subclass. * * Since: 3.0 **/ enum { RESOURCE_SET, LAST_SIGNAL }; enum { PROP_0, PROP_TITLE, PROP_RESOURCE, N_PROPS }; typedef struct { PikaResource *resource; gchar *title; const gchar *temp_callback_from_remote_dialog; GtkWidget *interior_widget; } PikaResourceSelectButtonPrivate; /* local function prototypes */ static void pika_resource_select_button_dispose (GObject *object); static void pika_resource_select_button_finalize (GObject *object); static void pika_resource_select_button_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void pika_resource_select_button_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void pika_resource_select_button_clicked (PikaResourceSelectButton *self); static void pika_resource_select_button_callback (PikaResource *resource, gboolean dialog_closing, gpointer user_data); static void pika_resource_select_drag_data_received (PikaResourceSelectButton *self, GdkDragContext *context, gint x, gint y, GtkSelectionData *selection, guint info, guint time); static void pika_resource_select_button_set_remote_dialog (PikaResourceSelectButton *self, PikaResource *resource); static void pika_resource_select_button_draw_interior (PikaResourceSelectButton *self, PikaResource *resource); static guint resource_button_signals[LAST_SIGNAL] = { 0 }; static GParamSpec *resource_button_props[N_PROPS] = { NULL, }; G_DEFINE_TYPE_WITH_PRIVATE (PikaResourceSelectButton, pika_resource_select_button, GTK_TYPE_BOX) static void pika_resource_select_button_class_init (PikaResourceSelectButtonClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = pika_resource_select_button_dispose; object_class->finalize = pika_resource_select_button_finalize; object_class->set_property = pika_resource_select_button_set_property; object_class->get_property = pika_resource_select_button_get_property; klass->resource_set = NULL; /** * PikaResourceSelectButton:title: * * The title to be used for the resource selection popup dialog. * * Since: 2.4 */ 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); /** * PikaResourceSelectButton:resource: * * The currently selected resource. * * Since: 2.4 */ 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); /** * PikaResourceSelectButton::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: 2.4 */ resource_button_signals[RESOURCE_SET] = g_signal_new ("resource-set", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaResourceSelectButtonClass, resource_set), NULL, NULL, _pikaui_marshal_VOID__POINTER_BOOLEAN, G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_BOOLEAN); } static void pika_resource_select_button_init (PikaResourceSelectButton *self) { gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_HORIZONTAL); gtk_box_set_spacing (GTK_BOX (self), 6); } static void pika_resource_select_button_dispose (GObject *self) { pika_resource_select_button_close_popup (PIKA_RESOURCE_SELECT_BUTTON (self)); G_OBJECT_CLASS (pika_resource_select_button_parent_class)->dispose (self); } static void pika_resource_select_button_finalize (GObject *object) { PikaResourceSelectButton *self = PIKA_RESOURCE_SELECT_BUTTON (object); PikaResourceSelectButtonPrivate *priv = pika_resource_select_button_get_instance_private (self); g_clear_pointer (&priv->title, g_free); G_OBJECT_CLASS (pika_resource_select_button_parent_class)->finalize (object); } /** * pika_resource_select_button_set_drag_target: * @self: A #PikaResourceSelectButton * @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_select_button_set_drag_target (PikaResourceSelectButton *self, GtkWidget *drag_region_widget, const GtkTargetEntry *drag_target) { g_return_if_fail (PIKA_IS_RESOURCE_SELECT_BUTTON (self)); 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 self's callback. */ g_signal_connect_swapped (drag_region_widget, "drag-data-received", G_CALLBACK (pika_resource_select_drag_data_received), self); } /** * pika_resource_select_button_set_clickable: * @self: A #PikaResourceSelectButton * @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_select_button_set_clickable (PikaResourceSelectButton *self, GtkWidget *widget) { g_return_if_fail (PIKA_IS_RESOURCE_SELECT_BUTTON (self)); 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_select_button_clicked), self); } /** * pika_resource_select_button_get_resource: * @self: A #PikaResourceSelectButton * * Gets the currently selected resource. * * Returns: (transfer none): an internal copy of the resource which must not be freed. * You should ref and unref it when you want to own the resource. * * Since: 2.4 */ PikaResource * pika_resource_select_button_get_resource (PikaResourceSelectButton *self) { PikaResourceSelectButtonPrivate *priv; g_return_val_if_fail (PIKA_IS_RESOURCE_SELECT_BUTTON (self), NULL); priv = pika_resource_select_button_get_instance_private (self); return priv->resource; } /** * pika_resource_select_button_set_resource: * @self: A #PikaResourceSelectButton * @resource: Resource to set. * * Sets the currently selected resource. * This will select the resource in both the button and any chooser popup. * * Since: 2.4 */ void pika_resource_select_button_set_resource (PikaResourceSelectButton *self, PikaResource *resource) { PikaResourceSelectButtonPrivate *priv; g_return_if_fail (PIKA_IS_RESOURCE_SELECT_BUTTON (self)); g_return_if_fail (resource != NULL); priv = pika_resource_select_button_get_instance_private (self); if (priv->temp_callback_from_remote_dialog) { /* 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_select_button_set_remote_dialog (self, resource); } else { /* Call our own setter. */ pika_resource_select_button_callback (resource, FALSE, self); } } /* Calls the virtual method of a similar name, which subclasses must override. * * resource: The instance to be drawn. * * A subclass knows how to draw its interior. * Called by super when the view is invalidated (needs to be redrawn.) * Not public. */ static void pika_resource_select_button_draw_interior (PikaResourceSelectButton *self, PikaResource *resource) { PikaResourceSelectButtonClass *klass; g_return_if_fail (PIKA_IS_RESOURCE_SELECT_BUTTON (self)); g_return_if_fail (PIKA_IS_RESOURCE (resource)); klass = PIKA_RESOURCE_SELECT_BUTTON_GET_CLASS (self); g_return_if_fail (klass->draw_interior != NULL); klass->draw_interior (self); } /* private functions */ static void pika_resource_select_button_set_remote_dialog (PikaResourceSelectButton *self, PikaResource *resource) { PikaResourceSelectButtonPrivate *priv; PikaResourceSelectButtonClass *klass; g_return_if_fail (PIKA_IS_RESOURCE_SELECT_BUTTON (self)); g_return_if_fail (resource != NULL); priv = pika_resource_select_button_get_instance_private (self); klass = PIKA_RESOURCE_SELECT_BUTTON_GET_CLASS (self); g_return_if_fail (klass->resource_type != G_TYPE_INVALID); /* The ultimate remote setter is e.g. pika_fonts_set_popup, a PDB procedure. * * !!! Use the passed resource, not priv->resource. * Expect a callback which will change priv->resource. */ pika_resource_select_set (priv->temp_callback_from_remote_dialog, resource, klass->resource_type); } static void pika_resource_select_button_set_property (GObject *object, guint property_id, const GValue *gvalue, GParamSpec *pspec) { PikaResourceSelectButton *self = PIKA_RESOURCE_SELECT_BUTTON (object); PikaResourceSelectButtonPrivate *priv = pika_resource_select_button_get_instance_private (self); switch (property_id) { case PROP_TITLE: priv->title = 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_select_button_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PikaResourceSelectButton *self = PIKA_RESOURCE_SELECT_BUTTON (object); PikaResourceSelectButtonPrivate *priv = pika_resource_select_button_get_instance_private (self); switch (property_id) { case PROP_TITLE: g_value_set_string (value, priv->title); 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_select_button_callback (PikaResource *resource, gboolean dialog_closing, gpointer user_data) { PikaResourceSelectButton *self = PIKA_RESOURCE_SELECT_BUTTON (user_data); PikaResourceSelectButtonPrivate *priv = pika_resource_select_button_get_instance_private (self); priv->resource = resource; /* Feedback user choice of resource into the look of the widget interior. * Call virtual method overridden by subclass. */ pika_resource_select_button_draw_interior (self, priv->resource); if (dialog_closing) priv->temp_callback_from_remote_dialog = NULL; 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_select_button_clicked (PikaResourceSelectButton *self) { PikaResourceSelectButtonPrivate *priv = pika_resource_select_button_get_instance_private (self); PikaResourceSelectButtonClass *klass = PIKA_RESOURCE_SELECT_BUTTON_GET_CLASS (self); if (priv->temp_callback_from_remote_dialog) { /* Popup already created. Calling setter raises the popup. */ pika_resource_select_button_set_remote_dialog (self, priv->resource); } else { /* Call PikaResourceSelect which dispatches on resource_type. */ priv->temp_callback_from_remote_dialog = pika_resource_select_new (priv->title, priv->resource, klass->resource_type, pika_resource_select_button_callback, self, NULL); /* No func to free data. */ } } /* Drag methods. */ static void pika_resource_select_drag_data_received (PikaResourceSelectButton *self, GdkDragContext *context, gint x, gint y, GtkSelectionData *selection, guint info, guint time) { gint length = gtk_selection_data_get_length (selection); gchar *str; PikaResourceSelectButtonClass *klass; klass = PIKA_RESOURCE_SELECT_BUTTON_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_select_button_set_resource (self, resource); } } g_free (str); } /** * pika_resource_select_button_close_popup: * @self: A #PikaResourceSelectButton * * Closes the popup resource chooser dialog associated with @self. * * FUTURE: Possibly obsolete this by making it private, * since only called by script-fu-interface.c. * Might be needed if we allow plugins to implement their own dialogs. * * Since: 2.4 */ void pika_resource_select_button_close_popup (PikaResourceSelectButton *self) { PikaResourceSelectButtonPrivate *priv; g_return_if_fail (PIKA_IS_RESOURCE_SELECT_BUTTON (self)); priv = pika_resource_select_button_get_instance_private (self); if (priv->temp_callback_from_remote_dialog) { pika_resource_select_destroy (priv->temp_callback_from_remote_dialog); priv->temp_callback_from_remote_dialog = NULL; } /* Else already closed. */ }