PIKApp/app/widgets/pikadialogfactory.c

1677 lines
55 KiB
C
Raw Permalink Normal View History

2023-09-26 00:35:21 +02:00
/* 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 <mitch@gimp.org>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include <gegl.h>
#include <gtk/gtk.h>
#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.
*
2023-12-10 03:23:03 +01:00
* See https://gitlab.gnome.org/GNOME/gimp/issues/1093
2023-09-26 00:35:21 +02:00
**/
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;
}