gimphelpui.c 15.6 KB
Newer Older
1
/* LIBGIMP - The GIMP Library
2 3 4
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * 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 9
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
10
 * version 3 of the License, or (at your option) any later version.
11
 *
12 13
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 16 17
 * Library General Public License for more details.
 *
 * 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 23
#include "config.h"

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 32
#include "libgimp/libgimp-intl.h"

33

34 35 36 37 38 39 40 41 42 43 44
/**
 * SECTION: gimphelpui
 * @title: GimpHelpUI
 * @short_description: Functions for setting tooltip and help identifier
 *                     used by the GIMP help system.
 *
 * Functions for setting tooltip and help identifier used by the GIMP
 * help system.
 **/


45 46
typedef enum
{
47 48 49
  GIMP_WIDGET_HELP_TOOLTIP    = GTK_WIDGET_HELP_TOOLTIP,
  GIMP_WIDGET_HELP_WHATS_THIS = GTK_WIDGET_HELP_WHATS_THIS,
  GIMP_WIDGET_HELP_TYPE_HELP  = 0xff
50 51 52
} GimpWidgetHelpType;


53
/*  local function prototypes  */
54

55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
static const gchar * gimp_help_get_help_data        (GtkWidget      *widget,
                                                     GtkWidget     **help_widget,
                                                     gpointer       *ret_data);
static gboolean   gimp_help_callback                (GtkWidget      *widget,
                                                     GimpWidgetHelpType help_type,
                                                     GimpHelpFunc    help_func);

static void       gimp_help_menu_item_set_tooltip   (GtkWidget      *widget,
                                                     const gchar    *tooltip,
                                                     const gchar    *help_id);
static gboolean   gimp_help_menu_item_query_tooltip (GtkWidget      *widget,
                                                     gint            x,
                                                     gint            y,
                                                     gboolean        keyboard_mode,
                                                     GtkTooltip     *tooltip);
static gboolean   gimp_context_help_idle_start      (gpointer        widget);
static gboolean   gimp_context_help_button_press    (GtkWidget      *widget,
                                                     GdkEventButton *bevent,
                                                     gpointer        data);
static gboolean   gimp_context_help_key_press       (GtkWidget      *widget,
                                                     GdkEventKey    *kevent,
                                                     gpointer        data);
static gboolean   gimp_context_help_idle_show_help  (gpointer        data);
78

79 80 81

/*  public functions  */

82 83 84 85 86 87 88 89
/**
 * gimp_standard_help_func:
 * @help_id:   A unique help identifier.
 * @help_data: The @help_data passed to gimp_help_connect().
 *
 * This is the standard GIMP help function which does nothing but calling
 * gimp_help(). It is the right function to use in almost all cases.
 **/
90
void
91
gimp_standard_help_func (const gchar *help_id,
92
                         gpointer     help_data)
93
{
94
  if (! _gimp_standard_help_func)
95
    {
96 97
      g_warning ("%s: you must call gimp_widgets_init() before using "
                 "the help system", G_STRFUNC);
98 99 100
      return;
    }

101
  (* _gimp_standard_help_func) (help_id, help_data);
102 103
}

104
/**
105
 * gimp_help_connect:
106 107 108
 * @widget: The widget you want to connect the help accelerator for. Will
 *          be a #GtkWindow in most cases.
 * @help_func: The function which will be called if the user presses "F1".
109 110
 * @help_id:   The @help_id which will be passed to @help_func.
 * @help_data: The @help_data pointer which will be passed to @help_func.
111
 *
Michael Natterer's avatar
Michael Natterer committed
112 113 114
 * Note that this function is automatically called by all libgimp dialog
 * constructors. You only have to call it for windows/dialogs you created
 * "manually".
115
 **/
116
void
117
gimp_help_connect (GtkWidget    *widget,
Michael Natterer's avatar
Michael Natterer committed
118 119
                   GimpHelpFunc  help_func,
                   const gchar  *help_id,
120
                   gpointer      help_data)
121
{
122 123
  static gboolean initialized = FALSE;

124 125
  g_return_if_fail (GTK_IS_WIDGET (widget));
  g_return_if_fail (help_func != NULL);
126

127
  /*  set up the help signals
128
   */
129
  if (! initialized)
130
    {
131 132
      GtkBindingSet *binding_set;

133 134
      binding_set =
        gtk_binding_set_by_class (g_type_class_peek (GTK_TYPE_WIDGET));
135

136
      gtk_binding_entry_add_signal (binding_set, GDK_KEY_F1, 0,
137
                                    "show-help", 1,
Michael Natterer's avatar
Michael Natterer committed
138 139
                                    GTK_TYPE_WIDGET_HELP_TYPE,
                                    GIMP_WIDGET_HELP_TYPE_HELP);
140
      gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_F1, 0,
141
                                    "show-help", 1,
Michael Natterer's avatar
Michael Natterer committed
142 143
                                    GTK_TYPE_WIDGET_HELP_TYPE,
                                    GIMP_WIDGET_HELP_TYPE_HELP);
144

145
      initialized = TRUE;
146
    }
147

148 149 150
  gimp_help_set_help_data (widget, NULL, help_id);

  g_object_set_data (G_OBJECT (widget), "gimp-help-data", help_data);
151

152
  g_signal_connect (widget, "show-help",
153
                    G_CALLBACK (gimp_help_callback),
154
                    help_func);
155 156 157 158

  gtk_widget_add_events (widget, GDK_BUTTON_PRESS_MASK);
}

159 160
/**
 * gimp_help_set_help_data:
161
 * @widget:  The #GtkWidget you want to set a @tooltip and/or @help_id for.
162
 * @tooltip: The text for this widget's tooltip (or %NULL).
163
 * @help_id: The @help_id for the #GtkTipsQuery tooltips inspector.
164
 *
Sven Neumann's avatar
Sven Neumann committed
165 166 167
 * The reason why we don't use gtk_widget_set_tooltip_text() is that
 * elements in the GIMP user interface should, if possible, also have
 * a @help_id set for context-sensitive help.
Michael Natterer's avatar
Michael Natterer committed
168
 *
169
 * This function can be called with #NULL for @tooltip. Use this feature
170
 * if you want to set a help link for a widget which shouldn't have
Michael Natterer's avatar
Michael Natterer committed
171
 * a visible tooltip.
172
 **/
173
void
174
gimp_help_set_help_data (GtkWidget   *widget,
Michael Natterer's avatar
Michael Natterer committed
175 176
                         const gchar *tooltip,
                         const gchar *help_id)
177 178 179
{
  g_return_if_fail (GTK_IS_WIDGET (widget));

180
  gtk_widget_set_tooltip_text (widget, tooltip);
181

182 183
  if (GTK_IS_MENU_ITEM (widget))
    gimp_help_menu_item_set_tooltip (widget, tooltip, help_id);
184 185 186 187 188 189 190 191 192 193

  g_object_set_qdata (G_OBJECT (widget), GIMP_HELP_ID, (gpointer) help_id);
}

/**
 * gimp_help_set_help_data_with_markup:
 * @widget:  The #GtkWidget you want to set a @tooltip and/or @help_id for.
 * @tooltip: The markup for this widget's tooltip (or %NULL).
 * @help_id: The @help_id for the #GtkTipsQuery tooltips inspector.
 *
194 195 196
 * Just like gimp_help_set_help_data(), but supports to pass text
 * which is marked up with <link linkend="PangoMarkupFormat">Pango
 * text markup language</link>.
197
 *
198
 * Since: 2.6
199 200 201 202 203 204 205 206
 **/
void
gimp_help_set_help_data_with_markup (GtkWidget   *widget,
                                     const gchar *tooltip,
                                     const gchar *help_id)
{
  g_return_if_fail (GTK_IS_WIDGET (widget));

207
  gtk_widget_set_tooltip_markup (widget, tooltip);
208

209 210
  if (GTK_IS_MENU_ITEM (widget))
    gimp_help_menu_item_set_tooltip (widget, tooltip, help_id);
211

212
  g_object_set_qdata (G_OBJECT (widget), GIMP_HELP_ID, (gpointer) help_id);
213 214
}

215 216
/**
 * gimp_context_help:
217
 * @widget: Any #GtkWidget on the screen.
218
 *
219
 * This function invokes the context help inspector.
Michael Natterer's avatar
Michael Natterer committed
220 221 222 223
 *
 * The mouse cursor will turn turn into a question mark and the user can
 * click on any widget of the application which started the inspector.
 *
224 225
 * If the widget the user clicked on has a @help_id string attached
 * (see gimp_help_set_help_data()), the corresponding help page will
Michael Natterer's avatar
Michael Natterer committed
226
 * be displayed. Otherwise the help system will ascend the widget hierarchy
227
 * until it finds an attached @help_id string (which should be the
Michael Natterer's avatar
Michael Natterer committed
228
 * case at least for every window/dialog).
229
 **/
230
void
231
gimp_context_help (GtkWidget *widget)
232
{
233 234
  g_return_if_fail (GTK_IS_WIDGET (widget));

235
  gimp_help_callback (widget, GIMP_WIDGET_HELP_WHATS_THIS, NULL);
236 237
}

238 239 240 241
/**
 * gimp_help_id_quark:
 *
 * This function returns the #GQuark which should be used as key when
242
 * attaching help IDs to widgets and objects.
243 244
 *
 * Return value: The #GQuark.
Sven Neumann's avatar
Sven Neumann committed
245
 *
246
 * Since: 2.2
247 248 249 250 251 252 253 254 255 256 257 258
 **/
GQuark
gimp_help_id_quark (void)
{
  static GQuark quark = 0;

  if (! quark)
    quark = g_quark_from_static_string ("gimp-help-id");

  return quark;
}

259

260 261 262 263
/*  private functions  */

static const gchar *
gimp_help_get_help_data (GtkWidget  *widget,
264 265
                         GtkWidget **help_widget,
                         gpointer   *ret_data)
266
{
267 268
  const gchar *help_id   = NULL;
  gpointer     help_data = NULL;
269

270
  for (; widget; widget = gtk_widget_get_parent (widget))
271
    {
272
      help_id   = g_object_get_qdata (G_OBJECT (widget), GIMP_HELP_ID);
273 274 275
      help_data = g_object_get_data (G_OBJECT (widget), "gimp-help-data");

      if (help_id)
276 277 278 279
        {
          if (help_widget)
            *help_widget = widget;

280 281 282
          if (ret_data)
            *ret_data = help_data;

283
          return help_id;
284 285 286
        }
    }

287 288 289 290 291 292
  if (help_widget)
    *help_widget = NULL;

  if (ret_data)
    *ret_data = NULL;

293 294 295 296
  return NULL;
}

static gboolean
297
gimp_help_callback (GtkWidget          *widget,
Michael Natterer's avatar
Michael Natterer committed
298 299
                    GimpWidgetHelpType  help_type,
                    GimpHelpFunc        help_func)
300
{
301 302 303
  switch (help_type)
    {
    case GIMP_WIDGET_HELP_TYPE_HELP:
304 305
      if (help_func)
        {
306 307
          help_func (g_object_get_qdata (G_OBJECT (widget), GIMP_HELP_ID),
                     g_object_get_data (G_OBJECT (widget), "gimp-help-data"));
308 309
        }
      return TRUE;
310

311
    case GIMP_WIDGET_HELP_WHATS_THIS:
312
      g_idle_add (gimp_context_help_idle_start, widget);
313
      return TRUE;
314 315 316 317

    default:
      break;
    }
318 319

  return FALSE;
320 321
}

322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
static void
gimp_help_menu_item_set_tooltip (GtkWidget   *widget,
                                 const gchar *tooltip,
                                 const gchar *help_id)
{
  g_return_if_fail (GTK_IS_MENU_ITEM (widget));

  if (tooltip && help_id)
    {
      g_object_set (widget, "has-tooltip", TRUE, NULL);

      g_signal_connect (widget, "query-tooltip",
                        G_CALLBACK (gimp_help_menu_item_query_tooltip),
                        NULL);
    }
  else if (! tooltip)
    {
      g_object_set (widget, "has-tooltip", FALSE, NULL);

      g_signal_handlers_disconnect_by_func (widget,
                                            gimp_help_menu_item_query_tooltip,
                                            NULL);
    }
}

static gboolean
gimp_help_menu_item_query_tooltip (GtkWidget  *widget,
                                   gint        x,
                                   gint        y,
                                   gboolean    keyboard_mode,
                                   GtkTooltip *tooltip)
{
  GtkWidget *vbox;
  GtkWidget *label;
  gchar     *text;
  gboolean   use_markup = TRUE;

  text = gtk_widget_get_tooltip_markup (widget);

  if (! text)
    {
      text = gtk_widget_get_tooltip_text (widget);
      use_markup = FALSE;
    }

  if (! text)
    return FALSE;

370
  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
371 372 373 374

  label = gtk_label_new (text);
  gtk_label_set_use_markup (GTK_LABEL (label), use_markup);
  gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
375
  gtk_label_set_xalign (GTK_LABEL (label), 0.0);
376 377 378 379 380 381 382 383
  gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
  gtk_widget_show (label);

  g_free (text);

  label = gtk_label_new (_("Press F1 for more help"));
  gimp_label_set_attributes (GTK_LABEL (label),
                             PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC,
384
                             PANGO_ATTR_SCALE, PANGO_SCALE_SMALL,
385
                             -1);
386
  gtk_label_set_xalign (GTK_LABEL (label), 1.0);
387 388 389 390 391 392 393 394 395
  gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
  gtk_widget_show (label);

  gtk_tooltip_set_custom (tooltip, vbox);

  return TRUE;
}


396
/*  Do all the actual context help calls in idle functions and check for
397 398 399
 *  some widget holding a grab before starting the query because strange
 *  things happen if (1) the help browser pops up while the query has
 *  grabbed the pointer or (2) the query grabs the pointer while some
400
 *  other part of GIMP has grabbed it (e.g. a tool, eek)
401 402
 */

403
static gboolean
404
gimp_context_help_idle_start (gpointer widget)
405
{
406 407 408 409 410
  if (! gtk_grab_get_current ())
    {
      GtkWidget     *invisible;
      GdkCursor     *cursor;
      GdkGrabStatus  status;
411

412 413
      invisible = gtk_invisible_new_for_screen (gtk_widget_get_screen (widget));
      gtk_widget_show (invisible);
414

415 416 417
      cursor = gdk_cursor_new_for_display (gtk_widget_get_display (invisible),
                                           GDK_QUESTION_ARROW);

418
      status = gdk_pointer_grab (gtk_widget_get_window (invisible), TRUE,
419 420 421 422 423 424 425
                                 GDK_BUTTON_PRESS_MASK   |
                                 GDK_BUTTON_RELEASE_MASK |
                                 GDK_ENTER_NOTIFY_MASK   |
                                 GDK_LEAVE_NOTIFY_MASK,
                                 NULL, cursor,
                                 GDK_CURRENT_TIME);

426
      g_object_unref (cursor);
427 428 429 430 431 432 433

      if (status != GDK_GRAB_SUCCESS)
        {
          gtk_widget_destroy (invisible);
          return FALSE;
        }

434
      if (gdk_keyboard_grab (gtk_widget_get_window (invisible), TRUE,
435 436 437 438 439 440 441 442
                             GDK_CURRENT_TIME) != GDK_GRAB_SUCCESS)
        {
          gdk_display_pointer_ungrab (gtk_widget_get_display (invisible),
                                      GDK_CURRENT_TIME);
          gtk_widget_destroy (invisible);
          return FALSE;
        }

443 444
      gtk_grab_add (invisible);

445
      g_signal_connect (invisible, "button-press-event",
446 447
                        G_CALLBACK (gimp_context_help_button_press),
                        NULL);
448 449 450
      g_signal_connect (invisible, "key-press-event",
                        G_CALLBACK (gimp_context_help_key_press),
                        NULL);
451
    }
452 453 454 455

  return FALSE;
}

456
static gboolean
457 458 459
gimp_context_help_button_press (GtkWidget      *widget,
                                GdkEventButton *bevent,
                                gpointer        data)
460
{
461
  GtkWidget *event_widget = gtk_get_event_widget ((GdkEvent *) bevent);
462 463 464

  if (event_widget && bevent->button == 1 && bevent->type == GDK_BUTTON_PRESS)
    {
465 466
      GdkDisplay *display = gtk_widget_get_display (widget);

467
      gtk_grab_remove (widget);
468 469
      gdk_display_keyboard_ungrab (display, bevent->time);
      gdk_display_pointer_ungrab (display, bevent->time);
470 471 472 473 474
      gtk_widget_destroy (widget);

      if (event_widget != widget)
        g_idle_add (gimp_context_help_idle_show_help, event_widget);
    }
475 476 477 478

  return TRUE;
}

479 480 481 482 483
static gboolean
gimp_context_help_key_press (GtkWidget   *widget,
                             GdkEventKey *kevent,
                             gpointer     data)
{
484
  if (kevent->keyval == GDK_KEY_Escape)
485 486 487 488 489 490 491 492 493 494 495 496
    {
      GdkDisplay *display = gtk_widget_get_display (widget);

      gtk_grab_remove (widget);
      gdk_display_keyboard_ungrab (display, kevent->time);
      gdk_display_pointer_ungrab (display, kevent->time);
      gtk_widget_destroy (widget);
    }

  return TRUE;
}

497
static gboolean
498
gimp_context_help_idle_show_help (gpointer data)
499
{
500 501 502 503 504 505 506 507 508
  GtkWidget   *help_widget;
  const gchar *help_id   = NULL;
  gpointer     help_data = NULL;

  help_id = gimp_help_get_help_data (GTK_WIDGET (data), &help_widget,
                                     &help_data);

  if (help_id)
    gimp_standard_help_func (help_id, help_data);
509 510 511

  return FALSE;
}