PIKApp/app/gui/gui.c

1022 lines
33 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
*
* 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 <gegl.h>
#include <gtk/gtk.h>
#include "libpikabase/pikabase.h"
#include "libpikawidgets/pikawidgets.h"
#include "libpikawidgets/pikawidgets-private.h"
#include "gui-types.h"
#include "pikaapp.h"
#include "config/pikaguiconfig.h"
#include "core/pika.h"
#include "core/pikacontainer.h"
#include "core/pikacontext.h"
#include "core/pikaimage.h"
#include "core/pikatoolinfo.h"
#include "plug-in/pikaenvirontable.h"
#include "plug-in/pikapluginmanager.h"
#include "display/pikadisplay.h"
#include "display/pikadisplay-foreach.h"
#include "display/pikadisplayshell.h"
#include "display/pikastatusbar.h"
#include "tools/pika-tools.h"
#include "tools/pikatool.h"
#include "tools/tool_manager.h"
#include "widgets/pikaaction.h"
#include "widgets/pikaactiongroup.h"
#include "widgets/pikaaction-history.h"
#include "widgets/pikaclipboard.h"
#include "widgets/pikacolorselectorpalette.h"
#include "widgets/pikacontrollers.h"
#include "widgets/pikadevices.h"
#include "widgets/pikadialogfactory.h"
#include "widgets/pikadnd.h"
#include "widgets/pikarender.h"
#include "widgets/pikahelp.h"
#include "widgets/pikahelp-ids.h"
#include "widgets/pikamenufactory.h"
#include "widgets/pikamessagebox.h"
#include "widgets/pikaradioaction.h"
#include "widgets/pikasessioninfo.h"
#include "widgets/pikauimanager.h"
#include "widgets/pikawidgets-utils.h"
#include "widgets/pikalanguagestore-parser.h"
#include "actions/actions.h"
#include "actions/windows-commands.h"
#include "menus/menus.h"
#include "dialogs/dialogs.h"
#include "pikauiconfigurer.h"
#include "gui.h"
#include "gui-unique.h"
#include "gui-vtable.h"
#include "icon-themes.h"
#include "modifiers.h"
#include "session.h"
#include "splash.h"
#include "themes.h"
2023-10-30 23:55:30 +01:00
#ifdef G_OS_WIN32
#include <windef.h>
#include <winbase.h>
#include <windows.h>
#endif
2023-09-26 00:35:21 +02:00
#ifdef GDK_WINDOWING_QUARTZ
#import <AppKit/AppKit.h>
/* Forward declare since we are building against old SDKs. */
#if !defined(MAC_OS_X_VERSION_10_12) || \
MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_12
@interface NSWindow(ForwardDeclarations)
+ (void)setAllowsAutomaticWindowTabbing:(BOOL)allow;
@end
#endif
#endif /* GDK_WINDOWING_QUARTZ */
#include "pika-intl.h"
/* local function prototypes */
static gchar * gui_sanity_check (void);
static void gui_help_func (const gchar *help_id,
gpointer help_data);
static gboolean gui_get_background_func (PikaRGB *color);
static gboolean gui_get_foreground_func (PikaRGB *color);
static void gui_initialize_after_callback (Pika *pika,
PikaInitStatusFunc callback);
static void gui_restore_callback (Pika *pika,
PikaInitStatusFunc callback);
static void gui_restore_after_callback (Pika *pika,
PikaInitStatusFunc callback);
static gboolean gui_exit_callback (Pika *pika,
gboolean force);
static gboolean gui_exit_after_callback (Pika *pika,
gboolean force);
static void gui_show_help_button_notify (PikaGuiConfig *gui_config,
GParamSpec *pspec,
Pika *pika);
static void gui_user_manual_notify (PikaGuiConfig *gui_config,
GParamSpec *pspec,
Pika *pika);
static void gui_single_window_mode_notify (PikaGuiConfig *gui_config,
GParamSpec *pspec,
PikaUIConfigurer *ui_configurer);
static void gui_clipboard_changed (Pika *pika);
static void gui_menu_show_tooltip (PikaUIManager *manager,
const gchar *tooltip,
Pika *pika);
static void gui_menu_hide_tooltip (PikaUIManager *manager,
Pika *pika);
static void gui_display_changed (PikaContext *context,
PikaDisplay *display,
Pika *pika);
static void gui_check_unique_accelerators (Pika *pika);
/* private variables */
static Pika *the_gui_pika = NULL;
static PikaUIConfigurer *ui_configurer = NULL;
static GdkMonitor *initial_monitor = NULL;
/* public functions */
void
gui_libs_init (GOptionContext *context)
{
g_return_if_fail (context != NULL);
g_option_context_add_group (context, gtk_get_option_group (TRUE));
/* make the PikaDisplay type known by name early, needed for the PDB */
g_type_class_ref (PIKA_TYPE_DISPLAY);
}
void
gui_abort (const gchar *abort_message)
{
GtkWidget *dialog;
GtkWidget *box;
g_return_if_fail (abort_message != NULL);
dialog = pika_dialog_new (_("PIKA Message"), "pika-abort",
NULL, GTK_DIALOG_MODAL, NULL, NULL,
_("_OK"), GTK_RESPONSE_OK,
NULL);
gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
box = g_object_new (PIKA_TYPE_MESSAGE_BOX,
"icon-name", PIKA_ICON_MASCOT_EEK,
"border-width", 12,
NULL);
pika_message_box_set_text (PIKA_MESSAGE_BOX (box), "%s", abort_message);
gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
box, TRUE, TRUE, 0);
gtk_widget_show (box);
pika_dialog_run (PIKA_DIALOG (dialog));
exit (EXIT_FAILURE);
}
/**
* gui_init:
* @pika:
* @no_splash:
* @test_base_dir: a base prefix directory.
*
* @test_base_dir should be set to %NULL in all our codebase except for
* unit testing calls.
*/
PikaInitStatusFunc
gui_init (Pika *pika,
gboolean no_splash,
PikaApp *app,
const gchar *test_base_dir)
{
PikaInitStatusFunc status_callback = NULL;
gchar *abort_message;
g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL);
g_return_val_if_fail (the_gui_pika == NULL, NULL);
g_return_val_if_fail (PIKA_IS_APP (app) || app == NULL, NULL);
abort_message = gui_sanity_check ();
if (abort_message)
gui_abort (abort_message);
the_gui_pika = pika;
/* Normally this should have been taken care of during command line
* parsing as a post-parse hook of gtk_get_option_group(), using the
* system locales.
* But user config may have overridden the language, therefore we must
* check the widget directions again.
*/
gtk_widget_set_default_direction (gtk_get_locale_direction ());
gui_unique_init (pika);
pika_language_store_parser_init ();
/* initialize icon themes before pika_widgets_init() so we avoid
* setting the configured theme twice
*/
icon_themes_init (pika);
pika_widgets_init (gui_help_func,
gui_get_foreground_func,
gui_get_background_func,
NULL, test_base_dir);
g_type_class_ref (PIKA_TYPE_COLOR_SELECT);
/* disable automatic startup notification */
gtk_window_set_auto_startup_notification (FALSE);
#ifdef GDK_WINDOWING_QUARTZ
/* Before the first window is created (typically the splash window),
* we need to disable automatic tabbing behavior introduced on Sierra.
* This is known to cause all kinds of weird issues (see for instance
* Bugzilla #776294) and needs proper GTK+ support if we would want to
* enable it.
*/
if ([NSWindow respondsToSelector:@selector(setAllowsAutomaticWindowTabbing:)])
[NSWindow setAllowsAutomaticWindowTabbing:NO];
#endif /* GDK_WINDOWING_QUARTZ */
pika_dnd_init (pika);
themes_init (pika);
initial_monitor = pika_get_monitor_at_pointer ();
if (! no_splash)
{
splash_create (pika, pika->be_verbose, initial_monitor, app);
status_callback = splash_update;
}
g_signal_connect_after (pika, "initialize",
G_CALLBACK (gui_initialize_after_callback),
NULL);
g_signal_connect (pika, "restore",
G_CALLBACK (gui_restore_callback),
NULL);
g_signal_connect_after (pika, "restore",
G_CALLBACK (gui_restore_after_callback),
NULL);
g_signal_connect (pika, "exit",
G_CALLBACK (gui_exit_callback),
NULL);
g_signal_connect_after (pika, "exit",
G_CALLBACK (gui_exit_after_callback),
NULL);
return status_callback;
}
/*
* gui_recover:
* @n_recoveries: number of recovered files.
*
* Query the user interactively if files were saved from a previous
* crash, asking whether to try and recover or discard them.
*
* Returns: TRUE if answer is to try and recover, FALSE otherwise.
*/
gboolean
gui_recover (gint n_recoveries)
{
GtkWidget *dialog;
GtkWidget *box;
gboolean recover;
dialog = pika_dialog_new (_("Image Recovery"), "pika-recovery",
NULL, GTK_DIALOG_MODAL, NULL, NULL,
_("_Discard"), GTK_RESPONSE_CANCEL,
_("_Recover"), GTK_RESPONSE_OK,
NULL);
gtk_dialog_set_default_response (GTK_DIALOG (dialog),
GTK_RESPONSE_OK);
box = pika_message_box_new (PIKA_ICON_MASCOT_EEK);
gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
box, TRUE, TRUE, 0);
gtk_widget_show (box);
pika_message_box_set_primary_text (PIKA_MESSAGE_BOX (box),
_("Eeek! It looks like PIKA recovered from a crash!"));
pika_message_box_set_text (PIKA_MESSAGE_BOX (box),
/* TRANSLATORS: even if English singular form does
* not use %d, you can use %d for translation in
* any singular/plural form of your language if
* suited. It will just work and be replaced by the
* number of images as expected.
*/
ngettext ("An image was salvaged from the crash. "
"Do you want to try and recover it?",
"%d images were salvaged from the crash. "
"Do you want to try and recover them?",
n_recoveries), n_recoveries);
recover = (pika_dialog_run (PIKA_DIALOG (dialog)) == GTK_RESPONSE_OK);
gtk_widget_destroy (dialog);
return recover;
}
GdkMonitor *
gui_get_initial_monitor (Pika *pika)
{
g_return_val_if_fail (PIKA_IS_PIKA (pika), 0);
return initial_monitor;
}
/* private functions */
static gchar *
gui_sanity_check (void)
{
#define GTK_REQUIRED_MAJOR 3
#define GTK_REQUIRED_MINOR 24
#define GTK_REQUIRED_MICRO 0
const gchar *mismatch = gtk_check_version (GTK_REQUIRED_MAJOR,
GTK_REQUIRED_MINOR,
GTK_REQUIRED_MICRO);
if (mismatch)
{
return g_strdup_printf
("%s\n\n"
"PIKA requires GTK version %d.%d.%d or later.\n"
"Installed GTK version is %d.%d.%d.\n\n"
"Somehow you or your software packager managed\n"
"to install PIKA with an older GTK version.\n\n"
"Please upgrade to GTK version %d.%d.%d or later.",
mismatch,
GTK_REQUIRED_MAJOR, GTK_REQUIRED_MINOR, GTK_REQUIRED_MICRO,
gtk_major_version, gtk_minor_version, gtk_micro_version,
GTK_REQUIRED_MAJOR, GTK_REQUIRED_MINOR, GTK_REQUIRED_MICRO);
}
#undef GTK_REQUIRED_MAJOR
#undef GTK_REQUIRED_MINOR
#undef GTK_REQUIRED_MICRO
return NULL;
}
static void
gui_help_func (const gchar *help_id,
gpointer help_data)
{
g_return_if_fail (PIKA_IS_PIKA (the_gui_pika));
pika_help (the_gui_pika, NULL, NULL, help_id);
}
static gboolean
gui_get_foreground_func (PikaRGB *color)
{
g_return_val_if_fail (color != NULL, FALSE);
g_return_val_if_fail (PIKA_IS_PIKA (the_gui_pika), FALSE);
pika_context_get_foreground (pika_get_user_context (the_gui_pika), color);
return TRUE;
}
static gboolean
gui_get_background_func (PikaRGB *color)
{
g_return_val_if_fail (color != NULL, FALSE);
g_return_val_if_fail (PIKA_IS_PIKA (the_gui_pika), FALSE);
pika_context_get_background (pika_get_user_context (the_gui_pika), color);
return TRUE;
}
static void
gui_initialize_after_callback (Pika *pika,
PikaInitStatusFunc status_callback)
{
const gchar *name = NULL;
g_return_if_fail (PIKA_IS_PIKA (pika));
if (pika->be_verbose)
g_print ("INIT: %s\n", G_STRFUNC);
#if defined (GDK_WINDOWING_X11)
name = "DISPLAY";
#elif defined (GDK_WINDOWING_DIRECTFB) || defined (GDK_WINDOWING_FB)
name = "GDK_DISPLAY";
#endif
/* TODO: Need to care about display migration with GTK+ 2.2 at some point */
if (name)
{
const gchar *display = gdk_display_get_name (gdk_display_get_default ());
pika_environ_table_add (pika->plug_in_manager->environ_table,
name, display, NULL);
}
pika_tools_init (pika);
pika_context_set_tool (pika_get_user_context (pika),
pika_tool_info_get_standard (pika));
}
static void
gui_restore_callback (Pika *pika,
PikaInitStatusFunc status_callback)
{
PikaDisplayConfig *display_config = PIKA_DISPLAY_CONFIG (pika->config);
PikaGuiConfig *gui_config = PIKA_GUI_CONFIG (pika->config);
if (pika->be_verbose)
g_print ("INIT: %s\n", G_STRFUNC);
gui_vtable_init (pika);
pika_dialogs_show_help_button (gui_config->use_help &&
gui_config->show_help_button);
g_signal_connect (gui_config, "notify::use-help",
G_CALLBACK (gui_show_help_button_notify),
pika);
g_signal_connect (gui_config, "notify::user-manual-online",
G_CALLBACK (gui_user_manual_notify),
pika);
g_signal_connect (gui_config, "notify::show-help-button",
G_CALLBACK (gui_show_help_button_notify),
pika);
g_signal_connect (pika_get_user_context (pika), "display-changed",
G_CALLBACK (gui_display_changed),
pika);
/* make sure the monitor resolution is valid */
if (display_config->monitor_res_from_gdk ||
display_config->monitor_xres < PIKA_MIN_RESOLUTION ||
display_config->monitor_yres < PIKA_MIN_RESOLUTION)
{
gdouble xres, yres;
pika_get_monitor_resolution (initial_monitor, &xres, &yres);
g_object_set (pika->config,
"monitor-xresolution", xres,
"monitor-yresolution", yres,
"monitor-resolution-from-windowing-system", TRUE,
NULL);
}
actions_init (pika);
menus_init (pika);
pika_render_init (pika);
dialogs_init (pika);
pika_clipboard_init (pika);
if (pika_get_clipboard_image (pika))
pika_clipboard_set_image (pika, pika_get_clipboard_image (pika));
else
pika_clipboard_set_buffer (pika, pika_get_clipboard_buffer (pika));
g_signal_connect (pika, "clipboard-changed",
G_CALLBACK (gui_clipboard_changed),
NULL);
pika_devices_init (pika);
pika_controllers_init (pika);
modifiers_init (pika);
session_init (pika);
g_type_class_unref (g_type_class_ref (PIKA_TYPE_COLOR_SELECTOR_PALETTE));
status_callback (NULL, _("Tool Options"), 1.0);
pika_tools_restore (pika);
}
static void
gui_restore_after_callback (Pika *pika,
PikaInitStatusFunc status_callback)
{
PikaGuiConfig *gui_config = PIKA_GUI_CONFIG (pika->config);
PikaUIManager *image_ui_manager;
PikaDisplay *display;
2023-10-30 23:55:30 +01:00
#ifdef G_OS_WIN32
STARTUPINFO StartupInfo;
GetStartupInfo (&StartupInfo);
#endif
2023-09-26 00:35:21 +02:00
if (pika->be_verbose)
g_print ("INIT: %s\n", G_STRFUNC);
pika->message_handler = PIKA_MESSAGE_BOX;
/* load the recent documents after pika_real_restore() because we
* need the mime-types implemented by plug-ins
*/
status_callback (NULL, _("Documents"), 0.9);
pika_recent_list_load (pika);
/* enable this to always have icons everywhere */
if (g_getenv ("PIKA_ICONS_LIKE_A_BOSS"))
{
GdkScreen *screen = gdk_screen_get_default ();
g_object_set (G_OBJECT (gtk_settings_get_for_screen (screen)),
"gtk-button-images", TRUE,
"gtk-menu-images", TRUE,
NULL);
}
ui_configurer = g_object_new (PIKA_TYPE_UI_CONFIGURER,
"pika", pika,
NULL);
image_ui_manager = menus_get_image_manager_singleton (pika);
pika_ui_manager_update (image_ui_manager, pika);
if (gui_config->restore_accels)
menus_restore (pika);
/* Check that every accelerator is unique. */
gui_check_unique_accelerators (pika);
pika_action_history_init (pika);
g_signal_connect_object (gui_config, "notify::single-window-mode",
G_CALLBACK (gui_single_window_mode_notify),
ui_configurer, 0);
g_signal_connect (image_ui_manager, "show-tooltip",
G_CALLBACK (gui_menu_show_tooltip),
pika);
g_signal_connect (image_ui_manager, "hide-tooltip",
G_CALLBACK (gui_menu_hide_tooltip),
pika);
pika_devices_restore (pika);
pika_controllers_restore (pika, image_ui_manager);
modifiers_restore (pika);
if (status_callback == splash_update)
splash_destroy ();
if (pika_get_show_gui (pika))
{
PikaDisplayShell *shell;
GtkWidget *toplevel;
/* create the empty display */
display = PIKA_DISPLAY (pika_create_display (pika, NULL,
PIKA_UNIT_PIXEL, 1.0,
G_OBJECT (initial_monitor)));
shell = pika_display_get_shell (display);
2023-10-30 23:55:30 +01:00
#ifdef G_OS_WIN32
themes_set_title_bar (pika);
#endif
2023-09-26 00:35:21 +02:00
if (gui_config->restore_session)
session_restore (pika, initial_monitor);
toplevel = gtk_widget_get_toplevel (GTK_WIDGET (shell));
2023-10-30 23:55:30 +01:00
#ifdef G_OS_WIN32
/* Prevents window from reappearing on start-up if the user
* requested it to be minimized via window hints
*/
if (StartupInfo.wShowWindow != SW_SHOWMINIMIZED &&
StartupInfo.wShowWindow != SW_SHOWMINNOACTIVE &&
StartupInfo.wShowWindow != SW_MINIMIZE)
#endif
2023-09-26 00:35:21 +02:00
/* move keyboard focus to the display */
gtk_window_present (GTK_WINDOW (toplevel));
}
/* indicate that the application has finished loading */
gdk_notify_startup_complete ();
/* clear startup monitor variables */
initial_monitor = NULL;
}
static gboolean
gui_exit_callback (Pika *pika,
gboolean force)
{
PikaGuiConfig *gui_config = PIKA_GUI_CONFIG (pika->config);
PikaTool *active_tool;
if (pika->be_verbose)
g_print ("EXIT: %s\n", G_STRFUNC);
if (! force && pika_displays_dirty (pika))
{
PikaContext *context = pika_get_user_context (pika);
PikaDisplay *display = pika_context_get_display (context);
GdkMonitor *monitor = pika_get_monitor_at_pointer ();
GtkWidget *parent = NULL;
if (display)
{
PikaDisplayShell *shell = pika_display_get_shell (display);
parent = GTK_WIDGET (pika_display_shell_get_window (shell));
}
pika_dialog_factory_dialog_raise (pika_dialog_factory_get_singleton (),
monitor, parent, "pika-quit-dialog", -1);
return TRUE; /* stop exit for now */
}
pika->message_handler = PIKA_CONSOLE;
gui_unique_exit ();
/* If any modifier is set when quitting (typically when exiting with
* Ctrl-q for instance!), when serializing the tool options, it will
* save any alternate value instead of the main one. Make sure that
* any modifier is reset before saving options.
*/
active_tool = tool_manager_get_active (pika);
if (active_tool && active_tool->focus_display)
pika_tool_set_modifier_state (active_tool, 0, active_tool->focus_display);
if (gui_config->save_session_info)
session_save (pika, FALSE);
if (gui_config->save_device_status)
pika_devices_save (pika, FALSE);
if (TRUE /* gui_config->save_controllers */)
pika_controllers_save (pika);
modifiers_save (pika, FALSE);
g_signal_handlers_disconnect_by_func (pika_get_user_context (pika),
gui_display_changed,
pika);
pika_displays_delete (pika);
if (gui_config->save_accels)
menus_save (pika, FALSE);
pika_tools_save (pika, gui_config->save_tool_options, FALSE);
pika_tools_exit (pika);
pika_language_store_parser_clean ();
return FALSE; /* continue exiting */
}
static gboolean
gui_exit_after_callback (Pika *pika,
gboolean force)
{
if (pika->be_verbose)
g_print ("EXIT: %s\n", G_STRFUNC);
g_signal_handlers_disconnect_by_func (pika->config,
gui_show_help_button_notify,
pika);
g_signal_handlers_disconnect_by_func (pika->config,
gui_user_manual_notify,
pika);
pika_action_history_exit (pika);
g_object_unref (ui_configurer);
ui_configurer = NULL;
/* exit the clipboard before shutting down the GUI because it runs
* a whole lot of code paths. See bug #731389.
*/
g_signal_handlers_disconnect_by_func (pika,
G_CALLBACK (gui_clipboard_changed),
NULL);
pika_clipboard_exit (pika);
session_exit (pika);
menus_exit (pika);
actions_exit (pika);
pika_render_exit (pika);
pika_controllers_exit (pika);
modifiers_exit (pika);
pika_devices_exit (pika);
dialogs_exit (pika);
themes_exit (pika);
g_type_class_unref (g_type_class_peek (PIKA_TYPE_COLOR_SELECT));
return FALSE; /* continue exiting */
}
static void
gui_show_help_button_notify (PikaGuiConfig *gui_config,
GParamSpec *param_spec,
Pika *pika)
{
pika_dialogs_show_help_button (gui_config->use_help &&
gui_config->show_help_button);
}
static void
gui_user_manual_notify (PikaGuiConfig *gui_config,
GParamSpec *param_spec,
Pika *pika)
{
pika_help_user_manual_changed (pika);
}
static void
gui_single_window_mode_notify (PikaGuiConfig *gui_config,
GParamSpec *pspec,
PikaUIConfigurer *ui_configurer)
{
pika_ui_configurer_configure (ui_configurer,
gui_config->single_window_mode);
}
static void
gui_clipboard_changed (Pika *pika)
{
if (pika_get_clipboard_image (pika))
pika_clipboard_set_image (pika, pika_get_clipboard_image (pika));
else
pika_clipboard_set_buffer (pika, pika_get_clipboard_buffer (pika));
}
static void
gui_menu_show_tooltip (PikaUIManager *manager,
const gchar *tooltip,
Pika *pika)
{
PikaContext *context = pika_get_user_context (pika);
PikaDisplay *display = pika_context_get_display (context);
if (display)
{
PikaDisplayShell *shell = pika_display_get_shell (display);
PikaStatusbar *statusbar = pika_display_shell_get_statusbar (shell);
pika_statusbar_push (statusbar, "menu-tooltip",
NULL, "%s", tooltip);
}
}
static void
gui_menu_hide_tooltip (PikaUIManager *manager,
Pika *pika)
{
PikaContext *context = pika_get_user_context (pika);
PikaDisplay *display = pika_context_get_display (context);
if (display)
{
PikaDisplayShell *shell = pika_display_get_shell (display);
PikaStatusbar *statusbar = pika_display_shell_get_statusbar (shell);
pika_statusbar_pop (statusbar, "menu-tooltip");
}
}
static void
gui_display_changed (PikaContext *context,
PikaDisplay *display,
Pika *pika)
{
if (! display)
{
PikaImage *image = pika_context_get_image (context);
if (image)
{
GList *list;
for (list = pika_get_display_iter (pika);
list;
list = g_list_next (list))
{
PikaDisplay *display2 = list->data;
if (pika_display_get_image (display2) == image)
{
pika_context_set_display (context, display2);
/* stop the emission of the original signal
* (the emission of the recursive signal is finished)
*/
g_signal_stop_emission_by_name (context, "display-changed");
return;
}
}
pika_context_set_image (context, NULL);
}
}
pika_ui_manager_update (menus_get_image_manager_singleton (pika),
display);
}
typedef struct
{
const gchar *path;
guint key;
GdkModifierType mods;
}
accelData;
static void
gui_check_unique_accelerators (Pika *pika)
{
gchar **actions;
actions = g_action_group_list_actions (G_ACTION_GROUP (pika->app));
for (gint i = 0; actions[i] != NULL; i++)
{
gchar **accels;
gchar *detailed_name;
PikaAction *action;
gint value;
action = (PikaAction *) g_action_map_lookup_action (G_ACTION_MAP (pika->app), actions[i]);
if (PIKA_IS_RADIO_ACTION (action))
{
g_object_get ((GObject *) action,
"value", &value,
NULL);
detailed_name = g_strdup_printf ("app.%s(%i)", actions[i],
value);
}
else
{
detailed_name = g_strdup_printf ("app.%s", actions[i]);
}
accels = gtk_application_get_accels_for_action (GTK_APPLICATION (pika->app),
detailed_name);
g_free (detailed_name);
for (gint j = 0; accels[j] != NULL; j++)
{
for (gint k = i + 1; actions[k] != NULL; k++)
{
gchar **accels2;
gchar *detailed_name2;
PikaAction *action2;
action2 = (PikaAction *) g_action_map_lookup_action (G_ACTION_MAP (pika->app), actions[k]);
if (PIKA_IS_RADIO_ACTION (action2))
{
g_object_get ((GObject *) action2,
"value", &value,
NULL);
detailed_name2 = g_strdup_printf ("app.%s(%i)", actions[k],
value);
}
else
{
detailed_name2 = g_strdup_printf ("app.%s", actions[k]);
}
accels2 = gtk_application_get_accels_for_action (GTK_APPLICATION (pika->app),
detailed_name2);
g_free (detailed_name2);
for (gint l = 0; accels2[l] != NULL; l++)
{
if (g_strcmp0 (accels[j], accels2[l]) == 0)
{
GAction *action;
gchar *disabled_action;
gchar **disabled_accels;
gint len;
gint remove;
2023-10-30 23:55:30 +01:00
gboolean print_warning = TRUE;
2023-09-26 00:35:21 +02:00
2023-10-30 23:55:30 +01:00
action = g_action_map_lookup_action (G_ACTION_MAP (pika->app),
actions[i]);
2023-09-26 00:35:21 +02:00
/* Just keep the first one (no reason other than we have
* to choose), unless it's a secondary shortcut, and the
* second is a primary shortcut.
*/
2023-10-30 23:55:30 +01:00
if ((l == 0 && j != 0) ||
/* If the first action is one of "view-zoom-1-*" and
* the shortcut default, we assume it's because of our
* trick to transform `Shift+num` shortcuts based on
* layout and we happen to be on a layout where it
* clashes with other shortcuts. In this case, we
* drop the duplicate shortcut on the zoom action. See
* special code in
* pika_action_group_add_action_with_accel()
*/
(g_str_has_prefix (actions[i], "view-zoom-1-") &&
pika_action_is_default_accel (PIKA_ACTION (action), accels[j])))
2023-09-26 00:35:21 +02:00
{
disabled_action = actions[i];
disabled_accels = accels;
remove = j;
}
else
{
disabled_action = actions[k];
disabled_accels = accels2;
remove = l;
}
2023-10-30 23:55:30 +01:00
action = g_action_map_lookup_action (G_ACTION_MAP (pika->app),
disabled_action);
if (g_str_has_prefix (disabled_action, "view-zoom-1-") &&
pika_action_is_default_accel (PIKA_ACTION (action), disabled_accels[remove]))
/* We drop the shortcut **silently** because it will be
* a case where we have 2 default accelerators clashing
* (because of the conversion code) while not being a
* real bug. Clashes with custom accelerators are
* handled by shortcuts_action_deserialize().
*/
print_warning = FALSE;
2023-09-26 00:35:21 +02:00
/* Remove only the duplicate shortcut but keep others. */
len = g_strv_length (disabled_accels);
g_free (disabled_accels[remove]);
memmove (&disabled_accels[remove],
&disabled_accels[remove + 1],
sizeof (char *) * (len - remove));
2023-10-30 23:55:30 +01:00
if (print_warning)
g_printerr ("Actions \"%s\" and \"%s\" use the same accelerator.\n"
" Disabling the accelerator on \"%s\".\n",
actions[i], actions[k], disabled_action);
2023-09-26 00:35:21 +02:00
pika_action_set_accels (PIKA_ACTION (action),
(const gchar **) disabled_accels);
}
}
g_strfreev (accels2);
}
}
g_strfreev (accels);
}
g_strfreev (actions);
}