/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * pikadialogfactory.c * Copyright (C) 2001-2008 Michael Natterer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include #include "libpikaconfig/pikaconfig.h" #include "libpikawidgets/pikawidgets.h" #include "widgets-types.h" #include "config/pikaguiconfig.h" #include "core/pika.h" #include "core/pikacontext.h" #include "menus/menus.h" #include "pikacursor.h" #include "pikadialogfactory.h" #include "pikadock.h" #include "pikadockbook.h" #include "pikadockable.h" #include "pikadockcontainer.h" #include "pikadockwindow.h" #include "pikamenufactory.h" #include "pikasessioninfo.h" #include "pikawidgets-utils.h" #include "pika-log.h" enum { DOCK_WINDOW_ADDED, DOCK_WINDOW_REMOVED, LAST_SIGNAL }; struct _PikaDialogFactoryPrivate { PikaContext *context; GList *open_dialogs; GList *session_infos; GList *registered_dialogs; PikaDialogsState dialog_state; }; static void pika_dialog_factory_dispose (GObject *object); static void pika_dialog_factory_finalize (GObject *object); static GtkWidget * pika_dialog_factory_constructor (PikaDialogFactory *factory, PikaDialogFactoryEntry *entry, PikaContext *context, PikaUIManager *ui_manager, gint view_size); static void pika_dialog_factory_config_notify (PikaDialogFactory *factory, GParamSpec *pspec, PikaGuiConfig *config); static void pika_dialog_factory_set_widget_data (GtkWidget *dialog, PikaDialogFactory *factory, PikaDialogFactoryEntry *entry); static void pika_dialog_factory_unset_widget_data (GtkWidget *dialog); static gboolean pika_dialog_factory_set_user_pos (GtkWidget *dialog, GdkEventConfigure *cevent, gpointer data); static gboolean pika_dialog_factory_dialog_configure (GtkWidget *dialog, GdkEventConfigure *cevent, PikaDialogFactory *factory); static void pika_dialog_factory_hide (PikaDialogFactory *factory); static void pika_dialog_factory_show (PikaDialogFactory *factory); G_DEFINE_TYPE_WITH_PRIVATE (PikaDialogFactory, pika_dialog_factory, PIKA_TYPE_OBJECT) #define parent_class pika_dialog_factory_parent_class static guint factory_signals[LAST_SIGNAL] = { 0 }; /* Is set by dialogs.c to a dialog factory initialized there. * * FIXME: The layer above should not do this kind of initialization of * layers below. */ static PikaDialogFactory *pika_toplevel_factory = NULL; static void pika_dialog_factory_class_init (PikaDialogFactoryClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = pika_dialog_factory_dispose; object_class->finalize = pika_dialog_factory_finalize; factory_signals[DOCK_WINDOW_ADDED] = g_signal_new ("dock-window-added", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (PikaDialogFactoryClass, dock_window_added), NULL, NULL, NULL, G_TYPE_NONE, 1, PIKA_TYPE_DOCK_WINDOW); factory_signals[DOCK_WINDOW_REMOVED] = g_signal_new ("dock-window-removed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (PikaDialogFactoryClass, dock_window_removed), NULL, NULL, NULL, G_TYPE_NONE, 1, PIKA_TYPE_DOCK_WINDOW); } static void pika_dialog_factory_init (PikaDialogFactory *factory) { factory->p = pika_dialog_factory_get_instance_private (factory); factory->p->dialog_state = PIKA_DIALOGS_SHOWN; } static void pika_dialog_factory_dispose (GObject *object) { PikaDialogFactory *factory = PIKA_DIALOG_FACTORY (object); GList *list; /* start iterating from the beginning each time we destroyed a * toplevel because destroying a dock may cause lots of items * to be removed from factory->p->open_dialogs */ while (factory->p->open_dialogs) { for (list = factory->p->open_dialogs; list; list = g_list_next (list)) { if (gtk_widget_is_toplevel (list->data)) { gtk_widget_destroy (GTK_WIDGET (list->data)); break; } } /* the list being non-empty without any toplevel is an error, * so eek and chain up */ if (! list) { g_warning ("%s: %d stale non-toplevel entries in factory->p->open_dialogs", G_STRFUNC, g_list_length (factory->p->open_dialogs)); break; } } if (factory->p->open_dialogs) { g_list_free (factory->p->open_dialogs); factory->p->open_dialogs = NULL; } if (factory->p->session_infos) { g_list_free_full (factory->p->session_infos, (GDestroyNotify) g_object_unref); factory->p->session_infos = NULL; } G_OBJECT_CLASS (parent_class)->dispose (object); } static void pika_dialog_factory_finalize (GObject *object) { PikaDialogFactory *factory = PIKA_DIALOG_FACTORY (object); GList *list; for (list = factory->p->registered_dialogs; list; list = g_list_next (list)) { PikaDialogFactoryEntry *entry = list->data; g_free (entry->identifier); g_free (entry->name); g_free (entry->blurb); g_free (entry->icon_name); g_free (entry->help_id); g_slice_free (PikaDialogFactoryEntry, entry); } if (factory->p->registered_dialogs) { g_list_free (factory->p->registered_dialogs); factory->p->registered_dialogs = NULL; } G_OBJECT_CLASS (parent_class)->finalize (object); } PikaDialogFactory * pika_dialog_factory_new (const gchar *name, PikaContext *context) { PikaDialogFactory *factory; PikaGuiConfig *config; g_return_val_if_fail (name != NULL, NULL); g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL); factory = g_object_new (PIKA_TYPE_DIALOG_FACTORY, NULL); pika_object_set_name (PIKA_OBJECT (factory), name); config = PIKA_GUI_CONFIG (context->pika->config); factory->p->context = context; factory->p->dialog_state = (config->hide_docks ? PIKA_DIALOGS_HIDDEN_EXPLICITLY : PIKA_DIALOGS_SHOWN); g_signal_connect_object (config, "notify::hide-docks", G_CALLBACK (pika_dialog_factory_config_notify), factory, G_CONNECT_SWAPPED); return factory; } void pika_dialog_factory_register_entry (PikaDialogFactory *factory, const gchar *identifier, const gchar *name, const gchar *blurb, const gchar *icon_name, const gchar *help_id, PikaDialogNewFunc new_func, PikaDialogRestoreFunc restore_func, gint view_size, gboolean singleton, gboolean session_managed, gboolean remember_size, gboolean remember_if_open, gboolean hideable, gboolean image_window, gboolean dockable) { PikaDialogFactoryEntry *entry; g_return_if_fail (PIKA_IS_DIALOG_FACTORY (factory)); g_return_if_fail (identifier != NULL); entry = g_slice_new0 (PikaDialogFactoryEntry); entry->identifier = g_strdup (identifier); entry->name = g_strdup (name); entry->blurb = g_strdup (blurb); entry->icon_name = g_strdup (icon_name); entry->help_id = g_strdup (help_id); entry->new_func = new_func; entry->restore_func = restore_func; entry->view_size = view_size; entry->singleton = singleton ? TRUE : FALSE; entry->session_managed = session_managed ? TRUE : FALSE; entry->remember_size = remember_size ? TRUE : FALSE; entry->remember_if_open = remember_if_open ? TRUE : FALSE; entry->hideable = hideable ? TRUE : FALSE; entry->image_window = image_window ? TRUE : FALSE; entry->dockable = dockable ? TRUE : FALSE; factory->p->registered_dialogs = g_list_prepend (factory->p->registered_dialogs, entry); } PikaDialogFactoryEntry * pika_dialog_factory_find_entry (PikaDialogFactory *factory, const gchar *identifier) { GList *list; g_return_val_if_fail (PIKA_IS_DIALOG_FACTORY (factory), NULL); g_return_val_if_fail (identifier != NULL, NULL); for (list = factory->p->registered_dialogs; list; list = g_list_next (list)) { PikaDialogFactoryEntry *entry = list->data; if (! strcmp (identifier, entry->identifier)) return entry; } return NULL; } PikaSessionInfo * pika_dialog_factory_find_session_info (PikaDialogFactory *factory, const gchar *identifier) { GList *list; g_return_val_if_fail (PIKA_IS_DIALOG_FACTORY (factory), NULL); g_return_val_if_fail (identifier != NULL, NULL); for (list = factory->p->session_infos; list; list = g_list_next (list)) { PikaSessionInfo *info = list->data; if (pika_session_info_get_factory_entry (info) && g_str_equal (identifier, pika_session_info_get_factory_entry (info)->identifier)) { return info; } } return NULL; } GtkWidget * pika_dialog_factory_find_widget (PikaDialogFactory *factory, const gchar *identifiers) { GtkWidget *widget = NULL; gchar **ids; gint i; g_return_val_if_fail (PIKA_IS_DIALOG_FACTORY (factory), NULL); g_return_val_if_fail (identifiers != NULL, NULL); ids = g_strsplit (identifiers, "|", 0); for (i = 0; ids[i]; i++) { PikaSessionInfo *info; info = pika_dialog_factory_find_session_info (factory, ids[i]); if (info) { widget = pika_session_info_get_widget (info); if (widget) break; } } g_strfreev (ids); return widget; } /** * pika_dialog_factory_dialog_sane: * @factory: * @widget_factory: * @widget_entry: * @widget: * * Makes sure that the @widget with the given @widget_entry that was * created by the given @widget_factory belongs to @efactory. * * Returns: %TRUE if that is the case, %FALSE otherwise. **/ static gboolean pika_dialog_factory_dialog_sane (PikaDialogFactory *factory, PikaDialogFactory *widget_factory, PikaDialogFactoryEntry *widget_entry, GtkWidget *widget) { if (! widget_factory || ! widget_entry) { g_warning ("%s: dialog was not created by a PikaDialogFactory", G_STRFUNC); return FALSE; } if (widget_factory != factory) { g_warning ("%s: dialog was created by a different PikaDialogFactory", G_STRFUNC); return FALSE; } return TRUE; } /** * pika_dialog_factory_dialog_new_internal: * @factory: * @monitor: * @context: * @ui_manager: * @parent: * @identifier: * @view_size: * @return_existing: If %TRUE, (or if the dialog is a singleton), * don't create a new dialog if it exists, instead * return the existing one * @present: If %TRUE, the toplevel that contains the dialog (if any) * will be gtk_window_present():ed * @create_containers: If %TRUE, then containers for the * dialog/dockable will be created as well. If you * want to manage your own containers, pass %FALSE * * This is the lowest level dialog factory creation function. * * Returns: A created or existing #GtkWidget. **/ static GtkWidget * pika_dialog_factory_dialog_new_internal (PikaDialogFactory *factory, GdkMonitor *monitor, PikaContext *context, PikaUIManager *ui_manager, GtkWidget *parent, const gchar *identifier, gint view_size, gboolean return_existing, gboolean present, gboolean create_containers) { PikaDialogFactoryEntry *entry = NULL; GtkWidget *dialog = NULL; GtkWidget *toplevel = NULL; g_return_val_if_fail (PIKA_IS_DIALOG_FACTORY (factory), NULL); g_return_val_if_fail (identifier != NULL, NULL); entry = pika_dialog_factory_find_entry (factory, identifier); if (! entry) { g_warning ("%s: no entry registered for \"%s\"", G_STRFUNC, identifier); return NULL; } if (! entry->new_func) { g_warning ("%s: entry for \"%s\" has no constructor", G_STRFUNC, identifier); return NULL; } /* a singleton dialog is always returned if it already exists */ if (return_existing || entry->singleton) { dialog = pika_dialog_factory_find_widget (factory, identifier); } /* create the dialog if it was not found */ if (! dialog) { GtkWidget *dock = NULL; GtkWidget *dockbook = NULL; GtkWidget *dock_window = NULL; /* What follows is special-case code for some entries. At some * point we might want to abstract this block of code away. */ if (create_containers) { if (entry->dockable) { /* It doesn't make sense to have a dockable without a dock * so create one. Create a new dock _before_ creating the * dialog. We do this because the new dockable needs to be * created in its dock's context. */ dock = pika_dock_with_window_new (factory, monitor, FALSE); dockbook = pika_dockbook_new (menus_get_global_menu_factory (factory->p->context->pika)); pika_dock_add_book (PIKA_DOCK (dock), PIKA_DOCKBOOK (dockbook), 0); } else if (strcmp ("pika-toolbox", entry->identifier) == 0) { PikaDockContainer *dock_container; dock_window = pika_dialog_factory_dialog_new (factory, monitor, NULL /*ui_manager*/, parent, "pika-toolbox-window", -1 /*view_size*/, FALSE /*present*/); /* When we get a dock window, we also get a UI * manager */ dock_container = PIKA_DOCK_CONTAINER (dock_window); ui_manager = pika_dock_container_get_ui_manager (dock_container); } } /* Create the new dialog in the appropriate context which is * - the passed context if not NULL * - the newly created dock's context if we just created it * - the factory's context, which happens when raising a toplevel * dialog was the original request. */ if (view_size < PIKA_VIEW_SIZE_TINY) view_size = entry->view_size; if (context) dialog = pika_dialog_factory_constructor (factory, entry, context, ui_manager, view_size); else if (dock) dialog = pika_dialog_factory_constructor (factory, entry, pika_dock_get_context (PIKA_DOCK (dock)), pika_dock_get_ui_manager (PIKA_DOCK (dock)), view_size); else dialog = pika_dialog_factory_constructor (factory, entry, factory->p->context, ui_manager, view_size); if (dialog) { pika_dialog_factory_set_widget_data (dialog, factory, entry); /* If we created a dock before, the newly created dialog is * supposed to be a PikaDockable. */ if (dock) { if (PIKA_IS_DOCKABLE (dialog)) { gtk_notebook_append_page (GTK_NOTEBOOK (dockbook), dialog, NULL); gtk_widget_show (dock); } else { g_warning ("%s: PikaDialogFactory is a dockable factory " "but constructor for \"%s\" did not return a " "PikaDockable", G_STRFUNC, identifier); gtk_widget_destroy (dialog); gtk_widget_destroy (dock); dialog = NULL; dock = NULL; } } else if (dock_window) { if (PIKA_IS_DOCK (dialog)) { pika_dock_window_add_dock (PIKA_DOCK_WINDOW (dock_window), PIKA_DOCK (dialog), -1 /*index*/); gtk_widget_set_visible (dialog, present); gtk_widget_set_visible (dock_window, present); } else { g_warning ("%s: PikaDialogFactory is a dock factory entry " "but constructor for \"%s\" did not return a " "PikaDock", G_STRFUNC, identifier); gtk_widget_destroy (dialog); gtk_widget_destroy (dock_window); dialog = NULL; dock_window = NULL; } } } else if (dock) { g_warning ("%s: constructor for \"%s\" returned NULL", G_STRFUNC, identifier); gtk_widget_destroy (dock); dock = NULL; } if (dialog) pika_dialog_factory_add_dialog (factory, dialog, monitor); } /* Finally, if we found an existing dialog or created a new one, raise it. */ if (! dialog) return NULL; if (gtk_widget_is_toplevel (dialog)) { gtk_window_set_screen (GTK_WINDOW (dialog), gdk_display_get_default_screen (gdk_monitor_get_display (monitor))); if (parent) { GtkWidget *parent_toplevel = gtk_widget_get_toplevel (parent); if (GTK_IS_WINDOW (parent_toplevel)) { gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_toplevel)); } } toplevel = dialog; } else if (PIKA_IS_DOCK (dialog)) { toplevel = gtk_widget_get_toplevel (dialog); } else if (PIKA_IS_DOCKABLE (dialog)) { PikaDockable *dockable = PIKA_DOCKABLE (dialog); if (pika_dockable_get_dockbook (dockable) && pika_dockbook_get_dock (pika_dockable_get_dockbook (dockable))) { GtkNotebook *notebook = GTK_NOTEBOOK (pika_dockable_get_dockbook (dockable)); gint num = gtk_notebook_page_num (notebook, dialog); if (num != -1) { gtk_notebook_set_current_page (notebook, num); pika_widget_blink (dialog); } } toplevel = gtk_widget_get_toplevel (dialog); } if (present && GTK_IS_WINDOW (toplevel)) { /* Work around focus-stealing protection, or whatever makes the * dock appear below the one where we clicked a button to open * it. See bug #630173. */ gtk_widget_show_now (toplevel); gdk_window_raise (gtk_widget_get_window (toplevel)); } return dialog; } /** * pika_dialog_factory_dialog_new: * @factory: a #PikaDialogFactory * @monitor the #GdkMonitor the dialog should appear on * @ui_manager: A #PikaUIManager, if applicable. * @parent: The parent widget, from which the call originated, if * applicable, NULL otherwise. * @identifier: the identifier of the dialog as registered with * pika_dialog_factory_register_entry() * @view_size: the initial preview size * @present: whether gtk_window_present() should be called * * Creates a new toplevel dialog or a #PikaDockable, depending on whether * %factory is a toplevel of dockable factory. * * Returns: the newly created dialog or an already existing singleton * dialog. **/ GtkWidget * pika_dialog_factory_dialog_new (PikaDialogFactory *factory, GdkMonitor *monitor, PikaUIManager *ui_manager, GtkWidget *parent, const gchar *identifier, gint view_size, gboolean present) { g_return_val_if_fail (PIKA_IS_DIALOG_FACTORY (factory), NULL); g_return_val_if_fail (GDK_IS_MONITOR (monitor), NULL); g_return_val_if_fail (identifier != NULL, NULL); return pika_dialog_factory_dialog_new_internal (factory, monitor, factory->p->context, ui_manager, parent, identifier, view_size, FALSE /*return_existing*/, present, FALSE /*create_containers*/); } PikaContext * pika_dialog_factory_get_context (PikaDialogFactory *factory) { g_return_val_if_fail (PIKA_IS_DIALOG_FACTORY (factory), NULL); return factory->p->context; } GList * pika_dialog_factory_get_open_dialogs (PikaDialogFactory *factory) { g_return_val_if_fail (PIKA_IS_DIALOG_FACTORY (factory), NULL); return factory->p->open_dialogs; } GList * pika_dialog_factory_get_session_infos (PikaDialogFactory *factory) { g_return_val_if_fail (PIKA_IS_DIALOG_FACTORY (factory), NULL); return factory->p->session_infos; } void pika_dialog_factory_add_session_info (PikaDialogFactory *factory, PikaSessionInfo *info) { g_return_if_fail (PIKA_IS_DIALOG_FACTORY (factory)); g_return_if_fail (PIKA_IS_SESSION_INFO (info)); /* We want to append rather than prepend so that the serialized * order in sessionrc remains the same */ factory->p->session_infos = g_list_append (factory->p->session_infos, g_object_ref (info)); } /** * pika_dialog_factory_dialog_raise: * @factory: a #PikaDialogFactory * @monitor: the #GdkMonitor the dialog should appear on * @parent: the #GtkWidget from which the raised dialog * originated, if applicable, %NULL otherwise. * @identifiers: a '|' separated list of identifiers of dialogs as * registered with pika_dialog_factory_register_entry() * @view_size: the initial preview size if a dialog needs to be created * * Raises any of a list of already existing toplevel dialog or * #PikaDockable if it was already created by this %facory. * * Implicitly creates the first dialog in the list if none of the dialogs * were found. * * Returns: the raised or newly created dialog. **/ GtkWidget * pika_dialog_factory_dialog_raise (PikaDialogFactory *factory, GdkMonitor *monitor, GtkWidget *parent, const gchar *identifiers, gint view_size) { GtkWidget *dialog; gchar **ids; gint i; g_return_val_if_fail (PIKA_IS_DIALOG_FACTORY (factory), NULL); g_return_val_if_fail (GDK_IS_MONITOR (monitor), NULL); g_return_val_if_fail (identifiers != NULL, NULL); /* If the identifier is a list, try to find a matching dialog and * raise it. If there's no match, use the first list item. * * (we split the identifier list manually here because we must pass * a single identifier, not a list, to new_internal() below) */ ids = g_strsplit (identifiers, "|", 0); for (i = 0; ids[i]; i++) { if (pika_dialog_factory_find_widget (factory, ids[i])) break; } dialog = pika_dialog_factory_dialog_new_internal (factory, monitor, NULL, NULL, parent, ids[i] ? ids[i] : ids[0], view_size, TRUE /*return_existing*/, TRUE /*present*/, TRUE /*create_containers*/); g_strfreev (ids); return dialog; } /** * pika_dialog_factory_dockable_new: * @factory: a #PikaDialogFactory * @dock: a #PikaDock created by this %factory. * @identifier: the identifier of the dialog as registered with * pika_dialog_factory_register_entry() * @view_size: * * Creates a new #PikaDockable in the context of the #PikaDock it will be * added to. * * Implicitly raises & returns an already existing singleton dockable, * so callers should check that pika_dockable_get_dockbook (dockable) * is NULL before trying to add it to it's #PikaDockbook. * * Returns: the newly created #PikaDockable or an already existing * singleton dockable. **/ GtkWidget * pika_dialog_factory_dockable_new (PikaDialogFactory *factory, PikaDock *dock, const gchar *identifier, gint view_size) { g_return_val_if_fail (PIKA_IS_DIALOG_FACTORY (factory), NULL); g_return_val_if_fail (PIKA_IS_DOCK (dock), NULL); g_return_val_if_fail (identifier != NULL, NULL); return pika_dialog_factory_dialog_new_internal (factory, pika_widget_get_monitor (GTK_WIDGET (dock)), pika_dock_get_context (dock), pika_dock_get_ui_manager (dock), NULL, identifier, view_size, FALSE /*return_existing*/, FALSE /*present*/, FALSE /*create_containers*/); } void pika_dialog_factory_add_dialog (PikaDialogFactory *factory, GtkWidget *dialog, GdkMonitor *monitor) { PikaDialogFactory *dialog_factory = NULL; PikaDialogFactoryEntry *entry = NULL; PikaSessionInfo *info = NULL; GList *list = NULL; gboolean toplevel = FALSE; g_return_if_fail (PIKA_IS_DIALOG_FACTORY (factory)); g_return_if_fail (GTK_IS_WIDGET (dialog)); g_return_if_fail (GDK_IS_MONITOR (monitor)); if (g_list_find (factory->p->open_dialogs, dialog)) { g_warning ("%s: dialog already registered", G_STRFUNC); return; } dialog_factory = pika_dialog_factory_from_widget (dialog, &entry); if (! pika_dialog_factory_dialog_sane (factory, dialog_factory, entry, dialog)) return; toplevel = gtk_widget_is_toplevel (dialog); if (entry) { /* dialog is a toplevel (but not a PikaDockWindow) or a PikaDockable */ PIKA_LOG (DIALOG_FACTORY, "adding %s \"%s\"", toplevel ? "toplevel" : "dockable", entry->identifier); for (list = factory->p->session_infos; list; list = g_list_next (list)) { PikaSessionInfo *current_info = list->data; if (pika_session_info_get_factory_entry (current_info) == entry) { if (pika_session_info_get_widget (current_info)) { if (pika_session_info_is_singleton (current_info)) { g_warning ("%s: singleton dialog \"%s\" created twice", G_STRFUNC, entry->identifier); PIKA_LOG (DIALOG_FACTORY, "corrupt session info: %p (widget %p)", current_info, pika_session_info_get_widget (current_info)); return; } continue; } pika_session_info_set_widget (current_info, dialog); PIKA_LOG (DIALOG_FACTORY, "updating session info %p (widget %p) for %s \"%s\"", current_info, pika_session_info_get_widget (current_info), toplevel ? "toplevel" : "dockable", entry->identifier); if (toplevel && pika_session_info_is_session_managed (current_info) && ! gtk_widget_get_visible (dialog)) { PikaGuiConfig *gui_config; gui_config = PIKA_GUI_CONFIG (factory->p->context->pika->config); pika_session_info_apply_geometry (current_info, monitor, gui_config->restore_monitor); } info = current_info; break; } } if (! info) { info = pika_session_info_new (); pika_session_info_set_widget (info, dialog); PIKA_LOG (DIALOG_FACTORY, "creating session info %p (widget %p) for %s \"%s\"", info, pika_session_info_get_widget (info), toplevel ? "toplevel" : "dockable", entry->identifier); pika_session_info_set_factory_entry (info, entry); if (pika_session_info_is_session_managed (info)) { /* Make the dialog show up at the user position the * first time it is shown. After it has been shown the * first time we don't want it to show at the mouse the * next time. Think of the use cases "hide and show with * tab" and "change virtual desktops" */ PIKA_LOG (WM, "setting GTK_WIN_POS_MOUSE for %p (\"%s\")\n", dialog, entry->identifier); gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); g_signal_connect (dialog, "configure-event", G_CALLBACK (pika_dialog_factory_set_user_pos), NULL); } pika_dialog_factory_add_session_info (factory, info); g_object_unref (info); } } /* Some special logic for dock windows */ if (PIKA_IS_DOCK_WINDOW (dialog)) { g_signal_emit (factory, factory_signals[DOCK_WINDOW_ADDED], 0, dialog); } factory->p->open_dialogs = g_list_prepend (factory->p->open_dialogs, dialog); g_signal_connect_object (dialog, "destroy", G_CALLBACK (pika_dialog_factory_remove_dialog), factory, G_CONNECT_SWAPPED); if (pika_session_info_is_session_managed (info)) g_signal_connect_object (dialog, "configure-event", G_CALLBACK (pika_dialog_factory_dialog_configure), factory, 0); } void pika_dialog_factory_add_foreign (PikaDialogFactory *factory, const gchar *identifier, GtkWidget *dialog, GdkMonitor *monitor) { PikaDialogFactory *dialog_factory; PikaDialogFactoryEntry *entry; g_return_if_fail (PIKA_IS_DIALOG_FACTORY (factory)); g_return_if_fail (identifier != NULL); g_return_if_fail (GTK_IS_WIDGET (dialog)); g_return_if_fail (gtk_widget_is_toplevel (dialog)); g_return_if_fail (GDK_IS_MONITOR (monitor)); dialog_factory = pika_dialog_factory_from_widget (dialog, &entry); if (dialog_factory || entry) { g_warning ("%s: dialog was created by a PikaDialogFactory", G_STRFUNC); return; } entry = pika_dialog_factory_find_entry (factory, identifier); if (! entry) { g_warning ("%s: no entry registered for \"%s\"", G_STRFUNC, identifier); return; } if (entry->new_func) { g_warning ("%s: entry for \"%s\" has a constructor (is not foreign)", G_STRFUNC, identifier); return; } pika_dialog_factory_set_widget_data (dialog, factory, entry); pika_dialog_factory_add_dialog (factory, dialog, monitor); } /** * pika_dialog_factory_position_dialog: * @factory: * @identifier: * @dialog: * @monitor: * * We correctly position all newly created dialog via * pika_dialog_factory_add_dialog(), but some dialogs (like various * color dialogs) are never destroyed but created only once per * session. On re-showing, whatever window managing magic kicks in and * the dialog sometimes goes where it shouldn't. * * This function correctly positions a dialog on re-showing so it * appears where it was before it was hidden. * * See https://gitlab.gnome.org/GNOME/gimp/issues/1093 **/ void pika_dialog_factory_position_dialog (PikaDialogFactory *factory, const gchar *identifier, GtkWidget *dialog, GdkMonitor *monitor) { PikaSessionInfo *info; PikaGuiConfig *gui_config; g_return_if_fail (PIKA_IS_DIALOG_FACTORY (factory)); g_return_if_fail (identifier != NULL); g_return_if_fail (GTK_IS_WIDGET (dialog)); g_return_if_fail (gtk_widget_is_toplevel (dialog)); g_return_if_fail (GDK_IS_MONITOR (monitor)); info = pika_dialog_factory_find_session_info (factory, identifier); if (! info) { g_warning ("%s: no session info found for \"%s\"", G_STRFUNC, identifier); return; } if (pika_session_info_get_widget (info) != dialog) { g_warning ("%s: session info for \"%s\" is for a different widget", G_STRFUNC, identifier); return; } gui_config = PIKA_GUI_CONFIG (factory->p->context->pika->config); pika_session_info_apply_geometry (info, monitor, gui_config->restore_monitor); } void pika_dialog_factory_remove_dialog (PikaDialogFactory *factory, GtkWidget *dialog) { PikaDialogFactory *dialog_factory; PikaDialogFactoryEntry *entry; GList *list; g_return_if_fail (PIKA_IS_DIALOG_FACTORY (factory)); g_return_if_fail (GTK_IS_WIDGET (dialog)); if (! g_list_find (factory->p->open_dialogs, dialog)) { g_warning ("%s: dialog not registered", G_STRFUNC); return; } factory->p->open_dialogs = g_list_remove (factory->p->open_dialogs, dialog); dialog_factory = pika_dialog_factory_from_widget (dialog, &entry); if (! pika_dialog_factory_dialog_sane (factory, dialog_factory, entry, dialog)) return; PIKA_LOG (DIALOG_FACTORY, "removing \"%s\" (dialog = %p)", entry->identifier, dialog); for (list = factory->p->session_infos; list; list = g_list_next (list)) { PikaSessionInfo *session_info = list->data; if (pika_session_info_get_widget (session_info) == dialog) { PIKA_LOG (DIALOG_FACTORY, "clearing session info %p (widget %p) for \"%s\"", session_info, pika_session_info_get_widget (session_info), entry->identifier); pika_session_info_set_widget (session_info, NULL); pika_dialog_factory_unset_widget_data (dialog); g_signal_handlers_disconnect_by_func (dialog, pika_dialog_factory_set_user_pos, NULL); g_signal_handlers_disconnect_by_func (dialog, pika_dialog_factory_remove_dialog, factory); if (pika_session_info_is_session_managed (session_info)) g_signal_handlers_disconnect_by_func (dialog, pika_dialog_factory_dialog_configure, factory); if (PIKA_IS_DOCK_WINDOW (dialog)) { /* don't save session info for empty docks */ factory->p->session_infos = g_list_remove (factory->p->session_infos, session_info); g_object_unref (session_info); g_signal_emit (factory, factory_signals[DOCK_WINDOW_REMOVED], 0, dialog); } break; } } } void pika_dialog_factory_hide_dialog (GtkWidget *dialog) { PikaDialogFactory *factory = NULL; g_return_if_fail (GTK_IS_WIDGET (dialog)); g_return_if_fail (gtk_widget_is_toplevel (dialog)); if (! (factory = pika_dialog_factory_from_widget (dialog, NULL))) { g_warning ("%s: dialog was not created by a PikaDialogFactory", G_STRFUNC); return; } gtk_widget_hide (dialog); if (factory->p->dialog_state != PIKA_DIALOGS_SHOWN) g_object_set_data (G_OBJECT (dialog), PIKA_DIALOG_VISIBILITY_KEY, GINT_TO_POINTER (PIKA_DIALOG_VISIBILITY_INVISIBLE)); } void pika_dialog_factory_set_state (PikaDialogFactory *factory, PikaDialogsState state) { g_return_if_fail (PIKA_IS_DIALOG_FACTORY (factory)); factory->p->dialog_state = state; if (state == PIKA_DIALOGS_SHOWN) { pika_dialog_factory_show (factory); } else { pika_dialog_factory_hide (factory); } } PikaDialogsState pika_dialog_factory_get_state (PikaDialogFactory *factory) { g_return_val_if_fail (PIKA_IS_DIALOG_FACTORY (factory), 0); return factory->p->dialog_state; } void pika_dialog_factory_show_with_display (PikaDialogFactory *factory) { g_return_if_fail (PIKA_IS_DIALOG_FACTORY (factory)); if (factory->p->dialog_state == PIKA_DIALOGS_HIDDEN_WITH_DISPLAY) { pika_dialog_factory_set_state (factory, PIKA_DIALOGS_SHOWN); } } void pika_dialog_factory_hide_with_display (PikaDialogFactory *factory) { g_return_if_fail (PIKA_IS_DIALOG_FACTORY (factory)); if (factory->p->dialog_state == PIKA_DIALOGS_SHOWN) { pika_dialog_factory_set_state (factory, PIKA_DIALOGS_HIDDEN_WITH_DISPLAY); } } static GQuark pika_dialog_factory_key = 0; static GQuark pika_dialog_factory_entry_key = 0; PikaDialogFactory * pika_dialog_factory_from_widget (GtkWidget *dialog, PikaDialogFactoryEntry **entry) { g_return_val_if_fail (GTK_IS_WIDGET (dialog), NULL); if (! pika_dialog_factory_key) { pika_dialog_factory_key = g_quark_from_static_string ("pika-dialog-factory"); pika_dialog_factory_entry_key = g_quark_from_static_string ("pika-dialog-factory-entry"); } if (entry) *entry = g_object_get_qdata (G_OBJECT (dialog), pika_dialog_factory_entry_key); return g_object_get_qdata (G_OBJECT (dialog), pika_dialog_factory_key); } #define PIKA_DIALOG_FACTORY_MIN_SIZE_KEY "pika-dialog-factory-min-size" void pika_dialog_factory_set_has_min_size (GtkWindow *window, gboolean has_min_size) { g_return_if_fail (GTK_IS_WINDOW (window)); g_object_set_data (G_OBJECT (window), PIKA_DIALOG_FACTORY_MIN_SIZE_KEY, GINT_TO_POINTER (has_min_size ? TRUE : FALSE)); } gboolean pika_dialog_factory_get_has_min_size (GtkWindow *window) { g_return_val_if_fail (GTK_IS_WINDOW (window), FALSE); return GPOINTER_TO_INT (g_object_get_data (G_OBJECT (window), PIKA_DIALOG_FACTORY_MIN_SIZE_KEY)); } /* private functions */ static GtkWidget * pika_dialog_factory_constructor (PikaDialogFactory *factory, PikaDialogFactoryEntry *entry, PikaContext *context, PikaUIManager *ui_manager, gint view_size) { GtkWidget *widget; widget = entry->new_func (factory, context, ui_manager, view_size); /* The entry is for a dockable, so we simply need to put the created * widget in a dockable */ if (widget && entry->dockable) { GtkWidget *dockable = NULL; dockable = pika_dockable_new (entry->name, entry->blurb, entry->icon_name, entry->help_id); gtk_container_add (GTK_CONTAINER (dockable), widget); gtk_widget_show (widget); /* EEK */ g_object_set_data (G_OBJECT (dockable), "pika-dialog-identifier", entry->identifier); /* Return the dockable instead */ widget = dockable; } return widget; } static void pika_dialog_factory_config_notify (PikaDialogFactory *factory, GParamSpec *pspec, PikaGuiConfig *config) { PikaDialogsState state = pika_dialog_factory_get_state (factory); PikaDialogsState new_state = state; /* Make sure the state and config are in sync */ if (config->hide_docks && state == PIKA_DIALOGS_SHOWN) new_state = PIKA_DIALOGS_HIDDEN_EXPLICITLY; else if (! config->hide_docks) new_state = PIKA_DIALOGS_SHOWN; if (state != new_state) pika_dialog_factory_set_state (factory, new_state); } static void pika_dialog_factory_set_widget_data (GtkWidget *dialog, PikaDialogFactory *factory, PikaDialogFactoryEntry *entry) { g_return_if_fail (GTK_IS_WIDGET (dialog)); g_return_if_fail (PIKA_IS_DIALOG_FACTORY (factory)); if (! pika_dialog_factory_key) { pika_dialog_factory_key = g_quark_from_static_string ("pika-dialog-factory"); pika_dialog_factory_entry_key = g_quark_from_static_string ("pika-dialog-factory-entry"); } g_object_set_qdata (G_OBJECT (dialog), pika_dialog_factory_key, factory); if (entry) g_object_set_qdata (G_OBJECT (dialog), pika_dialog_factory_entry_key, entry); } static void pika_dialog_factory_unset_widget_data (GtkWidget *dialog) { g_return_if_fail (GTK_IS_WIDGET (dialog)); if (! pika_dialog_factory_key) return; g_object_set_qdata (G_OBJECT (dialog), pika_dialog_factory_key, NULL); g_object_set_qdata (G_OBJECT (dialog), pika_dialog_factory_entry_key, NULL); } static gboolean pika_dialog_factory_set_user_pos (GtkWidget *dialog, GdkEventConfigure *cevent, gpointer data) { GdkWindowHints geometry_mask; /* Not only set geometry hints, also reset window position */ gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_NONE); g_signal_handlers_disconnect_by_func (dialog, pika_dialog_factory_set_user_pos, data); PIKA_LOG (WM, "setting GDK_HINT_USER_POS for %p\n", dialog); geometry_mask = GDK_HINT_USER_POS; if (pika_dialog_factory_get_has_min_size (GTK_WINDOW (dialog))) geometry_mask |= GDK_HINT_MIN_SIZE; gtk_window_set_geometry_hints (GTK_WINDOW (dialog), NULL, NULL, geometry_mask); return FALSE; } static gboolean pika_dialog_factory_dialog_configure (GtkWidget *dialog, GdkEventConfigure *cevent, PikaDialogFactory *factory) { PikaDialogFactory *dialog_factory; PikaDialogFactoryEntry *entry; GList *list; if (! g_list_find (factory->p->open_dialogs, dialog)) { g_warning ("%s: dialog not registered", G_STRFUNC); return FALSE; } dialog_factory = pika_dialog_factory_from_widget (dialog, &entry); if (! pika_dialog_factory_dialog_sane (factory, dialog_factory, entry, dialog)) return FALSE; for (list = factory->p->session_infos; list; list = g_list_next (list)) { PikaSessionInfo *session_info = list->data; if (pika_session_info_get_widget (session_info) == dialog) { pika_session_info_read_geometry (session_info, cevent); PIKA_LOG (DIALOG_FACTORY, "updated session info for \"%s\" from window geometry " "(x=%d y=%d %dx%d)", entry->identifier, pika_session_info_get_x (session_info), pika_session_info_get_y (session_info), pika_session_info_get_width (session_info), pika_session_info_get_height (session_info)); break; } } return FALSE; } void pika_dialog_factory_save (PikaDialogFactory *factory, PikaConfigWriter *writer) { GList *infos; for (infos = factory->p->session_infos; infos; infos = g_list_next (infos)) { PikaSessionInfo *info = infos->data; /* we keep session info entries for all toplevel dialogs created * by the factory but don't save them if they don't want to be * managed */ if (! pika_session_info_is_session_managed (info) || pika_session_info_get_factory_entry (info) == NULL) continue; if (pika_session_info_get_widget (info)) pika_session_info_get_info (info); pika_config_writer_open (writer, "session-info"); pika_config_writer_string (writer, pika_object_get_name (factory)); PIKA_CONFIG_GET_IFACE (info)->serialize (PIKA_CONFIG (info), writer, NULL); pika_config_writer_close (writer); if (pika_session_info_get_widget (info)) pika_session_info_clear_info (info); } } void pika_dialog_factory_restore (PikaDialogFactory *factory, GdkMonitor *monitor) { GList *infos; for (infos = factory->p->session_infos; infos; infos = g_list_next (infos)) { PikaSessionInfo *info = infos->data; if (pika_session_info_get_open (info)) { pika_session_info_restore (info, factory, monitor); } else { PIKA_LOG (DIALOG_FACTORY, "skipping to restore session info %p, not open", info); } } } static void pika_dialog_factory_hide (PikaDialogFactory *factory) { GList *list; for (list = factory->p->open_dialogs; list; list = g_list_next (list)) { GtkWidget *widget = list->data; if (GTK_IS_WIDGET (widget) && gtk_widget_is_toplevel (widget)) { PikaDialogFactoryEntry *entry = NULL; PikaDialogVisibilityState visibility = PIKA_DIALOG_VISIBILITY_UNKNOWN; pika_dialog_factory_from_widget (widget, &entry); if (! entry->hideable) continue; if (gtk_widget_get_visible (widget)) { gtk_widget_hide (widget); visibility = PIKA_DIALOG_VISIBILITY_HIDDEN; PIKA_LOG (WM, "Hiding '%s' [%p]", gtk_window_get_title (GTK_WINDOW (widget)), widget); } else { visibility = PIKA_DIALOG_VISIBILITY_INVISIBLE; } g_object_set_data (G_OBJECT (widget), PIKA_DIALOG_VISIBILITY_KEY, GINT_TO_POINTER (visibility)); } } } static void pika_dialog_factory_show (PikaDialogFactory *factory) { GList *list; for (list = factory->p->open_dialogs; list; list = g_list_next (list)) { GtkWidget *widget = list->data; if (GTK_IS_WIDGET (widget) && gtk_widget_is_toplevel (widget)) { PikaDialogVisibilityState visibility; visibility = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), PIKA_DIALOG_VISIBILITY_KEY)); if (! gtk_widget_get_visible (widget) && visibility == PIKA_DIALOG_VISIBILITY_HIDDEN) { PIKA_LOG (WM, "Showing '%s' [%p]", gtk_window_get_title (GTK_WINDOW (widget)), widget); /* Don't use gtk_window_present() here, we don't want the * keyboard focus to move. */ gtk_widget_show (widget); g_object_set_data (G_OBJECT (widget), PIKA_DIALOG_VISIBILITY_KEY, GINT_TO_POINTER (PIKA_DIALOG_VISIBILITY_VISIBLE)); if (gtk_widget_get_visible (widget)) gdk_window_raise (gtk_widget_get_window (widget)); } } } } void pika_dialog_factory_set_busy (PikaDialogFactory *factory) { GList *list; if (! factory) return; for (list = factory->p->open_dialogs; list; list = g_list_next (list)) { GtkWidget *widget = list->data; if (GTK_IS_WIDGET (widget) && gtk_widget_is_toplevel (widget)) { GdkWindow *window = gtk_widget_get_window (widget); if (window) { GdkCursor *cursor = pika_cursor_new (window, PIKA_HANDEDNESS_RIGHT, (PikaCursorType) GDK_WATCH, PIKA_TOOL_CURSOR_NONE, PIKA_CURSOR_MODIFIER_NONE); gdk_window_set_cursor (window, cursor); g_object_unref (cursor); } } } } void pika_dialog_factory_unset_busy (PikaDialogFactory *factory) { GList *list; if (! factory) return; for (list = factory->p->open_dialogs; list; list = g_list_next (list)) { GtkWidget *widget = list->data; if (GTK_IS_WIDGET (widget) && gtk_widget_is_toplevel (widget)) { GdkWindow *window = gtk_widget_get_window (widget); if (window) gdk_window_set_cursor (window, NULL); } } } /** * pika_dialog_factory_get_singleton: * * Returns: The toplevel PikaDialogFactory instance. **/ PikaDialogFactory * pika_dialog_factory_get_singleton (void) { g_return_val_if_fail (pika_toplevel_factory != NULL, NULL); return pika_toplevel_factory; } /** * pika_dialog_factory_set_singleton: * @: * * Set the toplevel PikaDialogFactory instance. Must only be called by * dialogs_init()!. **/ void pika_dialog_factory_set_singleton (PikaDialogFactory *factory) { g_return_if_fail (pika_toplevel_factory == NULL || factory == NULL); pika_toplevel_factory = factory; }