1 /* LIBGIMP - The GIMP Library
2 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3 *
4 * gimphelpui.c
5 * Copyright (C) 2000-2003 Michael Natterer <mitch@gimp.org>
6 *
7 * This library is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 3 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library. If not, see
19 * <https://www.gnu.org/licenses/>.
20 */
21
22 #include "config.h"
23
24 #include <gegl.h>
25 #include <gtk/gtk.h>
26 #include <gdk/gdkkeysyms.h>
27
28 #include "gimpwidgets.h"
29 #include "gimpwidgets-private.h"
30
31 #include "libgimp/libgimp-intl.h"
32
33
34 /**
35 * SECTION: gimphelpui
36 * @title: GimpHelpUI
37 * @short_description: Functions for setting tooltip and help identifier
38 * used by the GIMP help system.
39 *
40 * Functions for setting tooltip and help identifier used by the GIMP
41 * help system.
42 **/
43
44
45 typedef enum
46 {
47 GIMP_WIDGET_HELP_TOOLTIP = GTK_WIDGET_HELP_TOOLTIP,
48 GIMP_WIDGET_HELP_WHATS_THIS = GTK_WIDGET_HELP_WHATS_THIS,
49 GIMP_WIDGET_HELP_TYPE_HELP = 0xff
50 } GimpWidgetHelpType;
51
52
53 /* local variables */
54
55 static gboolean tooltips_enabled = TRUE;
56 static gboolean tooltips_enable_called = FALSE;
57
58
59 /* local function prototypes */
60
61 static const gchar * gimp_help_get_help_data (GtkWidget *widget,
62 GtkWidget **help_widget,
63 gpointer *ret_data);
64 static gboolean gimp_help_callback (GtkWidget *widget,
65 GimpWidgetHelpType help_type,
66 GimpHelpFunc help_func);
67
68 static void gimp_help_menu_item_set_tooltip (GtkWidget *widget,
69 const gchar *tooltip,
70 const gchar *help_id);
71 static gboolean gimp_help_menu_item_query_tooltip (GtkWidget *widget,
72 gint x,
73 gint y,
74 gboolean keyboard_mode,
75 GtkTooltip *tooltip);
76 static gboolean gimp_context_help_idle_start (gpointer widget);
77 static gboolean gimp_context_help_button_press (GtkWidget *widget,
78 GdkEventButton *bevent,
79 gpointer data);
80 static gboolean gimp_context_help_key_press (GtkWidget *widget,
81 GdkEventKey *kevent,
82 gpointer data);
83 static gboolean gimp_context_help_idle_show_help (gpointer data);
84
85
86 /* public functions */
87
88 /**
89 * gimp_help_enable_tooltips:
90 *
91 * Enable tooltips to be shown in the GIMP user interface.
92 *
93 * As a plug-in author, you don't need to care about this as this
94 * function is called for you from gimp_ui_init(). This ensures that
95 * the user setting from the GIMP preferences dialog is respected in
96 * all plug-in dialogs.
97 **/
98 void
gimp_help_enable_tooltips(void)99 gimp_help_enable_tooltips (void)
100 {
101 if (! tooltips_enable_called)
102 {
103 tooltips_enable_called = TRUE;
104 tooltips_enabled = TRUE;
105 }
106 }
107
108 /**
109 * gimp_help_disable_tooltips:
110 *
111 * Disable tooltips to be shown in the GIMP user interface.
112 *
113 * As a plug-in author, you don't need to care about this as this
114 * function is called for you from gimp_ui_init(). This ensures that
115 * the user setting from the GIMP preferences dialog is respected in
116 * all plug-in dialogs.
117 **/
118 void
gimp_help_disable_tooltips(void)119 gimp_help_disable_tooltips (void)
120 {
121 if (! tooltips_enable_called)
122 {
123 tooltips_enable_called = TRUE;
124 tooltips_enabled = FALSE;
125 }
126 }
127
128 /**
129 * gimp_standard_help_func:
130 * @help_id: A unique help identifier.
131 * @help_data: The @help_data passed to gimp_help_connect().
132 *
133 * This is the standard GIMP help function which does nothing but calling
134 * gimp_help(). It is the right function to use in almost all cases.
135 **/
136 void
gimp_standard_help_func(const gchar * help_id,gpointer help_data)137 gimp_standard_help_func (const gchar *help_id,
138 gpointer help_data)
139 {
140 if (! _gimp_standard_help_func)
141 {
142 g_warning ("%s: you must call gimp_widgets_init() before using "
143 "the help system", G_STRFUNC);
144 return;
145 }
146
147 (* _gimp_standard_help_func) (help_id, help_data);
148 }
149
150 /**
151 * gimp_help_connect:
152 * @widget: The widget you want to connect the help accelerator for. Will
153 * be a #GtkWindow in most cases.
154 * @help_func: The function which will be called if the user presses "F1".
155 * @help_id: The @help_id which will be passed to @help_func.
156 * @help_data: The @help_data pointer which will be passed to @help_func.
157 *
158 * Note that this function is automatically called by all libgimp dialog
159 * constructors. You only have to call it for windows/dialogs you created
160 * "manually".
161 **/
162 void
gimp_help_connect(GtkWidget * widget,GimpHelpFunc help_func,const gchar * help_id,gpointer help_data)163 gimp_help_connect (GtkWidget *widget,
164 GimpHelpFunc help_func,
165 const gchar *help_id,
166 gpointer help_data)
167 {
168 static gboolean initialized = FALSE;
169
170 g_return_if_fail (GTK_IS_WIDGET (widget));
171 g_return_if_fail (help_func != NULL);
172
173 /* set up the help signals
174 */
175 if (! initialized)
176 {
177 GtkBindingSet *binding_set;
178
179 binding_set =
180 gtk_binding_set_by_class (g_type_class_peek (GTK_TYPE_WIDGET));
181
182 gtk_binding_entry_add_signal (binding_set, GDK_KEY_F1, 0,
183 "show-help", 1,
184 GTK_TYPE_WIDGET_HELP_TYPE,
185 GIMP_WIDGET_HELP_TYPE_HELP);
186 gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_F1, 0,
187 "show-help", 1,
188 GTK_TYPE_WIDGET_HELP_TYPE,
189 GIMP_WIDGET_HELP_TYPE_HELP);
190
191 initialized = TRUE;
192 }
193
194 gimp_help_set_help_data (widget, NULL, help_id);
195
196 g_object_set_data (G_OBJECT (widget), "gimp-help-data", help_data);
197
198 g_signal_connect (widget, "show-help",
199 G_CALLBACK (gimp_help_callback),
200 help_func);
201
202 gtk_widget_add_events (widget, GDK_BUTTON_PRESS_MASK);
203 }
204
205 /**
206 * gimp_help_set_help_data:
207 * @widget: The #GtkWidget you want to set a @tooltip and/or @help_id for.
208 * @tooltip: The text for this widget's tooltip (or %NULL).
209 * @help_id: The @help_id for the #GtkTipsQuery tooltips inspector.
210 *
211 * The reason why we don't use gtk_widget_set_tooltip_text() is that
212 * elements in the GIMP user interface should, if possible, also have
213 * a @help_id set for context-sensitive help.
214 *
215 * This function can be called with #NULL for @tooltip. Use this feature
216 * if you want to set a help link for a widget which shouldn't have
217 * a visible tooltip.
218 **/
219 void
gimp_help_set_help_data(GtkWidget * widget,const gchar * tooltip,const gchar * help_id)220 gimp_help_set_help_data (GtkWidget *widget,
221 const gchar *tooltip,
222 const gchar *help_id)
223 {
224 g_return_if_fail (GTK_IS_WIDGET (widget));
225
226 if (tooltips_enabled)
227 {
228 gtk_widget_set_tooltip_text (widget, tooltip);
229
230 if (GTK_IS_MENU_ITEM (widget))
231 gimp_help_menu_item_set_tooltip (widget, tooltip, help_id);
232 }
233
234 g_object_set_qdata (G_OBJECT (widget), GIMP_HELP_ID, (gpointer) help_id);
235 }
236
237 /**
238 * gimp_help_set_help_data_with_markup:
239 * @widget: The #GtkWidget you want to set a @tooltip and/or @help_id for.
240 * @tooltip: The markup for this widget's tooltip (or %NULL).
241 * @help_id: The @help_id for the #GtkTipsQuery tooltips inspector.
242 *
243 * Just like gimp_help_set_help_data(), but supports to pass text
244 * which is marked up with <link linkend="PangoMarkupFormat">Pango
245 * text markup language</link>.
246 *
247 * Since: 2.6
248 **/
249 void
gimp_help_set_help_data_with_markup(GtkWidget * widget,const gchar * tooltip,const gchar * help_id)250 gimp_help_set_help_data_with_markup (GtkWidget *widget,
251 const gchar *tooltip,
252 const gchar *help_id)
253 {
254 g_return_if_fail (GTK_IS_WIDGET (widget));
255
256 if (tooltips_enabled)
257 {
258 gtk_widget_set_tooltip_markup (widget, tooltip);
259
260 if (GTK_IS_MENU_ITEM (widget))
261 gimp_help_menu_item_set_tooltip (widget, tooltip, help_id);
262 }
263
264 g_object_set_qdata (G_OBJECT (widget), GIMP_HELP_ID, (gpointer) help_id);
265 }
266
267 /**
268 * gimp_context_help:
269 * @widget: Any #GtkWidget on the screen.
270 *
271 * This function invokes the context help inspector.
272 *
273 * The mouse cursor will turn turn into a question mark and the user can
274 * click on any widget of the application which started the inspector.
275 *
276 * If the widget the user clicked on has a @help_id string attached
277 * (see gimp_help_set_help_data()), the corresponding help page will
278 * be displayed. Otherwise the help system will ascend the widget hierarchy
279 * until it finds an attached @help_id string (which should be the
280 * case at least for every window/dialog).
281 **/
282 void
gimp_context_help(GtkWidget * widget)283 gimp_context_help (GtkWidget *widget)
284 {
285 g_return_if_fail (GTK_IS_WIDGET (widget));
286
287 gimp_help_callback (widget, GIMP_WIDGET_HELP_WHATS_THIS, NULL);
288 }
289
290 /**
291 * gimp_help_id_quark:
292 *
293 * This function returns the #GQuark which should be used as key when
294 * attaching help IDs to widgets and objects.
295 *
296 * Return value: The #GQuark.
297 *
298 * Since: 2.2
299 **/
300 GQuark
gimp_help_id_quark(void)301 gimp_help_id_quark (void)
302 {
303 static GQuark quark = 0;
304
305 if (! quark)
306 quark = g_quark_from_static_string ("gimp-help-id");
307
308 return quark;
309 }
310
311
312 /* private functions */
313
314 static const gchar *
gimp_help_get_help_data(GtkWidget * widget,GtkWidget ** help_widget,gpointer * ret_data)315 gimp_help_get_help_data (GtkWidget *widget,
316 GtkWidget **help_widget,
317 gpointer *ret_data)
318 {
319 const gchar *help_id = NULL;
320 gpointer help_data = NULL;
321
322 for (; widget; widget = gtk_widget_get_parent (widget))
323 {
324 help_id = g_object_get_qdata (G_OBJECT (widget), GIMP_HELP_ID);
325 help_data = g_object_get_data (G_OBJECT (widget), "gimp-help-data");
326
327 if (help_id)
328 {
329 if (help_widget)
330 *help_widget = widget;
331
332 if (ret_data)
333 *ret_data = help_data;
334
335 return help_id;
336 }
337 }
338
339 if (help_widget)
340 *help_widget = NULL;
341
342 if (ret_data)
343 *ret_data = NULL;
344
345 return NULL;
346 }
347
348 static gboolean
gimp_help_callback(GtkWidget * widget,GimpWidgetHelpType help_type,GimpHelpFunc help_func)349 gimp_help_callback (GtkWidget *widget,
350 GimpWidgetHelpType help_type,
351 GimpHelpFunc help_func)
352 {
353 switch (help_type)
354 {
355 case GIMP_WIDGET_HELP_TYPE_HELP:
356 if (help_func)
357 {
358 help_func (g_object_get_qdata (G_OBJECT (widget), GIMP_HELP_ID),
359 g_object_get_data (G_OBJECT (widget), "gimp-help-data"));
360 }
361 return TRUE;
362
363 case GIMP_WIDGET_HELP_WHATS_THIS:
364 g_idle_add (gimp_context_help_idle_start, widget);
365 return TRUE;
366
367 default:
368 break;
369 }
370
371 return FALSE;
372 }
373
374 static void
gimp_help_menu_item_set_tooltip(GtkWidget * widget,const gchar * tooltip,const gchar * help_id)375 gimp_help_menu_item_set_tooltip (GtkWidget *widget,
376 const gchar *tooltip,
377 const gchar *help_id)
378 {
379 g_return_if_fail (GTK_IS_MENU_ITEM (widget));
380
381 if (tooltip && help_id)
382 {
383 g_object_set (widget, "has-tooltip", TRUE, NULL);
384
385 g_signal_connect (widget, "query-tooltip",
386 G_CALLBACK (gimp_help_menu_item_query_tooltip),
387 NULL);
388 }
389 else if (! tooltip)
390 {
391 g_object_set (widget, "has-tooltip", FALSE, NULL);
392
393 g_signal_handlers_disconnect_by_func (widget,
394 gimp_help_menu_item_query_tooltip,
395 NULL);
396 }
397 }
398
399 static gboolean
gimp_help_menu_item_query_tooltip(GtkWidget * widget,gint x,gint y,gboolean keyboard_mode,GtkTooltip * tooltip)400 gimp_help_menu_item_query_tooltip (GtkWidget *widget,
401 gint x,
402 gint y,
403 gboolean keyboard_mode,
404 GtkTooltip *tooltip)
405 {
406 GtkWidget *vbox;
407 GtkWidget *label;
408 gchar *text;
409 gboolean use_markup = TRUE;
410
411 text = gtk_widget_get_tooltip_markup (widget);
412
413 if (! text)
414 {
415 text = gtk_widget_get_tooltip_text (widget);
416 use_markup = FALSE;
417 }
418
419 if (! text)
420 return FALSE;
421
422 vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
423
424 label = gtk_label_new (text);
425 gtk_label_set_use_markup (GTK_LABEL (label), use_markup);
426 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
427 gtk_label_set_xalign (GTK_LABEL (label), 0.0);
428 gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
429 gtk_widget_show (label);
430
431 g_free (text);
432
433 label = gtk_label_new (_("Press F1 for more help"));
434 gimp_label_set_attributes (GTK_LABEL (label),
435 PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC,
436 PANGO_ATTR_SCALE, PANGO_SCALE_SMALL,
437 -1);
438 gtk_label_set_xalign (GTK_LABEL (label), 1.0);
439 gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
440 gtk_widget_show (label);
441
442 gtk_tooltip_set_custom (tooltip, vbox);
443
444 return TRUE;
445 }
446
447
448 /* Do all the actual context help calls in idle functions and check for
449 * some widget holding a grab before starting the query because strange
450 * things happen if (1) the help browser pops up while the query has
451 * grabbed the pointer or (2) the query grabs the pointer while some
452 * other part of GIMP has grabbed it (e.g. a tool, eek)
453 */
454
455 static gboolean
gimp_context_help_idle_start(gpointer widget)456 gimp_context_help_idle_start (gpointer widget)
457 {
458 if (! gtk_grab_get_current ())
459 {
460 GtkWidget *invisible;
461 GdkCursor *cursor;
462 GdkGrabStatus status;
463
464 invisible = gtk_invisible_new_for_screen (gtk_widget_get_screen (widget));
465 gtk_widget_show (invisible);
466
467 cursor = gdk_cursor_new_for_display (gtk_widget_get_display (invisible),
468 GDK_QUESTION_ARROW);
469
470 status = gdk_pointer_grab (gtk_widget_get_window (invisible), TRUE,
471 GDK_BUTTON_PRESS_MASK |
472 GDK_BUTTON_RELEASE_MASK |
473 GDK_ENTER_NOTIFY_MASK |
474 GDK_LEAVE_NOTIFY_MASK,
475 NULL, cursor,
476 GDK_CURRENT_TIME);
477
478 gdk_cursor_unref (cursor);
479
480 if (status != GDK_GRAB_SUCCESS)
481 {
482 gtk_widget_destroy (invisible);
483 return FALSE;
484 }
485
486 if (gdk_keyboard_grab (gtk_widget_get_window (invisible), TRUE,
487 GDK_CURRENT_TIME) != GDK_GRAB_SUCCESS)
488 {
489 gdk_display_pointer_ungrab (gtk_widget_get_display (invisible),
490 GDK_CURRENT_TIME);
491 gtk_widget_destroy (invisible);
492 return FALSE;
493 }
494
495 gtk_grab_add (invisible);
496
497 g_signal_connect (invisible, "button-press-event",
498 G_CALLBACK (gimp_context_help_button_press),
499 NULL);
500 g_signal_connect (invisible, "key-press-event",
501 G_CALLBACK (gimp_context_help_key_press),
502 NULL);
503 }
504
505 return FALSE;
506 }
507
508 static gboolean
gimp_context_help_button_press(GtkWidget * widget,GdkEventButton * bevent,gpointer data)509 gimp_context_help_button_press (GtkWidget *widget,
510 GdkEventButton *bevent,
511 gpointer data)
512 {
513 GtkWidget *event_widget = gtk_get_event_widget ((GdkEvent *) bevent);
514
515 if (event_widget && bevent->button == 1 && bevent->type == GDK_BUTTON_PRESS)
516 {
517 GdkDisplay *display = gtk_widget_get_display (widget);
518
519 gtk_grab_remove (widget);
520 gdk_display_keyboard_ungrab (display, bevent->time);
521 gdk_display_pointer_ungrab (display, bevent->time);
522 gtk_widget_destroy (widget);
523
524 if (event_widget != widget)
525 g_idle_add (gimp_context_help_idle_show_help, event_widget);
526 }
527
528 return TRUE;
529 }
530
531 static gboolean
gimp_context_help_key_press(GtkWidget * widget,GdkEventKey * kevent,gpointer data)532 gimp_context_help_key_press (GtkWidget *widget,
533 GdkEventKey *kevent,
534 gpointer data)
535 {
536 if (kevent->keyval == GDK_KEY_Escape)
537 {
538 GdkDisplay *display = gtk_widget_get_display (widget);
539
540 gtk_grab_remove (widget);
541 gdk_display_keyboard_ungrab (display, kevent->time);
542 gdk_display_pointer_ungrab (display, kevent->time);
543 gtk_widget_destroy (widget);
544 }
545
546 return TRUE;
547 }
548
549 static gboolean
gimp_context_help_idle_show_help(gpointer data)550 gimp_context_help_idle_show_help (gpointer data)
551 {
552 GtkWidget *help_widget;
553 const gchar *help_id = NULL;
554 gpointer help_data = NULL;
555
556 help_id = gimp_help_get_help_data (GTK_WIDGET (data), &help_widget,
557 &help_data);
558
559 if (help_id)
560 gimp_standard_help_func (help_id, help_data);
561
562 return FALSE;
563 }
564