/* LIBPIKA - The PIKA Library * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball * * This library is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * . */ #include "config.h" #include #ifdef GDK_WINDOWING_WIN32 #include #endif #ifdef GDK_WINDOWING_X11 #include #endif #ifdef GDK_WINDOWING_QUARTZ #include #endif #ifdef GDK_WINDOWING_WAYLAND #include #endif #include "pika.h" #include "pikaui.h" #include "libpikamodule/pikamodule.h" #include "libpikawidgets/pikawidgets.h" #include "libpikawidgets/pikawidgets-private.h" /** * SECTION: pikaui * @title: pikaui * @short_description: Common user interface functions. This header includes * all other PIKA User Interface Library headers. * @see_also: gtk_init(), gdk_set_use_xshm(), gtk_widget_set_default_visual(). * * Common user interface functions. This header includes all other * PIKA User Interface Library headers. **/ /* local function prototypes */ static void pika_ui_help_func (const gchar *help_id, gpointer help_data); static void pika_ui_theme_changed (GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, GtkCssProvider *css_provider); static void pika_ensure_modules (void); #ifdef GDK_WINDOWING_QUARTZ static gboolean pika_osx_focus_window (gpointer); #endif #ifndef GDK_WINDOWING_WIN32 static GdkWindow * pika_ui_get_foreign_window (gpointer window); #endif static gboolean pika_window_transient_on_mapped (GtkWidget *window, GdkEventAny *event, PikaDisplay *display); static gboolean pika_ui_initialized = FALSE; /* public functions */ /** * pika_ui_init: * @prog_name: The name of the plug-in which will be passed as argv[0] to * gtk_init(). It's a convention to use the name of the * executable and _not_ the PDB procedure name. * * This function initializes GTK+ with gtk_init(). * * It also sets up various other things so that the plug-in user looks * and behaves like the PIKA core. This includes selecting the GTK+ * theme and setting up the help system as chosen in the PIKA * preferences. Any plug-in that provides a user interface should call * this function. **/ void pika_ui_init (const gchar *prog_name) { const gchar *display_name; GtkCssProvider *css_provider; GFileMonitor *css_monitor; GFile *file; g_return_if_fail (prog_name != NULL); if (pika_ui_initialized) return; g_set_prgname (prog_name); display_name = pika_display_name (); if (display_name) { #if defined (GDK_WINDOWING_X11) g_setenv ("DISPLAY", display_name, TRUE); #else g_setenv ("GDK_DISPLAY", display_name, TRUE); #endif } if (pika_user_time ()) { /* Construct a fake startup ID as we only want to pass the * interaction timestamp, see _gdk_windowing_set_default_display(). */ gchar *startup_id = g_strdup_printf ("_TIME%u", pika_user_time ()); g_setenv ("DESKTOP_STARTUP_ID", startup_id, TRUE); g_free (startup_id); } gtk_init (NULL, NULL); css_provider = gtk_css_provider_new (); gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), GTK_STYLE_PROVIDER (css_provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); file = pika_directory_file ("theme.css", NULL); css_monitor = g_file_monitor (file, G_FILE_MONITOR_NONE, NULL, NULL); g_object_unref (file); pika_ui_theme_changed (css_monitor, NULL, NULL, G_FILE_MONITOR_EVENT_CHANGED, css_provider); g_signal_connect (css_monitor, "changed", G_CALLBACK (pika_ui_theme_changed), css_provider); g_object_unref (css_provider); gdk_set_program_class (pika_wm_class ()); if (pika_icon_theme_dir ()) { file = g_file_new_for_path (pika_icon_theme_dir ()); pika_icons_set_icon_theme (file); g_object_unref (file); } pika_widgets_init (pika_ui_help_func, pika_context_get_foreground, pika_context_get_background, pika_ensure_modules, NULL); pika_dialogs_show_help_button (pika_show_help_button ()); #ifdef GDK_WINDOWING_QUARTZ g_idle_add (pika_osx_focus_window, NULL); #endif /* Some widgets use GEGL buffers for thumbnails, previews, etc. */ gegl_init (NULL, NULL); pika_ui_initialized = TRUE; } #ifdef GDK_WINDOWING_QUARTZ static void pika_window_transient_show (GtkWidget *window) { g_signal_handlers_disconnect_by_func (window, pika_window_transient_show, NULL); [NSApp arrangeInFront: nil]; } #endif /** * pika_window_set_transient_for_display: * @window: the #GtkWindow that should become transient * @display: display of the image window that should become the parent * * Indicates to the window manager that @window is a transient dialog * associated with the PIKA image window that is identified by its * display. See gdk_window_set_transient_for () for more information. * * Most of the time you will want to use the convenience function * pika_window_set_transient(). * * Since: 2.4 */ void pika_window_set_transient_for_display (GtkWindow *window, PikaDisplay *display) { g_return_if_fail (pika_ui_initialized); g_return_if_fail (GTK_IS_WINDOW (window)); g_return_if_fail (PIKA_IS_DISPLAY (display)); g_signal_connect_after (window, "map-event", G_CALLBACK (pika_window_transient_on_mapped), display); if (gtk_widget_get_mapped (GTK_WIDGET (window))) pika_window_transient_on_mapped (GTK_WIDGET (window), NULL, display); } /** * pika_window_set_transient: * @window: the #GtkWindow that should become transient * * Indicates to the window manager that @window is a transient dialog * associated with the PIKA window that the plug-in has been * started from. See also pika_window_set_transient_for_display(). * * Since: 2.4 */ void pika_window_set_transient (GtkWindow *window) { g_return_if_fail (pika_ui_initialized); g_return_if_fail (GTK_IS_WINDOW (window)); g_signal_connect_after (window, "map-event", G_CALLBACK (pika_window_transient_on_mapped), NULL); if (gtk_widget_get_mapped (GTK_WIDGET (window))) pika_window_transient_on_mapped (GTK_WIDGET (window), NULL, NULL); } /* private functions */ static void pika_ui_help_func (const gchar *help_id, gpointer help_data) { pika_help (NULL, help_id); } static void pika_ui_theme_changed (GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, GtkCssProvider *css_provider) { GError *error = NULL; gchar *contents; file = pika_directory_file ("theme.css", NULL); if (g_file_load_contents (file, NULL, &contents, NULL, NULL, &error)) { gboolean prefer_dark_theme; prefer_dark_theme = strstr (contents, "/* prefer-dark-theme */") != NULL; g_object_set (gtk_settings_get_for_screen (gdk_screen_get_default ()), "gtk-application-prefer-dark-theme", prefer_dark_theme, NULL); g_free (contents); } else { g_printerr ("%s: error loading %s: %s\n", G_STRFUNC, pika_file_get_utf8_name (file), error->message); g_clear_error (&error); } if (! gtk_css_provider_load_from_file (css_provider, file, &error)) { g_printerr ("%s: error parsing %s: %s\n", G_STRFUNC, pika_file_get_utf8_name (file), error->message); g_clear_error (&error); } g_object_unref (file); } static void pika_ensure_modules (void) { static PikaModuleDB *module_db = NULL; if (! module_db) { gchar *load_inhibit = pika_get_module_load_inhibit (); gchar *module_path = pika_pikarc_query ("module-path"); module_db = pika_module_db_new (FALSE); pika_module_db_set_load_inhibit (module_db, load_inhibit); pika_module_db_load (module_db, module_path); g_free (module_path); g_free (load_inhibit); } } #ifdef GDK_WINDOWING_QUARTZ static gboolean pika_osx_focus_window (gpointer user_data) { [NSApp activateIgnoringOtherApps:YES]; return FALSE; } #endif static GdkWindow * pika_ui_get_foreign_window (gpointer window) { #ifdef GDK_WINDOWING_X11 if (GDK_IS_X11_DISPLAY (gdk_display_get_default ())) return gdk_x11_window_foreign_new_for_display (gdk_display_get_default (), (Window) window); #endif #ifdef GDK_WINDOWING_WIN32 return gdk_win32_window_foreign_new_for_display (gdk_display_get_default (), (HWND) window); #endif return NULL; } static gboolean pika_window_transient_on_mapped (GtkWidget *window, GdkEventAny *event, PikaDisplay *display) { GBytes *handle; gboolean transient_set = FALSE; if (display != NULL) handle = pika_display_get_window_handle (display); else handle = pika_progress_get_window_handle (); if (handle == NULL) return FALSE; #ifdef GDK_WINDOWING_WAYLAND if (GDK_IS_WAYLAND_DISPLAY (gdk_display_get_default ())) { char *wayland_handle; wayland_handle = (char *) g_bytes_get_data (handle, NULL); gdk_wayland_window_set_transient_for_exported (gtk_widget_get_window (window), wayland_handle); transient_set = TRUE; } #endif #ifdef GDK_WINDOWING_X11 if (! transient_set && GDK_IS_X11_DISPLAY (gdk_display_get_default ())) { GdkWindow *parent; Window *handle_data; Window parent_ID; gsize handle_size; handle_data = (Window *) g_bytes_get_data (handle, &handle_size); g_return_val_if_fail (handle_size == sizeof (Window), FALSE); parent_ID = *handle_data; parent = pika_ui_get_foreign_window ((gpointer) parent_ID); if (parent) gdk_window_set_transient_for (gtk_widget_get_window (window), parent); transient_set = TRUE; } #endif /* To know why it is disabled on Win32, see pika_window_set_transient_cb() in * app/widgets/pikawidgets-utils.c. */ #if 0 && defined (GDK_WINDOWING_WIN32) if (! transient_set) { GdkWindow *parent; HANDLE *handle_data; HANDLE parent_ID; gsize handle_size; handle_data = (HANDLE *) g_bytes_get_data (handle, &handle_size); g_return_val_if_fail (handle_size == sizeof (HANDLE), FALSE); parent_ID = *handle_data; parent = pika_ui_get_foreign_window ((gpointer) parent_ID); if (parent) gdk_window_set_transient_for (gtk_widget_get_window (window), parent); transient_set = TRUE; } #endif if (! transient_set) { /* if setting the window transient failed, at least set * WIN_POS_CENTER, which will center the window on the screen * where the mouse is (see bug #684003). */ gtk_window_set_position (GTK_WINDOW (window), GTK_WIN_POS_CENTER); #ifdef GDK_WINDOWING_QUARTZ g_signal_connect (window, "show", G_CALLBACK (pika_window_transient_show), NULL); #endif } g_bytes_unref (handle); return FALSE; }