1 /* GTK - The GIMP Toolkit
2 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
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 2 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, write to the Free
16 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 */
18
19 /*
20 * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
21 * file for a list of people on the GTK+ Team. See the ChangeLog
22 * files for a list of changes. These files are distributed with
23 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
24 */
25
26 #include "config.h"
27
28 #include <math.h>
29 #include <string.h>
30
31 #include "gtklabel.h"
32 #include "gtkaccellabel.h"
33 #include "gtkdnd.h"
34 #include "gtkmain.h"
35 #include "gtkmarshalers.h"
36 #include "gtkpango.h"
37 #include "gtkwindow.h"
38 #include "gdk/gdkkeysyms.h"
39 #include "gtkclipboard.h"
40 #include "gtkimagemenuitem.h"
41 #include "gtkintl.h"
42 #include "gtkseparatormenuitem.h"
43 #include "gtktextutil.h"
44 #include "gtkmenuitem.h"
45 #include "gtknotebook.h"
46 #include "gtkstock.h"
47 #include "gtkbindings.h"
48 #include "gtkbuildable.h"
49 #include "gtkimage.h"
50 #include "gtkshow.h"
51 #include "gtktooltip.h"
52 #include "gtkprivate.h"
53 #include "gtkalias.h"
54
55 #define GTK_LABEL_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_LABEL, GtkLabelPrivate))
56
57 typedef struct
58 {
59 gint wrap_width;
60 gint width_chars;
61 gint max_width_chars;
62
63 gboolean mnemonics_visible;
64 } GtkLabelPrivate;
65
66 /* Notes about the handling of links:
67 *
68 * Links share the GtkLabelSelectionInfo struct with selectable labels.
69 * There are some new fields for links. The links field contains the list
70 * of GtkLabelLink structs that describe the links which are embedded in
71 * the label. The active_link field points to the link under the mouse
72 * pointer. For keyboard navigation, the 'focus' link is determined by
73 * finding the link which contains the selection_anchor position.
74 * The link_clicked field is used with button press and release events
75 * to ensure that pressing inside a link and releasing outside of it
76 * does not activate the link.
77 *
78 * Links are rendered with the link-color/visited-link-color colors
79 * that are determined by the style and with an underline. When the mouse
80 * pointer is over a link, the pointer is changed to indicate the link,
81 * and the background behind the link is rendered with the base[PRELIGHT]
82 * color. While a button is pressed over a link, the background is rendered
83 * with the base[ACTIVE] color.
84 *
85 * Labels with links accept keyboard focus, and it is possible to move
86 * the focus between the embedded links using Tab/Shift-Tab. The focus
87 * is indicated by a focus rectangle that is drawn around the link text.
88 * Pressing Enter activates the focussed link, and there is a suitable
89 * context menu for links that can be opened with the Menu key. Pressing
90 * Control-C copies the link URI to the clipboard.
91 *
92 * In selectable labels with links, link functionality is only available
93 * when the selection is empty.
94 */
95 typedef struct
96 {
97 gchar *uri;
98 gchar *title; /* the title attribute, used as tooltip */
99 gboolean visited; /* get set when the link is activated; this flag
100 * gets preserved over later set_markup() calls
101 */
102 gint start; /* position of the link in the PangoLayout */
103 gint end;
104 } GtkLabelLink;
105
106 struct _GtkLabelSelectionInfo
107 {
108 GdkWindow *window;
109 gint selection_anchor;
110 gint selection_end;
111 GtkWidget *popup_menu;
112
113 GList *links;
114 GtkLabelLink *active_link;
115
116 gint drag_start_x;
117 gint drag_start_y;
118
119 guint in_drag : 1;
120 guint select_words : 1;
121 guint selectable : 1;
122 guint link_clicked : 1;
123 };
124
125 enum {
126 MOVE_CURSOR,
127 COPY_CLIPBOARD,
128 POPULATE_POPUP,
129 ACTIVATE_LINK,
130 ACTIVATE_CURRENT_LINK,
131 LAST_SIGNAL
132 };
133
134 enum {
135 PROP_0,
136 PROP_LABEL,
137 PROP_ATTRIBUTES,
138 PROP_USE_MARKUP,
139 PROP_USE_UNDERLINE,
140 PROP_JUSTIFY,
141 PROP_PATTERN,
142 PROP_WRAP,
143 PROP_WRAP_MODE,
144 PROP_SELECTABLE,
145 PROP_MNEMONIC_KEYVAL,
146 PROP_MNEMONIC_WIDGET,
147 PROP_CURSOR_POSITION,
148 PROP_SELECTION_BOUND,
149 PROP_ELLIPSIZE,
150 PROP_WIDTH_CHARS,
151 PROP_SINGLE_LINE_MODE,
152 PROP_ANGLE,
153 PROP_MAX_WIDTH_CHARS,
154 PROP_TRACK_VISITED_LINKS
155 };
156
157 static guint signals[LAST_SIGNAL] = { 0 };
158
159 static const GdkColor default_link_color = { 0, 0, 0, 0xeeee };
160 static const GdkColor default_visited_link_color = { 0, 0x5555, 0x1a1a, 0x8b8b };
161
162 static void gtk_label_set_property (GObject *object,
163 guint prop_id,
164 const GValue *value,
165 GParamSpec *pspec);
166 static void gtk_label_get_property (GObject *object,
167 guint prop_id,
168 GValue *value,
169 GParamSpec *pspec);
170 static void gtk_label_destroy (GtkObject *object);
171 static void gtk_label_finalize (GObject *object);
172 static void gtk_label_size_request (GtkWidget *widget,
173 GtkRequisition *requisition);
174 static void gtk_label_size_allocate (GtkWidget *widget,
175 GtkAllocation *allocation);
176 static void gtk_label_state_changed (GtkWidget *widget,
177 GtkStateType state);
178 static void gtk_label_style_set (GtkWidget *widget,
179 GtkStyle *previous_style);
180 static void gtk_label_direction_changed (GtkWidget *widget,
181 GtkTextDirection previous_dir);
182 static gint gtk_label_expose (GtkWidget *widget,
183 GdkEventExpose *event);
184 static gboolean gtk_label_focus (GtkWidget *widget,
185 GtkDirectionType direction);
186
187 static void gtk_label_realize (GtkWidget *widget);
188 static void gtk_label_unrealize (GtkWidget *widget);
189 static void gtk_label_map (GtkWidget *widget);
190 static void gtk_label_unmap (GtkWidget *widget);
191
192 static gboolean gtk_label_button_press (GtkWidget *widget,
193 GdkEventButton *event);
194 static gboolean gtk_label_button_release (GtkWidget *widget,
195 GdkEventButton *event);
196 static gboolean gtk_label_motion (GtkWidget *widget,
197 GdkEventMotion *event);
198 static gboolean gtk_label_leave_notify (GtkWidget *widget,
199 GdkEventCrossing *event);
200
201 static void gtk_label_grab_focus (GtkWidget *widget);
202
203 static gboolean gtk_label_query_tooltip (GtkWidget *widget,
204 gint x,
205 gint y,
206 gboolean keyboard_tip,
207 GtkTooltip *tooltip);
208
209 static void gtk_label_set_text_internal (GtkLabel *label,
210 gchar *str);
211 static void gtk_label_set_label_internal (GtkLabel *label,
212 gchar *str);
213 static void gtk_label_set_use_markup_internal (GtkLabel *label,
214 gboolean val);
215 static void gtk_label_set_use_underline_internal (GtkLabel *label,
216 gboolean val);
217 static void gtk_label_set_attributes_internal (GtkLabel *label,
218 PangoAttrList *attrs);
219 static void gtk_label_set_uline_text_internal (GtkLabel *label,
220 const gchar *str);
221 static void gtk_label_set_pattern_internal (GtkLabel *label,
222 const gchar *pattern,
223 gboolean is_mnemonic);
224 static void gtk_label_set_markup_internal (GtkLabel *label,
225 const gchar *str,
226 gboolean with_uline);
227 static void gtk_label_recalculate (GtkLabel *label);
228 static void gtk_label_hierarchy_changed (GtkWidget *widget,
229 GtkWidget *old_toplevel);
230 static void gtk_label_screen_changed (GtkWidget *widget,
231 GdkScreen *old_screen);
232 static gboolean gtk_label_popup_menu (GtkWidget *widget);
233
234 static void gtk_label_create_window (GtkLabel *label);
235 static void gtk_label_destroy_window (GtkLabel *label);
236 static void gtk_label_ensure_select_info (GtkLabel *label);
237 static void gtk_label_clear_select_info (GtkLabel *label);
238 static void gtk_label_update_cursor (GtkLabel *label);
239 static void gtk_label_clear_layout (GtkLabel *label);
240 static void gtk_label_ensure_layout (GtkLabel *label);
241 static void gtk_label_invalidate_wrap_width (GtkLabel *label);
242 static void gtk_label_select_region_index (GtkLabel *label,
243 gint anchor_index,
244 gint end_index);
245
246 static gboolean gtk_label_mnemonic_activate (GtkWidget *widget,
247 gboolean group_cycling);
248 static void gtk_label_setup_mnemonic (GtkLabel *label,
249 guint last_key);
250 static void gtk_label_drag_data_get (GtkWidget *widget,
251 GdkDragContext *context,
252 GtkSelectionData *selection_data,
253 guint info,
254 guint time);
255
256 static void gtk_label_buildable_interface_init (GtkBuildableIface *iface);
257 static gboolean gtk_label_buildable_custom_tag_start (GtkBuildable *buildable,
258 GtkBuilder *builder,
259 GObject *child,
260 const gchar *tagname,
261 GMarkupParser *parser,
262 gpointer *data);
263
264 static void gtk_label_buildable_custom_finished (GtkBuildable *buildable,
265 GtkBuilder *builder,
266 GObject *child,
267 const gchar *tagname,
268 gpointer user_data);
269
270
271 static void connect_mnemonics_visible_notify (GtkLabel *label);
272 static gboolean separate_uline_pattern (const gchar *str,
273 guint *accel_key,
274 gchar **new_str,
275 gchar **pattern);
276
277
278 /* For selectable labels: */
279 static void gtk_label_move_cursor (GtkLabel *label,
280 GtkMovementStep step,
281 gint count,
282 gboolean extend_selection);
283 static void gtk_label_copy_clipboard (GtkLabel *label);
284 static void gtk_label_select_all (GtkLabel *label);
285 static void gtk_label_do_popup (GtkLabel *label,
286 GdkEventButton *event);
287 static gint gtk_label_move_forward_word (GtkLabel *label,
288 gint start);
289 static gint gtk_label_move_backward_word (GtkLabel *label,
290 gint start);
291
292 /* For links: */
293 static void gtk_label_rescan_links (GtkLabel *label);
294 static void gtk_label_clear_links (GtkLabel *label);
295 static gboolean gtk_label_activate_link (GtkLabel *label,
296 const gchar *uri);
297 static void gtk_label_activate_current_link (GtkLabel *label);
298 static GtkLabelLink *gtk_label_get_current_link (GtkLabel *label);
299 static void gtk_label_get_link_colors (GtkWidget *widget,
300 GdkColor **link_color,
301 GdkColor **visited_link_color);
302 static void emit_activate_link (GtkLabel *label,
303 GtkLabelLink *link);
304
305 static GQuark quark_angle = 0;
306
307 static GtkBuildableIface *buildable_parent_iface = NULL;
308
309 G_DEFINE_TYPE_WITH_CODE (GtkLabel, gtk_label, GTK_TYPE_MISC,
310 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
311 gtk_label_buildable_interface_init));
312
313 static void
add_move_binding(GtkBindingSet * binding_set,guint keyval,guint modmask,GtkMovementStep step,gint count)314 add_move_binding (GtkBindingSet *binding_set,
315 guint keyval,
316 guint modmask,
317 GtkMovementStep step,
318 gint count)
319 {
320 g_return_if_fail ((modmask & GDK_SHIFT_MASK) == 0);
321
322 gtk_binding_entry_add_signal (binding_set, keyval, modmask,
323 "move-cursor", 3,
324 G_TYPE_ENUM, step,
325 G_TYPE_INT, count,
326 G_TYPE_BOOLEAN, FALSE);
327
328 /* Selection-extending version */
329 gtk_binding_entry_add_signal (binding_set, keyval, modmask | GDK_SHIFT_MASK,
330 "move-cursor", 3,
331 G_TYPE_ENUM, step,
332 G_TYPE_INT, count,
333 G_TYPE_BOOLEAN, TRUE);
334 }
335
336 static void
gtk_label_class_init(GtkLabelClass * class)337 gtk_label_class_init (GtkLabelClass *class)
338 {
339 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
340 GtkObjectClass *object_class = GTK_OBJECT_CLASS (class);
341 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
342 GtkBindingSet *binding_set;
343
344 quark_angle = g_quark_from_static_string ("angle");
345
346 gobject_class->set_property = gtk_label_set_property;
347 gobject_class->get_property = gtk_label_get_property;
348 gobject_class->finalize = gtk_label_finalize;
349
350 object_class->destroy = gtk_label_destroy;
351
352 widget_class->size_request = gtk_label_size_request;
353 widget_class->size_allocate = gtk_label_size_allocate;
354 widget_class->state_changed = gtk_label_state_changed;
355 widget_class->style_set = gtk_label_style_set;
356 widget_class->query_tooltip = gtk_label_query_tooltip;
357 widget_class->direction_changed = gtk_label_direction_changed;
358 widget_class->expose_event = gtk_label_expose;
359 widget_class->realize = gtk_label_realize;
360 widget_class->unrealize = gtk_label_unrealize;
361 widget_class->map = gtk_label_map;
362 widget_class->unmap = gtk_label_unmap;
363 widget_class->button_press_event = gtk_label_button_press;
364 widget_class->button_release_event = gtk_label_button_release;
365 widget_class->motion_notify_event = gtk_label_motion;
366 widget_class->leave_notify_event = gtk_label_leave_notify;
367 widget_class->hierarchy_changed = gtk_label_hierarchy_changed;
368 widget_class->screen_changed = gtk_label_screen_changed;
369 widget_class->mnemonic_activate = gtk_label_mnemonic_activate;
370 widget_class->drag_data_get = gtk_label_drag_data_get;
371 widget_class->grab_focus = gtk_label_grab_focus;
372 widget_class->popup_menu = gtk_label_popup_menu;
373 widget_class->focus = gtk_label_focus;
374
375 class->move_cursor = gtk_label_move_cursor;
376 class->copy_clipboard = gtk_label_copy_clipboard;
377 class->activate_link = gtk_label_activate_link;
378
379 /**
380 * GtkLabel::move-cursor:
381 * @entry: the object which received the signal
382 * @step: the granularity of the move, as a #GtkMovementStep
383 * @count: the number of @step units to move
384 * @extend_selection: %TRUE if the move should extend the selection
385 *
386 * The ::move-cursor signal is a
387 * <link linkend="keybinding-signals">keybinding signal</link>
388 * which gets emitted when the user initiates a cursor movement.
389 * If the cursor is not visible in @entry, this signal causes
390 * the viewport to be moved instead.
391 *
392 * Applications should not connect to it, but may emit it with
393 * g_signal_emit_by_name() if they need to control the cursor
394 * programmatically.
395 *
396 * The default bindings for this signal come in two variants,
397 * the variant with the Shift modifier extends the selection,
398 * the variant without the Shift modifer does not.
399 * There are too many key combinations to list them all here.
400 * <itemizedlist>
401 * <listitem>Arrow keys move by individual characters/lines</listitem>
402 * <listitem>Ctrl-arrow key combinations move by words/paragraphs</listitem>
403 * <listitem>Home/End keys move to the ends of the buffer</listitem>
404 * </itemizedlist>
405 */
406 signals[MOVE_CURSOR] =
407 g_signal_new (I_("move-cursor"),
408 G_OBJECT_CLASS_TYPE (gobject_class),
409 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
410 G_STRUCT_OFFSET (GtkLabelClass, move_cursor),
411 NULL, NULL,
412 _gtk_marshal_VOID__ENUM_INT_BOOLEAN,
413 G_TYPE_NONE, 3,
414 GTK_TYPE_MOVEMENT_STEP,
415 G_TYPE_INT,
416 G_TYPE_BOOLEAN);
417
418 /**
419 * GtkLabel::copy-clipboard:
420 * @label: the object which received the signal
421 *
422 * The ::copy-clipboard signal is a
423 * <link linkend="keybinding-signals">keybinding signal</link>
424 * which gets emitted to copy the selection to the clipboard.
425 *
426 * The default binding for this signal is Ctrl-c.
427 */
428 signals[COPY_CLIPBOARD] =
429 g_signal_new (I_("copy-clipboard"),
430 G_OBJECT_CLASS_TYPE (gobject_class),
431 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
432 G_STRUCT_OFFSET (GtkLabelClass, copy_clipboard),
433 NULL, NULL,
434 _gtk_marshal_VOID__VOID,
435 G_TYPE_NONE, 0);
436
437 /**
438 * GtkLabel::populate-popup:
439 * @label: The label on which the signal is emitted
440 * @menu: the menu that is being populated
441 *
442 * The ::populate-popup signal gets emitted before showing the
443 * context menu of the label. Note that only selectable labels
444 * have context menus.
445 *
446 * If you need to add items to the context menu, connect
447 * to this signal and append your menuitems to the @menu.
448 */
449 signals[POPULATE_POPUP] =
450 g_signal_new (I_("populate-popup"),
451 G_OBJECT_CLASS_TYPE (gobject_class),
452 G_SIGNAL_RUN_LAST,
453 G_STRUCT_OFFSET (GtkLabelClass, populate_popup),
454 NULL, NULL,
455 _gtk_marshal_VOID__OBJECT,
456 G_TYPE_NONE, 1,
457 GTK_TYPE_MENU);
458
459 /**
460 * GtkLabel::activate-current-link:
461 * @label: The label on which the signal was emitted
462 *
463 * A <link linkend="keybinding-signals">keybinding signal</link>
464 * which gets emitted when the user activates a link in the label.
465 *
466 * Applications may also emit the signal with g_signal_emit_by_name()
467 * if they need to control activation of URIs programmatically.
468 *
469 * The default bindings for this signal are all forms of the Enter key.
470 *
471 * Since: 2.18
472 */
473 signals[ACTIVATE_CURRENT_LINK] =
474 g_signal_new_class_handler ("activate-current-link",
475 G_TYPE_FROM_CLASS (object_class),
476 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
477 G_CALLBACK (gtk_label_activate_current_link),
478 NULL, NULL,
479 _gtk_marshal_VOID__VOID,
480 G_TYPE_NONE, 0);
481
482 /**
483 * GtkLabel::activate-link:
484 * @label: The label on which the signal was emitted
485 * @uri: the URI that is activated
486 *
487 * The signal which gets emitted to activate a URI.
488 * Applications may connect to it to override the default behaviour,
489 * which is to call gtk_show_uri().
490 *
491 * Returns: %TRUE if the link has been activated
492 *
493 * Since: 2.18
494 */
495 signals[ACTIVATE_LINK] =
496 g_signal_new ("activate-link",
497 G_TYPE_FROM_CLASS (object_class),
498 G_SIGNAL_RUN_LAST,
499 G_STRUCT_OFFSET (GtkLabelClass, activate_link),
500 _gtk_boolean_handled_accumulator, NULL,
501 _gtk_marshal_BOOLEAN__STRING,
502 G_TYPE_BOOLEAN, 1, G_TYPE_STRING);
503
504 g_object_class_install_property (gobject_class,
505 PROP_LABEL,
506 g_param_spec_string ("label",
507 P_("Label"),
508 P_("The text of the label"),
509 "",
510 GTK_PARAM_READWRITE));
511 g_object_class_install_property (gobject_class,
512 PROP_ATTRIBUTES,
513 g_param_spec_boxed ("attributes",
514 P_("Attributes"),
515 P_("A list of style attributes to apply to the text of the label"),
516 PANGO_TYPE_ATTR_LIST,
517 GTK_PARAM_READWRITE));
518 g_object_class_install_property (gobject_class,
519 PROP_USE_MARKUP,
520 g_param_spec_boolean ("use-markup",
521 P_("Use markup"),
522 P_("The text of the label includes XML markup. See pango_parse_markup()"),
523 FALSE,
524 GTK_PARAM_READWRITE));
525 g_object_class_install_property (gobject_class,
526 PROP_USE_UNDERLINE,
527 g_param_spec_boolean ("use-underline",
528 P_("Use underline"),
529 P_("If set, an underline in the text indicates the next character should be used for the mnemonic accelerator key"),
530 FALSE,
531 GTK_PARAM_READWRITE));
532
533 g_object_class_install_property (gobject_class,
534 PROP_JUSTIFY,
535 g_param_spec_enum ("justify",
536 P_("Justification"),
537 P_("The alignment of the lines in the text of the label relative to each other. This does NOT affect the alignment of the label within its allocation. See GtkMisc::xalign for that"),
538 GTK_TYPE_JUSTIFICATION,
539 GTK_JUSTIFY_LEFT,
540 GTK_PARAM_READWRITE));
541
542 g_object_class_install_property (gobject_class,
543 PROP_PATTERN,
544 g_param_spec_string ("pattern",
545 P_("Pattern"),
546 P_("A string with _ characters in positions correspond to characters in the text to underline"),
547 NULL,
548 GTK_PARAM_WRITABLE));
549
550 g_object_class_install_property (gobject_class,
551 PROP_WRAP,
552 g_param_spec_boolean ("wrap",
553 P_("Line wrap"),
554 P_("If set, wrap lines if the text becomes too wide"),
555 FALSE,
556 GTK_PARAM_READWRITE));
557 /**
558 * GtkLabel:wrap-mode:
559 *
560 * If line wrapping is on (see the #GtkLabel:wrap property) this controls
561 * how the line wrapping is done. The default is %PANGO_WRAP_WORD, which
562 * means wrap on word boundaries.
563 *
564 * Since: 2.10
565 */
566 g_object_class_install_property (gobject_class,
567 PROP_WRAP_MODE,
568 g_param_spec_enum ("wrap-mode",
569 P_("Line wrap mode"),
570 P_("If wrap is set, controls how linewrapping is done"),
571 PANGO_TYPE_WRAP_MODE,
572 PANGO_WRAP_WORD,
573 GTK_PARAM_READWRITE));
574 g_object_class_install_property (gobject_class,
575 PROP_SELECTABLE,
576 g_param_spec_boolean ("selectable",
577 P_("Selectable"),
578 P_("Whether the label text can be selected with the mouse"),
579 FALSE,
580 GTK_PARAM_READWRITE));
581 g_object_class_install_property (gobject_class,
582 PROP_MNEMONIC_KEYVAL,
583 g_param_spec_uint ("mnemonic-keyval",
584 P_("Mnemonic key"),
585 P_("The mnemonic accelerator key for this label"),
586 0,
587 G_MAXUINT,
588 GDK_VoidSymbol,
589 GTK_PARAM_READABLE));
590 g_object_class_install_property (gobject_class,
591 PROP_MNEMONIC_WIDGET,
592 g_param_spec_object ("mnemonic-widget",
593 P_("Mnemonic widget"),
594 P_("The widget to be activated when the label's mnemonic "
595 "key is pressed"),
596 GTK_TYPE_WIDGET,
597 GTK_PARAM_READWRITE));
598
599 g_object_class_install_property (gobject_class,
600 PROP_CURSOR_POSITION,
601 g_param_spec_int ("cursor-position",
602 P_("Cursor Position"),
603 P_("The current position of the insertion cursor in chars"),
604 0,
605 G_MAXINT,
606 0,
607 GTK_PARAM_READABLE));
608
609 g_object_class_install_property (gobject_class,
610 PROP_SELECTION_BOUND,
611 g_param_spec_int ("selection-bound",
612 P_("Selection Bound"),
613 P_("The position of the opposite end of the selection from the cursor in chars"),
614 0,
615 G_MAXINT,
616 0,
617 GTK_PARAM_READABLE));
618
619 /**
620 * GtkLabel:ellipsize:
621 *
622 * The preferred place to ellipsize the string, if the label does
623 * not have enough room to display the entire string, specified as a
624 * #PangoEllisizeMode.
625 *
626 * Note that setting this property to a value other than
627 * %PANGO_ELLIPSIZE_NONE has the side-effect that the label requests
628 * only enough space to display the ellipsis "...". In particular, this
629 * means that ellipsizing labels do not work well in notebook tabs, unless
630 * the tab's #GtkNotebook:tab-expand property is set to %TRUE. Other ways
631 * to set a label's width are gtk_widget_set_size_request() and
632 * gtk_label_set_width_chars().
633 *
634 * Since: 2.6
635 */
636 g_object_class_install_property (gobject_class,
637 PROP_ELLIPSIZE,
638 g_param_spec_enum ("ellipsize",
639 P_("Ellipsize"),
640 P_("The preferred place to ellipsize the string, if the label does not have enough room to display the entire string"),
641 PANGO_TYPE_ELLIPSIZE_MODE,
642 PANGO_ELLIPSIZE_NONE,
643 GTK_PARAM_READWRITE));
644
645 /**
646 * GtkLabel:width-chars:
647 *
648 * The desired width of the label, in characters. If this property is set to
649 * -1, the width will be calculated automatically, otherwise the label will
650 * request either 3 characters or the property value, whichever is greater.
651 * If the "width-chars" property is set to a positive value, then the
652 * #GtkLabel:max-width-chars property is ignored.
653 *
654 * Since: 2.6
655 **/
656 g_object_class_install_property (gobject_class,
657 PROP_WIDTH_CHARS,
658 g_param_spec_int ("width-chars",
659 P_("Width In Characters"),
660 P_("The desired width of the label, in characters"),
661 -1,
662 G_MAXINT,
663 -1,
664 GTK_PARAM_READWRITE));
665
666 /**
667 * GtkLabel:single-line-mode:
668 *
669 * Whether the label is in single line mode. In single line mode,
670 * the height of the label does not depend on the actual text, it
671 * is always set to ascent + descent of the font. This can be an
672 * advantage in situations where resizing the label because of text
673 * changes would be distracting, e.g. in a statusbar.
674 *
675 * Since: 2.6
676 **/
677 g_object_class_install_property (gobject_class,
678 PROP_SINGLE_LINE_MODE,
679 g_param_spec_boolean ("single-line-mode",
680 P_("Single Line Mode"),
681 P_("Whether the label is in single line mode"),
682 FALSE,
683 GTK_PARAM_READWRITE));
684
685 /**
686 * GtkLabel:angle:
687 *
688 * The angle that the baseline of the label makes with the horizontal,
689 * in degrees, measured counterclockwise. An angle of 90 reads from
690 * from bottom to top, an angle of 270, from top to bottom. Ignored
691 * if the label is selectable, wrapped, or ellipsized.
692 *
693 * Since: 2.6
694 **/
695 g_object_class_install_property (gobject_class,
696 PROP_ANGLE,
697 g_param_spec_double ("angle",
698 P_("Angle"),
699 P_("Angle at which the label is rotated"),
700 0.0,
701 360.0,
702 0.0,
703 GTK_PARAM_READWRITE));
704
705 /**
706 * GtkLabel:max-width-chars:
707 *
708 * The desired maximum width of the label, in characters. If this property
709 * is set to -1, the width will be calculated automatically, otherwise the
710 * label will request space for no more than the requested number of
711 * characters. If the #GtkLabel:width-chars property is set to a positive
712 * value, then the "max-width-chars" property is ignored.
713 *
714 * Since: 2.6
715 **/
716 g_object_class_install_property (gobject_class,
717 PROP_MAX_WIDTH_CHARS,
718 g_param_spec_int ("max-width-chars",
719 P_("Maximum Width In Characters"),
720 P_("The desired maximum width of the label, in characters"),
721 -1,
722 G_MAXINT,
723 -1,
724 GTK_PARAM_READWRITE));
725
726 /**
727 * GtkLabel:track-visited-links:
728 *
729 * Set this property to %TRUE to make the label track which links
730 * have been clicked. It will then apply the ::visited-link-color
731 * color, instead of ::link-color.
732 *
733 * Since: 2.18
734 */
735 g_object_class_install_property (gobject_class,
736 PROP_TRACK_VISITED_LINKS,
737 g_param_spec_boolean ("track-visited-links",
738 P_("Track visited links"),
739 P_("Whether visited links should be tracked"),
740 TRUE,
741 GTK_PARAM_READWRITE));
742 /*
743 * Key bindings
744 */
745
746 binding_set = gtk_binding_set_by_class (class);
747
748 /* Moving the insertion point */
749 add_move_binding (binding_set, GDK_Right, 0,
750 GTK_MOVEMENT_VISUAL_POSITIONS, 1);
751
752 add_move_binding (binding_set, GDK_Left, 0,
753 GTK_MOVEMENT_VISUAL_POSITIONS, -1);
754
755 add_move_binding (binding_set, GDK_KP_Right, 0,
756 GTK_MOVEMENT_VISUAL_POSITIONS, 1);
757
758 add_move_binding (binding_set, GDK_KP_Left, 0,
759 GTK_MOVEMENT_VISUAL_POSITIONS, -1);
760
761 add_move_binding (binding_set, GDK_f, GDK_CONTROL_MASK,
762 GTK_MOVEMENT_LOGICAL_POSITIONS, 1);
763
764 add_move_binding (binding_set, GDK_b, GDK_CONTROL_MASK,
765 GTK_MOVEMENT_LOGICAL_POSITIONS, -1);
766
767 add_move_binding (binding_set, GDK_Right, GDK_CONTROL_MASK,
768 GTK_MOVEMENT_WORDS, 1);
769
770 add_move_binding (binding_set, GDK_Left, GDK_CONTROL_MASK,
771 GTK_MOVEMENT_WORDS, -1);
772
773 add_move_binding (binding_set, GDK_KP_Right, GDK_CONTROL_MASK,
774 GTK_MOVEMENT_WORDS, 1);
775
776 add_move_binding (binding_set, GDK_KP_Left, GDK_CONTROL_MASK,
777 GTK_MOVEMENT_WORDS, -1);
778
779 /* select all */
780 gtk_binding_entry_add_signal (binding_set, GDK_a, GDK_CONTROL_MASK,
781 "move-cursor", 3,
782 G_TYPE_ENUM, GTK_MOVEMENT_PARAGRAPH_ENDS,
783 G_TYPE_INT, -1,
784 G_TYPE_BOOLEAN, FALSE);
785
786 gtk_binding_entry_add_signal (binding_set, GDK_a, GDK_CONTROL_MASK,
787 "move-cursor", 3,
788 G_TYPE_ENUM, GTK_MOVEMENT_PARAGRAPH_ENDS,
789 G_TYPE_INT, 1,
790 G_TYPE_BOOLEAN, TRUE);
791
792 gtk_binding_entry_add_signal (binding_set, GDK_slash, GDK_CONTROL_MASK,
793 "move-cursor", 3,
794 G_TYPE_ENUM, GTK_MOVEMENT_PARAGRAPH_ENDS,
795 G_TYPE_INT, -1,
796 G_TYPE_BOOLEAN, FALSE);
797
798 gtk_binding_entry_add_signal (binding_set, GDK_slash, GDK_CONTROL_MASK,
799 "move-cursor", 3,
800 G_TYPE_ENUM, GTK_MOVEMENT_PARAGRAPH_ENDS,
801 G_TYPE_INT, 1,
802 G_TYPE_BOOLEAN, TRUE);
803
804 /* unselect all */
805 gtk_binding_entry_add_signal (binding_set, GDK_a, GDK_SHIFT_MASK | GDK_CONTROL_MASK,
806 "move-cursor", 3,
807 G_TYPE_ENUM, GTK_MOVEMENT_PARAGRAPH_ENDS,
808 G_TYPE_INT, 0,
809 G_TYPE_BOOLEAN, FALSE);
810
811 gtk_binding_entry_add_signal (binding_set, GDK_backslash, GDK_CONTROL_MASK,
812 "move-cursor", 3,
813 G_TYPE_ENUM, GTK_MOVEMENT_PARAGRAPH_ENDS,
814 G_TYPE_INT, 0,
815 G_TYPE_BOOLEAN, FALSE);
816
817 add_move_binding (binding_set, GDK_f, GDK_MOD1_MASK,
818 GTK_MOVEMENT_WORDS, 1);
819
820 add_move_binding (binding_set, GDK_b, GDK_MOD1_MASK,
821 GTK_MOVEMENT_WORDS, -1);
822
823 add_move_binding (binding_set, GDK_Home, 0,
824 GTK_MOVEMENT_DISPLAY_LINE_ENDS, -1);
825
826 add_move_binding (binding_set, GDK_End, 0,
827 GTK_MOVEMENT_DISPLAY_LINE_ENDS, 1);
828
829 add_move_binding (binding_set, GDK_KP_Home, 0,
830 GTK_MOVEMENT_DISPLAY_LINE_ENDS, -1);
831
832 add_move_binding (binding_set, GDK_KP_End, 0,
833 GTK_MOVEMENT_DISPLAY_LINE_ENDS, 1);
834
835 add_move_binding (binding_set, GDK_Home, GDK_CONTROL_MASK,
836 GTK_MOVEMENT_BUFFER_ENDS, -1);
837
838 add_move_binding (binding_set, GDK_End, GDK_CONTROL_MASK,
839 GTK_MOVEMENT_BUFFER_ENDS, 1);
840
841 add_move_binding (binding_set, GDK_KP_Home, GDK_CONTROL_MASK,
842 GTK_MOVEMENT_BUFFER_ENDS, -1);
843
844 add_move_binding (binding_set, GDK_KP_End, GDK_CONTROL_MASK,
845 GTK_MOVEMENT_BUFFER_ENDS, 1);
846
847 /* copy */
848 gtk_binding_entry_add_signal (binding_set, GDK_c, GDK_CONTROL_MASK,
849 "copy-clipboard", 0);
850
851 gtk_binding_entry_add_signal (binding_set, GDK_Return, 0,
852 "activate-current-link", 0);
853 gtk_binding_entry_add_signal (binding_set, GDK_ISO_Enter, 0,
854 "activate-current-link", 0);
855 gtk_binding_entry_add_signal (binding_set, GDK_KP_Enter, 0,
856 "activate-current-link", 0);
857
858 g_type_class_add_private (class, sizeof (GtkLabelPrivate));
859 }
860
861 static void
gtk_label_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)862 gtk_label_set_property (GObject *object,
863 guint prop_id,
864 const GValue *value,
865 GParamSpec *pspec)
866 {
867 GtkLabel *label;
868
869 label = GTK_LABEL (object);
870
871 switch (prop_id)
872 {
873 case PROP_LABEL:
874 gtk_label_set_label (label, g_value_get_string (value));
875 break;
876 case PROP_ATTRIBUTES:
877 gtk_label_set_attributes (label, g_value_get_boxed (value));
878 break;
879 case PROP_USE_MARKUP:
880 gtk_label_set_use_markup (label, g_value_get_boolean (value));
881 break;
882 case PROP_USE_UNDERLINE:
883 gtk_label_set_use_underline (label, g_value_get_boolean (value));
884 break;
885 case PROP_JUSTIFY:
886 gtk_label_set_justify (label, g_value_get_enum (value));
887 break;
888 case PROP_PATTERN:
889 gtk_label_set_pattern (label, g_value_get_string (value));
890 break;
891 case PROP_WRAP:
892 gtk_label_set_line_wrap (label, g_value_get_boolean (value));
893 break;
894 case PROP_WRAP_MODE:
895 gtk_label_set_line_wrap_mode (label, g_value_get_enum (value));
896 break;
897 case PROP_SELECTABLE:
898 gtk_label_set_selectable (label, g_value_get_boolean (value));
899 break;
900 case PROP_MNEMONIC_WIDGET:
901 gtk_label_set_mnemonic_widget (label, (GtkWidget*) g_value_get_object (value));
902 break;
903 case PROP_ELLIPSIZE:
904 gtk_label_set_ellipsize (label, g_value_get_enum (value));
905 break;
906 case PROP_WIDTH_CHARS:
907 gtk_label_set_width_chars (label, g_value_get_int (value));
908 break;
909 case PROP_SINGLE_LINE_MODE:
910 gtk_label_set_single_line_mode (label, g_value_get_boolean (value));
911 break;
912 case PROP_ANGLE:
913 gtk_label_set_angle (label, g_value_get_double (value));
914 break;
915 case PROP_MAX_WIDTH_CHARS:
916 gtk_label_set_max_width_chars (label, g_value_get_int (value));
917 break;
918 case PROP_TRACK_VISITED_LINKS:
919 gtk_label_set_track_visited_links (label, g_value_get_boolean (value));
920 break;
921 default:
922 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
923 break;
924 }
925 }
926
927 static void
gtk_label_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)928 gtk_label_get_property (GObject *object,
929 guint prop_id,
930 GValue *value,
931 GParamSpec *pspec)
932 {
933 GtkLabel *label;
934
935 label = GTK_LABEL (object);
936
937 switch (prop_id)
938 {
939 case PROP_LABEL:
940 g_value_set_string (value, label->label);
941 break;
942 case PROP_ATTRIBUTES:
943 g_value_set_boxed (value, label->attrs);
944 break;
945 case PROP_USE_MARKUP:
946 g_value_set_boolean (value, label->use_markup);
947 break;
948 case PROP_USE_UNDERLINE:
949 g_value_set_boolean (value, label->use_underline);
950 break;
951 case PROP_JUSTIFY:
952 g_value_set_enum (value, label->jtype);
953 break;
954 case PROP_WRAP:
955 g_value_set_boolean (value, label->wrap);
956 break;
957 case PROP_WRAP_MODE:
958 g_value_set_enum (value, label->wrap_mode);
959 break;
960 case PROP_SELECTABLE:
961 g_value_set_boolean (value, gtk_label_get_selectable (label));
962 break;
963 case PROP_MNEMONIC_KEYVAL:
964 g_value_set_uint (value, label->mnemonic_keyval);
965 break;
966 case PROP_MNEMONIC_WIDGET:
967 g_value_set_object (value, (GObject*) label->mnemonic_widget);
968 break;
969 case PROP_CURSOR_POSITION:
970 if (label->select_info && label->select_info->selectable)
971 {
972 gint offset = g_utf8_pointer_to_offset (label->text,
973 label->text + label->select_info->selection_end);
974 g_value_set_int (value, offset);
975 }
976 else
977 g_value_set_int (value, 0);
978 break;
979 case PROP_SELECTION_BOUND:
980 if (label->select_info && label->select_info->selectable)
981 {
982 gint offset = g_utf8_pointer_to_offset (label->text,
983 label->text + label->select_info->selection_anchor);
984 g_value_set_int (value, offset);
985 }
986 else
987 g_value_set_int (value, 0);
988 break;
989 case PROP_ELLIPSIZE:
990 g_value_set_enum (value, label->ellipsize);
991 break;
992 case PROP_WIDTH_CHARS:
993 g_value_set_int (value, gtk_label_get_width_chars (label));
994 break;
995 case PROP_SINGLE_LINE_MODE:
996 g_value_set_boolean (value, gtk_label_get_single_line_mode (label));
997 break;
998 case PROP_ANGLE:
999 g_value_set_double (value, gtk_label_get_angle (label));
1000 break;
1001 case PROP_MAX_WIDTH_CHARS:
1002 g_value_set_int (value, gtk_label_get_max_width_chars (label));
1003 break;
1004 case PROP_TRACK_VISITED_LINKS:
1005 g_value_set_boolean (value, gtk_label_get_track_visited_links (label));
1006 break;
1007 default:
1008 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1009 break;
1010 }
1011 }
1012
1013 static void
gtk_label_init(GtkLabel * label)1014 gtk_label_init (GtkLabel *label)
1015 {
1016 GtkLabelPrivate *priv;
1017
1018 gtk_widget_set_has_window (GTK_WIDGET (label), FALSE);
1019
1020 priv = GTK_LABEL_GET_PRIVATE (label);
1021 priv->width_chars = -1;
1022 priv->max_width_chars = -1;
1023 priv->wrap_width = -1;
1024 label->label = NULL;
1025
1026 label->jtype = GTK_JUSTIFY_LEFT;
1027 label->wrap = FALSE;
1028 label->wrap_mode = PANGO_WRAP_WORD;
1029 label->ellipsize = PANGO_ELLIPSIZE_NONE;
1030
1031 label->use_underline = FALSE;
1032 label->use_markup = FALSE;
1033 label->pattern_set = FALSE;
1034 label->track_links = TRUE;
1035
1036 label->mnemonic_keyval = GDK_VoidSymbol;
1037 label->layout = NULL;
1038 label->text = NULL;
1039 label->attrs = NULL;
1040
1041 label->mnemonic_widget = NULL;
1042 label->mnemonic_window = NULL;
1043
1044 priv->mnemonics_visible = TRUE;
1045
1046 gtk_label_set_text (label, "");
1047 }
1048
1049
1050 static void
gtk_label_buildable_interface_init(GtkBuildableIface * iface)1051 gtk_label_buildable_interface_init (GtkBuildableIface *iface)
1052 {
1053 buildable_parent_iface = g_type_interface_peek_parent (iface);
1054
1055 iface->custom_tag_start = gtk_label_buildable_custom_tag_start;
1056 iface->custom_finished = gtk_label_buildable_custom_finished;
1057 }
1058
1059 typedef struct {
1060 GtkBuilder *builder;
1061 GObject *object;
1062 PangoAttrList *attrs;
1063 } PangoParserData;
1064
1065 static PangoAttribute *
attribute_from_text(GtkBuilder * builder,const gchar * name,const gchar * value,GError ** error)1066 attribute_from_text (GtkBuilder *builder,
1067 const gchar *name,
1068 const gchar *value,
1069 GError **error)
1070 {
1071 PangoAttribute *attribute = NULL;
1072 PangoAttrType type;
1073 PangoLanguage *language;
1074 PangoFontDescription *font_desc;
1075 GdkColor *color;
1076 GValue val = { 0, };
1077
1078 if (!gtk_builder_value_from_string_type (builder, PANGO_TYPE_ATTR_TYPE, name, &val, error))
1079 return NULL;
1080
1081 type = g_value_get_enum (&val);
1082 g_value_unset (&val);
1083
1084 switch (type)
1085 {
1086 /* PangoAttrLanguage */
1087 case PANGO_ATTR_LANGUAGE:
1088 if ((language = pango_language_from_string (value)))
1089 {
1090 attribute = pango_attr_language_new (language);
1091 g_value_init (&val, G_TYPE_INT);
1092 }
1093 break;
1094 /* PangoAttrInt */
1095 case PANGO_ATTR_STYLE:
1096 if (gtk_builder_value_from_string_type (builder, PANGO_TYPE_STYLE, value, &val, error))
1097 attribute = pango_attr_style_new (g_value_get_enum (&val));
1098 break;
1099 case PANGO_ATTR_WEIGHT:
1100 if (gtk_builder_value_from_string_type (builder, PANGO_TYPE_WEIGHT, value, &val, error))
1101 attribute = pango_attr_weight_new (g_value_get_enum (&val));
1102 break;
1103 case PANGO_ATTR_VARIANT:
1104 if (gtk_builder_value_from_string_type (builder, PANGO_TYPE_VARIANT, value, &val, error))
1105 attribute = pango_attr_variant_new (g_value_get_enum (&val));
1106 break;
1107 case PANGO_ATTR_STRETCH:
1108 if (gtk_builder_value_from_string_type (builder, PANGO_TYPE_STRETCH, value, &val, error))
1109 attribute = pango_attr_stretch_new (g_value_get_enum (&val));
1110 break;
1111 case PANGO_ATTR_UNDERLINE:
1112 if (gtk_builder_value_from_string_type (builder, G_TYPE_BOOLEAN, value, &val, error))
1113 attribute = pango_attr_underline_new (g_value_get_boolean (&val));
1114 break;
1115 case PANGO_ATTR_STRIKETHROUGH:
1116 if (gtk_builder_value_from_string_type (builder, G_TYPE_BOOLEAN, value, &val, error))
1117 attribute = pango_attr_strikethrough_new (g_value_get_boolean (&val));
1118 break;
1119 case PANGO_ATTR_GRAVITY:
1120 if (gtk_builder_value_from_string_type (builder, PANGO_TYPE_GRAVITY, value, &val, error))
1121 attribute = pango_attr_gravity_new (g_value_get_enum (&val));
1122 break;
1123 case PANGO_ATTR_GRAVITY_HINT:
1124 if (gtk_builder_value_from_string_type (builder, PANGO_TYPE_GRAVITY_HINT,
1125 value, &val, error))
1126 attribute = pango_attr_gravity_hint_new (g_value_get_enum (&val));
1127 break;
1128
1129 /* PangoAttrString */
1130 case PANGO_ATTR_FAMILY:
1131 attribute = pango_attr_family_new (value);
1132 g_value_init (&val, G_TYPE_INT);
1133 break;
1134
1135 /* PangoAttrSize */
1136 case PANGO_ATTR_SIZE:
1137 if (gtk_builder_value_from_string_type (builder, G_TYPE_INT,
1138 value, &val, error))
1139 attribute = pango_attr_size_new (g_value_get_int (&val));
1140 break;
1141 case PANGO_ATTR_ABSOLUTE_SIZE:
1142 if (gtk_builder_value_from_string_type (builder, G_TYPE_INT,
1143 value, &val, error))
1144 attribute = pango_attr_size_new_absolute (g_value_get_int (&val));
1145 break;
1146
1147 /* PangoAttrFontDesc */
1148 case PANGO_ATTR_FONT_DESC:
1149 if ((font_desc = pango_font_description_from_string (value)))
1150 {
1151 attribute = pango_attr_font_desc_new (font_desc);
1152 pango_font_description_free (font_desc);
1153 g_value_init (&val, G_TYPE_INT);
1154 }
1155 break;
1156
1157 /* PangoAttrColor */
1158 case PANGO_ATTR_FOREGROUND:
1159 if (gtk_builder_value_from_string_type (builder, GDK_TYPE_COLOR,
1160 value, &val, error))
1161 {
1162 color = g_value_get_boxed (&val);
1163 attribute = pango_attr_foreground_new (color->red, color->green, color->blue);
1164 }
1165 break;
1166 case PANGO_ATTR_BACKGROUND:
1167 if (gtk_builder_value_from_string_type (builder, GDK_TYPE_COLOR,
1168 value, &val, error))
1169 {
1170 color = g_value_get_boxed (&val);
1171 attribute = pango_attr_background_new (color->red, color->green, color->blue);
1172 }
1173 break;
1174 case PANGO_ATTR_UNDERLINE_COLOR:
1175 if (gtk_builder_value_from_string_type (builder, GDK_TYPE_COLOR,
1176 value, &val, error))
1177 {
1178 color = g_value_get_boxed (&val);
1179 attribute = pango_attr_underline_color_new (color->red, color->green, color->blue);
1180 }
1181 break;
1182 case PANGO_ATTR_STRIKETHROUGH_COLOR:
1183 if (gtk_builder_value_from_string_type (builder, GDK_TYPE_COLOR,
1184 value, &val, error))
1185 {
1186 color = g_value_get_boxed (&val);
1187 attribute = pango_attr_strikethrough_color_new (color->red, color->green, color->blue);
1188 }
1189 break;
1190
1191 /* PangoAttrShape */
1192 case PANGO_ATTR_SHAPE:
1193 /* Unsupported for now */
1194 break;
1195 /* PangoAttrFloat */
1196 case PANGO_ATTR_SCALE:
1197 if (gtk_builder_value_from_string_type (builder, G_TYPE_DOUBLE,
1198 value, &val, error))
1199 attribute = pango_attr_scale_new (g_value_get_double (&val));
1200 break;
1201
1202 case PANGO_ATTR_INVALID:
1203 case PANGO_ATTR_LETTER_SPACING:
1204 case PANGO_ATTR_RISE:
1205 case PANGO_ATTR_FALLBACK:
1206 default:
1207 break;
1208 }
1209
1210 g_value_unset (&val);
1211
1212 return attribute;
1213 }
1214
1215
1216 static void
pango_start_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** names,const gchar ** values,gpointer user_data,GError ** error)1217 pango_start_element (GMarkupParseContext *context,
1218 const gchar *element_name,
1219 const gchar **names,
1220 const gchar **values,
1221 gpointer user_data,
1222 GError **error)
1223 {
1224 PangoParserData *data = (PangoParserData*)user_data;
1225 GValue val = { 0, };
1226 guint i;
1227 gint line_number, char_number;
1228
1229 if (strcmp (element_name, "attribute") == 0)
1230 {
1231 PangoAttribute *attr = NULL;
1232 const gchar *name = NULL;
1233 const gchar *value = NULL;
1234 const gchar *start = NULL;
1235 const gchar *end = NULL;
1236 guint start_val = 0;
1237 guint end_val = G_MAXUINT;
1238
1239 for (i = 0; names[i]; i++)
1240 {
1241 if (strcmp (names[i], "name") == 0)
1242 name = values[i];
1243 else if (strcmp (names[i], "value") == 0)
1244 value = values[i];
1245 else if (strcmp (names[i], "start") == 0)
1246 start = values[i];
1247 else if (strcmp (names[i], "end") == 0)
1248 end = values[i];
1249 else
1250 {
1251 g_markup_parse_context_get_position (context,
1252 &line_number,
1253 &char_number);
1254 g_set_error (error,
1255 GTK_BUILDER_ERROR,
1256 GTK_BUILDER_ERROR_INVALID_ATTRIBUTE,
1257 "%s:%d:%d '%s' is not a valid attribute of <%s>",
1258 "<input>",
1259 line_number, char_number, names[i], "attribute");
1260 return;
1261 }
1262 }
1263
1264 if (!name || !value)
1265 {
1266 g_markup_parse_context_get_position (context,
1267 &line_number,
1268 &char_number);
1269 g_set_error (error,
1270 GTK_BUILDER_ERROR,
1271 GTK_BUILDER_ERROR_MISSING_ATTRIBUTE,
1272 "%s:%d:%d <%s> requires attribute \"%s\"",
1273 "<input>",
1274 line_number, char_number, "attribute",
1275 name ? "value" : "name");
1276 return;
1277 }
1278
1279 if (start)
1280 {
1281 if (!gtk_builder_value_from_string_type (data->builder, G_TYPE_UINT,
1282 start, &val, error))
1283 return;
1284 start_val = g_value_get_uint (&val);
1285 g_value_unset (&val);
1286 }
1287
1288 if (end)
1289 {
1290 if (!gtk_builder_value_from_string_type (data->builder, G_TYPE_UINT,
1291 end, &val, error))
1292 return;
1293 end_val = g_value_get_uint (&val);
1294 g_value_unset (&val);
1295 }
1296
1297 attr = attribute_from_text (data->builder, name, value, error);
1298
1299 if (attr)
1300 {
1301 attr->start_index = start_val;
1302 attr->end_index = end_val;
1303
1304 if (!data->attrs)
1305 data->attrs = pango_attr_list_new ();
1306
1307 pango_attr_list_insert (data->attrs, attr);
1308 }
1309 }
1310 else if (strcmp (element_name, "attributes") == 0)
1311 ;
1312 else
1313 g_warning ("Unsupported tag for GtkLabel: %s\n", element_name);
1314 }
1315
1316 static const GMarkupParser pango_parser =
1317 {
1318 pango_start_element,
1319 };
1320
1321 static gboolean
gtk_label_buildable_custom_tag_start(GtkBuildable * buildable,GtkBuilder * builder,GObject * child,const gchar * tagname,GMarkupParser * parser,gpointer * data)1322 gtk_label_buildable_custom_tag_start (GtkBuildable *buildable,
1323 GtkBuilder *builder,
1324 GObject *child,
1325 const gchar *tagname,
1326 GMarkupParser *parser,
1327 gpointer *data)
1328 {
1329 if (buildable_parent_iface->custom_tag_start (buildable, builder, child,
1330 tagname, parser, data))
1331 return TRUE;
1332
1333 if (strcmp (tagname, "attributes") == 0)
1334 {
1335 PangoParserData *parser_data;
1336
1337 parser_data = g_slice_new0 (PangoParserData);
1338 parser_data->builder = g_object_ref (builder);
1339 parser_data->object = g_object_ref (buildable);
1340 *parser = pango_parser;
1341 *data = parser_data;
1342 return TRUE;
1343 }
1344 return FALSE;
1345 }
1346
1347 static void
gtk_label_buildable_custom_finished(GtkBuildable * buildable,GtkBuilder * builder,GObject * child,const gchar * tagname,gpointer user_data)1348 gtk_label_buildable_custom_finished (GtkBuildable *buildable,
1349 GtkBuilder *builder,
1350 GObject *child,
1351 const gchar *tagname,
1352 gpointer user_data)
1353 {
1354 PangoParserData *data;
1355
1356 buildable_parent_iface->custom_finished (buildable, builder, child,
1357 tagname, user_data);
1358
1359 if (strcmp (tagname, "attributes") == 0)
1360 {
1361 data = (PangoParserData*)user_data;
1362
1363 if (data->attrs)
1364 {
1365 gtk_label_set_attributes (GTK_LABEL (buildable), data->attrs);
1366 pango_attr_list_unref (data->attrs);
1367 }
1368
1369 g_object_unref (data->object);
1370 g_object_unref (data->builder);
1371 g_slice_free (PangoParserData, data);
1372 }
1373 }
1374
1375
1376 /**
1377 * gtk_label_new:
1378 * @str: The text of the label
1379 *
1380 * Creates a new label with the given text inside it. You can
1381 * pass %NULL to get an empty label widget.
1382 *
1383 * Return value: the new #GtkLabel
1384 **/
1385 GtkWidget*
gtk_label_new(const gchar * str)1386 gtk_label_new (const gchar *str)
1387 {
1388 GtkLabel *label;
1389
1390 label = g_object_new (GTK_TYPE_LABEL, NULL);
1391
1392 if (str && *str)
1393 gtk_label_set_text (label, str);
1394
1395 return GTK_WIDGET (label);
1396 }
1397
1398 /**
1399 * gtk_label_new_with_mnemonic:
1400 * @str: The text of the label, with an underscore in front of the
1401 * mnemonic character
1402 *
1403 * Creates a new #GtkLabel, containing the text in @str.
1404 *
1405 * If characters in @str are preceded by an underscore, they are
1406 * underlined. If you need a literal underscore character in a label, use
1407 * '__' (two underscores). The first underlined character represents a
1408 * keyboard accelerator called a mnemonic. The mnemonic key can be used
1409 * to activate another widget, chosen automatically, or explicitly using
1410 * gtk_label_set_mnemonic_widget().
1411 *
1412 * If gtk_label_set_mnemonic_widget() is not called, then the first
1413 * activatable ancestor of the #GtkLabel will be chosen as the mnemonic
1414 * widget. For instance, if the label is inside a button or menu item,
1415 * the button or menu item will automatically become the mnemonic widget
1416 * and be activated by the mnemonic.
1417 *
1418 * Return value: the new #GtkLabel
1419 **/
1420 GtkWidget*
gtk_label_new_with_mnemonic(const gchar * str)1421 gtk_label_new_with_mnemonic (const gchar *str)
1422 {
1423 GtkLabel *label;
1424
1425 label = g_object_new (GTK_TYPE_LABEL, NULL);
1426
1427 if (str && *str)
1428 gtk_label_set_text_with_mnemonic (label, str);
1429
1430 return GTK_WIDGET (label);
1431 }
1432
1433 static gboolean
gtk_label_mnemonic_activate(GtkWidget * widget,gboolean group_cycling)1434 gtk_label_mnemonic_activate (GtkWidget *widget,
1435 gboolean group_cycling)
1436 {
1437 GtkWidget *parent;
1438
1439 if (GTK_LABEL (widget)->mnemonic_widget)
1440 return gtk_widget_mnemonic_activate (GTK_LABEL (widget)->mnemonic_widget, group_cycling);
1441
1442 /* Try to find the widget to activate by traversing the
1443 * widget's ancestry.
1444 */
1445 parent = widget->parent;
1446
1447 if (GTK_IS_NOTEBOOK (parent))
1448 return FALSE;
1449
1450 while (parent)
1451 {
1452 if (gtk_widget_get_can_focus (parent) ||
1453 (!group_cycling && GTK_WIDGET_GET_CLASS (parent)->activate_signal) ||
1454 GTK_IS_NOTEBOOK (parent->parent) ||
1455 GTK_IS_MENU_ITEM (parent))
1456 return gtk_widget_mnemonic_activate (parent, group_cycling);
1457 parent = parent->parent;
1458 }
1459
1460 /* barf if there was nothing to activate */
1461 g_warning ("Couldn't find a target for a mnemonic activation.");
1462 gtk_widget_error_bell (widget);
1463
1464 return FALSE;
1465 }
1466
1467 static void
gtk_label_setup_mnemonic(GtkLabel * label,guint last_key)1468 gtk_label_setup_mnemonic (GtkLabel *label,
1469 guint last_key)
1470 {
1471 GtkWidget *widget = GTK_WIDGET (label);
1472 GtkWidget *toplevel;
1473 GtkWidget *mnemonic_menu;
1474
1475 mnemonic_menu = g_object_get_data (G_OBJECT (label), "gtk-mnemonic-menu");
1476
1477 if (last_key != GDK_VoidSymbol)
1478 {
1479 if (label->mnemonic_window)
1480 {
1481 gtk_window_remove_mnemonic (label->mnemonic_window,
1482 last_key,
1483 widget);
1484 label->mnemonic_window = NULL;
1485 }
1486 if (mnemonic_menu)
1487 {
1488 _gtk_menu_shell_remove_mnemonic (GTK_MENU_SHELL (mnemonic_menu),
1489 last_key,
1490 widget);
1491 mnemonic_menu = NULL;
1492 }
1493 }
1494
1495 if (label->mnemonic_keyval == GDK_VoidSymbol)
1496 goto done;
1497
1498 connect_mnemonics_visible_notify (GTK_LABEL (widget));
1499
1500 toplevel = gtk_widget_get_toplevel (widget);
1501 if (gtk_widget_is_toplevel (toplevel))
1502 {
1503 GtkWidget *menu_shell;
1504
1505 menu_shell = gtk_widget_get_ancestor (widget,
1506 GTK_TYPE_MENU_SHELL);
1507
1508 if (menu_shell)
1509 {
1510 _gtk_menu_shell_add_mnemonic (GTK_MENU_SHELL (menu_shell),
1511 label->mnemonic_keyval,
1512 widget);
1513 mnemonic_menu = menu_shell;
1514 }
1515
1516 if (!GTK_IS_MENU (menu_shell))
1517 {
1518 gtk_window_add_mnemonic (GTK_WINDOW (toplevel),
1519 label->mnemonic_keyval,
1520 widget);
1521 label->mnemonic_window = GTK_WINDOW (toplevel);
1522 }
1523 }
1524
1525 done:
1526 g_object_set_data (G_OBJECT (label), I_("gtk-mnemonic-menu"), mnemonic_menu);
1527 }
1528
1529 static void
gtk_label_hierarchy_changed(GtkWidget * widget,GtkWidget * old_toplevel)1530 gtk_label_hierarchy_changed (GtkWidget *widget,
1531 GtkWidget *old_toplevel)
1532 {
1533 GtkLabel *label = GTK_LABEL (widget);
1534
1535 gtk_label_setup_mnemonic (label, label->mnemonic_keyval);
1536 }
1537
1538 static void
label_shortcut_setting_apply(GtkLabel * label)1539 label_shortcut_setting_apply (GtkLabel *label)
1540 {
1541 gtk_label_recalculate (label);
1542 if (GTK_IS_ACCEL_LABEL (label))
1543 gtk_accel_label_refetch (GTK_ACCEL_LABEL (label));
1544 }
1545
1546 static void
label_shortcut_setting_traverse_container(GtkWidget * widget,gpointer data)1547 label_shortcut_setting_traverse_container (GtkWidget *widget,
1548 gpointer data)
1549 {
1550 if (GTK_IS_LABEL (widget))
1551 label_shortcut_setting_apply (GTK_LABEL (widget));
1552 else if (GTK_IS_CONTAINER (widget))
1553 gtk_container_forall (GTK_CONTAINER (widget),
1554 label_shortcut_setting_traverse_container, data);
1555 }
1556
1557 static void
label_shortcut_setting_changed(GtkSettings * settings)1558 label_shortcut_setting_changed (GtkSettings *settings)
1559 {
1560 GList *list, *l;
1561
1562 list = gtk_window_list_toplevels ();
1563
1564 for (l = list; l ; l = l->next)
1565 {
1566 GtkWidget *widget = l->data;
1567
1568 if (gtk_widget_get_settings (widget) == settings)
1569 gtk_container_forall (GTK_CONTAINER (widget),
1570 label_shortcut_setting_traverse_container, NULL);
1571 }
1572
1573 g_list_free (list);
1574 }
1575
1576 static void
mnemonics_visible_apply(GtkWidget * widget,gboolean mnemonics_visible)1577 mnemonics_visible_apply (GtkWidget *widget,
1578 gboolean mnemonics_visible)
1579 {
1580 GtkLabel *label;
1581 GtkLabelPrivate *priv;
1582
1583 label = GTK_LABEL (widget);
1584
1585 priv = GTK_LABEL_GET_PRIVATE (label);
1586
1587 mnemonics_visible = mnemonics_visible != FALSE;
1588
1589 if (priv->mnemonics_visible != mnemonics_visible)
1590 {
1591 priv->mnemonics_visible = mnemonics_visible;
1592
1593 gtk_label_recalculate (label);
1594 }
1595 }
1596
1597 static void
label_mnemonics_visible_traverse_container(GtkWidget * widget,gpointer data)1598 label_mnemonics_visible_traverse_container (GtkWidget *widget,
1599 gpointer data)
1600 {
1601 gboolean mnemonics_visible = GPOINTER_TO_INT (data);
1602
1603 _gtk_label_mnemonics_visible_apply_recursively (widget, mnemonics_visible);
1604 }
1605
1606 void
_gtk_label_mnemonics_visible_apply_recursively(GtkWidget * widget,gboolean mnemonics_visible)1607 _gtk_label_mnemonics_visible_apply_recursively (GtkWidget *widget,
1608 gboolean mnemonics_visible)
1609 {
1610 if (GTK_IS_LABEL (widget))
1611 mnemonics_visible_apply (widget, mnemonics_visible);
1612 else if (GTK_IS_CONTAINER (widget))
1613 gtk_container_forall (GTK_CONTAINER (widget),
1614 label_mnemonics_visible_traverse_container,
1615 GINT_TO_POINTER (mnemonics_visible));
1616 }
1617
1618 static void
label_mnemonics_visible_changed(GtkWindow * window,GParamSpec * pspec,gpointer data)1619 label_mnemonics_visible_changed (GtkWindow *window,
1620 GParamSpec *pspec,
1621 gpointer data)
1622 {
1623 gboolean mnemonics_visible;
1624
1625 g_object_get (window, "mnemonics-visible", &mnemonics_visible, NULL);
1626
1627 gtk_container_forall (GTK_CONTAINER (window),
1628 label_mnemonics_visible_traverse_container,
1629 GINT_TO_POINTER (mnemonics_visible));
1630 }
1631
1632 static void
gtk_label_screen_changed(GtkWidget * widget,GdkScreen * old_screen)1633 gtk_label_screen_changed (GtkWidget *widget,
1634 GdkScreen *old_screen)
1635 {
1636 GtkSettings *settings;
1637 gboolean shortcuts_connected;
1638
1639 if (!gtk_widget_has_screen (widget))
1640 return;
1641
1642 settings = gtk_widget_get_settings (widget);
1643
1644 shortcuts_connected =
1645 GPOINTER_TO_INT (g_object_get_data (G_OBJECT (settings),
1646 "gtk-label-shortcuts-connected"));
1647
1648 if (! shortcuts_connected)
1649 {
1650 g_signal_connect (settings, "notify::gtk-enable-mnemonics",
1651 G_CALLBACK (label_shortcut_setting_changed),
1652 NULL);
1653 g_signal_connect (settings, "notify::gtk-enable-accels",
1654 G_CALLBACK (label_shortcut_setting_changed),
1655 NULL);
1656
1657 g_object_set_data (G_OBJECT (settings), "gtk-label-shortcuts-connected",
1658 GINT_TO_POINTER (TRUE));
1659 }
1660
1661 label_shortcut_setting_apply (GTK_LABEL (widget));
1662 }
1663
1664
1665 static void
label_mnemonic_widget_weak_notify(gpointer data,GObject * where_the_object_was)1666 label_mnemonic_widget_weak_notify (gpointer data,
1667 GObject *where_the_object_was)
1668 {
1669 GtkLabel *label = data;
1670
1671 label->mnemonic_widget = NULL;
1672 g_object_notify (G_OBJECT (label), "mnemonic-widget");
1673 }
1674
1675 /**
1676 * gtk_label_set_mnemonic_widget:
1677 * @label: a #GtkLabel
1678 * @widget: (allow-none): the target #GtkWidget
1679 *
1680 * If the label has been set so that it has an mnemonic key (using
1681 * i.e. gtk_label_set_markup_with_mnemonic(),
1682 * gtk_label_set_text_with_mnemonic(), gtk_label_new_with_mnemonic()
1683 * or the "use_underline" property) the label can be associated with a
1684 * widget that is the target of the mnemonic. When the label is inside
1685 * a widget (like a #GtkButton or a #GtkNotebook tab) it is
1686 * automatically associated with the correct widget, but sometimes
1687 * (i.e. when the target is a #GtkEntry next to the label) you need to
1688 * set it explicitly using this function.
1689 *
1690 * The target widget will be accelerated by emitting the
1691 * GtkWidget::mnemonic-activate signal on it. The default handler for
1692 * this signal will activate the widget if there are no mnemonic collisions
1693 * and toggle focus between the colliding widgets otherwise.
1694 **/
1695 void
gtk_label_set_mnemonic_widget(GtkLabel * label,GtkWidget * widget)1696 gtk_label_set_mnemonic_widget (GtkLabel *label,
1697 GtkWidget *widget)
1698 {
1699 g_return_if_fail (GTK_IS_LABEL (label));
1700 if (widget)
1701 g_return_if_fail (GTK_IS_WIDGET (widget));
1702
1703 if (label->mnemonic_widget)
1704 {
1705 gtk_widget_remove_mnemonic_label (label->mnemonic_widget, GTK_WIDGET (label));
1706 g_object_weak_unref (G_OBJECT (label->mnemonic_widget),
1707 label_mnemonic_widget_weak_notify,
1708 label);
1709 }
1710 label->mnemonic_widget = widget;
1711 if (label->mnemonic_widget)
1712 {
1713 g_object_weak_ref (G_OBJECT (label->mnemonic_widget),
1714 label_mnemonic_widget_weak_notify,
1715 label);
1716 gtk_widget_add_mnemonic_label (label->mnemonic_widget, GTK_WIDGET (label));
1717 }
1718
1719 g_object_notify (G_OBJECT (label), "mnemonic-widget");
1720 }
1721
1722 /**
1723 * gtk_label_get_mnemonic_widget:
1724 * @label: a #GtkLabel
1725 *
1726 * Retrieves the target of the mnemonic (keyboard shortcut) of this
1727 * label. See gtk_label_set_mnemonic_widget().
1728 *
1729 * Return value: (transfer none): the target of the label's mnemonic,
1730 * or %NULL if none has been set and the default algorithm will be used.
1731 **/
1732 GtkWidget *
gtk_label_get_mnemonic_widget(GtkLabel * label)1733 gtk_label_get_mnemonic_widget (GtkLabel *label)
1734 {
1735 g_return_val_if_fail (GTK_IS_LABEL (label), NULL);
1736
1737 return label->mnemonic_widget;
1738 }
1739
1740 /**
1741 * gtk_label_get_mnemonic_keyval:
1742 * @label: a #GtkLabel
1743 *
1744 * If the label has been set so that it has an mnemonic key this function
1745 * returns the keyval used for the mnemonic accelerator. If there is no
1746 * mnemonic set up it returns #GDK_VoidSymbol.
1747 *
1748 * Returns: GDK keyval usable for accelerators, or #GDK_VoidSymbol
1749 **/
1750 guint
gtk_label_get_mnemonic_keyval(GtkLabel * label)1751 gtk_label_get_mnemonic_keyval (GtkLabel *label)
1752 {
1753 g_return_val_if_fail (GTK_IS_LABEL (label), GDK_VoidSymbol);
1754
1755 return label->mnemonic_keyval;
1756 }
1757
1758 static void
gtk_label_set_text_internal(GtkLabel * label,gchar * str)1759 gtk_label_set_text_internal (GtkLabel *label,
1760 gchar *str)
1761 {
1762 g_free (label->text);
1763
1764 label->text = str;
1765
1766 gtk_label_select_region_index (label, 0, 0);
1767 }
1768
1769 static void
gtk_label_set_label_internal(GtkLabel * label,gchar * str)1770 gtk_label_set_label_internal (GtkLabel *label,
1771 gchar *str)
1772 {
1773 g_free (label->label);
1774
1775 label->label = str;
1776
1777 g_object_notify (G_OBJECT (label), "label");
1778 }
1779
1780 static void
gtk_label_set_use_markup_internal(GtkLabel * label,gboolean val)1781 gtk_label_set_use_markup_internal (GtkLabel *label,
1782 gboolean val)
1783 {
1784 val = val != FALSE;
1785 if (label->use_markup != val)
1786 {
1787 label->use_markup = val;
1788
1789 g_object_notify (G_OBJECT (label), "use-markup");
1790 }
1791 }
1792
1793 static void
gtk_label_set_use_underline_internal(GtkLabel * label,gboolean val)1794 gtk_label_set_use_underline_internal (GtkLabel *label,
1795 gboolean val)
1796 {
1797 val = val != FALSE;
1798 if (label->use_underline != val)
1799 {
1800 label->use_underline = val;
1801
1802 g_object_notify (G_OBJECT (label), "use-underline");
1803 }
1804 }
1805
1806 static void
gtk_label_compose_effective_attrs(GtkLabel * label)1807 gtk_label_compose_effective_attrs (GtkLabel *label)
1808 {
1809 PangoAttrIterator *iter;
1810 PangoAttribute *attr;
1811 GSList *iter_attrs, *l;
1812
1813 if (label->attrs)
1814 {
1815 if (label->effective_attrs)
1816 {
1817 if ((iter = pango_attr_list_get_iterator (label->attrs)))
1818 {
1819 do
1820 {
1821 iter_attrs = pango_attr_iterator_get_attrs (iter);
1822 for (l = iter_attrs; l; l = l->next)
1823 {
1824 attr = l->data;
1825 pango_attr_list_insert (label->effective_attrs, attr);
1826 }
1827 g_slist_free (iter_attrs);
1828 }
1829 while (pango_attr_iterator_next (iter));
1830 pango_attr_iterator_destroy (iter);
1831 }
1832 }
1833 else
1834 label->effective_attrs =
1835 pango_attr_list_ref (label->attrs);
1836 }
1837 }
1838
1839 static void
gtk_label_set_attributes_internal(GtkLabel * label,PangoAttrList * attrs)1840 gtk_label_set_attributes_internal (GtkLabel *label,
1841 PangoAttrList *attrs)
1842 {
1843 if (attrs)
1844 pango_attr_list_ref (attrs);
1845
1846 if (label->attrs)
1847 pango_attr_list_unref (label->attrs);
1848 label->attrs = attrs;
1849
1850 g_object_notify (G_OBJECT (label), "attributes");
1851 }
1852
1853
1854 /* Calculates text, attrs and mnemonic_keyval from
1855 * label, use_underline and use_markup
1856 */
1857 static void
gtk_label_recalculate(GtkLabel * label)1858 gtk_label_recalculate (GtkLabel *label)
1859 {
1860 guint keyval = label->mnemonic_keyval;
1861
1862 if (label->use_markup)
1863 gtk_label_set_markup_internal (label, label->label, label->use_underline);
1864 else if (label->use_underline)
1865 gtk_label_set_uline_text_internal (label, label->label);
1866 else
1867 {
1868 if (!label->pattern_set)
1869 {
1870 if (label->effective_attrs)
1871 pango_attr_list_unref (label->effective_attrs);
1872 label->effective_attrs = NULL;
1873 }
1874 gtk_label_set_text_internal (label, g_strdup (label->label));
1875 }
1876
1877 gtk_label_compose_effective_attrs (label);
1878
1879 if (!label->use_underline)
1880 label->mnemonic_keyval = GDK_VoidSymbol;
1881
1882 if (keyval != label->mnemonic_keyval)
1883 {
1884 gtk_label_setup_mnemonic (label, keyval);
1885 g_object_notify (G_OBJECT (label), "mnemonic-keyval");
1886 }
1887
1888 gtk_label_clear_layout (label);
1889 gtk_label_clear_select_info (label);
1890 gtk_widget_queue_resize (GTK_WIDGET (label));
1891 }
1892
1893 /**
1894 * gtk_label_set_text:
1895 * @label: a #GtkLabel
1896 * @str: The text you want to set
1897 *
1898 * Sets the text within the #GtkLabel widget. It overwrites any text that
1899 * was there before.
1900 *
1901 * This will also clear any previously set mnemonic accelerators.
1902 **/
1903 void
gtk_label_set_text(GtkLabel * label,const gchar * str)1904 gtk_label_set_text (GtkLabel *label,
1905 const gchar *str)
1906 {
1907 g_return_if_fail (GTK_IS_LABEL (label));
1908
1909 g_object_freeze_notify (G_OBJECT (label));
1910
1911 gtk_label_set_label_internal (label, g_strdup (str ? str : ""));
1912 gtk_label_set_use_markup_internal (label, FALSE);
1913 gtk_label_set_use_underline_internal (label, FALSE);
1914
1915 gtk_label_recalculate (label);
1916
1917 g_object_thaw_notify (G_OBJECT (label));
1918 }
1919
1920 /**
1921 * gtk_label_set_attributes:
1922 * @label: a #GtkLabel
1923 * @attrs: a #PangoAttrList
1924 *
1925 * Sets a #PangoAttrList; the attributes in the list are applied to the
1926 * label text.
1927 *
1928 * <note><para>The attributes set with this function will be applied
1929 * and merged with any other attributes previously effected by way
1930 * of the #GtkLabel:use-underline or #GtkLabel:use-markup properties.
1931 * While it is not recommended to mix markup strings with manually set
1932 * attributes, if you must; know that the attributes will be applied
1933 * to the label after the markup string is parsed.</para></note>
1934 **/
1935 void
gtk_label_set_attributes(GtkLabel * label,PangoAttrList * attrs)1936 gtk_label_set_attributes (GtkLabel *label,
1937 PangoAttrList *attrs)
1938 {
1939 g_return_if_fail (GTK_IS_LABEL (label));
1940
1941 gtk_label_set_attributes_internal (label, attrs);
1942
1943 gtk_label_recalculate (label);
1944
1945 gtk_label_clear_layout (label);
1946 gtk_widget_queue_resize (GTK_WIDGET (label));
1947 }
1948
1949 /**
1950 * gtk_label_get_attributes:
1951 * @label: a #GtkLabel
1952 *
1953 * Gets the attribute list that was set on the label using
1954 * gtk_label_set_attributes(), if any. This function does
1955 * not reflect attributes that come from the labels markup
1956 * (see gtk_label_set_markup()). If you want to get the
1957 * effective attributes for the label, use
1958 * pango_layout_get_attribute (gtk_label_get_layout (label)).
1959 *
1960 * Return value: (transfer none): the attribute list, or %NULL
1961 * if none was set.
1962 **/
1963 PangoAttrList *
gtk_label_get_attributes(GtkLabel * label)1964 gtk_label_get_attributes (GtkLabel *label)
1965 {
1966 g_return_val_if_fail (GTK_IS_LABEL (label), NULL);
1967
1968 return label->attrs;
1969 }
1970
1971 /**
1972 * gtk_label_set_label:
1973 * @label: a #GtkLabel
1974 * @str: the new text to set for the label
1975 *
1976 * Sets the text of the label. The label is interpreted as
1977 * including embedded underlines and/or Pango markup depending
1978 * on the values of the #GtkLabel:use-underline" and
1979 * #GtkLabel:use-markup properties.
1980 **/
1981 void
gtk_label_set_label(GtkLabel * label,const gchar * str)1982 gtk_label_set_label (GtkLabel *label,
1983 const gchar *str)
1984 {
1985 g_return_if_fail (GTK_IS_LABEL (label));
1986
1987 g_object_freeze_notify (G_OBJECT (label));
1988
1989 gtk_label_set_label_internal (label, g_strdup (str ? str : ""));
1990 gtk_label_recalculate (label);
1991
1992 g_object_thaw_notify (G_OBJECT (label));
1993 }
1994
1995 /**
1996 * gtk_label_get_label:
1997 * @label: a #GtkLabel
1998 *
1999 * Fetches the text from a label widget including any embedded
2000 * underlines indicating mnemonics and Pango markup. (See
2001 * gtk_label_get_text()).
2002 *
2003 * Return value: the text of the label widget. This string is
2004 * owned by the widget and must not be modified or freed.
2005 **/
2006 const gchar *
gtk_label_get_label(GtkLabel * label)2007 gtk_label_get_label (GtkLabel *label)
2008 {
2009 g_return_val_if_fail (GTK_IS_LABEL (label), NULL);
2010
2011 return label->label;
2012 }
2013
2014 typedef struct
2015 {
2016 GtkLabel *label;
2017 GList *links;
2018 GString *new_str;
2019 GdkColor *link_color;
2020 GdkColor *visited_link_color;
2021 } UriParserData;
2022
2023 static void
start_element_handler(GMarkupParseContext * context,const gchar * element_name,const gchar ** attribute_names,const gchar ** attribute_values,gpointer user_data,GError ** error)2024 start_element_handler (GMarkupParseContext *context,
2025 const gchar *element_name,
2026 const gchar **attribute_names,
2027 const gchar **attribute_values,
2028 gpointer user_data,
2029 GError **error)
2030 {
2031 UriParserData *pdata = user_data;
2032
2033 if (strcmp (element_name, "a") == 0)
2034 {
2035 GtkLabelLink *link;
2036 const gchar *uri = NULL;
2037 const gchar *title = NULL;
2038 gboolean visited = FALSE;
2039 gint line_number;
2040 gint char_number;
2041 gint i;
2042 GdkColor *color = NULL;
2043
2044 g_markup_parse_context_get_position (context, &line_number, &char_number);
2045
2046 for (i = 0; attribute_names[i] != NULL; i++)
2047 {
2048 const gchar *attr = attribute_names[i];
2049
2050 if (strcmp (attr, "href") == 0)
2051 uri = attribute_values[i];
2052 else if (strcmp (attr, "title") == 0)
2053 title = attribute_values[i];
2054 else
2055 {
2056 g_set_error (error,
2057 G_MARKUP_ERROR,
2058 G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
2059 "Attribute '%s' is not allowed on the <a> tag "
2060 "on line %d char %d",
2061 attr, line_number, char_number);
2062 return;
2063 }
2064 }
2065
2066 if (uri == NULL)
2067 {
2068 g_set_error (error,
2069 G_MARKUP_ERROR,
2070 G_MARKUP_ERROR_INVALID_CONTENT,
2071 "Attribute 'href' was missing on the <a> tag "
2072 "on line %d char %d",
2073 line_number, char_number);
2074 return;
2075 }
2076
2077 visited = FALSE;
2078 if (pdata->label->track_links && pdata->label->select_info)
2079 {
2080 GList *l;
2081 for (l = pdata->label->select_info->links; l; l = l->next)
2082 {
2083 link = l->data;
2084 if (strcmp (uri, link->uri) == 0)
2085 {
2086 visited = link->visited;
2087 break;
2088 }
2089 }
2090 }
2091
2092 if (visited)
2093 color = pdata->visited_link_color;
2094 else
2095 color = pdata->link_color;
2096
2097 g_string_append_printf (pdata->new_str,
2098 "<span color=\"#%04x%04x%04x\" underline=\"single\">",
2099 color->red,
2100 color->green,
2101 color->blue);
2102
2103 link = g_new0 (GtkLabelLink, 1);
2104 link->uri = g_strdup (uri);
2105 link->title = g_strdup (title);
2106 link->visited = visited;
2107 pdata->links = g_list_append (pdata->links, link);
2108 }
2109 else
2110 {
2111 gint i;
2112
2113 g_string_append_c (pdata->new_str, '<');
2114 g_string_append (pdata->new_str, element_name);
2115
2116 for (i = 0; attribute_names[i] != NULL; i++)
2117 {
2118 const gchar *attr = attribute_names[i];
2119 const gchar *value = attribute_values[i];
2120 gchar *newvalue;
2121
2122 newvalue = g_markup_escape_text (value, -1);
2123
2124 g_string_append_c (pdata->new_str, ' ');
2125 g_string_append (pdata->new_str, attr);
2126 g_string_append (pdata->new_str, "=\"");
2127 g_string_append (pdata->new_str, newvalue);
2128 g_string_append_c (pdata->new_str, '\"');
2129
2130 g_free (newvalue);
2131 }
2132 g_string_append_c (pdata->new_str, '>');
2133 }
2134 }
2135
2136 static void
end_element_handler(GMarkupParseContext * context,const gchar * element_name,gpointer user_data,GError ** error)2137 end_element_handler (GMarkupParseContext *context,
2138 const gchar *element_name,
2139 gpointer user_data,
2140 GError **error)
2141 {
2142 UriParserData *pdata = user_data;
2143
2144 if (!strcmp (element_name, "a"))
2145 g_string_append (pdata->new_str, "</span>");
2146 else
2147 {
2148 g_string_append (pdata->new_str, "</");
2149 g_string_append (pdata->new_str, element_name);
2150 g_string_append_c (pdata->new_str, '>');
2151 }
2152 }
2153
2154 static void
text_handler(GMarkupParseContext * context,const gchar * text,gsize text_len,gpointer user_data,GError ** error)2155 text_handler (GMarkupParseContext *context,
2156 const gchar *text,
2157 gsize text_len,
2158 gpointer user_data,
2159 GError **error)
2160 {
2161 UriParserData *pdata = user_data;
2162 gchar *newtext;
2163
2164 newtext = g_markup_escape_text (text, text_len);
2165 g_string_append (pdata->new_str, newtext);
2166 g_free (newtext);
2167 }
2168
2169 static const GMarkupParser markup_parser =
2170 {
2171 start_element_handler,
2172 end_element_handler,
2173 text_handler,
2174 NULL,
2175 NULL
2176 };
2177
2178 static gboolean
xml_isspace(gchar c)2179 xml_isspace (gchar c)
2180 {
2181 return (c == ' ' || c == '\t' || c == '\n' || c == '\r');
2182 }
2183
2184 static void
link_free(GtkLabelLink * link)2185 link_free (GtkLabelLink *link)
2186 {
2187 g_free (link->uri);
2188 g_free (link->title);
2189 g_free (link);
2190 }
2191
2192 static void
gtk_label_get_link_colors(GtkWidget * widget,GdkColor ** link_color,GdkColor ** visited_link_color)2193 gtk_label_get_link_colors (GtkWidget *widget,
2194 GdkColor **link_color,
2195 GdkColor **visited_link_color)
2196 {
2197 gtk_widget_ensure_style (widget);
2198 gtk_widget_style_get (widget,
2199 "link-color", link_color,
2200 "visited-link-color", visited_link_color,
2201 NULL);
2202 if (!*link_color)
2203 *link_color = gdk_color_copy (&default_link_color);
2204 if (!*visited_link_color)
2205 *visited_link_color = gdk_color_copy (&default_visited_link_color);
2206 }
2207
2208 static gboolean
parse_uri_markup(GtkLabel * label,const gchar * str,gchar ** new_str,GList ** links,GError ** error)2209 parse_uri_markup (GtkLabel *label,
2210 const gchar *str,
2211 gchar **new_str,
2212 GList **links,
2213 GError **error)
2214 {
2215 GMarkupParseContext *context = NULL;
2216 const gchar *p, *end;
2217 gboolean needs_root = TRUE;
2218 gsize length;
2219 UriParserData pdata;
2220
2221 length = strlen (str);
2222 p = str;
2223 end = str + length;
2224
2225 pdata.label = label;
2226 pdata.links = NULL;
2227 pdata.new_str = g_string_sized_new (length);
2228
2229 gtk_label_get_link_colors (GTK_WIDGET (label), &pdata.link_color, &pdata.visited_link_color);
2230
2231 while (p != end && xml_isspace (*p))
2232 p++;
2233
2234 if (end - p >= 8 && strncmp (p, "<markup>", 8) == 0)
2235 needs_root = FALSE;
2236
2237 context = g_markup_parse_context_new (&markup_parser, 0, &pdata, NULL);
2238
2239 if (needs_root)
2240 {
2241 if (!g_markup_parse_context_parse (context, "<markup>", -1, error))
2242 goto failed;
2243 }
2244
2245 if (!g_markup_parse_context_parse (context, str, length, error))
2246 goto failed;
2247
2248 if (needs_root)
2249 {
2250 if (!g_markup_parse_context_parse (context, "</markup>", -1, error))
2251 goto failed;
2252 }
2253
2254 if (!g_markup_parse_context_end_parse (context, error))
2255 goto failed;
2256
2257 g_markup_parse_context_free (context);
2258
2259 *new_str = g_string_free (pdata.new_str, FALSE);
2260 *links = pdata.links;
2261
2262 gdk_color_free (pdata.link_color);
2263 gdk_color_free (pdata.visited_link_color);
2264
2265 return TRUE;
2266
2267 failed:
2268 g_markup_parse_context_free (context);
2269 g_string_free (pdata.new_str, TRUE);
2270 g_list_foreach (pdata.links, (GFunc)link_free, NULL);
2271 g_list_free (pdata.links);
2272 gdk_color_free (pdata.link_color);
2273 gdk_color_free (pdata.visited_link_color);
2274
2275 return FALSE;
2276 }
2277
2278 static void
gtk_label_ensure_has_tooltip(GtkLabel * label)2279 gtk_label_ensure_has_tooltip (GtkLabel *label)
2280 {
2281 GList *l;
2282 gboolean has_tooltip = FALSE;
2283
2284 for (l = label->select_info->links; l; l = l->next)
2285 {
2286 GtkLabelLink *link = l->data;
2287 if (link->title)
2288 {
2289 has_tooltip = TRUE;
2290 break;
2291 }
2292 }
2293
2294 gtk_widget_set_has_tooltip (GTK_WIDGET (label), has_tooltip);
2295 }
2296
2297 static void
gtk_label_set_markup_internal(GtkLabel * label,const gchar * str,gboolean with_uline)2298 gtk_label_set_markup_internal (GtkLabel *label,
2299 const gchar *str,
2300 gboolean with_uline)
2301 {
2302 GtkLabelPrivate *priv = GTK_LABEL_GET_PRIVATE (label);
2303 gchar *text = NULL;
2304 GError *error = NULL;
2305 PangoAttrList *attrs = NULL;
2306 gunichar accel_char = 0;
2307 gchar *new_str;
2308 GList *links = NULL;
2309
2310 if (!parse_uri_markup (label, str, &new_str, &links, &error))
2311 {
2312 g_warning ("Failed to set text from markup due to error parsing markup: %s",
2313 error->message);
2314 g_error_free (error);
2315 return;
2316 }
2317
2318 gtk_label_clear_links (label);
2319 if (links)
2320 {
2321 gtk_label_ensure_select_info (label);
2322 label->select_info->links = links;
2323 gtk_label_ensure_has_tooltip (label);
2324 }
2325
2326 if (with_uline)
2327 {
2328 gboolean enable_mnemonics;
2329 gboolean auto_mnemonics;
2330
2331 g_object_get (gtk_widget_get_settings (GTK_WIDGET (label)),
2332 "gtk-enable-mnemonics", &enable_mnemonics,
2333 "gtk-auto-mnemonics", &auto_mnemonics,
2334 NULL);
2335
2336 if (!(enable_mnemonics && priv->mnemonics_visible &&
2337 (!auto_mnemonics ||
2338 (gtk_widget_is_sensitive (GTK_WIDGET (label)) &&
2339 (!label->mnemonic_widget ||
2340 gtk_widget_is_sensitive (label->mnemonic_widget))))))
2341 {
2342 gchar *tmp;
2343 gchar *pattern;
2344 guint key;
2345
2346 if (separate_uline_pattern (new_str, &key, &tmp, &pattern))
2347 {
2348 g_free (new_str);
2349 new_str = tmp;
2350 g_free (pattern);
2351 }
2352 }
2353 }
2354
2355 if (!pango_parse_markup (new_str,
2356 -1,
2357 with_uline ? '_' : 0,
2358 &attrs,
2359 &text,
2360 with_uline ? &accel_char : NULL,
2361 &error))
2362 {
2363 g_warning ("Failed to set text from markup due to error parsing markup: %s",
2364 error->message);
2365 g_free (new_str);
2366 g_error_free (error);
2367 return;
2368 }
2369
2370 g_free (new_str);
2371
2372 if (text)
2373 gtk_label_set_text_internal (label, text);
2374
2375 if (attrs)
2376 {
2377 if (label->effective_attrs)
2378 pango_attr_list_unref (label->effective_attrs);
2379 label->effective_attrs = attrs;
2380 }
2381
2382 if (accel_char != 0)
2383 label->mnemonic_keyval = gdk_keyval_to_lower (gdk_unicode_to_keyval (accel_char));
2384 else
2385 label->mnemonic_keyval = GDK_VoidSymbol;
2386 }
2387
2388 /**
2389 * gtk_label_set_markup:
2390 * @label: a #GtkLabel
2391 * @str: a markup string (see <link linkend="PangoMarkupFormat">Pango markup format</link>)
2392 *
2393 * Parses @str which is marked up with the <link
2394 * linkend="PangoMarkupFormat">Pango text markup language</link>, setting the
2395 * label's text and attribute list based on the parse results. If the @str is
2396 * external data, you may need to escape it with g_markup_escape_text() or
2397 * g_markup_printf_escaped()<!-- -->:
2398 * |[
2399 * char *markup;
2400 *
2401 * markup = g_markup_printf_escaped ("<span style=\"italic\">%s</span>", str);
2402 * gtk_label_set_markup (GTK_LABEL (label), markup);
2403 * g_free (markup);
2404 * ]|
2405 **/
2406 void
gtk_label_set_markup(GtkLabel * label,const gchar * str)2407 gtk_label_set_markup (GtkLabel *label,
2408 const gchar *str)
2409 {
2410 g_return_if_fail (GTK_IS_LABEL (label));
2411
2412 g_object_freeze_notify (G_OBJECT (label));
2413
2414 gtk_label_set_label_internal (label, g_strdup (str ? str : ""));
2415 gtk_label_set_use_markup_internal (label, TRUE);
2416 gtk_label_set_use_underline_internal (label, FALSE);
2417
2418 gtk_label_recalculate (label);
2419
2420 g_object_thaw_notify (G_OBJECT (label));
2421 }
2422
2423 /**
2424 * gtk_label_set_markup_with_mnemonic:
2425 * @label: a #GtkLabel
2426 * @str: a markup string (see <link linkend="PangoMarkupFormat">Pango markup format</link>)
2427 *
2428 * Parses @str which is marked up with the <link linkend="PangoMarkupFormat">Pango text markup language</link>,
2429 * setting the label's text and attribute list based on the parse results.
2430 * If characters in @str are preceded by an underscore, they are underlined
2431 * indicating that they represent a keyboard accelerator called a mnemonic.
2432 *
2433 * The mnemonic key can be used to activate another widget, chosen
2434 * automatically, or explicitly using gtk_label_set_mnemonic_widget().
2435 **/
2436 void
gtk_label_set_markup_with_mnemonic(GtkLabel * label,const gchar * str)2437 gtk_label_set_markup_with_mnemonic (GtkLabel *label,
2438 const gchar *str)
2439 {
2440 g_return_if_fail (GTK_IS_LABEL (label));
2441
2442 g_object_freeze_notify (G_OBJECT (label));
2443
2444 gtk_label_set_label_internal (label, g_strdup (str ? str : ""));
2445 gtk_label_set_use_markup_internal (label, TRUE);
2446 gtk_label_set_use_underline_internal (label, TRUE);
2447
2448 gtk_label_recalculate (label);
2449
2450 g_object_thaw_notify (G_OBJECT (label));
2451 }
2452
2453 /**
2454 * gtk_label_get_text:
2455 * @label: a #GtkLabel
2456 *
2457 * Fetches the text from a label widget, as displayed on the
2458 * screen. This does not include any embedded underlines
2459 * indicating mnemonics or Pango markup. (See gtk_label_get_label())
2460 *
2461 * Return value: the text in the label widget. This is the internal
2462 * string used by the label, and must not be modified.
2463 **/
2464 const gchar *
gtk_label_get_text(GtkLabel * label)2465 gtk_label_get_text (GtkLabel *label)
2466 {
2467 g_return_val_if_fail (GTK_IS_LABEL (label), NULL);
2468
2469 return label->text;
2470 }
2471
2472 static PangoAttrList *
gtk_label_pattern_to_attrs(GtkLabel * label,const gchar * pattern)2473 gtk_label_pattern_to_attrs (GtkLabel *label,
2474 const gchar *pattern)
2475 {
2476 const char *start;
2477 const char *p = label->text;
2478 const char *q = pattern;
2479 PangoAttrList *attrs;
2480
2481 attrs = pango_attr_list_new ();
2482
2483 while (1)
2484 {
2485 while (*p && *q && *q != '_')
2486 {
2487 p = g_utf8_next_char (p);
2488 q++;
2489 }
2490 start = p;
2491 while (*p && *q && *q == '_')
2492 {
2493 p = g_utf8_next_char (p);
2494 q++;
2495 }
2496
2497 if (p > start)
2498 {
2499 PangoAttribute *attr = pango_attr_underline_new (PANGO_UNDERLINE_LOW);
2500 attr->start_index = start - label->text;
2501 attr->end_index = p - label->text;
2502
2503 pango_attr_list_insert (attrs, attr);
2504 }
2505 else
2506 break;
2507 }
2508
2509 return attrs;
2510 }
2511
2512 static void
gtk_label_set_pattern_internal(GtkLabel * label,const gchar * pattern,gboolean is_mnemonic)2513 gtk_label_set_pattern_internal (GtkLabel *label,
2514 const gchar *pattern,
2515 gboolean is_mnemonic)
2516 {
2517 GtkLabelPrivate *priv = GTK_LABEL_GET_PRIVATE (label);
2518 PangoAttrList *attrs;
2519 gboolean enable_mnemonics;
2520 gboolean auto_mnemonics;
2521
2522 g_return_if_fail (GTK_IS_LABEL (label));
2523
2524 if (label->pattern_set)
2525 return;
2526
2527 if (is_mnemonic)
2528 {
2529 g_object_get (gtk_widget_get_settings (GTK_WIDGET (label)),
2530 "gtk-enable-mnemonics", &enable_mnemonics,
2531 "gtk-auto-mnemonics", &auto_mnemonics,
2532 NULL);
2533
2534 if (enable_mnemonics && priv->mnemonics_visible && pattern &&
2535 (!auto_mnemonics ||
2536 (gtk_widget_is_sensitive (GTK_WIDGET (label)) &&
2537 (!label->mnemonic_widget ||
2538 gtk_widget_is_sensitive (label->mnemonic_widget)))))
2539 attrs = gtk_label_pattern_to_attrs (label, pattern);
2540 else
2541 attrs = NULL;
2542 }
2543 else
2544 attrs = gtk_label_pattern_to_attrs (label, pattern);
2545
2546 if (label->effective_attrs)
2547 pango_attr_list_unref (label->effective_attrs);
2548 label->effective_attrs = attrs;
2549 }
2550
2551 void
gtk_label_set_pattern(GtkLabel * label,const gchar * pattern)2552 gtk_label_set_pattern (GtkLabel *label,
2553 const gchar *pattern)
2554 {
2555 g_return_if_fail (GTK_IS_LABEL (label));
2556
2557 label->pattern_set = FALSE;
2558
2559 if (pattern)
2560 {
2561 gtk_label_set_pattern_internal (label, pattern, FALSE);
2562 label->pattern_set = TRUE;
2563 }
2564 else
2565 gtk_label_recalculate (label);
2566
2567 gtk_label_clear_layout (label);
2568 gtk_widget_queue_resize (GTK_WIDGET (label));
2569 }
2570
2571
2572 /**
2573 * gtk_label_set_justify:
2574 * @label: a #GtkLabel
2575 * @jtype: a #GtkJustification
2576 *
2577 * Sets the alignment of the lines in the text of the label relative to
2578 * each other. %GTK_JUSTIFY_LEFT is the default value when the
2579 * widget is first created with gtk_label_new(). If you instead want
2580 * to set the alignment of the label as a whole, use
2581 * gtk_misc_set_alignment() instead. gtk_label_set_justify() has no
2582 * effect on labels containing only a single line.
2583 **/
2584 void
gtk_label_set_justify(GtkLabel * label,GtkJustification jtype)2585 gtk_label_set_justify (GtkLabel *label,
2586 GtkJustification jtype)
2587 {
2588 g_return_if_fail (GTK_IS_LABEL (label));
2589 g_return_if_fail (jtype >= GTK_JUSTIFY_LEFT && jtype <= GTK_JUSTIFY_FILL);
2590
2591 if ((GtkJustification) label->jtype != jtype)
2592 {
2593 label->jtype = jtype;
2594
2595 /* No real need to be this drastic, but easier than duplicating the code */
2596 gtk_label_clear_layout (label);
2597
2598 g_object_notify (G_OBJECT (label), "justify");
2599 gtk_widget_queue_resize (GTK_WIDGET (label));
2600 }
2601 }
2602
2603 /**
2604 * gtk_label_get_justify:
2605 * @label: a #GtkLabel
2606 *
2607 * Returns the justification of the label. See gtk_label_set_justify().
2608 *
2609 * Return value: #GtkJustification
2610 **/
2611 GtkJustification
gtk_label_get_justify(GtkLabel * label)2612 gtk_label_get_justify (GtkLabel *label)
2613 {
2614 g_return_val_if_fail (GTK_IS_LABEL (label), 0);
2615
2616 return label->jtype;
2617 }
2618
2619 /**
2620 * gtk_label_set_ellipsize:
2621 * @label: a #GtkLabel
2622 * @mode: a #PangoEllipsizeMode
2623 *
2624 * Sets the mode used to ellipsize (add an ellipsis: "...") to the text
2625 * if there is not enough space to render the entire string.
2626 *
2627 * Since: 2.6
2628 **/
2629 void
gtk_label_set_ellipsize(GtkLabel * label,PangoEllipsizeMode mode)2630 gtk_label_set_ellipsize (GtkLabel *label,
2631 PangoEllipsizeMode mode)
2632 {
2633 g_return_if_fail (GTK_IS_LABEL (label));
2634 g_return_if_fail (mode >= PANGO_ELLIPSIZE_NONE && mode <= PANGO_ELLIPSIZE_END);
2635
2636 if ((PangoEllipsizeMode) label->ellipsize != mode)
2637 {
2638 label->ellipsize = mode;
2639
2640 /* No real need to be this drastic, but easier than duplicating the code */
2641 gtk_label_clear_layout (label);
2642
2643 g_object_notify (G_OBJECT (label), "ellipsize");
2644 gtk_widget_queue_resize (GTK_WIDGET (label));
2645 }
2646 }
2647
2648 /**
2649 * gtk_label_get_ellipsize:
2650 * @label: a #GtkLabel
2651 *
2652 * Returns the ellipsizing position of the label. See gtk_label_set_ellipsize().
2653 *
2654 * Return value: #PangoEllipsizeMode
2655 *
2656 * Since: 2.6
2657 **/
2658 PangoEllipsizeMode
gtk_label_get_ellipsize(GtkLabel * label)2659 gtk_label_get_ellipsize (GtkLabel *label)
2660 {
2661 g_return_val_if_fail (GTK_IS_LABEL (label), PANGO_ELLIPSIZE_NONE);
2662
2663 return label->ellipsize;
2664 }
2665
2666 /**
2667 * gtk_label_set_width_chars:
2668 * @label: a #GtkLabel
2669 * @n_chars: the new desired width, in characters.
2670 *
2671 * Sets the desired width in characters of @label to @n_chars.
2672 *
2673 * Since: 2.6
2674 **/
2675 void
gtk_label_set_width_chars(GtkLabel * label,gint n_chars)2676 gtk_label_set_width_chars (GtkLabel *label,
2677 gint n_chars)
2678 {
2679 GtkLabelPrivate *priv;
2680
2681 g_return_if_fail (GTK_IS_LABEL (label));
2682
2683 priv = GTK_LABEL_GET_PRIVATE (label);
2684
2685 if (priv->width_chars != n_chars)
2686 {
2687 priv->width_chars = n_chars;
2688 g_object_notify (G_OBJECT (label), "width-chars");
2689 gtk_label_invalidate_wrap_width (label);
2690 gtk_widget_queue_resize (GTK_WIDGET (label));
2691 }
2692 }
2693
2694 /**
2695 * gtk_label_get_width_chars:
2696 * @label: a #GtkLabel
2697 *
2698 * Retrieves the desired width of @label, in characters. See
2699 * gtk_label_set_width_chars().
2700 *
2701 * Return value: the width of the label in characters.
2702 *
2703 * Since: 2.6
2704 **/
2705 gint
gtk_label_get_width_chars(GtkLabel * label)2706 gtk_label_get_width_chars (GtkLabel *label)
2707 {
2708 g_return_val_if_fail (GTK_IS_LABEL (label), -1);
2709
2710 return GTK_LABEL_GET_PRIVATE (label)->width_chars;
2711 }
2712
2713 /**
2714 * gtk_label_set_max_width_chars:
2715 * @label: a #GtkLabel
2716 * @n_chars: the new desired maximum width, in characters.
2717 *
2718 * Sets the desired maximum width in characters of @label to @n_chars.
2719 *
2720 * Since: 2.6
2721 **/
2722 void
gtk_label_set_max_width_chars(GtkLabel * label,gint n_chars)2723 gtk_label_set_max_width_chars (GtkLabel *label,
2724 gint n_chars)
2725 {
2726 GtkLabelPrivate *priv;
2727
2728 g_return_if_fail (GTK_IS_LABEL (label));
2729
2730 priv = GTK_LABEL_GET_PRIVATE (label);
2731
2732 if (priv->max_width_chars != n_chars)
2733 {
2734 priv->max_width_chars = n_chars;
2735
2736 g_object_notify (G_OBJECT (label), "max-width-chars");
2737 gtk_label_invalidate_wrap_width (label);
2738 gtk_widget_queue_resize (GTK_WIDGET (label));
2739 }
2740 }
2741
2742 /**
2743 * gtk_label_get_max_width_chars:
2744 * @label: a #GtkLabel
2745 *
2746 * Retrieves the desired maximum width of @label, in characters. See
2747 * gtk_label_set_width_chars().
2748 *
2749 * Return value: the maximum width of the label in characters.
2750 *
2751 * Since: 2.6
2752 **/
2753 gint
gtk_label_get_max_width_chars(GtkLabel * label)2754 gtk_label_get_max_width_chars (GtkLabel *label)
2755 {
2756 g_return_val_if_fail (GTK_IS_LABEL (label), -1);
2757
2758 return GTK_LABEL_GET_PRIVATE (label)->max_width_chars;
2759 }
2760
2761 /**
2762 * gtk_label_set_line_wrap:
2763 * @label: a #GtkLabel
2764 * @wrap: the setting
2765 *
2766 * Toggles line wrapping within the #GtkLabel widget. %TRUE makes it break
2767 * lines if text exceeds the widget's size. %FALSE lets the text get cut off
2768 * by the edge of the widget if it exceeds the widget size.
2769 *
2770 * Note that setting line wrapping to %TRUE does not make the label
2771 * wrap at its parent container's width, because GTK+ widgets
2772 * conceptually can't make their requisition depend on the parent
2773 * container's size. For a label that wraps at a specific position,
2774 * set the label's width using gtk_widget_set_size_request().
2775 **/
2776 void
gtk_label_set_line_wrap(GtkLabel * label,gboolean wrap)2777 gtk_label_set_line_wrap (GtkLabel *label,
2778 gboolean wrap)
2779 {
2780 g_return_if_fail (GTK_IS_LABEL (label));
2781
2782 wrap = wrap != FALSE;
2783
2784 if (label->wrap != wrap)
2785 {
2786 label->wrap = wrap;
2787
2788 gtk_label_clear_layout (label);
2789 gtk_widget_queue_resize (GTK_WIDGET (label));
2790 g_object_notify (G_OBJECT (label), "wrap");
2791 }
2792 }
2793
2794 /**
2795 * gtk_label_get_line_wrap:
2796 * @label: a #GtkLabel
2797 *
2798 * Returns whether lines in the label are automatically wrapped.
2799 * See gtk_label_set_line_wrap().
2800 *
2801 * Return value: %TRUE if the lines of the label are automatically wrapped.
2802 */
2803 gboolean
gtk_label_get_line_wrap(GtkLabel * label)2804 gtk_label_get_line_wrap (GtkLabel *label)
2805 {
2806 g_return_val_if_fail (GTK_IS_LABEL (label), FALSE);
2807
2808 return label->wrap;
2809 }
2810
2811 /**
2812 * gtk_label_set_line_wrap_mode:
2813 * @label: a #GtkLabel
2814 * @wrap_mode: the line wrapping mode
2815 *
2816 * If line wrapping is on (see gtk_label_set_line_wrap()) this controls how
2817 * the line wrapping is done. The default is %PANGO_WRAP_WORD which means
2818 * wrap on word boundaries.
2819 *
2820 * Since: 2.10
2821 **/
2822 void
gtk_label_set_line_wrap_mode(GtkLabel * label,PangoWrapMode wrap_mode)2823 gtk_label_set_line_wrap_mode (GtkLabel *label,
2824 PangoWrapMode wrap_mode)
2825 {
2826 g_return_if_fail (GTK_IS_LABEL (label));
2827
2828 if (label->wrap_mode != wrap_mode)
2829 {
2830 label->wrap_mode = wrap_mode;
2831 g_object_notify (G_OBJECT (label), "wrap-mode");
2832
2833 gtk_widget_queue_resize (GTK_WIDGET (label));
2834 }
2835 }
2836
2837 /**
2838 * gtk_label_get_line_wrap_mode:
2839 * @label: a #GtkLabel
2840 *
2841 * Returns line wrap mode used by the label. See gtk_label_set_line_wrap_mode().
2842 *
2843 * Return value: %TRUE if the lines of the label are automatically wrapped.
2844 *
2845 * Since: 2.10
2846 */
2847 PangoWrapMode
gtk_label_get_line_wrap_mode(GtkLabel * label)2848 gtk_label_get_line_wrap_mode (GtkLabel *label)
2849 {
2850 g_return_val_if_fail (GTK_IS_LABEL (label), FALSE);
2851
2852 return label->wrap_mode;
2853 }
2854
2855
2856 void
gtk_label_get(GtkLabel * label,gchar ** str)2857 gtk_label_get (GtkLabel *label,
2858 gchar **str)
2859 {
2860 g_return_if_fail (GTK_IS_LABEL (label));
2861 g_return_if_fail (str != NULL);
2862
2863 *str = label->text;
2864 }
2865
2866 static void
gtk_label_destroy(GtkObject * object)2867 gtk_label_destroy (GtkObject *object)
2868 {
2869 GtkLabel *label = GTK_LABEL (object);
2870
2871 gtk_label_set_mnemonic_widget (label, NULL);
2872
2873 GTK_OBJECT_CLASS (gtk_label_parent_class)->destroy (object);
2874 }
2875
2876 static void
gtk_label_finalize(GObject * object)2877 gtk_label_finalize (GObject *object)
2878 {
2879 GtkLabel *label = GTK_LABEL (object);
2880
2881 g_free (label->label);
2882 g_free (label->text);
2883
2884 if (label->layout)
2885 g_object_unref (label->layout);
2886
2887 if (label->attrs)
2888 pango_attr_list_unref (label->attrs);
2889
2890 if (label->effective_attrs)
2891 pango_attr_list_unref (label->effective_attrs);
2892
2893 gtk_label_clear_links (label);
2894 g_free (label->select_info);
2895
2896 G_OBJECT_CLASS (gtk_label_parent_class)->finalize (object);
2897 }
2898
2899 static void
gtk_label_clear_layout(GtkLabel * label)2900 gtk_label_clear_layout (GtkLabel *label)
2901 {
2902 if (label->layout)
2903 {
2904 g_object_unref (label->layout);
2905 label->layout = NULL;
2906
2907 //gtk_label_clear_links (label);
2908 }
2909 }
2910
2911 static gint
get_label_char_width(GtkLabel * label)2912 get_label_char_width (GtkLabel *label)
2913 {
2914 GtkLabelPrivate *priv;
2915 PangoContext *context;
2916 PangoFontMetrics *metrics;
2917 gint char_width, digit_width, char_pixels, w;
2918
2919 priv = GTK_LABEL_GET_PRIVATE (label);
2920
2921 context = pango_layout_get_context (label->layout);
2922 metrics = pango_context_get_metrics (context, GTK_WIDGET (label)->style->font_desc,
2923 pango_context_get_language (context));
2924
2925 char_width = pango_font_metrics_get_approximate_char_width (metrics);
2926 digit_width = pango_font_metrics_get_approximate_digit_width (metrics);
2927 char_pixels = MAX (char_width, digit_width);
2928 pango_font_metrics_unref (metrics);
2929
2930 if (priv->width_chars < 0)
2931 {
2932 PangoRectangle rect;
2933
2934 pango_layout_set_width (label->layout, -1);
2935 pango_layout_get_extents (label->layout, NULL, &rect);
2936
2937 w = char_pixels * MAX (priv->max_width_chars, 3);
2938 w = MIN (rect.width, w);
2939 }
2940 else
2941 {
2942 /* enforce minimum width for ellipsized labels at ~3 chars */
2943 w = char_pixels * MAX (priv->width_chars, 3);
2944 }
2945
2946 return w;
2947 }
2948
2949 static void
gtk_label_invalidate_wrap_width(GtkLabel * label)2950 gtk_label_invalidate_wrap_width (GtkLabel *label)
2951 {
2952 GtkLabelPrivate *priv;
2953
2954 priv = GTK_LABEL_GET_PRIVATE (label);
2955
2956 priv->wrap_width = -1;
2957 }
2958
2959 static gint
get_label_wrap_width(GtkLabel * label)2960 get_label_wrap_width (GtkLabel *label)
2961 {
2962 GtkLabelPrivate *priv;
2963
2964 priv = GTK_LABEL_GET_PRIVATE (label);
2965
2966 if (priv->wrap_width < 0)
2967 {
2968 if (priv->width_chars > 0 || priv->max_width_chars > 0)
2969 priv->wrap_width = get_label_char_width (label);
2970 else
2971 {
2972 PangoLayout *layout;
2973
2974 layout = gtk_widget_create_pango_layout (GTK_WIDGET (label),
2975 "This long string gives a good enough length for any line to have.");
2976 pango_layout_get_size (layout, &priv->wrap_width, NULL);
2977 g_object_unref (layout);
2978 }
2979 }
2980
2981 return priv->wrap_width;
2982 }
2983
2984 static void
gtk_label_ensure_layout(GtkLabel * label)2985 gtk_label_ensure_layout (GtkLabel *label)
2986 {
2987 GtkWidget *widget;
2988 PangoRectangle logical_rect;
2989 gboolean rtl;
2990
2991 widget = GTK_WIDGET (label);
2992
2993 rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL;
2994
2995 if (!label->layout)
2996 {
2997 PangoAlignment align = PANGO_ALIGN_LEFT; /* Quiet gcc */
2998 gdouble angle = gtk_label_get_angle (label);
2999
3000 if (angle != 0.0 && !label->wrap && !label->ellipsize && !label->select_info)
3001 {
3002 /* We rotate the standard singleton PangoContext for the widget,
3003 * depending on the fact that it's meant pretty much exclusively
3004 * for our use.
3005 */
3006 PangoMatrix matrix = PANGO_MATRIX_INIT;
3007
3008 pango_matrix_rotate (&matrix, angle);
3009
3010 pango_context_set_matrix (gtk_widget_get_pango_context (widget), &matrix);
3011
3012 label->have_transform = TRUE;
3013 }
3014 else
3015 {
3016 if (label->have_transform)
3017 pango_context_set_matrix (gtk_widget_get_pango_context (widget), NULL);
3018
3019 label->have_transform = FALSE;
3020 }
3021
3022 label->layout = gtk_widget_create_pango_layout (widget, label->text);
3023
3024 if (label->effective_attrs)
3025 pango_layout_set_attributes (label->layout, label->effective_attrs);
3026
3027 gtk_label_rescan_links (label);
3028
3029 switch (label->jtype)
3030 {
3031 case GTK_JUSTIFY_LEFT:
3032 align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
3033 break;
3034 case GTK_JUSTIFY_RIGHT:
3035 align = rtl ? PANGO_ALIGN_LEFT : PANGO_ALIGN_RIGHT;
3036 break;
3037 case GTK_JUSTIFY_CENTER:
3038 align = PANGO_ALIGN_CENTER;
3039 break;
3040 case GTK_JUSTIFY_FILL:
3041 align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
3042 pango_layout_set_justify (label->layout, TRUE);
3043 break;
3044 default:
3045 g_assert_not_reached();
3046 }
3047
3048 pango_layout_set_alignment (label->layout, align);
3049 pango_layout_set_ellipsize (label->layout, label->ellipsize);
3050 pango_layout_set_single_paragraph_mode (label->layout, label->single_line_mode);
3051
3052 if (label->ellipsize)
3053 pango_layout_set_width (label->layout,
3054 widget->allocation.width * PANGO_SCALE);
3055 else if (label->wrap)
3056 {
3057 GtkWidgetAuxInfo *aux_info;
3058 gint longest_paragraph;
3059 gint width, height;
3060
3061 pango_layout_set_wrap (label->layout, label->wrap_mode);
3062
3063 aux_info = _gtk_widget_get_aux_info (widget, FALSE);
3064 if (aux_info && aux_info->width > 0)
3065 pango_layout_set_width (label->layout, aux_info->width * PANGO_SCALE);
3066 else
3067 {
3068 GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (label));
3069 gint wrap_width;
3070
3071 pango_layout_set_width (label->layout, -1);
3072 pango_layout_get_extents (label->layout, NULL, &logical_rect);
3073
3074 width = logical_rect.width;
3075
3076 /* Try to guess a reasonable maximum width */
3077 longest_paragraph = width;
3078
3079 wrap_width = get_label_wrap_width (label);
3080 width = MIN (width, wrap_width);
3081 width = MIN (width,
3082 PANGO_SCALE * (gdk_screen_get_width (screen) + 1) / 2);
3083
3084 pango_layout_set_width (label->layout, width);
3085 pango_layout_get_extents (label->layout, NULL, &logical_rect);
3086 width = logical_rect.width;
3087 height = logical_rect.height;
3088
3089 /* Unfortunately, the above may leave us with a very unbalanced looking paragraph,
3090 * so we try short search for a narrower width that leaves us with the same height
3091 */
3092 if (longest_paragraph > 0)
3093 {
3094 gint nlines, perfect_width;
3095
3096 nlines = pango_layout_get_line_count (label->layout);
3097 perfect_width = (longest_paragraph + nlines - 1) / nlines;
3098
3099 if (perfect_width < width)
3100 {
3101 pango_layout_set_width (label->layout, perfect_width);
3102 pango_layout_get_extents (label->layout, NULL, &logical_rect);
3103
3104 if (logical_rect.height <= height)
3105 width = logical_rect.width;
3106 else
3107 {
3108 gint mid_width = (perfect_width + width) / 2;
3109
3110 if (mid_width > perfect_width)
3111 {
3112 pango_layout_set_width (label->layout, mid_width);
3113 pango_layout_get_extents (label->layout, NULL, &logical_rect);
3114
3115 if (logical_rect.height <= height)
3116 width = logical_rect.width;
3117 }
3118 }
3119 }
3120 }
3121 pango_layout_set_width (label->layout, width);
3122 }
3123 }
3124 else /* !label->wrap */
3125 pango_layout_set_width (label->layout, -1);
3126 }
3127 }
3128
3129 static void
gtk_label_size_request(GtkWidget * widget,GtkRequisition * requisition)3130 gtk_label_size_request (GtkWidget *widget,
3131 GtkRequisition *requisition)
3132 {
3133 GtkLabel *label = GTK_LABEL (widget);
3134 GtkLabelPrivate *priv;
3135 gint width, height;
3136 PangoRectangle logical_rect;
3137 GtkWidgetAuxInfo *aux_info;
3138
3139 priv = GTK_LABEL_GET_PRIVATE (widget);
3140
3141 /*
3142 * If word wrapping is on, then the height requisition can depend
3143 * on:
3144 *
3145 * - Any width set on the widget via gtk_widget_set_size_request().
3146 * - The padding of the widget (xpad, set by gtk_misc_set_padding)
3147 *
3148 * Instead of trying to detect changes to these quantities, if we
3149 * are wrapping, we just rewrap for each size request. Since
3150 * size requisitions are cached by the GTK+ core, this is not
3151 * expensive.
3152 */
3153
3154 if (label->wrap)
3155 gtk_label_clear_layout (label);
3156
3157 gtk_label_ensure_layout (label);
3158
3159 width = label->misc.xpad * 2;
3160 height = label->misc.ypad * 2;
3161
3162 aux_info = _gtk_widget_get_aux_info (widget, FALSE);
3163
3164 if (label->have_transform)
3165 {
3166 PangoRectangle rect;
3167 PangoContext *context = pango_layout_get_context (label->layout);
3168 const PangoMatrix *matrix = pango_context_get_matrix (context);
3169
3170 pango_layout_get_extents (label->layout, NULL, &rect);
3171 pango_matrix_transform_rectangle (matrix, &rect);
3172 pango_extents_to_pixels (&rect, NULL);
3173
3174 requisition->width = width + rect.width;
3175 requisition->height = height + rect.height;
3176
3177 return;
3178 }
3179 else
3180 pango_layout_get_extents (label->layout, NULL, &logical_rect);
3181
3182 if ((label->wrap || label->ellipsize ||
3183 priv->width_chars > 0 || priv->max_width_chars > 0) &&
3184 aux_info && aux_info->width > 0)
3185 width += aux_info->width;
3186 else if (label->ellipsize || priv->width_chars > 0 || priv->max_width_chars > 0)
3187 {
3188 width += PANGO_PIXELS (get_label_char_width (label));
3189 }
3190 else
3191 width += PANGO_PIXELS (logical_rect.width);
3192
3193 if (label->single_line_mode)
3194 {
3195 PangoContext *context;
3196 PangoFontMetrics *metrics;
3197 gint ascent, descent;
3198
3199 context = pango_layout_get_context (label->layout);
3200 metrics = pango_context_get_metrics (context, widget->style->font_desc,
3201 pango_context_get_language (context));
3202
3203 ascent = pango_font_metrics_get_ascent (metrics);
3204 descent = pango_font_metrics_get_descent (metrics);
3205 pango_font_metrics_unref (metrics);
3206
3207 height += PANGO_PIXELS (ascent + descent);
3208 }
3209 else
3210 height += PANGO_PIXELS (logical_rect.height);
3211
3212 requisition->width = width;
3213 requisition->height = height;
3214 }
3215
3216 static void
gtk_label_size_allocate(GtkWidget * widget,GtkAllocation * allocation)3217 gtk_label_size_allocate (GtkWidget *widget,
3218 GtkAllocation *allocation)
3219 {
3220 GtkLabel *label;
3221
3222 label = GTK_LABEL (widget);
3223
3224 GTK_WIDGET_CLASS (gtk_label_parent_class)->size_allocate (widget, allocation);
3225
3226 if (label->ellipsize)
3227 {
3228 if (label->layout)
3229 {
3230 gint width;
3231 PangoRectangle logical;
3232
3233 width = (allocation->width - label->misc.xpad * 2) * PANGO_SCALE;
3234
3235 pango_layout_set_width (label->layout, -1);
3236 pango_layout_get_extents (label->layout, NULL, &logical);
3237
3238 if (logical.width > width)
3239 pango_layout_set_width (label->layout, width);
3240 }
3241 }
3242
3243 if (label->select_info && label->select_info->window)
3244 {
3245 gdk_window_move_resize (label->select_info->window,
3246 allocation->x,
3247 allocation->y,
3248 allocation->width,
3249 allocation->height);
3250 }
3251 }
3252
3253 static void
gtk_label_update_cursor(GtkLabel * label)3254 gtk_label_update_cursor (GtkLabel *label)
3255 {
3256 GtkWidget *widget;
3257
3258 if (!label->select_info)
3259 return;
3260
3261 widget = GTK_WIDGET (label);
3262
3263 if (gtk_widget_get_realized (widget))
3264 {
3265 GdkDisplay *display;
3266 GdkCursor *cursor;
3267
3268 if (gtk_widget_is_sensitive (widget))
3269 {
3270 display = gtk_widget_get_display (widget);
3271
3272 if (label->select_info->active_link)
3273 cursor = gdk_cursor_new_for_display (display, GDK_HAND2);
3274 else if (label->select_info->selectable)
3275 cursor = gdk_cursor_new_for_display (display, GDK_XTERM);
3276 else
3277 cursor = NULL;
3278 }
3279 else
3280 cursor = NULL;
3281
3282 gdk_window_set_cursor (label->select_info->window, cursor);
3283
3284 if (cursor)
3285 gdk_cursor_unref (cursor);
3286 }
3287 }
3288
3289 static void
gtk_label_state_changed(GtkWidget * widget,GtkStateType prev_state)3290 gtk_label_state_changed (GtkWidget *widget,
3291 GtkStateType prev_state)
3292 {
3293 GtkLabel *label = GTK_LABEL (widget);
3294
3295 if (label->select_info)
3296 {
3297 gtk_label_select_region (label, 0, 0);
3298 gtk_label_update_cursor (label);
3299 }
3300
3301 if (GTK_WIDGET_CLASS (gtk_label_parent_class)->state_changed)
3302 GTK_WIDGET_CLASS (gtk_label_parent_class)->state_changed (widget, prev_state);
3303 }
3304
3305 static void
gtk_label_style_set(GtkWidget * widget,GtkStyle * previous_style)3306 gtk_label_style_set (GtkWidget *widget,
3307 GtkStyle *previous_style)
3308 {
3309 GtkLabel *label = GTK_LABEL (widget);
3310
3311 /* We have to clear the layout, fonts etc. may have changed */
3312 gtk_label_clear_layout (label);
3313 gtk_label_invalidate_wrap_width (label);
3314 }
3315
3316 static void
gtk_label_direction_changed(GtkWidget * widget,GtkTextDirection previous_dir)3317 gtk_label_direction_changed (GtkWidget *widget,
3318 GtkTextDirection previous_dir)
3319 {
3320 GtkLabel *label = GTK_LABEL (widget);
3321
3322 if (label->layout)
3323 pango_layout_context_changed (label->layout);
3324
3325 GTK_WIDGET_CLASS (gtk_label_parent_class)->direction_changed (widget, previous_dir);
3326 }
3327
3328 static void
get_layout_location(GtkLabel * label,gint * xp,gint * yp)3329 get_layout_location (GtkLabel *label,
3330 gint *xp,
3331 gint *yp)
3332 {
3333 GtkMisc *misc;
3334 GtkWidget *widget;
3335 GtkLabelPrivate *priv;
3336 gfloat xalign;
3337 gint req_width, x, y;
3338 PangoRectangle logical;
3339
3340 misc = GTK_MISC (label);
3341 widget = GTK_WIDGET (label);
3342 priv = GTK_LABEL_GET_PRIVATE (label);
3343
3344 if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR)
3345 xalign = misc->xalign;
3346 else
3347 xalign = 1.0 - misc->xalign;
3348
3349 pango_layout_get_pixel_extents (label->layout, NULL, &logical);
3350
3351 if (label->ellipsize || priv->width_chars > 0)
3352 {
3353 int width;
3354
3355 width = pango_layout_get_width (label->layout);
3356
3357 req_width = logical.width;
3358 if (width != -1)
3359 req_width = MIN(PANGO_PIXELS (width), req_width);
3360 req_width += 2 * misc->xpad;
3361 }
3362 else
3363 req_width = widget->requisition.width;
3364
3365 x = floor (widget->allocation.x + (gint)misc->xpad +
3366 xalign * (widget->allocation.width - req_width));
3367
3368 if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR)
3369 x = MAX (x, widget->allocation.x + misc->xpad);
3370 else
3371 x = MIN (x, widget->allocation.x + widget->allocation.width - misc->xpad);
3372 x -= logical.x;
3373
3374 /* bgo#315462 - For single-line labels, *do* align the requisition with
3375 * respect to the allocation, even if we are under-allocated. For multi-line
3376 * labels, always show the top of the text when they are under-allocated. The
3377 * rationale is this:
3378 *
3379 * - Single-line labels appear in GtkButtons, and it is very easy to get them
3380 * to be smaller than their requisition. The button may clip the label, but
3381 * the label will still be able to show most of itself and the focus
3382 * rectangle. Also, it is fairly easy to read a single line of clipped text.
3383 *
3384 * - Multi-line labels should not be clipped to showing "something in the
3385 * middle". You want to read the first line, at least, to get some context.
3386 */
3387 if (pango_layout_get_line_count (label->layout) == 1)
3388 y = floor (widget->allocation.y + (gint)misc->ypad
3389 + (widget->allocation.height - widget->requisition.height) * misc->yalign);
3390 else
3391 y = floor (widget->allocation.y + (gint)misc->ypad
3392 + MAX (((widget->allocation.height - widget->requisition.height) * misc->yalign),
3393 0));
3394
3395 if (xp)
3396 *xp = x;
3397
3398 if (yp)
3399 *yp = y;
3400 }
3401
3402 static void
draw_insertion_cursor(GtkLabel * label,GdkRectangle * cursor_location,gboolean is_primary,PangoDirection direction,gboolean draw_arrow)3403 draw_insertion_cursor (GtkLabel *label,
3404 GdkRectangle *cursor_location,
3405 gboolean is_primary,
3406 PangoDirection direction,
3407 gboolean draw_arrow)
3408 {
3409 GtkWidget *widget = GTK_WIDGET (label);
3410 GtkTextDirection text_dir;
3411
3412 if (direction == PANGO_DIRECTION_LTR)
3413 text_dir = GTK_TEXT_DIR_LTR;
3414 else
3415 text_dir = GTK_TEXT_DIR_RTL;
3416
3417 gtk_draw_insertion_cursor (widget, widget->window, &(widget->allocation),
3418 cursor_location,
3419 is_primary, text_dir, draw_arrow);
3420 }
3421
3422 static PangoDirection
get_cursor_direction(GtkLabel * label)3423 get_cursor_direction (GtkLabel *label)
3424 {
3425 GSList *l;
3426
3427 g_assert (label->select_info);
3428
3429 gtk_label_ensure_layout (label);
3430
3431 for (l = pango_layout_get_lines_readonly (label->layout); l; l = l->next)
3432 {
3433 PangoLayoutLine *line = l->data;
3434
3435 /* If label->select_info->selection_end is at the very end of
3436 * the line, we don't know if the cursor is on this line or
3437 * the next without looking ahead at the next line. (End
3438 * of paragraph is different from line break.) But it's
3439 * definitely in this paragraph, which is good enough
3440 * to figure out the resolved direction.
3441 */
3442 if (line->start_index + line->length >= label->select_info->selection_end)
3443 return line->resolved_dir;
3444 }
3445
3446 return PANGO_DIRECTION_LTR;
3447 }
3448
3449 static void
gtk_label_draw_cursor(GtkLabel * label,gint xoffset,gint yoffset)3450 gtk_label_draw_cursor (GtkLabel *label, gint xoffset, gint yoffset)
3451 {
3452 GtkWidget *widget;
3453
3454 if (label->select_info == NULL)
3455 return;
3456
3457 widget = GTK_WIDGET (label);
3458
3459 if (gtk_widget_is_drawable (widget))
3460 {
3461 PangoDirection keymap_direction;
3462 PangoDirection cursor_direction;
3463 PangoRectangle strong_pos, weak_pos;
3464 gboolean split_cursor;
3465 PangoRectangle *cursor1 = NULL;
3466 PangoRectangle *cursor2 = NULL;
3467 GdkRectangle cursor_location;
3468 PangoDirection dir1 = PANGO_DIRECTION_NEUTRAL;
3469 PangoDirection dir2 = PANGO_DIRECTION_NEUTRAL;
3470
3471 keymap_direction = gdk_keymap_get_direction (gdk_keymap_get_for_display (gtk_widget_get_display (widget)));
3472 cursor_direction = get_cursor_direction (label);
3473
3474 gtk_label_ensure_layout (label);
3475
3476 pango_layout_get_cursor_pos (label->layout, label->select_info->selection_end,
3477 &strong_pos, &weak_pos);
3478
3479 g_object_get (gtk_widget_get_settings (widget),
3480 "gtk-split-cursor", &split_cursor,
3481 NULL);
3482
3483 dir1 = cursor_direction;
3484
3485 if (split_cursor)
3486 {
3487 cursor1 = &strong_pos;
3488
3489 if (strong_pos.x != weak_pos.x ||
3490 strong_pos.y != weak_pos.y)
3491 {
3492 dir2 = (cursor_direction == PANGO_DIRECTION_LTR) ? PANGO_DIRECTION_RTL : PANGO_DIRECTION_LTR;
3493 cursor2 = &weak_pos;
3494 }
3495 }
3496 else
3497 {
3498 if (keymap_direction == cursor_direction)
3499 cursor1 = &strong_pos;
3500 else
3501 cursor1 = &weak_pos;
3502 }
3503
3504 cursor_location.x = xoffset + PANGO_PIXELS (cursor1->x);
3505 cursor_location.y = yoffset + PANGO_PIXELS (cursor1->y);
3506 cursor_location.width = 0;
3507 cursor_location.height = PANGO_PIXELS (cursor1->height);
3508
3509 draw_insertion_cursor (label,
3510 &cursor_location, TRUE, dir1,
3511 dir2 != PANGO_DIRECTION_NEUTRAL);
3512
3513 if (dir2 != PANGO_DIRECTION_NEUTRAL)
3514 {
3515 cursor_location.x = xoffset + PANGO_PIXELS (cursor2->x);
3516 cursor_location.y = yoffset + PANGO_PIXELS (cursor2->y);
3517 cursor_location.width = 0;
3518 cursor_location.height = PANGO_PIXELS (cursor2->height);
3519
3520 draw_insertion_cursor (label,
3521 &cursor_location, FALSE, dir2,
3522 TRUE);
3523 }
3524 }
3525 }
3526
3527 static GtkLabelLink *
gtk_label_get_focus_link(GtkLabel * label)3528 gtk_label_get_focus_link (GtkLabel *label)
3529 {
3530 GtkLabelSelectionInfo *info = label->select_info;
3531 GList *l;
3532
3533 if (!info)
3534 return NULL;
3535
3536 if (info->selection_anchor != info->selection_end)
3537 return NULL;
3538
3539 for (l = info->links; l; l = l->next)
3540 {
3541 GtkLabelLink *link = l->data;
3542 if (link->start <= info->selection_anchor &&
3543 info->selection_anchor <= link->end)
3544 return link;
3545 }
3546
3547 return NULL;
3548 }
3549
3550 static gint
gtk_label_expose(GtkWidget * widget,GdkEventExpose * event)3551 gtk_label_expose (GtkWidget *widget,
3552 GdkEventExpose *event)
3553 {
3554 GtkLabel *label = GTK_LABEL (widget);
3555 GtkLabelSelectionInfo *info = label->select_info;
3556 gint x, y;
3557
3558 gtk_label_ensure_layout (label);
3559
3560 if (gtk_widget_get_visible (widget) && gtk_widget_get_mapped (widget) &&
3561 label->text && (*label->text != '\0'))
3562 {
3563 get_layout_location (label, &x, &y);
3564
3565 gtk_paint_layout (widget->style,
3566 widget->window,
3567 gtk_widget_get_state (widget),
3568 FALSE,
3569 &event->area,
3570 widget,
3571 "label",
3572 x, y,
3573 label->layout);
3574
3575 if (info &&
3576 (info->selection_anchor != info->selection_end))
3577 {
3578 gint range[2];
3579 GdkRegion *clip;
3580 GtkStateType state;
3581 cairo_t *cr;
3582
3583 range[0] = info->selection_anchor;
3584 range[1] = info->selection_end;
3585
3586 if (range[0] > range[1])
3587 {
3588 gint tmp = range[0];
3589 range[0] = range[1];
3590 range[1] = tmp;
3591 }
3592
3593 clip = gdk_pango_layout_get_clip_region (label->layout,
3594 x, y,
3595 range,
3596 1);
3597 gdk_region_intersect (clip, event->region);
3598
3599 /* FIXME should use gtk_paint, but it can't use a clip
3600 * region
3601 */
3602
3603 cr = gdk_cairo_create (event->window);
3604
3605 gdk_cairo_region (cr, clip);
3606 cairo_clip (cr);
3607
3608 state = GTK_STATE_SELECTED;
3609 if (!gtk_widget_has_focus (widget))
3610 state = GTK_STATE_ACTIVE;
3611
3612 gdk_cairo_set_source_color (cr, &widget->style->base[state]);
3613 cairo_paint (cr);
3614
3615 gdk_cairo_set_source_color (cr, &widget->style->text[state]);
3616 cairo_move_to (cr, x, y);
3617 _gtk_pango_fill_layout (cr, label->layout);
3618
3619 cairo_destroy (cr);
3620 gdk_region_destroy (clip);
3621 }
3622 else if (info)
3623 {
3624 GtkLabelLink *focus_link;
3625 GtkLabelLink *active_link;
3626 gint range[2];
3627 GdkRegion *clip;
3628 GdkRectangle rect;
3629 GdkColor *text_color;
3630 GdkColor *base_color;
3631 GdkColor *link_color;
3632 GdkColor *visited_link_color;
3633
3634 if (info->selectable && gtk_widget_has_focus (widget))
3635 gtk_label_draw_cursor (label, x, y);
3636
3637 focus_link = gtk_label_get_focus_link (label);
3638 active_link = info->active_link;
3639
3640
3641 if (active_link)
3642 {
3643 cairo_t *cr;
3644
3645 range[0] = active_link->start;
3646 range[1] = active_link->end;
3647
3648 cr = gdk_cairo_create (event->window);
3649
3650 gdk_cairo_region (cr, event->region);
3651 cairo_clip (cr);
3652
3653 clip = gdk_pango_layout_get_clip_region (label->layout,
3654 x, y,
3655 range,
3656 1);
3657 gdk_cairo_region (cr, clip);
3658 cairo_clip (cr);
3659 gdk_region_destroy (clip);
3660
3661 gtk_label_get_link_colors (widget, &link_color, &visited_link_color);
3662 if (active_link->visited)
3663 text_color = visited_link_color;
3664 else
3665 text_color = link_color;
3666 if (info->link_clicked)
3667 base_color = &widget->style->base[GTK_STATE_ACTIVE];
3668 else
3669 base_color = &widget->style->base[GTK_STATE_PRELIGHT];
3670
3671 gdk_cairo_set_source_color (cr, base_color);
3672 cairo_paint (cr);
3673
3674 gdk_cairo_set_source_color (cr, text_color);
3675 cairo_move_to (cr, x, y);
3676 _gtk_pango_fill_layout (cr, label->layout);
3677
3678 gdk_color_free (link_color);
3679 gdk_color_free (visited_link_color);
3680
3681 cairo_destroy (cr);
3682 }
3683
3684 if (focus_link && gtk_widget_has_focus (widget))
3685 {
3686 range[0] = focus_link->start;
3687 range[1] = focus_link->end;
3688
3689 clip = gdk_pango_layout_get_clip_region (label->layout,
3690 x, y,
3691 range,
3692 1);
3693 gdk_region_get_clipbox (clip, &rect);
3694
3695 gtk_paint_focus (widget->style, widget->window, gtk_widget_get_state (widget),
3696 &event->area, widget, "label",
3697 rect.x, rect.y, rect.width, rect.height);
3698
3699 gdk_region_destroy (clip);
3700 }
3701 }
3702 }
3703
3704 return FALSE;
3705 }
3706
3707 static gboolean
separate_uline_pattern(const gchar * str,guint * accel_key,gchar ** new_str,gchar ** pattern)3708 separate_uline_pattern (const gchar *str,
3709 guint *accel_key,
3710 gchar **new_str,
3711 gchar **pattern)
3712 {
3713 gboolean underscore;
3714 const gchar *src;
3715 gchar *dest;
3716 gchar *pattern_dest;
3717
3718 *accel_key = GDK_VoidSymbol;
3719 *new_str = g_new (gchar, strlen (str) + 1);
3720 *pattern = g_new (gchar, g_utf8_strlen (str, -1) + 1);
3721
3722 underscore = FALSE;
3723
3724 src = str;
3725 dest = *new_str;
3726 pattern_dest = *pattern;
3727
3728 while (*src)
3729 {
3730 gunichar c;
3731 const gchar *next_src;
3732
3733 c = g_utf8_get_char (src);
3734 if (c == (gunichar)-1)
3735 {
3736 g_warning ("Invalid input string");
3737 g_free (*new_str);
3738 g_free (*pattern);
3739
3740 return FALSE;
3741 }
3742 next_src = g_utf8_next_char (src);
3743
3744 if (underscore)
3745 {
3746 if (c == '_')
3747 *pattern_dest++ = ' ';
3748 else
3749 {
3750 *pattern_dest++ = '_';
3751 if (*accel_key == GDK_VoidSymbol)
3752 *accel_key = gdk_keyval_to_lower (gdk_unicode_to_keyval (c));
3753 }
3754
3755 while (src < next_src)
3756 *dest++ = *src++;
3757
3758 underscore = FALSE;
3759 }
3760 else
3761 {
3762 if (c == '_')
3763 {
3764 underscore = TRUE;
3765 src = next_src;
3766 }
3767 else
3768 {
3769 while (src < next_src)
3770 *dest++ = *src++;
3771
3772 *pattern_dest++ = ' ';
3773 }
3774 }
3775 }
3776
3777 *dest = 0;
3778 *pattern_dest = 0;
3779
3780 return TRUE;
3781 }
3782
3783 static void
gtk_label_set_uline_text_internal(GtkLabel * label,const gchar * str)3784 gtk_label_set_uline_text_internal (GtkLabel *label,
3785 const gchar *str)
3786 {
3787 guint accel_key = GDK_VoidSymbol;
3788 gchar *new_str;
3789 gchar *pattern;
3790
3791 g_return_if_fail (GTK_IS_LABEL (label));
3792 g_return_if_fail (str != NULL);
3793
3794 /* Split text into the base text and a separate pattern
3795 * of underscores.
3796 */
3797 if (!separate_uline_pattern (str, &accel_key, &new_str, &pattern))
3798 return;
3799
3800 gtk_label_set_text_internal (label, new_str);
3801 gtk_label_set_pattern_internal (label, pattern, TRUE);
3802 label->mnemonic_keyval = accel_key;
3803
3804 g_free (pattern);
3805 }
3806
3807 guint
gtk_label_parse_uline(GtkLabel * label,const gchar * str)3808 gtk_label_parse_uline (GtkLabel *label,
3809 const gchar *str)
3810 {
3811 guint keyval;
3812
3813 g_return_val_if_fail (GTK_IS_LABEL (label), GDK_VoidSymbol);
3814 g_return_val_if_fail (str != NULL, GDK_VoidSymbol);
3815
3816 g_object_freeze_notify (G_OBJECT (label));
3817
3818 gtk_label_set_label_internal (label, g_strdup (str ? str : ""));
3819 gtk_label_set_use_markup_internal (label, FALSE);
3820 gtk_label_set_use_underline_internal (label, TRUE);
3821
3822 gtk_label_recalculate (label);
3823
3824 keyval = label->mnemonic_keyval;
3825 if (keyval != GDK_VoidSymbol)
3826 {
3827 label->mnemonic_keyval = GDK_VoidSymbol;
3828 gtk_label_setup_mnemonic (label, keyval);
3829 g_object_notify (G_OBJECT (label), "mnemonic-keyval");
3830 }
3831
3832 g_object_thaw_notify (G_OBJECT (label));
3833
3834 return keyval;
3835 }
3836
3837 /**
3838 * gtk_label_set_text_with_mnemonic:
3839 * @label: a #GtkLabel
3840 * @str: a string
3841 *
3842 * Sets the label's text from the string @str.
3843 * If characters in @str are preceded by an underscore, they are underlined
3844 * indicating that they represent a keyboard accelerator called a mnemonic.
3845 * The mnemonic key can be used to activate another widget, chosen
3846 * automatically, or explicitly using gtk_label_set_mnemonic_widget().
3847 **/
3848 void
gtk_label_set_text_with_mnemonic(GtkLabel * label,const gchar * str)3849 gtk_label_set_text_with_mnemonic (GtkLabel *label,
3850 const gchar *str)
3851 {
3852 g_return_if_fail (GTK_IS_LABEL (label));
3853 g_return_if_fail (str != NULL);
3854
3855 g_object_freeze_notify (G_OBJECT (label));
3856
3857 gtk_label_set_label_internal (label, g_strdup (str ? str : ""));
3858 gtk_label_set_use_markup_internal (label, FALSE);
3859 gtk_label_set_use_underline_internal (label, TRUE);
3860
3861 gtk_label_recalculate (label);
3862
3863 g_object_thaw_notify (G_OBJECT (label));
3864 }
3865
3866 static void
gtk_label_realize(GtkWidget * widget)3867 gtk_label_realize (GtkWidget *widget)
3868 {
3869 GtkLabel *label;
3870
3871 label = GTK_LABEL (widget);
3872
3873 GTK_WIDGET_CLASS (gtk_label_parent_class)->realize (widget);
3874
3875 if (label->select_info)
3876 gtk_label_create_window (label);
3877 }
3878
3879 static void
gtk_label_unrealize(GtkWidget * widget)3880 gtk_label_unrealize (GtkWidget *widget)
3881 {
3882 GtkLabel *label;
3883
3884 label = GTK_LABEL (widget);
3885
3886 if (label->select_info)
3887 gtk_label_destroy_window (label);
3888
3889 GTK_WIDGET_CLASS (gtk_label_parent_class)->unrealize (widget);
3890 }
3891
3892 static void
gtk_label_map(GtkWidget * widget)3893 gtk_label_map (GtkWidget *widget)
3894 {
3895 GtkLabel *label;
3896
3897 label = GTK_LABEL (widget);
3898
3899 GTK_WIDGET_CLASS (gtk_label_parent_class)->map (widget);
3900
3901 if (label->select_info)
3902 gdk_window_show (label->select_info->window);
3903 }
3904
3905 static void
gtk_label_unmap(GtkWidget * widget)3906 gtk_label_unmap (GtkWidget *widget)
3907 {
3908 GtkLabel *label;
3909
3910 label = GTK_LABEL (widget);
3911
3912 if (label->select_info)
3913 gdk_window_hide (label->select_info->window);
3914
3915 GTK_WIDGET_CLASS (gtk_label_parent_class)->unmap (widget);
3916 }
3917
3918 static void
window_to_layout_coords(GtkLabel * label,gint * x,gint * y)3919 window_to_layout_coords (GtkLabel *label,
3920 gint *x,
3921 gint *y)
3922 {
3923 gint lx, ly;
3924 GtkWidget *widget;
3925
3926 widget = GTK_WIDGET (label);
3927
3928 /* get layout location in widget->window coords */
3929 get_layout_location (label, &lx, &ly);
3930
3931 if (x)
3932 {
3933 *x += widget->allocation.x; /* go to widget->window */
3934 *x -= lx; /* go to layout */
3935 }
3936
3937 if (y)
3938 {
3939 *y += widget->allocation.y; /* go to widget->window */
3940 *y -= ly; /* go to layout */
3941 }
3942 }
3943
3944 #if 0
3945 static void
3946 layout_to_window_coords (GtkLabel *label,
3947 gint *x,
3948 gint *y)
3949 {
3950 gint lx, ly;
3951 GtkWidget *widget;
3952
3953 widget = GTK_WIDGET (label);
3954
3955 /* get layout location in widget->window coords */
3956 get_layout_location (label, &lx, &ly);
3957
3958 if (x)
3959 {
3960 *x += lx; /* go to widget->window */
3961 *x -= widget->allocation.x; /* go to selection window */
3962 }
3963
3964 if (y)
3965 {
3966 *y += ly; /* go to widget->window */
3967 *y -= widget->allocation.y; /* go to selection window */
3968 }
3969 }
3970 #endif
3971
3972 static gboolean
get_layout_index(GtkLabel * label,gint x,gint y,gint * index)3973 get_layout_index (GtkLabel *label,
3974 gint x,
3975 gint y,
3976 gint *index)
3977 {
3978 gint trailing = 0;
3979 const gchar *cluster;
3980 const gchar *cluster_end;
3981 gboolean inside;
3982
3983 *index = 0;
3984
3985 gtk_label_ensure_layout (label);
3986
3987 window_to_layout_coords (label, &x, &y);
3988
3989 x *= PANGO_SCALE;
3990 y *= PANGO_SCALE;
3991
3992 inside = pango_layout_xy_to_index (label->layout,
3993 x, y,
3994 index, &trailing);
3995
3996 cluster = label->text + *index;
3997 cluster_end = cluster;
3998 while (trailing)
3999 {
4000 cluster_end = g_utf8_next_char (cluster_end);
4001 --trailing;
4002 }
4003
4004 *index += (cluster_end - cluster);
4005
4006 return inside;
4007 }
4008
4009 static void
gtk_label_select_word(GtkLabel * label)4010 gtk_label_select_word (GtkLabel *label)
4011 {
4012 gint min, max;
4013
4014 gint start_index = gtk_label_move_backward_word (label, label->select_info->selection_end);
4015 gint end_index = gtk_label_move_forward_word (label, label->select_info->selection_end);
4016
4017 min = MIN (label->select_info->selection_anchor,
4018 label->select_info->selection_end);
4019 max = MAX (label->select_info->selection_anchor,
4020 label->select_info->selection_end);
4021
4022 min = MIN (min, start_index);
4023 max = MAX (max, end_index);
4024
4025 gtk_label_select_region_index (label, min, max);
4026 }
4027
4028 static void
gtk_label_grab_focus(GtkWidget * widget)4029 gtk_label_grab_focus (GtkWidget *widget)
4030 {
4031 GtkLabel *label;
4032 gboolean select_on_focus;
4033 GtkLabelLink *link;
4034
4035 label = GTK_LABEL (widget);
4036
4037 if (label->select_info == NULL)
4038 return;
4039
4040 GTK_WIDGET_CLASS (gtk_label_parent_class)->grab_focus (widget);
4041
4042 if (label->select_info->selectable)
4043 {
4044 g_object_get (gtk_widget_get_settings (widget),
4045 "gtk-label-select-on-focus",
4046 &select_on_focus,
4047 NULL);
4048
4049 if (select_on_focus && !label->in_click)
4050 gtk_label_select_region (label, 0, -1);
4051 }
4052 else
4053 {
4054 if (label->select_info->links && !label->in_click)
4055 {
4056 link = label->select_info->links->data;
4057 label->select_info->selection_anchor = link->start;
4058 label->select_info->selection_end = link->start;
4059 }
4060 }
4061 }
4062
4063 static gboolean
gtk_label_focus(GtkWidget * widget,GtkDirectionType direction)4064 gtk_label_focus (GtkWidget *widget,
4065 GtkDirectionType direction)
4066 {
4067 GtkLabel *label = GTK_LABEL (widget);
4068 GtkLabelSelectionInfo *info = label->select_info;
4069 GtkLabelLink *focus_link;
4070 GList *l;
4071
4072 if (!gtk_widget_is_focus (widget))
4073 {
4074 gtk_widget_grab_focus (widget);
4075 if (info)
4076 {
4077 focus_link = gtk_label_get_focus_link (label);
4078 if (focus_link && direction == GTK_DIR_TAB_BACKWARD)
4079 {
4080 l = g_list_last (info->links);
4081 focus_link = l->data;
4082 info->selection_anchor = focus_link->start;
4083 info->selection_end = focus_link->start;
4084 }
4085 }
4086
4087 return TRUE;
4088 }
4089
4090 if (!info)
4091 return FALSE;
4092
4093 if (info->selectable)
4094 {
4095 gint index;
4096
4097 if (info->selection_anchor != info->selection_end)
4098 goto out;
4099
4100 index = info->selection_anchor;
4101
4102 if (direction == GTK_DIR_TAB_FORWARD)
4103 for (l = info->links; l; l = l->next)
4104 {
4105 GtkLabelLink *link = l->data;
4106
4107 if (link->start > index)
4108 {
4109 gtk_label_select_region_index (label, link->start, link->start);
4110 return TRUE;
4111 }
4112 }
4113 else if (direction == GTK_DIR_TAB_BACKWARD)
4114 for (l = g_list_last (info->links); l; l = l->prev)
4115 {
4116 GtkLabelLink *link = l->data;
4117
4118 if (link->end < index)
4119 {
4120 gtk_label_select_region_index (label, link->start, link->start);
4121 return TRUE;
4122 }
4123 }
4124
4125 goto out;
4126 }
4127 else
4128 {
4129 focus_link = gtk_label_get_focus_link (label);
4130 switch (direction)
4131 {
4132 case GTK_DIR_TAB_FORWARD:
4133 if (focus_link)
4134 {
4135 l = g_list_find (info->links, focus_link);
4136 l = l->next;
4137 }
4138 else
4139 l = info->links;
4140 break;
4141
4142 case GTK_DIR_TAB_BACKWARD:
4143 if (focus_link)
4144 {
4145 l = g_list_find (info->links, focus_link);
4146 l = l->prev;
4147 }
4148 else
4149 l = g_list_last (info->links);
4150 break;
4151
4152 default:
4153 goto out;
4154 }
4155
4156 if (l)
4157 {
4158 focus_link = l->data;
4159 info->selection_anchor = focus_link->start;
4160 info->selection_end = focus_link->start;
4161 gtk_widget_queue_draw (widget);
4162
4163 return TRUE;
4164 }
4165 }
4166
4167 out:
4168
4169 return FALSE;
4170 }
4171
4172 static gboolean
gtk_label_button_press(GtkWidget * widget,GdkEventButton * event)4173 gtk_label_button_press (GtkWidget *widget,
4174 GdkEventButton *event)
4175 {
4176 GtkLabel *label = GTK_LABEL (widget);
4177 GtkLabelSelectionInfo *info = label->select_info;
4178 gint index = 0;
4179 gint min, max;
4180
4181 if (info == NULL)
4182 return FALSE;
4183
4184 if (info->active_link)
4185 {
4186 if (_gtk_button_event_triggers_context_menu (event))
4187 {
4188 info->link_clicked = 1;
4189 gtk_label_do_popup (label, event);
4190 return TRUE;
4191 }
4192 else if (event->button == 1)
4193 {
4194 info->link_clicked = 1;
4195 gtk_widget_queue_draw (widget);
4196 }
4197 }
4198
4199 if (!info->selectable)
4200 return FALSE;
4201
4202 info->in_drag = FALSE;
4203 info->select_words = FALSE;
4204
4205 if (_gtk_button_event_triggers_context_menu (event))
4206 {
4207 gtk_label_do_popup (label, event);
4208
4209 return TRUE;
4210 }
4211 else if (event->button == 1)
4212 {
4213 if (!gtk_widget_has_focus (widget))
4214 {
4215 label->in_click = TRUE;
4216 gtk_widget_grab_focus (widget);
4217 label->in_click = FALSE;
4218 }
4219
4220 if (event->type == GDK_3BUTTON_PRESS)
4221 {
4222 gtk_label_select_region_index (label, 0, strlen (label->text));
4223 return TRUE;
4224 }
4225
4226 if (event->type == GDK_2BUTTON_PRESS)
4227 {
4228 info->select_words = TRUE;
4229 gtk_label_select_word (label);
4230 return TRUE;
4231 }
4232
4233 get_layout_index (label, event->x, event->y, &index);
4234
4235 min = MIN (info->selection_anchor, info->selection_end);
4236 max = MAX (info->selection_anchor, info->selection_end);
4237
4238 if ((info->selection_anchor != info->selection_end) &&
4239 (event->state & GDK_SHIFT_MASK))
4240 {
4241 /* extend (same as motion) */
4242 min = MIN (min, index);
4243 max = MAX (max, index);
4244
4245 /* ensure the anchor is opposite index */
4246 if (index == min)
4247 {
4248 gint tmp = min;
4249 min = max;
4250 max = tmp;
4251 }
4252
4253 gtk_label_select_region_index (label, min, max);
4254 }
4255 else
4256 {
4257 if (event->type == GDK_3BUTTON_PRESS)
4258 gtk_label_select_region_index (label, 0, strlen (label->text));
4259 else if (event->type == GDK_2BUTTON_PRESS)
4260 gtk_label_select_word (label);
4261 else if (min < max && min <= index && index <= max)
4262 {
4263 info->in_drag = TRUE;
4264 info->drag_start_x = event->x;
4265 info->drag_start_y = event->y;
4266 }
4267 else
4268 /* start a replacement */
4269 gtk_label_select_region_index (label, index, index);
4270 }
4271
4272 return TRUE;
4273 }
4274
4275 return FALSE;
4276 }
4277
4278 static gboolean
gtk_label_button_release(GtkWidget * widget,GdkEventButton * event)4279 gtk_label_button_release (GtkWidget *widget,
4280 GdkEventButton *event)
4281
4282 {
4283 GtkLabel *label = GTK_LABEL (widget);
4284 GtkLabelSelectionInfo *info = label->select_info;
4285 gint index;
4286
4287 if (info == NULL)
4288 return FALSE;
4289
4290 if (info->in_drag)
4291 {
4292 info->in_drag = 0;
4293
4294 get_layout_index (label, event->x, event->y, &index);
4295 gtk_label_select_region_index (label, index, index);
4296
4297 return FALSE;
4298 }
4299
4300 if (event->button != 1)
4301 return FALSE;
4302
4303 if (info->active_link &&
4304 info->selection_anchor == info->selection_end &&
4305 info->link_clicked)
4306 {
4307 emit_activate_link (label, info->active_link);
4308 info->link_clicked = 0;
4309
4310 return TRUE;
4311 }
4312
4313 /* The goal here is to return TRUE iff we ate the
4314 * button press to start selecting.
4315 */
4316 return TRUE;
4317 }
4318
4319 static void
connect_mnemonics_visible_notify(GtkLabel * label)4320 connect_mnemonics_visible_notify (GtkLabel *label)
4321 {
4322 GtkLabelPrivate *priv = GTK_LABEL_GET_PRIVATE (label);
4323 GtkWidget *toplevel;
4324 gboolean connected;
4325
4326 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (label));
4327
4328 if (!GTK_IS_WINDOW (toplevel))
4329 return;
4330
4331 /* always set up this widgets initial value */
4332 priv->mnemonics_visible =
4333 gtk_window_get_mnemonics_visible (GTK_WINDOW (toplevel));
4334
4335 connected =
4336 GPOINTER_TO_INT (g_object_get_data (G_OBJECT (toplevel),
4337 "gtk-label-mnemonics-visible-connected"));
4338
4339 if (!connected)
4340 {
4341 g_signal_connect (toplevel,
4342 "notify::mnemonics-visible",
4343 G_CALLBACK (label_mnemonics_visible_changed),
4344 label);
4345 g_object_set_data (G_OBJECT (toplevel),
4346 "gtk-label-mnemonics-visible-connected",
4347 GINT_TO_POINTER (1));
4348 }
4349 }
4350
4351 static void
drag_begin_cb(GtkWidget * widget,GdkDragContext * context,gpointer data)4352 drag_begin_cb (GtkWidget *widget,
4353 GdkDragContext *context,
4354 gpointer data)
4355 {
4356 GtkLabel *label;
4357 GdkPixmap *pixmap = NULL;
4358
4359 g_signal_handlers_disconnect_by_func (widget, drag_begin_cb, NULL);
4360
4361 label = GTK_LABEL (widget);
4362
4363 if ((label->select_info->selection_anchor !=
4364 label->select_info->selection_end) &&
4365 label->text)
4366 {
4367 gint start, end;
4368 gint len;
4369
4370 start = MIN (label->select_info->selection_anchor,
4371 label->select_info->selection_end);
4372 end = MAX (label->select_info->selection_anchor,
4373 label->select_info->selection_end);
4374
4375 len = strlen (label->text);
4376
4377 if (end > len)
4378 end = len;
4379
4380 if (start > len)
4381 start = len;
4382
4383 pixmap = _gtk_text_util_create_drag_icon (widget,
4384 label->text + start,
4385 end - start);
4386 }
4387
4388 if (pixmap)
4389 gtk_drag_set_icon_pixmap (context,
4390 gdk_drawable_get_colormap (pixmap),
4391 pixmap,
4392 NULL,
4393 -2, -2);
4394 else
4395 gtk_drag_set_icon_default (context);
4396
4397 if (pixmap)
4398 g_object_unref (pixmap);
4399 }
4400
4401 static gboolean
gtk_label_motion(GtkWidget * widget,GdkEventMotion * event)4402 gtk_label_motion (GtkWidget *widget,
4403 GdkEventMotion *event)
4404 {
4405 GtkLabel *label = GTK_LABEL (widget);
4406 GtkLabelSelectionInfo *info = label->select_info;
4407 gint index;
4408 gint x, y;
4409
4410 if (info == NULL)
4411 return FALSE;
4412
4413 if (info->links && !info->in_drag)
4414 {
4415 GList *l;
4416 GtkLabelLink *link;
4417 gboolean found = FALSE;
4418
4419 if (info->selection_anchor == info->selection_end)
4420 {
4421 gdk_window_get_pointer (event->window, &x, &y, NULL);
4422 if (get_layout_index (label, x, y, &index))
4423 {
4424 for (l = info->links; l != NULL; l = l->next)
4425 {
4426 link = l->data;
4427 if (index >= link->start && index <= link->end)
4428 {
4429 found = TRUE;
4430 break;
4431 }
4432 }
4433 }
4434 }
4435
4436 if (found)
4437 {
4438 if (info->active_link != link)
4439 {
4440 info->link_clicked = 0;
4441 info->active_link = link;
4442 gtk_label_update_cursor (label);
4443 gtk_widget_queue_draw (widget);
4444 }
4445 }
4446 else
4447 {
4448 if (info->active_link != NULL)
4449 {
4450 info->link_clicked = 0;
4451 info->active_link = NULL;
4452 gtk_label_update_cursor (label);
4453 gtk_widget_queue_draw (widget);
4454 }
4455 }
4456 }
4457
4458 if (!info->selectable)
4459 return FALSE;
4460
4461 if ((event->state & GDK_BUTTON1_MASK) == 0)
4462 return FALSE;
4463
4464 gdk_window_get_pointer (info->window, &x, &y, NULL);
4465
4466 if (info->in_drag)
4467 {
4468 if (gtk_drag_check_threshold (widget,
4469 info->drag_start_x,
4470 info->drag_start_y,
4471 event->x, event->y))
4472 {
4473 GtkTargetList *target_list = gtk_target_list_new (NULL, 0);
4474
4475 gtk_target_list_add_text_targets (target_list, 0);
4476
4477 g_signal_connect (widget, "drag-begin",
4478 G_CALLBACK (drag_begin_cb), NULL);
4479 gtk_drag_begin (widget, target_list,
4480 GDK_ACTION_COPY,
4481 1, (GdkEvent *)event);
4482
4483 info->in_drag = FALSE;
4484
4485 gtk_target_list_unref (target_list);
4486 }
4487 }
4488 else
4489 {
4490 get_layout_index (label, x, y, &index);
4491
4492 if (info->select_words)
4493 {
4494 gint min, max;
4495 gint old_min, old_max;
4496 gint anchor, end;
4497
4498 min = gtk_label_move_backward_word (label, index);
4499 max = gtk_label_move_forward_word (label, index);
4500
4501 anchor = info->selection_anchor;
4502 end = info->selection_end;
4503
4504 old_min = MIN (anchor, end);
4505 old_max = MAX (anchor, end);
4506
4507 if (min < old_min)
4508 {
4509 anchor = min;
4510 end = old_max;
4511 }
4512 else if (old_max < max)
4513 {
4514 anchor = max;
4515 end = old_min;
4516 }
4517 else if (anchor == old_min)
4518 {
4519 if (anchor != min)
4520 anchor = max;
4521 }
4522 else
4523 {
4524 if (anchor != max)
4525 anchor = min;
4526 }
4527
4528 gtk_label_select_region_index (label, anchor, end);
4529 }
4530 else
4531 gtk_label_select_region_index (label, info->selection_anchor, index);
4532 }
4533
4534 return TRUE;
4535 }
4536
4537 static gboolean
gtk_label_leave_notify(GtkWidget * widget,GdkEventCrossing * event)4538 gtk_label_leave_notify (GtkWidget *widget,
4539 GdkEventCrossing *event)
4540 {
4541 GtkLabel *label = GTK_LABEL (widget);
4542
4543 if (label->select_info)
4544 {
4545 label->select_info->active_link = NULL;
4546 gtk_label_update_cursor (label);
4547 gtk_widget_queue_draw (widget);
4548 }
4549
4550 if (GTK_WIDGET_CLASS (gtk_label_parent_class)->leave_notify_event)
4551 return GTK_WIDGET_CLASS (gtk_label_parent_class)->leave_notify_event (widget, event);
4552
4553 return FALSE;
4554 }
4555
4556 static void
gtk_label_create_window(GtkLabel * label)4557 gtk_label_create_window (GtkLabel *label)
4558 {
4559 GtkWidget *widget;
4560 GdkWindowAttr attributes;
4561 gint attributes_mask;
4562
4563 g_assert (label->select_info);
4564 widget = GTK_WIDGET (label);
4565 g_assert (gtk_widget_get_realized (widget));
4566
4567 if (label->select_info->window)
4568 return;
4569
4570 attributes.x = widget->allocation.x;
4571 attributes.y = widget->allocation.y;
4572 attributes.width = widget->allocation.width;
4573 attributes.height = widget->allocation.height;
4574 attributes.window_type = GDK_WINDOW_CHILD;
4575 attributes.wclass = GDK_INPUT_ONLY;
4576 attributes.override_redirect = TRUE;
4577 attributes.event_mask = gtk_widget_get_events (widget) |
4578 GDK_BUTTON_PRESS_MASK |
4579 GDK_BUTTON_RELEASE_MASK |
4580 GDK_LEAVE_NOTIFY_MASK |
4581 GDK_BUTTON_MOTION_MASK |
4582 GDK_POINTER_MOTION_MASK |
4583 GDK_POINTER_MOTION_HINT_MASK;
4584 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_NOREDIR;
4585 if (gtk_widget_is_sensitive (widget))
4586 {
4587 attributes.cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget),
4588 GDK_XTERM);
4589 attributes_mask |= GDK_WA_CURSOR;
4590 }
4591
4592
4593 label->select_info->window = gdk_window_new (widget->window,
4594 &attributes, attributes_mask);
4595 gdk_window_set_user_data (label->select_info->window, widget);
4596
4597 if (attributes_mask & GDK_WA_CURSOR)
4598 gdk_cursor_unref (attributes.cursor);
4599 }
4600
4601 static void
gtk_label_destroy_window(GtkLabel * label)4602 gtk_label_destroy_window (GtkLabel *label)
4603 {
4604 g_assert (label->select_info);
4605
4606 if (label->select_info->window == NULL)
4607 return;
4608
4609 gdk_window_set_user_data (label->select_info->window, NULL);
4610 gdk_window_destroy (label->select_info->window);
4611 label->select_info->window = NULL;
4612 }
4613
4614 static void
gtk_label_ensure_select_info(GtkLabel * label)4615 gtk_label_ensure_select_info (GtkLabel *label)
4616 {
4617 if (label->select_info == NULL)
4618 {
4619 label->select_info = g_new0 (GtkLabelSelectionInfo, 1);
4620
4621 gtk_widget_set_can_focus (GTK_WIDGET (label), TRUE);
4622
4623 if (gtk_widget_get_realized (GTK_WIDGET (label)))
4624 gtk_label_create_window (label);
4625
4626 if (gtk_widget_get_mapped (GTK_WIDGET (label)))
4627 gdk_window_show (label->select_info->window);
4628 }
4629 }
4630
4631 static void
gtk_label_clear_select_info(GtkLabel * label)4632 gtk_label_clear_select_info (GtkLabel *label)
4633 {
4634 if (label->select_info == NULL)
4635 return;
4636
4637 if (!label->select_info->selectable && !label->select_info->links)
4638 {
4639 gtk_label_destroy_window (label);
4640
4641 g_free (label->select_info);
4642 label->select_info = NULL;
4643
4644 gtk_widget_set_can_focus (GTK_WIDGET (label), FALSE);
4645 }
4646 }
4647
4648 /**
4649 * gtk_label_set_selectable:
4650 * @label: a #GtkLabel
4651 * @setting: %TRUE to allow selecting text in the label
4652 *
4653 * Selectable labels allow the user to select text from the label, for
4654 * copy-and-paste.
4655 **/
4656 void
gtk_label_set_selectable(GtkLabel * label,gboolean setting)4657 gtk_label_set_selectable (GtkLabel *label,
4658 gboolean setting)
4659 {
4660 gboolean old_setting;
4661
4662 g_return_if_fail (GTK_IS_LABEL (label));
4663
4664 setting = setting != FALSE;
4665 old_setting = label->select_info && label->select_info->selectable;
4666
4667 if (setting)
4668 {
4669 gtk_label_ensure_select_info (label);
4670 label->select_info->selectable = TRUE;
4671 gtk_label_update_cursor (label);
4672 }
4673 else
4674 {
4675 if (old_setting)
4676 {
4677 /* unselect, to give up the selection */
4678 gtk_label_select_region (label, 0, 0);
4679
4680 label->select_info->selectable = FALSE;
4681 gtk_label_clear_select_info (label);
4682 gtk_label_update_cursor (label);
4683 }
4684 }
4685 if (setting != old_setting)
4686 {
4687 g_object_freeze_notify (G_OBJECT (label));
4688 g_object_notify (G_OBJECT (label), "selectable");
4689 g_object_notify (G_OBJECT (label), "cursor-position");
4690 g_object_notify (G_OBJECT (label), "selection-bound");
4691 g_object_thaw_notify (G_OBJECT (label));
4692 gtk_widget_queue_draw (GTK_WIDGET (label));
4693 }
4694 }
4695
4696 /**
4697 * gtk_label_get_selectable:
4698 * @label: a #GtkLabel
4699 *
4700 * Gets the value set by gtk_label_set_selectable().
4701 *
4702 * Return value: %TRUE if the user can copy text from the label
4703 **/
4704 gboolean
gtk_label_get_selectable(GtkLabel * label)4705 gtk_label_get_selectable (GtkLabel *label)
4706 {
4707 g_return_val_if_fail (GTK_IS_LABEL (label), FALSE);
4708
4709 return label->select_info && label->select_info->selectable;
4710 }
4711
4712 static void
free_angle(gpointer angle)4713 free_angle (gpointer angle)
4714 {
4715 g_slice_free (gdouble, angle);
4716 }
4717
4718 /**
4719 * gtk_label_set_angle:
4720 * @label: a #GtkLabel
4721 * @angle: the angle that the baseline of the label makes with
4722 * the horizontal, in degrees, measured counterclockwise
4723 *
4724 * Sets the angle of rotation for the label. An angle of 90 reads from
4725 * from bottom to top, an angle of 270, from top to bottom. The angle
4726 * setting for the label is ignored if the label is selectable,
4727 * wrapped, or ellipsized.
4728 *
4729 * Since: 2.6
4730 **/
4731 void
gtk_label_set_angle(GtkLabel * label,gdouble angle)4732 gtk_label_set_angle (GtkLabel *label,
4733 gdouble angle)
4734 {
4735 gdouble *label_angle;
4736
4737 g_return_if_fail (GTK_IS_LABEL (label));
4738
4739 label_angle = (gdouble *)g_object_get_qdata (G_OBJECT (label), quark_angle);
4740
4741 if (!label_angle)
4742 {
4743 label_angle = g_slice_new (gdouble);
4744 *label_angle = 0.0;
4745 g_object_set_qdata_full (G_OBJECT (label), quark_angle,
4746 label_angle, free_angle);
4747 }
4748
4749 /* Canonicalize to [0,360]. We don't canonicalize 360 to 0, because
4750 * double property ranges are inclusive, and changing 360 to 0 would
4751 * make a property editor behave strangely.
4752 */
4753 if (angle < 0 || angle > 360.0)
4754 angle = angle - 360. * floor (angle / 360.);
4755
4756 if (*label_angle != angle)
4757 {
4758 *label_angle = angle;
4759
4760 gtk_label_clear_layout (label);
4761 gtk_widget_queue_resize (GTK_WIDGET (label));
4762
4763 g_object_notify (G_OBJECT (label), "angle");
4764 }
4765 }
4766
4767 /**
4768 * gtk_label_get_angle:
4769 * @label: a #GtkLabel
4770 *
4771 * Gets the angle of rotation for the label. See
4772 * gtk_label_set_angle().
4773 *
4774 * Return value: the angle of rotation for the label
4775 *
4776 * Since: 2.6
4777 **/
4778 gdouble
gtk_label_get_angle(GtkLabel * label)4779 gtk_label_get_angle (GtkLabel *label)
4780 {
4781 gdouble *angle;
4782
4783 g_return_val_if_fail (GTK_IS_LABEL (label), 0.0);
4784
4785 angle = (gdouble *)g_object_get_qdata (G_OBJECT (label), quark_angle);
4786
4787 if (angle)
4788 return *angle;
4789 else
4790 return 0.0;
4791 }
4792
4793 static void
gtk_label_set_selection_text(GtkLabel * label,GtkSelectionData * selection_data)4794 gtk_label_set_selection_text (GtkLabel *label,
4795 GtkSelectionData *selection_data)
4796 {
4797 if ((label->select_info->selection_anchor !=
4798 label->select_info->selection_end) &&
4799 label->text)
4800 {
4801 gint start, end;
4802 gint len;
4803
4804 start = MIN (label->select_info->selection_anchor,
4805 label->select_info->selection_end);
4806 end = MAX (label->select_info->selection_anchor,
4807 label->select_info->selection_end);
4808
4809 len = strlen (label->text);
4810
4811 if (end > len)
4812 end = len;
4813
4814 if (start > len)
4815 start = len;
4816
4817 gtk_selection_data_set_text (selection_data,
4818 label->text + start,
4819 end - start);
4820 }
4821 }
4822
4823 static void
gtk_label_drag_data_get(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection_data,guint info,guint time)4824 gtk_label_drag_data_get (GtkWidget *widget,
4825 GdkDragContext *context,
4826 GtkSelectionData *selection_data,
4827 guint info,
4828 guint time)
4829 {
4830 gtk_label_set_selection_text (GTK_LABEL (widget), selection_data);
4831 }
4832
4833 static void
get_text_callback(GtkClipboard * clipboard,GtkSelectionData * selection_data,guint info,gpointer user_data_or_owner)4834 get_text_callback (GtkClipboard *clipboard,
4835 GtkSelectionData *selection_data,
4836 guint info,
4837 gpointer user_data_or_owner)
4838 {
4839 gtk_label_set_selection_text (GTK_LABEL (user_data_or_owner), selection_data);
4840 }
4841
4842 static void
clear_text_callback(GtkClipboard * clipboard,gpointer user_data_or_owner)4843 clear_text_callback (GtkClipboard *clipboard,
4844 gpointer user_data_or_owner)
4845 {
4846 GtkLabel *label;
4847
4848 label = GTK_LABEL (user_data_or_owner);
4849
4850 if (label->select_info)
4851 {
4852 label->select_info->selection_anchor = label->select_info->selection_end;
4853
4854 gtk_widget_queue_draw (GTK_WIDGET (label));
4855 }
4856 }
4857
4858 static void
gtk_label_select_region_index(GtkLabel * label,gint anchor_index,gint end_index)4859 gtk_label_select_region_index (GtkLabel *label,
4860 gint anchor_index,
4861 gint end_index)
4862 {
4863 g_return_if_fail (GTK_IS_LABEL (label));
4864
4865 if (label->select_info && label->select_info->selectable)
4866 {
4867 GtkClipboard *clipboard;
4868
4869 if (label->select_info->selection_anchor == anchor_index &&
4870 label->select_info->selection_end == end_index)
4871 return;
4872
4873 label->select_info->selection_anchor = anchor_index;
4874 label->select_info->selection_end = end_index;
4875
4876 clipboard = gtk_widget_get_clipboard (GTK_WIDGET (label),
4877 GDK_SELECTION_PRIMARY);
4878
4879 if (anchor_index != end_index)
4880 {
4881 GtkTargetList *list;
4882 GtkTargetEntry *targets;
4883 gint n_targets;
4884
4885 list = gtk_target_list_new (NULL, 0);
4886 gtk_target_list_add_text_targets (list, 0);
4887 targets = gtk_target_table_new_from_list (list, &n_targets);
4888
4889 gtk_clipboard_set_with_owner (clipboard,
4890 targets, n_targets,
4891 get_text_callback,
4892 clear_text_callback,
4893 G_OBJECT (label));
4894
4895 gtk_target_table_free (targets, n_targets);
4896 gtk_target_list_unref (list);
4897 }
4898 else
4899 {
4900 if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (label))
4901 gtk_clipboard_clear (clipboard);
4902 }
4903
4904 gtk_widget_queue_draw (GTK_WIDGET (label));
4905
4906 g_object_freeze_notify (G_OBJECT (label));
4907 g_object_notify (G_OBJECT (label), "cursor-position");
4908 g_object_notify (G_OBJECT (label), "selection-bound");
4909 g_object_thaw_notify (G_OBJECT (label));
4910 }
4911 }
4912
4913 /**
4914 * gtk_label_select_region:
4915 * @label: a #GtkLabel
4916 * @start_offset: start offset (in characters not bytes)
4917 * @end_offset: end offset (in characters not bytes)
4918 *
4919 * Selects a range of characters in the label, if the label is selectable.
4920 * See gtk_label_set_selectable(). If the label is not selectable,
4921 * this function has no effect. If @start_offset or
4922 * @end_offset are -1, then the end of the label will be substituted.
4923 **/
4924 void
gtk_label_select_region(GtkLabel * label,gint start_offset,gint end_offset)4925 gtk_label_select_region (GtkLabel *label,
4926 gint start_offset,
4927 gint end_offset)
4928 {
4929 g_return_if_fail (GTK_IS_LABEL (label));
4930
4931 if (label->text && label->select_info)
4932 {
4933 if (start_offset < 0)
4934 start_offset = g_utf8_strlen (label->text, -1);
4935
4936 if (end_offset < 0)
4937 end_offset = g_utf8_strlen (label->text, -1);
4938
4939 gtk_label_select_region_index (label,
4940 g_utf8_offset_to_pointer (label->text, start_offset) - label->text,
4941 g_utf8_offset_to_pointer (label->text, end_offset) - label->text);
4942 }
4943 }
4944
4945 /**
4946 * gtk_label_get_selection_bounds:
4947 * @label: a #GtkLabel
4948 * @start: (out): return location for start of selection, as a character offset
4949 * @end: (out): return location for end of selection, as a character offset
4950 *
4951 * Gets the selected range of characters in the label, returning %TRUE
4952 * if there's a selection.
4953 *
4954 * Return value: %TRUE if selection is non-empty
4955 **/
4956 gboolean
gtk_label_get_selection_bounds(GtkLabel * label,gint * start,gint * end)4957 gtk_label_get_selection_bounds (GtkLabel *label,
4958 gint *start,
4959 gint *end)
4960 {
4961 g_return_val_if_fail (GTK_IS_LABEL (label), FALSE);
4962
4963 if (label->select_info == NULL)
4964 {
4965 /* not a selectable label */
4966 if (start)
4967 *start = 0;
4968 if (end)
4969 *end = 0;
4970
4971 return FALSE;
4972 }
4973 else
4974 {
4975 gint start_index, end_index;
4976 gint start_offset, end_offset;
4977 gint len;
4978
4979 start_index = MIN (label->select_info->selection_anchor,
4980 label->select_info->selection_end);
4981 end_index = MAX (label->select_info->selection_anchor,
4982 label->select_info->selection_end);
4983
4984 len = strlen (label->text);
4985
4986 if (end_index > len)
4987 end_index = len;
4988
4989 if (start_index > len)
4990 start_index = len;
4991
4992 start_offset = g_utf8_strlen (label->text, start_index);
4993 end_offset = g_utf8_strlen (label->text, end_index);
4994
4995 if (start_offset > end_offset)
4996 {
4997 gint tmp = start_offset;
4998 start_offset = end_offset;
4999 end_offset = tmp;
5000 }
5001
5002 if (start)
5003 *start = start_offset;
5004
5005 if (end)
5006 *end = end_offset;
5007
5008 return start_offset != end_offset;
5009 }
5010 }
5011
5012
5013 /**
5014 * gtk_label_get_layout:
5015 * @label: a #GtkLabel
5016 *
5017 * Gets the #PangoLayout used to display the label.
5018 * The layout is useful to e.g. convert text positions to
5019 * pixel positions, in combination with gtk_label_get_layout_offsets().
5020 * The returned layout is owned by the label so need not be
5021 * freed by the caller.
5022 *
5023 * Return value: (transfer none): the #PangoLayout for this label
5024 **/
5025 PangoLayout*
gtk_label_get_layout(GtkLabel * label)5026 gtk_label_get_layout (GtkLabel *label)
5027 {
5028 g_return_val_if_fail (GTK_IS_LABEL (label), NULL);
5029
5030 gtk_label_ensure_layout (label);
5031
5032 return label->layout;
5033 }
5034
5035 /**
5036 * gtk_label_get_layout_offsets:
5037 * @label: a #GtkLabel
5038 * @x: (out) (allow-none): location to store X offset of layout, or %NULL
5039 * @y: (out) (allow-none): location to store Y offset of layout, or %NULL
5040 *
5041 * Obtains the coordinates where the label will draw the #PangoLayout
5042 * representing the text in the label; useful to convert mouse events
5043 * into coordinates inside the #PangoLayout, e.g. to take some action
5044 * if some part of the label is clicked. Of course you will need to
5045 * create a #GtkEventBox to receive the events, and pack the label
5046 * inside it, since labels are a #GTK_NO_WINDOW widget. Remember
5047 * when using the #PangoLayout functions you need to convert to
5048 * and from pixels using PANGO_PIXELS() or #PANGO_SCALE.
5049 **/
5050 void
gtk_label_get_layout_offsets(GtkLabel * label,gint * x,gint * y)5051 gtk_label_get_layout_offsets (GtkLabel *label,
5052 gint *x,
5053 gint *y)
5054 {
5055 g_return_if_fail (GTK_IS_LABEL (label));
5056
5057 gtk_label_ensure_layout (label);
5058
5059 get_layout_location (label, x, y);
5060 }
5061
5062 /**
5063 * gtk_label_set_use_markup:
5064 * @label: a #GtkLabel
5065 * @setting: %TRUE if the label's text should be parsed for markup.
5066 *
5067 * Sets whether the text of the label contains markup in <link
5068 * linkend="PangoMarkupFormat">Pango's text markup
5069 * language</link>. See gtk_label_set_markup().
5070 **/
5071 void
gtk_label_set_use_markup(GtkLabel * label,gboolean setting)5072 gtk_label_set_use_markup (GtkLabel *label,
5073 gboolean setting)
5074 {
5075 g_return_if_fail (GTK_IS_LABEL (label));
5076
5077 gtk_label_set_use_markup_internal (label, setting);
5078 gtk_label_recalculate (label);
5079 }
5080
5081 /**
5082 * gtk_label_get_use_markup:
5083 * @label: a #GtkLabel
5084 *
5085 * Returns whether the label's text is interpreted as marked up with
5086 * the <link linkend="PangoMarkupFormat">Pango text markup
5087 * language</link>. See gtk_label_set_use_markup ().
5088 *
5089 * Return value: %TRUE if the label's text will be parsed for markup.
5090 **/
5091 gboolean
gtk_label_get_use_markup(GtkLabel * label)5092 gtk_label_get_use_markup (GtkLabel *label)
5093 {
5094 g_return_val_if_fail (GTK_IS_LABEL (label), FALSE);
5095
5096 return label->use_markup;
5097 }
5098
5099 /**
5100 * gtk_label_set_use_underline:
5101 * @label: a #GtkLabel
5102 * @setting: %TRUE if underlines in the text indicate mnemonics
5103 *
5104 * If true, an underline in the text indicates the next character should be
5105 * used for the mnemonic accelerator key.
5106 */
5107 void
gtk_label_set_use_underline(GtkLabel * label,gboolean setting)5108 gtk_label_set_use_underline (GtkLabel *label,
5109 gboolean setting)
5110 {
5111 g_return_if_fail (GTK_IS_LABEL (label));
5112
5113 gtk_label_set_use_underline_internal (label, setting);
5114 gtk_label_recalculate (label);
5115 }
5116
5117 /**
5118 * gtk_label_get_use_underline:
5119 * @label: a #GtkLabel
5120 *
5121 * Returns whether an embedded underline in the label indicates a
5122 * mnemonic. See gtk_label_set_use_underline().
5123 *
5124 * Return value: %TRUE whether an embedded underline in the label indicates
5125 * the mnemonic accelerator keys.
5126 **/
5127 gboolean
gtk_label_get_use_underline(GtkLabel * label)5128 gtk_label_get_use_underline (GtkLabel *label)
5129 {
5130 g_return_val_if_fail (GTK_IS_LABEL (label), FALSE);
5131
5132 return label->use_underline;
5133 }
5134
5135 /**
5136 * gtk_label_set_single_line_mode:
5137 * @label: a #GtkLabel
5138 * @single_line_mode: %TRUE if the label should be in single line mode
5139 *
5140 * Sets whether the label is in single line mode.
5141 *
5142 * Since: 2.6
5143 */
5144 void
gtk_label_set_single_line_mode(GtkLabel * label,gboolean single_line_mode)5145 gtk_label_set_single_line_mode (GtkLabel *label,
5146 gboolean single_line_mode)
5147 {
5148 g_return_if_fail (GTK_IS_LABEL (label));
5149
5150 single_line_mode = single_line_mode != FALSE;
5151
5152 if (label->single_line_mode != single_line_mode)
5153 {
5154 label->single_line_mode = single_line_mode;
5155
5156 gtk_label_clear_layout (label);
5157 gtk_widget_queue_resize (GTK_WIDGET (label));
5158
5159 g_object_notify (G_OBJECT (label), "single-line-mode");
5160 }
5161 }
5162
5163 /**
5164 * gtk_label_get_single_line_mode:
5165 * @label: a #GtkLabel
5166 *
5167 * Returns whether the label is in single line mode.
5168 *
5169 * Return value: %TRUE when the label is in single line mode.
5170 *
5171 * Since: 2.6
5172 **/
5173 gboolean
gtk_label_get_single_line_mode(GtkLabel * label)5174 gtk_label_get_single_line_mode (GtkLabel *label)
5175 {
5176 g_return_val_if_fail (GTK_IS_LABEL (label), FALSE);
5177
5178 return label->single_line_mode;
5179 }
5180
5181 /* Compute the X position for an offset that corresponds to the "more important
5182 * cursor position for that offset. We use this when trying to guess to which
5183 * end of the selection we should go to when the user hits the left or
5184 * right arrow key.
5185 */
5186 static void
get_better_cursor(GtkLabel * label,gint index,gint * x,gint * y)5187 get_better_cursor (GtkLabel *label,
5188 gint index,
5189 gint *x,
5190 gint *y)
5191 {
5192 GdkKeymap *keymap = gdk_keymap_get_for_display (gtk_widget_get_display (GTK_WIDGET (label)));
5193 PangoDirection keymap_direction = gdk_keymap_get_direction (keymap);
5194 PangoDirection cursor_direction = get_cursor_direction (label);
5195 gboolean split_cursor;
5196 PangoRectangle strong_pos, weak_pos;
5197
5198 g_object_get (gtk_widget_get_settings (GTK_WIDGET (label)),
5199 "gtk-split-cursor", &split_cursor,
5200 NULL);
5201
5202 gtk_label_ensure_layout (label);
5203
5204 pango_layout_get_cursor_pos (label->layout, index,
5205 &strong_pos, &weak_pos);
5206
5207 if (split_cursor)
5208 {
5209 *x = strong_pos.x / PANGO_SCALE;
5210 *y = strong_pos.y / PANGO_SCALE;
5211 }
5212 else
5213 {
5214 if (keymap_direction == cursor_direction)
5215 {
5216 *x = strong_pos.x / PANGO_SCALE;
5217 *y = strong_pos.y / PANGO_SCALE;
5218 }
5219 else
5220 {
5221 *x = weak_pos.x / PANGO_SCALE;
5222 *y = weak_pos.y / PANGO_SCALE;
5223 }
5224 }
5225 }
5226
5227
5228 static gint
gtk_label_move_logically(GtkLabel * label,gint start,gint count)5229 gtk_label_move_logically (GtkLabel *label,
5230 gint start,
5231 gint count)
5232 {
5233 gint offset = g_utf8_pointer_to_offset (label->text,
5234 label->text + start);
5235
5236 if (label->text)
5237 {
5238 PangoLogAttr *log_attrs;
5239 gint n_attrs;
5240 gint length;
5241
5242 gtk_label_ensure_layout (label);
5243
5244 length = g_utf8_strlen (label->text, -1);
5245
5246 pango_layout_get_log_attrs (label->layout, &log_attrs, &n_attrs);
5247
5248 while (count > 0 && offset < length)
5249 {
5250 do
5251 offset++;
5252 while (offset < length && !log_attrs[offset].is_cursor_position);
5253
5254 count--;
5255 }
5256 while (count < 0 && offset > 0)
5257 {
5258 do
5259 offset--;
5260 while (offset > 0 && !log_attrs[offset].is_cursor_position);
5261
5262 count++;
5263 }
5264
5265 g_free (log_attrs);
5266 }
5267
5268 return g_utf8_offset_to_pointer (label->text, offset) - label->text;
5269 }
5270
5271 static gint
gtk_label_move_visually(GtkLabel * label,gint start,gint count)5272 gtk_label_move_visually (GtkLabel *label,
5273 gint start,
5274 gint count)
5275 {
5276 gint index;
5277
5278 index = start;
5279
5280 while (count != 0)
5281 {
5282 int new_index, new_trailing;
5283 gboolean split_cursor;
5284 gboolean strong;
5285
5286 gtk_label_ensure_layout (label);
5287
5288 g_object_get (gtk_widget_get_settings (GTK_WIDGET (label)),
5289 "gtk-split-cursor", &split_cursor,
5290 NULL);
5291
5292 if (split_cursor)
5293 strong = TRUE;
5294 else
5295 {
5296 GdkKeymap *keymap = gdk_keymap_get_for_display (gtk_widget_get_display (GTK_WIDGET (label)));
5297 PangoDirection keymap_direction = gdk_keymap_get_direction (keymap);
5298
5299 strong = keymap_direction == get_cursor_direction (label);
5300 }
5301
5302 if (count > 0)
5303 {
5304 pango_layout_move_cursor_visually (label->layout, strong, index, 0, 1, &new_index, &new_trailing);
5305 count--;
5306 }
5307 else
5308 {
5309 pango_layout_move_cursor_visually (label->layout, strong, index, 0, -1, &new_index, &new_trailing);
5310 count++;
5311 }
5312
5313 if (new_index < 0 || new_index == G_MAXINT)
5314 break;
5315
5316 index = new_index;
5317
5318 while (new_trailing--)
5319 index = g_utf8_next_char (label->text + new_index) - label->text;
5320 }
5321
5322 return index;
5323 }
5324
5325 static gint
gtk_label_move_forward_word(GtkLabel * label,gint start)5326 gtk_label_move_forward_word (GtkLabel *label,
5327 gint start)
5328 {
5329 gint new_pos = g_utf8_pointer_to_offset (label->text,
5330 label->text + start);
5331 gint length;
5332
5333 length = g_utf8_strlen (label->text, -1);
5334 if (new_pos < length)
5335 {
5336 PangoLogAttr *log_attrs;
5337 gint n_attrs;
5338
5339 gtk_label_ensure_layout (label);
5340
5341 pango_layout_get_log_attrs (label->layout, &log_attrs, &n_attrs);
5342
5343 /* Find the next word end */
5344 new_pos++;
5345 while (new_pos < n_attrs && !log_attrs[new_pos].is_word_end)
5346 new_pos++;
5347
5348 g_free (log_attrs);
5349 }
5350
5351 return g_utf8_offset_to_pointer (label->text, new_pos) - label->text;
5352 }
5353
5354
5355 static gint
gtk_label_move_backward_word(GtkLabel * label,gint start)5356 gtk_label_move_backward_word (GtkLabel *label,
5357 gint start)
5358 {
5359 gint new_pos = g_utf8_pointer_to_offset (label->text,
5360 label->text + start);
5361
5362 if (new_pos > 0)
5363 {
5364 PangoLogAttr *log_attrs;
5365 gint n_attrs;
5366
5367 gtk_label_ensure_layout (label);
5368
5369 pango_layout_get_log_attrs (label->layout, &log_attrs, &n_attrs);
5370
5371 new_pos -= 1;
5372
5373 /* Find the previous word beginning */
5374 while (new_pos > 0 && !log_attrs[new_pos].is_word_start)
5375 new_pos--;
5376
5377 g_free (log_attrs);
5378 }
5379
5380 return g_utf8_offset_to_pointer (label->text, new_pos) - label->text;
5381 }
5382
5383 static void
gtk_label_move_cursor(GtkLabel * label,GtkMovementStep step,gint count,gboolean extend_selection)5384 gtk_label_move_cursor (GtkLabel *label,
5385 GtkMovementStep step,
5386 gint count,
5387 gboolean extend_selection)
5388 {
5389 gint old_pos;
5390 gint new_pos;
5391
5392 if (label->select_info == NULL)
5393 return;
5394
5395 old_pos = new_pos = label->select_info->selection_end;
5396
5397 if (label->select_info->selection_end != label->select_info->selection_anchor &&
5398 !extend_selection)
5399 {
5400 /* If we have a current selection and aren't extending it, move to the
5401 * start/or end of the selection as appropriate
5402 */
5403 switch (step)
5404 {
5405 case GTK_MOVEMENT_VISUAL_POSITIONS:
5406 {
5407 gint end_x, end_y;
5408 gint anchor_x, anchor_y;
5409 gboolean end_is_left;
5410
5411 get_better_cursor (label, label->select_info->selection_end, &end_x, &end_y);
5412 get_better_cursor (label, label->select_info->selection_anchor, &anchor_x, &anchor_y);
5413
5414 end_is_left = (end_y < anchor_y) || (end_y == anchor_y && end_x < anchor_x);
5415
5416 if (count < 0)
5417 new_pos = end_is_left ? label->select_info->selection_end : label->select_info->selection_anchor;
5418 else
5419 new_pos = !end_is_left ? label->select_info->selection_end : label->select_info->selection_anchor;
5420 break;
5421 }
5422 case GTK_MOVEMENT_LOGICAL_POSITIONS:
5423 case GTK_MOVEMENT_WORDS:
5424 if (count < 0)
5425 new_pos = MIN (label->select_info->selection_end, label->select_info->selection_anchor);
5426 else
5427 new_pos = MAX (label->select_info->selection_end, label->select_info->selection_anchor);
5428 break;
5429 case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
5430 case GTK_MOVEMENT_PARAGRAPH_ENDS:
5431 case GTK_MOVEMENT_BUFFER_ENDS:
5432 /* FIXME: Can do better here */
5433 new_pos = count < 0 ? 0 : strlen (label->text);
5434 break;
5435 case GTK_MOVEMENT_DISPLAY_LINES:
5436 case GTK_MOVEMENT_PARAGRAPHS:
5437 case GTK_MOVEMENT_PAGES:
5438 case GTK_MOVEMENT_HORIZONTAL_PAGES:
5439 break;
5440 }
5441 }
5442 else
5443 {
5444 switch (step)
5445 {
5446 case GTK_MOVEMENT_LOGICAL_POSITIONS:
5447 new_pos = gtk_label_move_logically (label, new_pos, count);
5448 break;
5449 case GTK_MOVEMENT_VISUAL_POSITIONS:
5450 new_pos = gtk_label_move_visually (label, new_pos, count);
5451 if (new_pos == old_pos)
5452 {
5453 if (!extend_selection)
5454 {
5455 if (!gtk_widget_keynav_failed (GTK_WIDGET (label),
5456 count > 0 ?
5457 GTK_DIR_RIGHT : GTK_DIR_LEFT))
5458 {
5459 GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (label));
5460
5461 if (toplevel)
5462 gtk_widget_child_focus (toplevel,
5463 count > 0 ?
5464 GTK_DIR_RIGHT : GTK_DIR_LEFT);
5465 }
5466 }
5467 else
5468 {
5469 gtk_widget_error_bell (GTK_WIDGET (label));
5470 }
5471 }
5472 break;
5473 case GTK_MOVEMENT_WORDS:
5474 while (count > 0)
5475 {
5476 new_pos = gtk_label_move_forward_word (label, new_pos);
5477 count--;
5478 }
5479 while (count < 0)
5480 {
5481 new_pos = gtk_label_move_backward_word (label, new_pos);
5482 count++;
5483 }
5484 if (new_pos == old_pos)
5485 gtk_widget_error_bell (GTK_WIDGET (label));
5486 break;
5487 case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
5488 case GTK_MOVEMENT_PARAGRAPH_ENDS:
5489 case GTK_MOVEMENT_BUFFER_ENDS:
5490 /* FIXME: Can do better here */
5491 new_pos = count < 0 ? 0 : strlen (label->text);
5492 if (new_pos == old_pos)
5493 gtk_widget_error_bell (GTK_WIDGET (label));
5494 break;
5495 case GTK_MOVEMENT_DISPLAY_LINES:
5496 case GTK_MOVEMENT_PARAGRAPHS:
5497 case GTK_MOVEMENT_PAGES:
5498 case GTK_MOVEMENT_HORIZONTAL_PAGES:
5499 break;
5500 }
5501 }
5502
5503 if (extend_selection)
5504 gtk_label_select_region_index (label,
5505 label->select_info->selection_anchor,
5506 new_pos);
5507 else
5508 gtk_label_select_region_index (label, new_pos, new_pos);
5509 }
5510
5511 static void
gtk_label_copy_clipboard(GtkLabel * label)5512 gtk_label_copy_clipboard (GtkLabel *label)
5513 {
5514 if (label->text && label->select_info)
5515 {
5516 gint start, end;
5517 gint len;
5518 GtkClipboard *clipboard;
5519
5520 start = MIN (label->select_info->selection_anchor,
5521 label->select_info->selection_end);
5522 end = MAX (label->select_info->selection_anchor,
5523 label->select_info->selection_end);
5524
5525 len = strlen (label->text);
5526
5527 if (end > len)
5528 end = len;
5529
5530 if (start > len)
5531 start = len;
5532
5533 clipboard = gtk_widget_get_clipboard (GTK_WIDGET (label), GDK_SELECTION_CLIPBOARD);
5534
5535 if (start != end)
5536 gtk_clipboard_set_text (clipboard, label->text + start, end - start);
5537 else
5538 {
5539 GtkLabelLink *link;
5540
5541 link = gtk_label_get_focus_link (label);
5542 if (link)
5543 gtk_clipboard_set_text (clipboard, link->uri, -1);
5544 }
5545 }
5546 }
5547
5548 static void
gtk_label_select_all(GtkLabel * label)5549 gtk_label_select_all (GtkLabel *label)
5550 {
5551 gtk_label_select_region_index (label, 0, strlen (label->text));
5552 }
5553
5554 /* Quick hack of a popup menu
5555 */
5556 static void
activate_cb(GtkWidget * menuitem,GtkLabel * label)5557 activate_cb (GtkWidget *menuitem,
5558 GtkLabel *label)
5559 {
5560 const gchar *signal = g_object_get_data (G_OBJECT (menuitem), "gtk-signal");
5561 g_signal_emit_by_name (label, signal);
5562 }
5563
5564 static void
append_action_signal(GtkLabel * label,GtkWidget * menu,const gchar * stock_id,const gchar * signal,gboolean sensitive)5565 append_action_signal (GtkLabel *label,
5566 GtkWidget *menu,
5567 const gchar *stock_id,
5568 const gchar *signal,
5569 gboolean sensitive)
5570 {
5571 GtkWidget *menuitem = gtk_image_menu_item_new_from_stock (stock_id, NULL);
5572
5573 g_object_set_data (G_OBJECT (menuitem), I_("gtk-signal"), (char *)signal);
5574 g_signal_connect (menuitem, "activate",
5575 G_CALLBACK (activate_cb), label);
5576
5577 gtk_widget_set_sensitive (menuitem, sensitive);
5578
5579 gtk_widget_show (menuitem);
5580 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
5581 }
5582
5583 static void
popup_menu_detach(GtkWidget * attach_widget,GtkMenu * menu)5584 popup_menu_detach (GtkWidget *attach_widget,
5585 GtkMenu *menu)
5586 {
5587 GtkLabel *label = GTK_LABEL (attach_widget);
5588
5589 if (label->select_info)
5590 label->select_info->popup_menu = NULL;
5591 }
5592
5593 static void
popup_position_func(GtkMenu * menu,gint * x,gint * y,gboolean * push_in,gpointer user_data)5594 popup_position_func (GtkMenu *menu,
5595 gint *x,
5596 gint *y,
5597 gboolean *push_in,
5598 gpointer user_data)
5599 {
5600 GtkLabel *label;
5601 GtkWidget *widget;
5602 GtkRequisition req;
5603 GdkScreen *screen;
5604
5605 label = GTK_LABEL (user_data);
5606 widget = GTK_WIDGET (label);
5607
5608 g_return_if_fail (gtk_widget_get_realized (widget));
5609
5610 screen = gtk_widget_get_screen (widget);
5611 gdk_window_get_origin (widget->window, x, y);
5612
5613 *x += widget->allocation.x;
5614 *y += widget->allocation.y;
5615
5616 gtk_widget_size_request (GTK_WIDGET (menu), &req);
5617
5618 *x += widget->allocation.width / 2;
5619 *y += widget->allocation.height;
5620
5621 *x = CLAMP (*x, 0, MAX (0, gdk_screen_get_width (screen) - req.width));
5622 *y = CLAMP (*y, 0, MAX (0, gdk_screen_get_height (screen) - req.height));
5623 }
5624
5625 static void
open_link_activate_cb(GtkMenuItem * menu_item,GtkLabel * label)5626 open_link_activate_cb (GtkMenuItem *menu_item,
5627 GtkLabel *label)
5628 {
5629 GtkLabelLink *link;
5630
5631 link = gtk_label_get_current_link (label);
5632
5633 if (link)
5634 emit_activate_link (label, link);
5635 }
5636
5637 static void
copy_link_activate_cb(GtkMenuItem * menu_item,GtkLabel * label)5638 copy_link_activate_cb (GtkMenuItem *menu_item,
5639 GtkLabel *label)
5640 {
5641 GtkClipboard *clipboard;
5642 const gchar *uri;
5643
5644 uri = gtk_label_get_current_uri (label);
5645 if (uri)
5646 {
5647 clipboard = gtk_widget_get_clipboard (GTK_WIDGET (label), GDK_SELECTION_CLIPBOARD);
5648 gtk_clipboard_set_text (clipboard, uri, -1);
5649 }
5650 }
5651
5652 static gboolean
gtk_label_popup_menu(GtkWidget * widget)5653 gtk_label_popup_menu (GtkWidget *widget)
5654 {
5655 gtk_label_do_popup (GTK_LABEL (widget), NULL);
5656
5657 return TRUE;
5658 }
5659
5660 static void
gtk_label_do_popup(GtkLabel * label,GdkEventButton * event)5661 gtk_label_do_popup (GtkLabel *label,
5662 GdkEventButton *event)
5663 {
5664 GtkWidget *menuitem;
5665 GtkWidget *menu;
5666 GtkWidget *image;
5667 gboolean have_selection;
5668 GtkLabelLink *link;
5669
5670 if (!label->select_info)
5671 return;
5672
5673 if (label->select_info->popup_menu)
5674 gtk_widget_destroy (label->select_info->popup_menu);
5675
5676 label->select_info->popup_menu = menu = gtk_menu_new ();
5677
5678 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (label), popup_menu_detach);
5679
5680 have_selection =
5681 label->select_info->selection_anchor != label->select_info->selection_end;
5682
5683 if (event)
5684 {
5685 if (label->select_info->link_clicked)
5686 link = label->select_info->active_link;
5687 else
5688 link = NULL;
5689 }
5690 else
5691 link = gtk_label_get_focus_link (label);
5692
5693 if (!have_selection && link)
5694 {
5695 /* Open Link */
5696 menuitem = gtk_image_menu_item_new_with_mnemonic (_("_Open Link"));
5697 gtk_widget_show (menuitem);
5698 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
5699
5700 g_signal_connect (G_OBJECT (menuitem), "activate",
5701 G_CALLBACK (open_link_activate_cb), label);
5702
5703 image = gtk_image_new_from_stock (GTK_STOCK_JUMP_TO, GTK_ICON_SIZE_MENU);
5704 gtk_widget_show (image);
5705 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menuitem), image);
5706
5707 /* Copy Link Address */
5708 menuitem = gtk_image_menu_item_new_with_mnemonic (_("Copy _Link Address"));
5709 gtk_widget_show (menuitem);
5710 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
5711
5712 g_signal_connect (G_OBJECT (menuitem), "activate",
5713 G_CALLBACK (copy_link_activate_cb), label);
5714
5715 image = gtk_image_new_from_stock (GTK_STOCK_COPY, GTK_ICON_SIZE_MENU);
5716 gtk_widget_show (image);
5717 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menuitem), image);
5718 }
5719 else
5720 {
5721 append_action_signal (label, menu, GTK_STOCK_CUT, "cut-clipboard", FALSE);
5722 append_action_signal (label, menu, GTK_STOCK_COPY, "copy-clipboard", have_selection);
5723 append_action_signal (label, menu, GTK_STOCK_PASTE, "paste-clipboard", FALSE);
5724
5725 menuitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_DELETE, NULL);
5726 gtk_widget_set_sensitive (menuitem, FALSE);
5727 gtk_widget_show (menuitem);
5728 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
5729
5730 menuitem = gtk_separator_menu_item_new ();
5731 gtk_widget_show (menuitem);
5732 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
5733
5734 menuitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_SELECT_ALL, NULL);
5735 g_signal_connect_swapped (menuitem, "activate",
5736 G_CALLBACK (gtk_label_select_all), label);
5737 gtk_widget_show (menuitem);
5738 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
5739 }
5740
5741 g_signal_emit (label, signals[POPULATE_POPUP], 0, menu);
5742
5743 if (event)
5744 gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
5745 NULL, NULL,
5746 event->button, event->time);
5747 else
5748 {
5749 gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
5750 popup_position_func, label,
5751 0, gtk_get_current_event_time ());
5752 gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
5753 }
5754 }
5755
5756 static void
gtk_label_clear_links(GtkLabel * label)5757 gtk_label_clear_links (GtkLabel *label)
5758 {
5759 if (!label->select_info)
5760 return;
5761
5762 g_list_foreach (label->select_info->links, (GFunc)link_free, NULL);
5763 g_list_free (label->select_info->links);
5764 label->select_info->links = NULL;
5765 label->select_info->active_link = NULL;
5766 }
5767
5768 static void
gtk_label_rescan_links(GtkLabel * label)5769 gtk_label_rescan_links (GtkLabel *label)
5770 {
5771 PangoLayout *layout = label->layout;
5772 PangoAttrList *attlist;
5773 PangoAttrIterator *iter;
5774 GList *links;
5775
5776 if (!label->select_info || !label->select_info->links)
5777 return;
5778
5779 attlist = pango_layout_get_attributes (layout);
5780
5781 if (attlist == NULL)
5782 return;
5783
5784 iter = pango_attr_list_get_iterator (attlist);
5785
5786 links = label->select_info->links;
5787
5788 do
5789 {
5790 PangoAttribute *underline;
5791 PangoAttribute *color;
5792
5793 underline = pango_attr_iterator_get (iter, PANGO_ATTR_UNDERLINE);
5794 color = pango_attr_iterator_get (iter, PANGO_ATTR_FOREGROUND);
5795
5796 if (underline != NULL && color != NULL)
5797 {
5798 gint start, end;
5799 PangoRectangle start_pos;
5800 PangoRectangle end_pos;
5801 GtkLabelLink *link;
5802
5803 pango_attr_iterator_range (iter, &start, &end);
5804 pango_layout_index_to_pos (layout, start, &start_pos);
5805 pango_layout_index_to_pos (layout, end, &end_pos);
5806
5807 if (links == NULL)
5808 {
5809 g_warning ("Ran out of links");
5810 break;
5811 }
5812 link = links->data;
5813 links = links->next;
5814 link->start = start;
5815 link->end = end;
5816 }
5817 } while (pango_attr_iterator_next (iter));
5818
5819 pango_attr_iterator_destroy (iter);
5820 }
5821
5822 static gboolean
gtk_label_activate_link(GtkLabel * label,const gchar * uri)5823 gtk_label_activate_link (GtkLabel *label,
5824 const gchar *uri)
5825 {
5826 GtkWidget *widget = GTK_WIDGET (label);
5827 GError *error = NULL;
5828
5829 if (!gtk_show_uri (gtk_widget_get_screen (widget),
5830 uri, gtk_get_current_event_time (), &error))
5831 {
5832 g_warning ("Unable to show '%s': %s", uri, error->message);
5833 g_error_free (error);
5834 }
5835
5836 return TRUE;
5837 }
5838
5839 static void
emit_activate_link(GtkLabel * label,GtkLabelLink * link)5840 emit_activate_link (GtkLabel *label,
5841 GtkLabelLink *link)
5842 {
5843 gboolean handled;
5844
5845 g_signal_emit (label, signals[ACTIVATE_LINK], 0, link->uri, &handled);
5846 if (handled && label->track_links && !link->visited)
5847 {
5848 link->visited = TRUE;
5849 /* FIXME: shouldn't have to redo everything here */
5850 gtk_label_recalculate (label);
5851 }
5852 }
5853
5854 static void
gtk_label_activate_current_link(GtkLabel * label)5855 gtk_label_activate_current_link (GtkLabel *label)
5856 {
5857 GtkLabelLink *link;
5858 GtkWidget *widget = GTK_WIDGET (label);
5859
5860 link = gtk_label_get_focus_link (label);
5861
5862 if (link)
5863 {
5864 emit_activate_link (label, link);
5865 }
5866 else
5867 {
5868 GtkWidget *toplevel;
5869 GtkWindow *window;
5870
5871 toplevel = gtk_widget_get_toplevel (widget);
5872 if (GTK_IS_WINDOW (toplevel))
5873 {
5874 window = GTK_WINDOW (toplevel);
5875
5876 if (window &&
5877 window->default_widget != widget &&
5878 !(widget == window->focus_widget &&
5879 (!window->default_widget || !gtk_widget_is_sensitive (window->default_widget))))
5880 gtk_window_activate_default (window);
5881 }
5882 }
5883 }
5884
5885 static GtkLabelLink *
gtk_label_get_current_link(GtkLabel * label)5886 gtk_label_get_current_link (GtkLabel *label)
5887 {
5888 GtkLabelLink *link;
5889
5890 if (!label->select_info)
5891 return NULL;
5892
5893 if (label->select_info->link_clicked)
5894 link = label->select_info->active_link;
5895 else
5896 link = gtk_label_get_focus_link (label);
5897
5898 return link;
5899 }
5900
5901 /**
5902 * gtk_label_get_current_uri:
5903 * @label: a #GtkLabel
5904 *
5905 * Returns the URI for the currently active link in the label.
5906 * The active link is the one under the mouse pointer or, in a
5907 * selectable label, the link in which the text cursor is currently
5908 * positioned.
5909 *
5910 * This function is intended for use in a #GtkLabel::activate-link handler
5911 * or for use in a #GtkWidget::query-tooltip handler.
5912 *
5913 * Returns: the currently active URI. The string is owned by GTK+ and must
5914 * not be freed or modified.
5915 *
5916 * Since: 2.18
5917 */
5918 const gchar *
gtk_label_get_current_uri(GtkLabel * label)5919 gtk_label_get_current_uri (GtkLabel *label)
5920 {
5921 GtkLabelLink *link;
5922 g_return_val_if_fail (GTK_IS_LABEL (label), NULL);
5923
5924 link = gtk_label_get_current_link (label);
5925
5926 if (link)
5927 return link->uri;
5928
5929 return NULL;
5930 }
5931
5932 /**
5933 * gtk_label_set_track_visited_links:
5934 * @label: a #GtkLabel
5935 * @track_links: %TRUE to track visited links
5936 *
5937 * Sets whether the label should keep track of clicked
5938 * links (and use a different color for them).
5939 *
5940 * Since: 2.18
5941 */
5942 void
gtk_label_set_track_visited_links(GtkLabel * label,gboolean track_links)5943 gtk_label_set_track_visited_links (GtkLabel *label,
5944 gboolean track_links)
5945 {
5946 g_return_if_fail (GTK_IS_LABEL (label));
5947
5948 track_links = track_links != FALSE;
5949
5950 if (label->track_links != track_links)
5951 {
5952 label->track_links = track_links;
5953
5954 /* FIXME: shouldn't have to redo everything here */
5955 gtk_label_recalculate (label);
5956
5957 g_object_notify (G_OBJECT (label), "track-visited-links");
5958 }
5959 }
5960
5961 /**
5962 * gtk_label_get_track_visited_links:
5963 * @label: a #GtkLabel
5964 *
5965 * Returns whether the label is currently keeping track
5966 * of clicked links.
5967 *
5968 * Returns: %TRUE if clicked links are remembered
5969 *
5970 * Since: 2.18
5971 */
5972 gboolean
gtk_label_get_track_visited_links(GtkLabel * label)5973 gtk_label_get_track_visited_links (GtkLabel *label)
5974 {
5975 g_return_val_if_fail (GTK_IS_LABEL (label), FALSE);
5976
5977 return label->track_links;
5978 }
5979
5980 static gboolean
gtk_label_query_tooltip(GtkWidget * widget,gint x,gint y,gboolean keyboard_tip,GtkTooltip * tooltip)5981 gtk_label_query_tooltip (GtkWidget *widget,
5982 gint x,
5983 gint y,
5984 gboolean keyboard_tip,
5985 GtkTooltip *tooltip)
5986 {
5987 GtkLabel *label = GTK_LABEL (widget);
5988 GtkLabelSelectionInfo *info = label->select_info;
5989 gint index = -1;
5990 GList *l;
5991
5992 if (info && info->links)
5993 {
5994 if (keyboard_tip)
5995 {
5996 if (info->selection_anchor == info->selection_end)
5997 index = info->selection_anchor;
5998 }
5999 else
6000 {
6001 if (!get_layout_index (label, x, y, &index))
6002 index = -1;
6003 }
6004
6005 if (index != -1)
6006 {
6007 for (l = info->links; l != NULL; l = l->next)
6008 {
6009 GtkLabelLink *link = l->data;
6010 if (index >= link->start && index <= link->end)
6011 {
6012 if (link->title)
6013 {
6014 gtk_tooltip_set_markup (tooltip, link->title);
6015 return TRUE;
6016 }
6017 break;
6018 }
6019 }
6020 }
6021 }
6022
6023 return GTK_WIDGET_CLASS (gtk_label_parent_class)->query_tooltip (widget,
6024 x, y,
6025 keyboard_tip,
6026 tooltip);
6027 }
6028
6029
6030 #define __GTK_LABEL_C__
6031 #include "gtkaliasdef.c"
6032