1 /* LIBGIMP - The GIMP Library
2  * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
3  *
4  * This library is free software: you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 3 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library.  If not, see
16  * <https://www.gnu.org/licenses/>.
17  */
18 
19 #include "config.h"
20 
21 #ifdef GDK_DISABLE_DEPRECATED
22 #undef GDK_DISABLE_DEPRECATED
23 #endif
24 #include <gtk/gtk.h>
25 
26 #ifdef GDK_WINDOWING_WIN32
27 #include <gdk/gdkwin32.h>
28 #endif
29 
30 #ifdef GDK_WINDOWING_X11
31 #include <gdk/gdkx.h>
32 #endif
33 
34 #ifdef GDK_WINDOWING_QUARTZ
35 #include <Cocoa/Cocoa.h>
36 #endif
37 
38 #include "gimp.h"
39 #include "gimpui.h"
40 
41 #include "libgimpmodule/gimpmodule.h"
42 
43 #include "libgimpwidgets/gimpwidgets.h"
44 #include "libgimpwidgets/gimpwidgets-private.h"
45 
46 
47 /**
48  * SECTION: gimpui
49  * @title: gimpui
50  * @short_description: Common user interface functions. This header includes
51  *                     all other GIMP User Interface Library headers.
52  * @see_also: gtk_init(), gdk_set_use_xshm(), gdk_rgb_get_visual(),
53  *            gdk_rgb_get_cmap(), gtk_widget_set_default_visual(),
54  *            gtk_widget_set_default_colormap(), gtk_preview_set_gamma().
55  *
56  * Common user interface functions. This header includes all other
57  * GIMP User Interface Library headers.
58  **/
59 
60 
61 /*  local function prototypes  */
62 
63 static void      gimp_ui_help_func              (const gchar   *help_id,
64                                                  gpointer       help_data);
65 static void      gimp_ensure_modules            (void);
66 static void      gimp_window_transient_realized (GtkWidget     *window,
67                                                  GdkWindow     *parent);
68 static gboolean  gimp_window_set_transient_for  (GtkWindow     *window,
69                                                  GdkWindow     *parent);
70 
71 static void      gimp_ui_theme_changed          (void);
72 static void      gimp_ui_fix_pixbuf_style       (void);
73 static void      gimp_ui_draw_pixbuf_layout     (GtkStyle      *style,
74                                                  GdkWindow     *window,
75                                                  GtkStateType   state_type,
76                                                  gboolean       use_text,
77                                                  GdkRectangle  *area,
78                                                  GtkWidget     *widget,
79                                                  const gchar   *detail,
80                                                  gint           x,
81                                                  gint           y,
82                                                  PangoLayout   *layout);
83 #ifdef GDK_WINDOWING_QUARTZ
84 static gboolean  gimp_osx_focus_window          (gpointer);
85 #endif
86 
87 static gboolean gimp_ui_initialized = FALSE;
88 
89 
90 /*  public functions  */
91 
92 /**
93  * gimp_ui_init:
94  * @prog_name: The name of the plug-in which will be passed as argv[0] to
95  *             gtk_init(). It's a convention to use the name of the
96  *             executable and _not_ the PDB procedure name.
97  * @preview:   This parameter is unused and exists for historical
98  *             reasons only.
99  *
100  * This function initializes GTK+ with gtk_init() and initializes GDK's
101  * image rendering subsystem (GdkRGB) to follow the GIMP main program's
102  * colormap allocation/installation policy.
103  *
104  * It also sets up various other things so that the plug-in user looks
105  * and behaves like the GIMP core. This includes selecting the GTK+
106  * theme and setting up the help system as chosen in the GIMP
107  * preferences. Any plug-in that provides a user interface should call
108  * this function.
109  **/
110 void
gimp_ui_init(const gchar * prog_name,gboolean preview)111 gimp_ui_init (const gchar *prog_name,
112               gboolean     preview)
113 {
114   GdkScreen    *screen;
115   const gchar  *display_name;
116   gchar        *themerc;
117   GFileMonitor *rc_monitor;
118   GFile        *file;
119 
120   g_return_if_fail (prog_name != NULL);
121 
122   if (gimp_ui_initialized)
123     return;
124 
125   g_set_prgname (prog_name);
126 
127   display_name = gimp_display_name ();
128 
129   if (display_name)
130     {
131 #if defined (GDK_WINDOWING_X11)
132       g_setenv ("DISPLAY", display_name, TRUE);
133 #else
134       g_setenv ("GDK_DISPLAY", display_name, TRUE);
135 #endif
136     }
137 
138   if (gimp_user_time ())
139     {
140       /* Construct a fake startup ID as we only want to pass the
141        * interaction timestamp, see _gdk_windowing_set_default_display().
142        */
143       gchar *startup_id = g_strdup_printf ("_TIME%u", gimp_user_time ());
144 
145       g_setenv ("DESKTOP_STARTUP_ID", startup_id, TRUE);
146       g_free (startup_id);
147     }
148 
149   gtk_init (NULL, NULL);
150 
151   themerc = gimp_personal_rc_file ("themerc");
152   gtk_rc_parse (themerc);
153 
154   file = g_file_new_for_path (themerc);
155   g_free (themerc);
156 
157   rc_monitor = g_file_monitor (file, G_FILE_MONITOR_NONE, NULL, NULL);
158   g_object_unref (file);
159 
160   g_signal_connect (rc_monitor, "changed",
161                     G_CALLBACK (gimp_ui_theme_changed),
162                     NULL);
163 
164   gdk_set_program_class (gimp_wm_class ());
165 
166   screen = gdk_screen_get_default ();
167   gtk_widget_set_default_colormap (gdk_screen_get_rgb_colormap (screen));
168 
169   if (gimp_icon_theme_dir ())
170     {
171       file = g_file_new_for_path (gimp_icon_theme_dir ());
172       gimp_icons_set_icon_theme (file);
173       g_object_unref (file);
174     }
175 
176   gimp_widgets_init (gimp_ui_help_func,
177                      gimp_context_get_foreground,
178                      gimp_context_get_background,
179                      gimp_ensure_modules);
180 
181   if (! gimp_show_tool_tips ())
182     gimp_help_disable_tooltips ();
183 
184   gimp_dialogs_show_help_button (gimp_show_help_button ());
185 
186 #ifdef GDK_WINDOWING_QUARTZ
187   g_idle_add (gimp_osx_focus_window, NULL);
188 #endif
189 
190   gimp_ui_fix_pixbuf_style ();
191   gimp_ui_initialized = TRUE;
192 }
193 
194 static GdkWindow *
gimp_ui_get_foreign_window(guint32 window)195 gimp_ui_get_foreign_window (guint32 window)
196 {
197 #ifdef GDK_WINDOWING_X11
198   return gdk_x11_window_foreign_new_for_display (gdk_display_get_default (),
199                                                  window);
200 #endif
201 
202 #ifdef GDK_WINDOWING_WIN32
203   return gdk_win32_window_foreign_new_for_display (gdk_display_get_default (),
204                                                    (HWND) (uintptr_t) window);
205 #endif
206 
207   return NULL;
208 }
209 
210 /**
211  * gimp_ui_get_display_window:
212  * @gdisp_ID: a GimpDisplay ID.
213  *
214  * Returns the #GdkWindow of a display window. The purpose is to allow
215  * to make plug-in dialogs transient to the image display as explained
216  * with gdk_window_set_transient_for().
217  *
218  * You shouldn't have to call this function directly. Use
219  * gimp_window_set_transient_for_display() instead.
220  *
221  * Return value: A reference to a #GdkWindow or %NULL. You should
222  *               unref the window using g_object_unref() as soon as
223  *               you don't need it any longer.
224  *
225  * Since: 2.4
226  */
227 GdkWindow *
gimp_ui_get_display_window(guint32 gdisp_ID)228 gimp_ui_get_display_window (guint32 gdisp_ID)
229 {
230   guint32 window;
231 
232   g_return_val_if_fail (gimp_ui_initialized, NULL);
233 
234   window = gimp_display_get_window_handle (gdisp_ID);
235   if (window)
236     return gimp_ui_get_foreign_window (window);
237 
238   return NULL;
239 }
240 
241 /**
242  * gimp_ui_get_progress_window:
243  *
244  * Returns the #GdkWindow of the window this plug-in's progress bar is
245  * shown in. Use it to make plug-in dialogs transient to this window
246  * as explained with gdk_window_set_transient_for().
247  *
248  * You shouldn't have to call this function directly. Use
249  * gimp_window_set_transient() instead.
250  *
251  * Return value: A reference to a #GdkWindow or %NULL. You should
252  *               unref the window using g_object_unref() as soon as
253  *               you don't need it any longer.
254  *
255  * Since: 2.4
256  */
257 GdkWindow *
gimp_ui_get_progress_window(void)258 gimp_ui_get_progress_window (void)
259 {
260   guint32  window;
261 
262   g_return_val_if_fail (gimp_ui_initialized, NULL);
263 
264   window = gimp_progress_get_window_handle ();
265   if (window)
266      return gimp_ui_get_foreign_window (window);
267 
268   return NULL;
269 }
270 
271 #ifdef GDK_WINDOWING_QUARTZ
272 static void
gimp_window_transient_show(GtkWidget * window)273 gimp_window_transient_show (GtkWidget *window)
274 {
275   g_signal_handlers_disconnect_by_func (window,
276                                         gimp_window_transient_show,
277                                         NULL);
278   [NSApp arrangeInFront: nil];
279 }
280 #endif
281 
282 /**
283  * gimp_window_set_transient_for_display:
284  * @window:   the #GtkWindow that should become transient
285  * @gdisp_ID: display ID of the image window that should become the parent
286  *
287  * Indicates to the window manager that @window is a transient dialog
288  * associated with the GIMP image window that is identified by it's
289  * display ID.  See gdk_window_set_transient_for () for more information.
290  *
291  * Most of the time you will want to use the convenience function
292  * gimp_window_set_transient().
293  *
294  * Since: 2.4
295  */
296 void
gimp_window_set_transient_for_display(GtkWindow * window,guint32 gdisp_ID)297 gimp_window_set_transient_for_display (GtkWindow *window,
298                                        guint32    gdisp_ID)
299 {
300   g_return_if_fail (gimp_ui_initialized);
301   g_return_if_fail (GTK_IS_WINDOW (window));
302 
303   if (! gimp_window_set_transient_for (window,
304                                        gimp_ui_get_display_window (gdisp_ID)))
305     {
306       /*  if setting the window transient failed, at least set
307        *  WIN_POS_CENTER, which will center the window on the screen
308        *  where the mouse is (see bug #684003).
309        */
310       gtk_window_set_position (window, GTK_WIN_POS_CENTER);
311 
312 #ifdef GDK_WINDOWING_QUARTZ
313       g_signal_connect (window, "show",
314                         G_CALLBACK (gimp_window_transient_show),
315                         NULL);
316 #endif
317     }
318 }
319 
320 /**
321  * gimp_window_set_transient:
322  * @window: the #GtkWindow that should become transient
323  *
324  * Indicates to the window manager that @window is a transient dialog
325  * associated with the GIMP window that the plug-in has been
326  * started from. See also gimp_window_set_transient_for_display().
327  *
328  * Since: 2.4
329  */
330 void
gimp_window_set_transient(GtkWindow * window)331 gimp_window_set_transient (GtkWindow *window)
332 {
333   g_return_if_fail (gimp_ui_initialized);
334   g_return_if_fail (GTK_IS_WINDOW (window));
335 
336   if (! gimp_window_set_transient_for (window, gimp_ui_get_progress_window ()))
337     {
338       /*  see above  */
339       gtk_window_set_position (window, GTK_WIN_POS_CENTER);
340 
341 #ifdef GDK_WINDOWING_QUARTZ
342       g_signal_connect (window, "show",
343                         G_CALLBACK (gimp_window_transient_show),
344                         NULL);
345 #endif
346     }
347 }
348 
349 
350 /*  private functions  */
351 
352 static void
gimp_ui_help_func(const gchar * help_id,gpointer help_data)353 gimp_ui_help_func (const gchar *help_id,
354                    gpointer     help_data)
355 {
356   gimp_help (NULL, help_id);
357 }
358 
359 static void
gimp_ensure_modules(void)360 gimp_ensure_modules (void)
361 {
362   static GimpModuleDB *module_db = NULL;
363 
364   if (! module_db)
365     {
366       gchar *load_inhibit = gimp_get_module_load_inhibit ();
367       gchar *module_path  = gimp_gimprc_query ("module-path");
368 
369       module_db = gimp_module_db_new (FALSE);
370 
371       gimp_module_db_set_load_inhibit (module_db, load_inhibit);
372       gimp_module_db_load (module_db, module_path);
373 
374       g_free (module_path);
375       g_free (load_inhibit);
376     }
377 }
378 
379 static void
gimp_window_transient_realized(GtkWidget * window,GdkWindow * parent)380 gimp_window_transient_realized (GtkWidget *window,
381                                 GdkWindow *parent)
382 {
383   if (gtk_widget_get_realized (window))
384     gdk_window_set_transient_for (gtk_widget_get_window (window), parent);
385 }
386 
387 static gboolean
gimp_window_set_transient_for(GtkWindow * window,GdkWindow * parent)388 gimp_window_set_transient_for (GtkWindow *window,
389                                GdkWindow *parent)
390 {
391   gtk_window_set_transient_for (window, NULL);
392 
393 #ifndef GDK_WINDOWING_WIN32
394   g_signal_handlers_disconnect_matched (window, G_SIGNAL_MATCH_FUNC,
395                                         0, 0, NULL,
396                                         gimp_window_transient_realized,
397                                         NULL);
398 
399   if (! parent)
400     return FALSE;
401 
402   if (gtk_widget_get_realized (GTK_WIDGET (window)))
403     gdk_window_set_transient_for (gtk_widget_get_window (GTK_WIDGET (window)),
404                                   parent);
405 
406   g_signal_connect_object (window, "realize",
407                            G_CALLBACK (gimp_window_transient_realized),
408                            parent, 0);
409   g_object_unref (parent);
410 
411   return TRUE;
412 #endif
413 
414   return FALSE;
415 }
416 
417 static void
gimp_ui_theme_changed(void)418 gimp_ui_theme_changed (void)
419 {
420   gtk_rc_reparse_all ();
421 
422   gimp_ui_fix_pixbuf_style ();
423 }
424 
425 static void
gimp_ui_fix_pixbuf_style(void)426 gimp_ui_fix_pixbuf_style (void)
427 {
428   /*  Same hack as in app/gui/themes.c, to be removed for GTK+ 3.x  */
429 
430   static GtkStyleClass *pixbuf_style_class = NULL;
431 
432   if (! pixbuf_style_class)
433     {
434       GType type = g_type_from_name ("PixbufStyle");
435 
436       if (type)
437         {
438           pixbuf_style_class = g_type_class_ref (type);
439 
440           if (pixbuf_style_class)
441             pixbuf_style_class->draw_layout = gimp_ui_draw_pixbuf_layout;
442         }
443     }
444 }
445 
446 static void
gimp_ui_draw_pixbuf_layout(GtkStyle * style,GdkWindow * window,GtkStateType state_type,gboolean use_text,GdkRectangle * area,GtkWidget * widget,const gchar * detail,gint x,gint y,PangoLayout * layout)447 gimp_ui_draw_pixbuf_layout (GtkStyle      *style,
448                             GdkWindow     *window,
449                             GtkStateType   state_type,
450                             gboolean       use_text,
451                             GdkRectangle  *area,
452                             GtkWidget     *widget,
453                             const gchar   *detail,
454                             gint           x,
455                             gint           y,
456                             PangoLayout   *layout)
457 {
458   GdkGC *gc;
459 
460   gc = use_text ? style->text_gc[state_type] : style->fg_gc[state_type];
461 
462   if (area)
463     gdk_gc_set_clip_rectangle (gc, area);
464 
465   if (state_type == GTK_STATE_INSENSITIVE)
466     {
467       GdkGC       *copy = gdk_gc_new (window);
468       GdkGCValues  orig;
469       GdkColor     fore;
470       guint16      r, g, b;
471 
472       gdk_gc_copy (copy, gc);
473       gdk_gc_get_values (gc, &orig);
474 
475       r = 0x40 + (((orig.foreground.pixel >> 16) & 0xff) >> 1);
476       g = 0x40 + (((orig.foreground.pixel >>  8) & 0xff) >> 1);
477       b = 0x40 + (((orig.foreground.pixel >>  0) & 0xff) >> 1);
478 
479       fore.pixel = (r << 16) | (g << 8) | b;
480       fore.red   = r * 257;
481       fore.green = g * 257;
482       fore.blue  = b * 257;
483 
484       gdk_gc_set_foreground (copy, &fore);
485       gdk_draw_layout (window, copy, x, y, layout);
486 
487       g_object_unref (copy);
488     }
489   else
490     {
491       gdk_draw_layout (window, gc, x, y, layout);
492     }
493 
494   if (area)
495     gdk_gc_set_clip_rectangle (gc, NULL);
496 }
497 
498 #ifdef GDK_WINDOWING_QUARTZ
499 static gboolean
gimp_osx_focus_window(gpointer user_data)500 gimp_osx_focus_window (gpointer user_data)
501 {
502   [NSApp activateIgnoringOtherApps:YES];
503   return FALSE;
504 }
505 #endif
506