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, see <http://www.gnu.org/licenses/>.Free
16 */
17
18 /*
19 * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
20 * file for a list of people on the GTK+ Team. See the ChangeLog
21 * files for a list of changes. These files are distributed with
22 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
23 */
24
25 #include "config.h"
26
27 #include <math.h>
28 #include <string.h>
29
30 #include "gtklabel.h"
31 #include "gtklabelprivate.h"
32 #include "gtkaccellabel.h"
33 #include "gtkbindings.h"
34 #include "gtkbuildable.h"
35 #include "gtkbuilderprivate.h"
36 #include "gtkclipboard.h"
37 #include "gtkcssshadowsvalueprivate.h"
38 #include "gtkcssstylepropertyprivate.h"
39 #include "gtkdnd.h"
40 #include "gtkimage.h"
41 #include "gtkintl.h"
42 #include "gtkmain.h"
43 #include "gtkmarshalers.h"
44 #include "gtkmenuitem.h"
45 #include "gtkmenushellprivate.h"
46 #include "gtknotebook.h"
47 #include "gtkpango.h"
48 #include "gtkprivate.h"
49 #include "gtkseparatormenuitem.h"
50 #include "gtkshow.h"
51 #include "gtkstylecontextprivate.h"
52 #include "gtktextutil.h"
53 #include "gtktooltip.h"
54 #include "gtktypebuiltins.h"
55 #include "gtkwidgetprivate.h"
56 #include "gtkwindow.h"
57 #include "gtkcssnodeprivate.h"
58 #include "gtkcsscustomgadgetprivate.h"
59 #include "gtkwidgetprivate.h"
60
61 #include "a11y/gtklabelaccessibleprivate.h"
62
63 /* this is in case rint() is not provided by the compiler,
64 * such as in the case of C89 compilers, like MSVC
65 */
66 #include "fallback-c89.c"
67
68 /**
69 * SECTION:gtklabel
70 * @Short_description: A widget that displays a small to medium amount of text
71 * @Title: GtkLabel
72 *
73 * The #GtkLabel widget displays a small amount of text. As the name
74 * implies, most labels are used to label another widget such as a
75 * #GtkButton, a #GtkMenuItem, or a #GtkComboBox.
76 *
77 * # CSS nodes
78 *
79 * |[<!-- language="plain" -->
80 * label
81 * ├── [selection]
82 * ├── [link]
83 * ┊
84 * ╰── [link]
85 * ]|
86 *
87 * GtkLabel has a single CSS node with the name label. A wide variety
88 * of style classes may be applied to labels, such as .title, .subtitle,
89 * .dim-label, etc. In the #GtkShortcutsWindow, labels are used wth the
90 * .keycap style class.
91 *
92 * If the label has a selection, it gets a subnode with name selection.
93 *
94 * If the label has links, there is one subnode per link. These subnodes
95 * carry the link or visited state depending on whether they have been
96 * visited.
97 *
98 * # GtkLabel as GtkBuildable
99 *
100 * The GtkLabel implementation of the GtkBuildable interface supports a
101 * custom `<attributes>` element, which supports any number of `<attribute>`
102 * elements. The `<attribute>` element has attributes named “name“, “value“,
103 * “start“ and “end“ and allows you to specify #PangoAttribute values for
104 * this label.
105 *
106 * An example of a UI definition fragment specifying Pango attributes:
107 *
108 * |[<!-- language="xml" -->
109 * <object class="GtkLabel">
110 * <attributes>
111 * <attribute name="weight" value="PANGO_WEIGHT_BOLD"/>
112 * <attribute name="background" value="red" start="5" end="10"/>
113 * </attributes>
114 * </object>
115 * ]|
116 *
117 * The start and end attributes specify the range of characters to which the
118 * Pango attribute applies. If start and end are not specified, the attribute is
119 * applied to the whole text. Note that specifying ranges does not make much
120 * sense with translatable attributes. Use markup embedded in the translatable
121 * content instead.
122 *
123 * # Mnemonics
124 *
125 * Labels may contain “mnemonics”. Mnemonics are
126 * underlined characters in the label, used for keyboard navigation.
127 * Mnemonics are created by providing a string with an underscore before
128 * the mnemonic character, such as `"_File"`, to the
129 * functions gtk_label_new_with_mnemonic() or
130 * gtk_label_set_text_with_mnemonic().
131 *
132 * Mnemonics automatically activate any activatable widget the label is
133 * inside, such as a #GtkButton; if the label is not inside the
134 * mnemonic’s target widget, you have to tell the label about the target
135 * using gtk_label_set_mnemonic_widget(). Here’s a simple example where
136 * the label is inside a button:
137 *
138 * |[<!-- language="C" -->
139 * // Pressing Alt+H will activate this button
140 * GtkWidget *button = gtk_button_new ();
141 * GtkWidget *label = gtk_label_new_with_mnemonic ("_Hello");
142 * gtk_container_add (GTK_CONTAINER (button), label);
143 * ]|
144 *
145 * There’s a convenience function to create buttons with a mnemonic label
146 * already inside:
147 *
148 * |[<!-- language="C" -->
149 * // Pressing Alt+H will activate this button
150 * GtkWidget *button = gtk_button_new_with_mnemonic ("_Hello");
151 * ]|
152 *
153 * To create a mnemonic for a widget alongside the label, such as a
154 * #GtkEntry, you have to point the label at the entry with
155 * gtk_label_set_mnemonic_widget():
156 *
157 * |[<!-- language="C" -->
158 * // Pressing Alt+H will focus the entry
159 * GtkWidget *entry = gtk_entry_new ();
160 * GtkWidget *label = gtk_label_new_with_mnemonic ("_Hello");
161 * gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);
162 * ]|
163 *
164 * # Markup (styled text)
165 *
166 * To make it easy to format text in a label (changing colors,
167 * fonts, etc.), label text can be provided in a simple
168 * [markup format][PangoMarkupFormat].
169 *
170 * Here’s how to create a label with a small font:
171 * |[<!-- language="C" -->
172 * GtkWidget *label = gtk_label_new (NULL);
173 * gtk_label_set_markup (GTK_LABEL (label), "<small>Small text</small>");
174 * ]|
175 *
176 * (See [complete documentation][PangoMarkupFormat] of available
177 * tags in the Pango manual.)
178 *
179 * The markup passed to gtk_label_set_markup() must be valid; for example,
180 * literal <, > and & characters must be escaped as <, >, and &.
181 * If you pass text obtained from the user, file, or a network to
182 * gtk_label_set_markup(), you’ll want to escape it with
183 * g_markup_escape_text() or g_markup_printf_escaped().
184 *
185 * Markup strings are just a convenient way to set the #PangoAttrList on
186 * a label; gtk_label_set_attributes() may be a simpler way to set
187 * attributes in some cases. Be careful though; #PangoAttrList tends to
188 * cause internationalization problems, unless you’re applying attributes
189 * to the entire string (i.e. unless you set the range of each attribute
190 * to [0, %G_MAXINT)). The reason is that specifying the start_index and
191 * end_index for a #PangoAttribute requires knowledge of the exact string
192 * being displayed, so translations will cause problems.
193 *
194 * # Selectable labels
195 *
196 * Labels can be made selectable with gtk_label_set_selectable().
197 * Selectable labels allow the user to copy the label contents to
198 * the clipboard. Only labels that contain useful-to-copy information
199 * — such as error messages — should be made selectable.
200 *
201 * # Text layout # {#label-text-layout}
202 *
203 * A label can contain any number of paragraphs, but will have
204 * performance problems if it contains more than a small number.
205 * Paragraphs are separated by newlines or other paragraph separators
206 * understood by Pango.
207 *
208 * Labels can automatically wrap text if you call
209 * gtk_label_set_line_wrap().
210 *
211 * gtk_label_set_justify() sets how the lines in a label align
212 * with one another. If you want to set how the label as a whole
213 * aligns in its available space, see the #GtkWidget:halign and
214 * #GtkWidget:valign properties.
215 *
216 * The #GtkLabel:width-chars and #GtkLabel:max-width-chars properties
217 * can be used to control the size allocation of ellipsized or wrapped
218 * labels. For ellipsizing labels, if either is specified (and less
219 * than the actual text size), it is used as the minimum width, and the actual
220 * text size is used as the natural width of the label. For wrapping labels,
221 * width-chars is used as the minimum width, if specified, and max-width-chars
222 * is used as the natural width. Even if max-width-chars specified, wrapping
223 * labels will be rewrapped to use all of the available width.
224 *
225 * Note that the interpretation of #GtkLabel:width-chars and
226 * #GtkLabel:max-width-chars has changed a bit with the introduction of
227 * [width-for-height geometry management.][geometry-management]
228 *
229 * # Links
230 *
231 * Since 2.18, GTK+ supports markup for clickable hyperlinks in addition
232 * to regular Pango markup. The markup for links is borrowed from HTML,
233 * using the `<a>` with “href“ and “title“ attributes. GTK+ renders links
234 * similar to the way they appear in web browsers, with colored, underlined
235 * text. The “title“ attribute is displayed as a tooltip on the link.
236 *
237 * An example looks like this:
238 *
239 * |[<!-- language="C" -->
240 * const gchar *text =
241 * "Go to the"
242 * "<a href=\"http://www.gtk.org title=\"<i>Our</i> website\">"
243 * "GTK+ website</a> for more...";
244 * GtkWidget *label = gtk_label_new (NULL);
245 * gtk_label_set_markup (GTK_LABEL (label), text);
246 * ]|
247 *
248 * It is possible to implement custom handling for links and their tooltips with
249 * the #GtkLabel::activate-link signal and the gtk_label_get_current_uri() function.
250 */
251
252 struct _GtkLabelPrivate
253 {
254 GtkLabelSelectionInfo *select_info;
255 GtkWidget *mnemonic_widget;
256 GtkWindow *mnemonic_window;
257 GtkCssGadget *gadget;
258
259 PangoAttrList *attrs;
260 PangoAttrList *markup_attrs;
261 PangoLayout *layout;
262
263 gchar *label;
264 gchar *text;
265
266 gdouble angle;
267 gfloat xalign;
268 gfloat yalign;
269
270 guint mnemonics_visible : 1;
271 guint jtype : 2;
272 guint wrap : 1;
273 guint use_underline : 1;
274 guint use_markup : 1;
275 guint ellipsize : 3;
276 guint single_line_mode : 1;
277 guint have_transform : 1;
278 guint in_click : 1;
279 guint wrap_mode : 3;
280 guint pattern_set : 1;
281 guint track_links : 1;
282
283 guint mnemonic_keyval;
284
285 gint width_chars;
286 gint max_width_chars;
287 gint lines;
288 };
289
290 /* Notes about the handling of links:
291 *
292 * Links share the GtkLabelSelectionInfo struct with selectable labels.
293 * There are some new fields for links. The links field contains the list
294 * of GtkLabelLink structs that describe the links which are embedded in
295 * the label. The active_link field points to the link under the mouse
296 * pointer. For keyboard navigation, the “focus” link is determined by
297 * finding the link which contains the selection_anchor position.
298 * The link_clicked field is used with button press and release events
299 * to ensure that pressing inside a link and releasing outside of it
300 * does not activate the link.
301 *
302 * Links are rendered with the #GTK_STATE_FLAG_LINK/#GTK_STATE_FLAG_VISITED
303 * state flags. When the mouse pointer is over a link, the pointer is changed
304 * to indicate the link.
305 *
306 * Labels with links accept keyboard focus, and it is possible to move
307 * the focus between the embedded links using Tab/Shift-Tab. The focus
308 * is indicated by a focus rectangle that is drawn around the link text.
309 * Pressing Enter activates the focused link, and there is a suitable
310 * context menu for links that can be opened with the Menu key. Pressing
311 * Control-C copies the link URI to the clipboard.
312 *
313 * In selectable labels with links, link functionality is only available
314 * when the selection is empty.
315 */
316 typedef struct
317 {
318 gchar *uri;
319 gchar *title; /* the title attribute, used as tooltip */
320
321 GtkCssNode *cssnode;
322
323 gboolean visited; /* get set when the link is activated; this flag
324 * gets preserved over later set_markup() calls
325 */
326 gint start; /* position of the link in the PangoLayout */
327 gint end;
328 } GtkLabelLink;
329
330 struct _GtkLabelSelectionInfo
331 {
332 GdkWindow *window;
333 gint selection_anchor;
334 gint selection_end;
335 GtkWidget *popup_menu;
336 GtkCssNode *selection_node;
337
338 GList *links;
339 GtkLabelLink *active_link;
340
341 GtkGesture *drag_gesture;
342 GtkGesture *multipress_gesture;
343
344 gint drag_start_x;
345 gint drag_start_y;
346
347 guint in_drag : 1;
348 guint select_words : 1;
349 guint selectable : 1;
350 guint link_clicked : 1;
351 };
352
353 enum {
354 MOVE_CURSOR,
355 COPY_CLIPBOARD,
356 POPULATE_POPUP,
357 ACTIVATE_LINK,
358 ACTIVATE_CURRENT_LINK,
359 LAST_SIGNAL
360 };
361
362 enum {
363 PROP_0,
364 PROP_LABEL,
365 PROP_ATTRIBUTES,
366 PROP_USE_MARKUP,
367 PROP_USE_UNDERLINE,
368 PROP_JUSTIFY,
369 PROP_PATTERN,
370 PROP_WRAP,
371 PROP_WRAP_MODE,
372 PROP_SELECTABLE,
373 PROP_MNEMONIC_KEYVAL,
374 PROP_MNEMONIC_WIDGET,
375 PROP_CURSOR_POSITION,
376 PROP_SELECTION_BOUND,
377 PROP_ELLIPSIZE,
378 PROP_WIDTH_CHARS,
379 PROP_SINGLE_LINE_MODE,
380 PROP_ANGLE,
381 PROP_MAX_WIDTH_CHARS,
382 PROP_TRACK_VISITED_LINKS,
383 PROP_LINES,
384 PROP_XALIGN,
385 PROP_YALIGN,
386 NUM_PROPERTIES
387 };
388
389 static GParamSpec *label_props[NUM_PROPERTIES] = { NULL, };
390
391 /* When rotating ellipsizable text we want the natural size to request
392 * more to ensure the label wont ever ellipsize in an allocation of full natural size.
393 * */
394 #define ROTATION_ELLIPSIZE_PADDING 2
395
396 static guint signals[LAST_SIGNAL] = { 0 };
397
398 static GQuark quark_shortcuts_connected;
399 static GQuark quark_mnemonic_menu;
400 static GQuark quark_mnemonics_visible_connected;
401 static GQuark quark_gtk_signal;
402 static GQuark quark_link;
403
404 static void gtk_label_set_property (GObject *object,
405 guint prop_id,
406 const GValue *value,
407 GParamSpec *pspec);
408 static void gtk_label_get_property (GObject *object,
409 guint prop_id,
410 GValue *value,
411 GParamSpec *pspec);
412 static void gtk_label_finalize (GObject *object);
413 static void gtk_label_destroy (GtkWidget *widget);
414 static void gtk_label_size_allocate (GtkWidget *widget,
415 GtkAllocation *allocation);
416 static void gtk_label_state_flags_changed (GtkWidget *widget,
417 GtkStateFlags prev_state);
418 static void gtk_label_style_updated (GtkWidget *widget);
419 static gboolean gtk_label_draw (GtkWidget *widget,
420 cairo_t *cr);
421 static gboolean gtk_label_focus (GtkWidget *widget,
422 GtkDirectionType direction);
423
424 static void gtk_label_realize (GtkWidget *widget);
425 static void gtk_label_unrealize (GtkWidget *widget);
426 static void gtk_label_map (GtkWidget *widget);
427 static void gtk_label_unmap (GtkWidget *widget);
428
429 static gboolean gtk_label_motion (GtkWidget *widget,
430 GdkEventMotion *event);
431 static gboolean gtk_label_leave_notify (GtkWidget *widget,
432 GdkEventCrossing *event);
433
434 static void gtk_label_grab_focus (GtkWidget *widget);
435
436 static gboolean gtk_label_query_tooltip (GtkWidget *widget,
437 gint x,
438 gint y,
439 gboolean keyboard_tip,
440 GtkTooltip *tooltip);
441
442 static void gtk_label_set_text_internal (GtkLabel *label,
443 gchar *str);
444 static void gtk_label_set_label_internal (GtkLabel *label,
445 gchar *str);
446 static gboolean gtk_label_set_use_markup_internal (GtkLabel *label,
447 gboolean val);
448 static gboolean gtk_label_set_use_underline_internal (GtkLabel *label,
449 gboolean val);
450 static void gtk_label_set_uline_text_internal (GtkLabel *label,
451 const gchar *str);
452 static void gtk_label_set_pattern_internal (GtkLabel *label,
453 const gchar *pattern,
454 gboolean is_mnemonic);
455 static void gtk_label_set_markup_internal (GtkLabel *label,
456 const gchar *str,
457 gboolean with_uline);
458 static void gtk_label_recalculate (GtkLabel *label);
459 static void gtk_label_hierarchy_changed (GtkWidget *widget,
460 GtkWidget *old_toplevel);
461 static void gtk_label_screen_changed (GtkWidget *widget,
462 GdkScreen *old_screen);
463 static gboolean gtk_label_popup_menu (GtkWidget *widget);
464
465 static void gtk_label_create_window (GtkLabel *label);
466 static void gtk_label_destroy_window (GtkLabel *label);
467 static void gtk_label_ensure_select_info (GtkLabel *label);
468 static void gtk_label_clear_select_info (GtkLabel *label);
469 static void gtk_label_update_cursor (GtkLabel *label);
470 static void gtk_label_clear_layout (GtkLabel *label);
471 static void gtk_label_ensure_layout (GtkLabel *label);
472 static void gtk_label_select_region_index (GtkLabel *label,
473 gint anchor_index,
474 gint end_index);
475
476 static void gtk_label_update_active_link (GtkWidget *widget,
477 gdouble x,
478 gdouble y);
479
480 static gboolean gtk_label_mnemonic_activate (GtkWidget *widget,
481 gboolean group_cycling);
482 static void gtk_label_setup_mnemonic (GtkLabel *label,
483 guint last_key);
484 static void gtk_label_drag_data_get (GtkWidget *widget,
485 GdkDragContext *context,
486 GtkSelectionData *selection_data,
487 guint info,
488 guint time);
489
490 static void gtk_label_buildable_interface_init (GtkBuildableIface *iface);
491 static gboolean gtk_label_buildable_custom_tag_start (GtkBuildable *buildable,
492 GtkBuilder *builder,
493 GObject *child,
494 const gchar *tagname,
495 GMarkupParser *parser,
496 gpointer *data);
497
498 static void gtk_label_buildable_custom_finished (GtkBuildable *buildable,
499 GtkBuilder *builder,
500 GObject *child,
501 const gchar *tagname,
502 gpointer user_data);
503
504
505 static void connect_mnemonics_visible_notify (GtkLabel *label);
506 static gboolean separate_uline_pattern (const gchar *str,
507 guint *accel_key,
508 gchar **new_str,
509 gchar **pattern);
510
511
512 /* For selectable labels: */
513 static void gtk_label_move_cursor (GtkLabel *label,
514 GtkMovementStep step,
515 gint count,
516 gboolean extend_selection);
517 static void gtk_label_copy_clipboard (GtkLabel *label);
518 static void gtk_label_select_all (GtkLabel *label);
519 static void gtk_label_do_popup (GtkLabel *label,
520 const GdkEvent *event);
521 static gint gtk_label_move_forward_word (GtkLabel *label,
522 gint start);
523 static gint gtk_label_move_backward_word (GtkLabel *label,
524 gint start);
525
526 /* For links: */
527 static void gtk_label_clear_links (GtkLabel *label);
528 static gboolean gtk_label_activate_link (GtkLabel *label,
529 const gchar *uri);
530 static void gtk_label_activate_current_link (GtkLabel *label);
531 static GtkLabelLink *gtk_label_get_current_link (GtkLabel *label);
532 static void emit_activate_link (GtkLabel *label,
533 GtkLabelLink *link);
534
535 /* Event controller callbacks */
536 static void gtk_label_multipress_gesture_pressed (GtkGestureMultiPress *gesture,
537 gint n_press,
538 gdouble x,
539 gdouble y,
540 GtkLabel *label);
541 static void gtk_label_multipress_gesture_released (GtkGestureMultiPress *gesture,
542 gint n_press,
543 gdouble x,
544 gdouble y,
545 GtkLabel *label);
546 static void gtk_label_drag_gesture_begin (GtkGestureDrag *gesture,
547 gdouble start_x,
548 gdouble start_y,
549 GtkLabel *label);
550 static void gtk_label_drag_gesture_update (GtkGestureDrag *gesture,
551 gdouble offset_x,
552 gdouble offset_y,
553 GtkLabel *label);
554
555 static GtkSizeRequestMode gtk_label_get_request_mode (GtkWidget *widget);
556 static void gtk_label_get_preferred_width (GtkWidget *widget,
557 gint *minimum_size,
558 gint *natural_size);
559 static void gtk_label_get_preferred_height (GtkWidget *widget,
560 gint *minimum_size,
561 gint *natural_size);
562 static void gtk_label_get_preferred_width_for_height (GtkWidget *widget,
563 gint height,
564 gint *minimum_width,
565 gint *natural_width);
566 static void gtk_label_get_preferred_height_for_width (GtkWidget *widget,
567 gint width,
568 gint *minimum_height,
569 gint *natural_height);
570 static void gtk_label_get_preferred_height_and_baseline_for_width (GtkWidget *widget,
571 gint width,
572 gint *minimum_height,
573 gint *natural_height,
574 gint *minimum_baseline,
575 gint *natural_baseline);
576
577 static void gtk_label_measure (GtkCssGadget *gadget,
578 GtkOrientation orientation,
579 int for_size,
580 int *minimum,
581 int *natural,
582 int *minimum_baseline,
583 int *natural_baseline,
584 gpointer unused);
585 static gboolean gtk_label_render (GtkCssGadget *gadget,
586 cairo_t *cr,
587 int x,
588 int y,
589 int width,
590 int height,
591 gpointer data);
592
593 static GtkBuildableIface *buildable_parent_iface = NULL;
594
595 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
G_DEFINE_TYPE_WITH_CODE(GtkLabel,gtk_label,GTK_TYPE_MISC,G_ADD_PRIVATE (GtkLabel)G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,gtk_label_buildable_interface_init))596 G_DEFINE_TYPE_WITH_CODE (GtkLabel, gtk_label, GTK_TYPE_MISC,
597 G_ADD_PRIVATE (GtkLabel)
598 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
599 gtk_label_buildable_interface_init))
600 G_GNUC_END_IGNORE_DEPRECATIONS
601
602 static void
603 add_move_binding (GtkBindingSet *binding_set,
604 guint keyval,
605 guint modmask,
606 GtkMovementStep step,
607 gint count)
608 {
609 g_return_if_fail ((modmask & GDK_SHIFT_MASK) == 0);
610
611 gtk_binding_entry_add_signal (binding_set, keyval, modmask,
612 "move-cursor", 3,
613 G_TYPE_ENUM, step,
614 G_TYPE_INT, count,
615 G_TYPE_BOOLEAN, FALSE);
616
617 /* Selection-extending version */
618 gtk_binding_entry_add_signal (binding_set, keyval, modmask | GDK_SHIFT_MASK,
619 "move-cursor", 3,
620 G_TYPE_ENUM, step,
621 G_TYPE_INT, count,
622 G_TYPE_BOOLEAN, TRUE);
623 }
624
625 static void
gtk_label_class_init(GtkLabelClass * class)626 gtk_label_class_init (GtkLabelClass *class)
627 {
628 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
629 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
630 GtkBindingSet *binding_set;
631
632 gobject_class->set_property = gtk_label_set_property;
633 gobject_class->get_property = gtk_label_get_property;
634 gobject_class->finalize = gtk_label_finalize;
635
636 widget_class->destroy = gtk_label_destroy;
637 widget_class->size_allocate = gtk_label_size_allocate;
638 widget_class->state_flags_changed = gtk_label_state_flags_changed;
639 widget_class->style_updated = gtk_label_style_updated;
640 widget_class->query_tooltip = gtk_label_query_tooltip;
641 widget_class->draw = gtk_label_draw;
642 widget_class->realize = gtk_label_realize;
643 widget_class->unrealize = gtk_label_unrealize;
644 widget_class->map = gtk_label_map;
645 widget_class->unmap = gtk_label_unmap;
646 widget_class->motion_notify_event = gtk_label_motion;
647 widget_class->leave_notify_event = gtk_label_leave_notify;
648 widget_class->hierarchy_changed = gtk_label_hierarchy_changed;
649 widget_class->screen_changed = gtk_label_screen_changed;
650 widget_class->mnemonic_activate = gtk_label_mnemonic_activate;
651 widget_class->drag_data_get = gtk_label_drag_data_get;
652 widget_class->grab_focus = gtk_label_grab_focus;
653 widget_class->popup_menu = gtk_label_popup_menu;
654 widget_class->focus = gtk_label_focus;
655 widget_class->get_request_mode = gtk_label_get_request_mode;
656 widget_class->get_preferred_width = gtk_label_get_preferred_width;
657 widget_class->get_preferred_height = gtk_label_get_preferred_height;
658 widget_class->get_preferred_width_for_height = gtk_label_get_preferred_width_for_height;
659 widget_class->get_preferred_height_for_width = gtk_label_get_preferred_height_for_width;
660 widget_class->get_preferred_height_and_baseline_for_width = gtk_label_get_preferred_height_and_baseline_for_width;
661
662 class->move_cursor = gtk_label_move_cursor;
663 class->copy_clipboard = gtk_label_copy_clipboard;
664 class->activate_link = gtk_label_activate_link;
665
666 /**
667 * GtkLabel::move-cursor:
668 * @entry: the object which received the signal
669 * @step: the granularity of the move, as a #GtkMovementStep
670 * @count: the number of @step units to move
671 * @extend_selection: %TRUE if the move should extend the selection
672 *
673 * The ::move-cursor signal is a
674 * [keybinding signal][GtkBindingSignal]
675 * which gets emitted when the user initiates a cursor movement.
676 * If the cursor is not visible in @entry, this signal causes
677 * the viewport to be moved instead.
678 *
679 * Applications should not connect to it, but may emit it with
680 * g_signal_emit_by_name() if they need to control the cursor
681 * programmatically.
682 *
683 * The default bindings for this signal come in two variants,
684 * the variant with the Shift modifier extends the selection,
685 * the variant without the Shift modifer does not.
686 * There are too many key combinations to list them all here.
687 * - Arrow keys move by individual characters/lines
688 * - Ctrl-arrow key combinations move by words/paragraphs
689 * - Home/End keys move to the ends of the buffer
690 */
691 signals[MOVE_CURSOR] =
692 g_signal_new (I_("move-cursor"),
693 G_OBJECT_CLASS_TYPE (gobject_class),
694 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
695 G_STRUCT_OFFSET (GtkLabelClass, move_cursor),
696 NULL, NULL,
697 _gtk_marshal_VOID__ENUM_INT_BOOLEAN,
698 G_TYPE_NONE, 3,
699 GTK_TYPE_MOVEMENT_STEP,
700 G_TYPE_INT,
701 G_TYPE_BOOLEAN);
702
703 /**
704 * GtkLabel::copy-clipboard:
705 * @label: the object which received the signal
706 *
707 * The ::copy-clipboard signal is a
708 * [keybinding signal][GtkBindingSignal]
709 * which gets emitted to copy the selection to the clipboard.
710 *
711 * The default binding for this signal is Ctrl-c.
712 */
713 signals[COPY_CLIPBOARD] =
714 g_signal_new (I_("copy-clipboard"),
715 G_OBJECT_CLASS_TYPE (gobject_class),
716 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
717 G_STRUCT_OFFSET (GtkLabelClass, copy_clipboard),
718 NULL, NULL,
719 NULL,
720 G_TYPE_NONE, 0);
721
722 /**
723 * GtkLabel::populate-popup:
724 * @label: The label on which the signal is emitted
725 * @menu: the menu that is being populated
726 *
727 * The ::populate-popup signal gets emitted before showing the
728 * context menu of the label. Note that only selectable labels
729 * have context menus.
730 *
731 * If you need to add items to the context menu, connect
732 * to this signal and append your menuitems to the @menu.
733 */
734 signals[POPULATE_POPUP] =
735 g_signal_new (I_("populate-popup"),
736 G_OBJECT_CLASS_TYPE (gobject_class),
737 G_SIGNAL_RUN_LAST,
738 G_STRUCT_OFFSET (GtkLabelClass, populate_popup),
739 NULL, NULL,
740 NULL,
741 G_TYPE_NONE, 1,
742 GTK_TYPE_MENU);
743
744 /**
745 * GtkLabel::activate-current-link:
746 * @label: The label on which the signal was emitted
747 *
748 * A [keybinding signal][GtkBindingSignal]
749 * which gets emitted when the user activates a link in the label.
750 *
751 * Applications may also emit the signal with g_signal_emit_by_name()
752 * if they need to control activation of URIs programmatically.
753 *
754 * The default bindings for this signal are all forms of the Enter key.
755 *
756 * Since: 2.18
757 */
758 signals[ACTIVATE_CURRENT_LINK] =
759 g_signal_new_class_handler (I_("activate-current-link"),
760 G_TYPE_FROM_CLASS (gobject_class),
761 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
762 G_CALLBACK (gtk_label_activate_current_link),
763 NULL, NULL,
764 NULL,
765 G_TYPE_NONE, 0);
766
767 /**
768 * GtkLabel::activate-link:
769 * @label: The label on which the signal was emitted
770 * @uri: the URI that is activated
771 *
772 * The signal which gets emitted to activate a URI.
773 * Applications may connect to it to override the default behaviour,
774 * which is to call gtk_show_uri_on_window().
775 *
776 * Returns: %TRUE if the link has been activated
777 *
778 * Since: 2.18
779 */
780 signals[ACTIVATE_LINK] =
781 g_signal_new (I_("activate-link"),
782 G_TYPE_FROM_CLASS (gobject_class),
783 G_SIGNAL_RUN_LAST,
784 G_STRUCT_OFFSET (GtkLabelClass, activate_link),
785 _gtk_boolean_handled_accumulator, NULL,
786 _gtk_marshal_BOOLEAN__STRING,
787 G_TYPE_BOOLEAN, 1, G_TYPE_STRING);
788
789 /**
790 * GtkLabel:label:
791 *
792 * The contents of the label.
793 *
794 * If the string contains [Pango XML markup][PangoMarkupFormat], you will
795 * have to set the #GtkLabel:use-markup property to %TRUE in order for the
796 * label to display the markup attributes. See also gtk_label_set_markup()
797 * for a convenience function that sets both this property and the
798 * #GtkLabel:use-markup property at the same time.
799 *
800 * If the string contains underlines acting as mnemonics, you will have to
801 * set the #GtkLabel:use-underline property to %TRUE in order for the label
802 * to display them.
803 */
804 label_props[PROP_LABEL] =
805 g_param_spec_string ("label",
806 P_("Label"),
807 P_("The text of the label"),
808 "",
809 GTK_PARAM_READWRITE);
810
811 label_props[PROP_ATTRIBUTES] =
812 g_param_spec_boxed ("attributes",
813 P_("Attributes"),
814 P_("A list of style attributes to apply to the text of the label"),
815 PANGO_TYPE_ATTR_LIST,
816 GTK_PARAM_READWRITE);
817
818 label_props[PROP_USE_MARKUP] =
819 g_param_spec_boolean ("use-markup",
820 P_("Use markup"),
821 P_("The text of the label includes XML markup. See pango_parse_markup()"),
822 FALSE,
823 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
824
825 label_props[PROP_USE_UNDERLINE] =
826 g_param_spec_boolean ("use-underline",
827 P_("Use underline"),
828 P_("If set, an underline in the text indicates the next character should be used for the mnemonic accelerator key"),
829 FALSE,
830 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
831
832 label_props[PROP_JUSTIFY] =
833 g_param_spec_enum ("justify",
834 P_("Justification"),
835 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 GtkLabel:xalign for that"),
836 GTK_TYPE_JUSTIFICATION,
837 GTK_JUSTIFY_LEFT,
838 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
839
840 /**
841 * GtkLabel:xalign:
842 *
843 * The xalign property determines the horizontal aligment of the label text
844 * inside the labels size allocation. Compare this to #GtkWidget:halign,
845 * which determines how the labels size allocation is positioned in the
846 * space available for the label.
847 *
848 * Since: 3.16
849 */
850 label_props[PROP_XALIGN] =
851 g_param_spec_float ("xalign",
852 P_("X align"),
853 P_("The horizontal alignment, from 0 (left) to 1 (right). Reversed for RTL layouts."),
854 0.0, 1.0,
855 0.5,
856 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
857
858 /**
859 * GtkLabel:yalign:
860 *
861 * The yalign property determines the vertical aligment of the label text
862 * inside the labels size allocation. Compare this to #GtkWidget:valign,
863 * which determines how the labels size allocation is positioned in the
864 * space available for the label.
865 *
866 * Since: 3.16
867 */
868 label_props[PROP_YALIGN] =
869 g_param_spec_float ("yalign",
870 P_("Y align"),
871 P_("The vertical alignment, from 0 (top) to 1 (bottom)"),
872 0.0, 1.0,
873 0.5,
874 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
875
876 label_props[PROP_PATTERN] =
877 g_param_spec_string ("pattern",
878 P_("Pattern"),
879 P_("A string with _ characters in positions correspond to characters in the text to underline"),
880 NULL,
881 GTK_PARAM_WRITABLE);
882
883 label_props[PROP_WRAP] =
884 g_param_spec_boolean ("wrap",
885 P_("Line wrap"),
886 P_("If set, wrap lines if the text becomes too wide"),
887 FALSE,
888 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
889
890 /**
891 * GtkLabel:wrap-mode:
892 *
893 * If line wrapping is on (see the #GtkLabel:wrap property) this controls
894 * how the line wrapping is done. The default is %PANGO_WRAP_WORD, which
895 * means wrap on word boundaries.
896 *
897 * Since: 2.10
898 */
899 label_props[PROP_WRAP_MODE] =
900 g_param_spec_enum ("wrap-mode",
901 P_("Line wrap mode"),
902 P_("If wrap is set, controls how linewrapping is done"),
903 PANGO_TYPE_WRAP_MODE,
904 PANGO_WRAP_WORD,
905 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
906
907 label_props[PROP_SELECTABLE] =
908 g_param_spec_boolean ("selectable",
909 P_("Selectable"),
910 P_("Whether the label text can be selected with the mouse"),
911 FALSE,
912 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
913
914 label_props[PROP_MNEMONIC_KEYVAL] =
915 g_param_spec_uint ("mnemonic-keyval",
916 P_("Mnemonic key"),
917 P_("The mnemonic accelerator key for this label"),
918 0, G_MAXUINT,
919 GDK_KEY_VoidSymbol,
920 GTK_PARAM_READABLE);
921
922 label_props[PROP_MNEMONIC_WIDGET] =
923 g_param_spec_object ("mnemonic-widget",
924 P_("Mnemonic widget"),
925 P_("The widget to be activated when the label's mnemonic key is pressed"),
926 GTK_TYPE_WIDGET,
927 GTK_PARAM_READWRITE);
928
929 label_props[PROP_CURSOR_POSITION] =
930 g_param_spec_int ("cursor-position",
931 P_("Cursor Position"),
932 P_("The current position of the insertion cursor in chars"),
933 0, G_MAXINT,
934 0,
935 GTK_PARAM_READABLE);
936
937 label_props[PROP_SELECTION_BOUND] =
938 g_param_spec_int ("selection-bound",
939 P_("Selection Bound"),
940 P_("The position of the opposite end of the selection from the cursor in chars"),
941 0, G_MAXINT,
942 0,
943 GTK_PARAM_READABLE);
944
945 /**
946 * GtkLabel:ellipsize:
947 *
948 * The preferred place to ellipsize the string, if the label does
949 * not have enough room to display the entire string, specified as a
950 * #PangoEllipsizeMode.
951 *
952 * Note that setting this property to a value other than
953 * %PANGO_ELLIPSIZE_NONE has the side-effect that the label requests
954 * only enough space to display the ellipsis "...". In particular, this
955 * means that ellipsizing labels do not work well in notebook tabs, unless
956 * the #GtkNotebook tab-expand child property is set to %TRUE. Other ways
957 * to set a label's width are gtk_widget_set_size_request() and
958 * gtk_label_set_width_chars().
959 *
960 * Since: 2.6
961 */
962 label_props[PROP_ELLIPSIZE] =
963 g_param_spec_enum ("ellipsize",
964 P_("Ellipsize"),
965 P_("The preferred place to ellipsize the string, if the label does not have enough room to display the entire string"),
966 PANGO_TYPE_ELLIPSIZE_MODE,
967 PANGO_ELLIPSIZE_NONE,
968 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
969
970 /**
971 * GtkLabel:width-chars:
972 *
973 * The desired width of the label, in characters. If this property is set to
974 * -1, the width will be calculated automatically.
975 *
976 * See the section on [text layout][label-text-layout]
977 * for details of how #GtkLabel:width-chars and #GtkLabel:max-width-chars
978 * determine the width of ellipsized and wrapped labels.
979 *
980 * Since: 2.6
981 **/
982 label_props[PROP_WIDTH_CHARS] =
983 g_param_spec_int ("width-chars",
984 P_("Width In Characters"),
985 P_("The desired width of the label, in characters"),
986 -1, G_MAXINT,
987 -1,
988 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
989
990 /**
991 * GtkLabel:single-line-mode:
992 *
993 * Whether the label is in single line mode. In single line mode,
994 * the height of the label does not depend on the actual text, it
995 * is always set to ascent + descent of the font. This can be an
996 * advantage in situations where resizing the label because of text
997 * changes would be distracting, e.g. in a statusbar.
998 *
999 * Since: 2.6
1000 **/
1001 label_props[PROP_SINGLE_LINE_MODE] =
1002 g_param_spec_boolean ("single-line-mode",
1003 P_("Single Line Mode"),
1004 P_("Whether the label is in single line mode"),
1005 FALSE,
1006 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
1007
1008 /**
1009 * GtkLabel:angle:
1010 *
1011 * The angle that the baseline of the label makes with the horizontal,
1012 * in degrees, measured counterclockwise. An angle of 90 reads from
1013 * from bottom to top, an angle of 270, from top to bottom. Ignored
1014 * if the label is selectable.
1015 *
1016 * Since: 2.6
1017 **/
1018 label_props[PROP_ANGLE] =
1019 g_param_spec_double ("angle",
1020 P_("Angle"),
1021 P_("Angle at which the label is rotated"),
1022 0.0, 360.0,
1023 0.0,
1024 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
1025
1026 /**
1027 * GtkLabel:max-width-chars:
1028 *
1029 * The desired maximum width of the label, in characters. If this property
1030 * is set to -1, the width will be calculated automatically.
1031 *
1032 * See the section on [text layout][label-text-layout]
1033 * for details of how #GtkLabel:width-chars and #GtkLabel:max-width-chars
1034 * determine the width of ellipsized and wrapped labels.
1035 *
1036 * Since: 2.6
1037 **/
1038 label_props[PROP_MAX_WIDTH_CHARS] =
1039 g_param_spec_int ("max-width-chars",
1040 P_("Maximum Width In Characters"),
1041 P_("The desired maximum width of the label, in characters"),
1042 -1, G_MAXINT,
1043 -1,
1044 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
1045
1046 /**
1047 * GtkLabel:track-visited-links:
1048 *
1049 * Set this property to %TRUE to make the label track which links
1050 * have been visited. It will then apply the #GTK_STATE_FLAG_VISITED
1051 * when rendering this link, in addition to #GTK_STATE_FLAG_LINK.
1052 *
1053 * Since: 2.18
1054 */
1055 label_props[PROP_TRACK_VISITED_LINKS] =
1056 g_param_spec_boolean ("track-visited-links",
1057 P_("Track visited links"),
1058 P_("Whether visited links should be tracked"),
1059 TRUE,
1060 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
1061
1062 /**
1063 * GtkLabel:lines:
1064 *
1065 * The number of lines to which an ellipsized, wrapping label
1066 * should be limited. This property has no effect if the
1067 * label is not wrapping or ellipsized. Set this property to
1068 * -1 if you don't want to limit the number of lines.
1069 *
1070 * Since: 3.10
1071 */
1072 label_props[PROP_LINES] =
1073 g_param_spec_int ("lines",
1074 P_("Number of lines"),
1075 P_("The desired number of lines, when ellipsizing a wrapping label"),
1076 -1, G_MAXINT,
1077 -1,
1078 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
1079
1080 g_object_class_install_properties (gobject_class, NUM_PROPERTIES, label_props);
1081
1082 /*
1083 * Key bindings
1084 */
1085 binding_set = gtk_binding_set_by_class (class);
1086
1087 /* Moving the insertion point */
1088 add_move_binding (binding_set, GDK_KEY_Right, 0,
1089 GTK_MOVEMENT_VISUAL_POSITIONS, 1);
1090
1091 add_move_binding (binding_set, GDK_KEY_Left, 0,
1092 GTK_MOVEMENT_VISUAL_POSITIONS, -1);
1093
1094 add_move_binding (binding_set, GDK_KEY_KP_Right, 0,
1095 GTK_MOVEMENT_VISUAL_POSITIONS, 1);
1096
1097 add_move_binding (binding_set, GDK_KEY_KP_Left, 0,
1098 GTK_MOVEMENT_VISUAL_POSITIONS, -1);
1099
1100 add_move_binding (binding_set, GDK_KEY_f, GDK_CONTROL_MASK,
1101 GTK_MOVEMENT_LOGICAL_POSITIONS, 1);
1102
1103 add_move_binding (binding_set, GDK_KEY_b, GDK_CONTROL_MASK,
1104 GTK_MOVEMENT_LOGICAL_POSITIONS, -1);
1105
1106 add_move_binding (binding_set, GDK_KEY_Right, GDK_CONTROL_MASK,
1107 GTK_MOVEMENT_WORDS, 1);
1108
1109 add_move_binding (binding_set, GDK_KEY_Left, GDK_CONTROL_MASK,
1110 GTK_MOVEMENT_WORDS, -1);
1111
1112 add_move_binding (binding_set, GDK_KEY_KP_Right, GDK_CONTROL_MASK,
1113 GTK_MOVEMENT_WORDS, 1);
1114
1115 add_move_binding (binding_set, GDK_KEY_KP_Left, GDK_CONTROL_MASK,
1116 GTK_MOVEMENT_WORDS, -1);
1117
1118 /* select all */
1119 gtk_binding_entry_add_signal (binding_set, GDK_KEY_a, GDK_CONTROL_MASK,
1120 "move-cursor", 3,
1121 G_TYPE_ENUM, GTK_MOVEMENT_PARAGRAPH_ENDS,
1122 G_TYPE_INT, -1,
1123 G_TYPE_BOOLEAN, FALSE);
1124
1125 gtk_binding_entry_add_signal (binding_set, GDK_KEY_a, GDK_CONTROL_MASK,
1126 "move-cursor", 3,
1127 G_TYPE_ENUM, GTK_MOVEMENT_PARAGRAPH_ENDS,
1128 G_TYPE_INT, 1,
1129 G_TYPE_BOOLEAN, TRUE);
1130
1131 gtk_binding_entry_add_signal (binding_set, GDK_KEY_slash, GDK_CONTROL_MASK,
1132 "move-cursor", 3,
1133 G_TYPE_ENUM, GTK_MOVEMENT_PARAGRAPH_ENDS,
1134 G_TYPE_INT, -1,
1135 G_TYPE_BOOLEAN, FALSE);
1136
1137 gtk_binding_entry_add_signal (binding_set, GDK_KEY_slash, GDK_CONTROL_MASK,
1138 "move-cursor", 3,
1139 G_TYPE_ENUM, GTK_MOVEMENT_PARAGRAPH_ENDS,
1140 G_TYPE_INT, 1,
1141 G_TYPE_BOOLEAN, TRUE);
1142
1143 /* unselect all */
1144 gtk_binding_entry_add_signal (binding_set, GDK_KEY_a, GDK_SHIFT_MASK | GDK_CONTROL_MASK,
1145 "move-cursor", 3,
1146 G_TYPE_ENUM, GTK_MOVEMENT_PARAGRAPH_ENDS,
1147 G_TYPE_INT, 0,
1148 G_TYPE_BOOLEAN, FALSE);
1149
1150 gtk_binding_entry_add_signal (binding_set, GDK_KEY_backslash, GDK_CONTROL_MASK,
1151 "move-cursor", 3,
1152 G_TYPE_ENUM, GTK_MOVEMENT_PARAGRAPH_ENDS,
1153 G_TYPE_INT, 0,
1154 G_TYPE_BOOLEAN, FALSE);
1155
1156 add_move_binding (binding_set, GDK_KEY_f, GDK_MOD1_MASK,
1157 GTK_MOVEMENT_WORDS, 1);
1158
1159 add_move_binding (binding_set, GDK_KEY_b, GDK_MOD1_MASK,
1160 GTK_MOVEMENT_WORDS, -1);
1161
1162 add_move_binding (binding_set, GDK_KEY_Home, 0,
1163 GTK_MOVEMENT_DISPLAY_LINE_ENDS, -1);
1164
1165 add_move_binding (binding_set, GDK_KEY_End, 0,
1166 GTK_MOVEMENT_DISPLAY_LINE_ENDS, 1);
1167
1168 add_move_binding (binding_set, GDK_KEY_KP_Home, 0,
1169 GTK_MOVEMENT_DISPLAY_LINE_ENDS, -1);
1170
1171 add_move_binding (binding_set, GDK_KEY_KP_End, 0,
1172 GTK_MOVEMENT_DISPLAY_LINE_ENDS, 1);
1173
1174 add_move_binding (binding_set, GDK_KEY_Home, GDK_CONTROL_MASK,
1175 GTK_MOVEMENT_BUFFER_ENDS, -1);
1176
1177 add_move_binding (binding_set, GDK_KEY_End, GDK_CONTROL_MASK,
1178 GTK_MOVEMENT_BUFFER_ENDS, 1);
1179
1180 add_move_binding (binding_set, GDK_KEY_KP_Home, GDK_CONTROL_MASK,
1181 GTK_MOVEMENT_BUFFER_ENDS, -1);
1182
1183 add_move_binding (binding_set, GDK_KEY_KP_End, GDK_CONTROL_MASK,
1184 GTK_MOVEMENT_BUFFER_ENDS, 1);
1185
1186 /* copy */
1187 gtk_binding_entry_add_signal (binding_set, GDK_KEY_c, GDK_CONTROL_MASK,
1188 "copy-clipboard", 0);
1189
1190 gtk_binding_entry_add_signal (binding_set, GDK_KEY_Return, 0,
1191 "activate-current-link", 0);
1192 gtk_binding_entry_add_signal (binding_set, GDK_KEY_ISO_Enter, 0,
1193 "activate-current-link", 0);
1194 gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Enter, 0,
1195 "activate-current-link", 0);
1196
1197 gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_LABEL_ACCESSIBLE);
1198
1199 gtk_widget_class_set_css_name (widget_class, "label");
1200
1201 quark_shortcuts_connected = g_quark_from_static_string ("gtk-label-shortcuts-connected");
1202 quark_mnemonic_menu = g_quark_from_static_string ("gtk-mnemonic-menu");
1203 quark_mnemonics_visible_connected = g_quark_from_static_string ("gtk-label-mnemonics-visible-connected");
1204 quark_gtk_signal = g_quark_from_static_string ("gtk-signal");
1205 quark_link = g_quark_from_static_string ("link");
1206 }
1207
1208 static void
gtk_label_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1209 gtk_label_set_property (GObject *object,
1210 guint prop_id,
1211 const GValue *value,
1212 GParamSpec *pspec)
1213 {
1214 GtkLabel *label = GTK_LABEL (object);
1215
1216 switch (prop_id)
1217 {
1218 case PROP_LABEL:
1219 gtk_label_set_label (label, g_value_get_string (value));
1220 break;
1221 case PROP_ATTRIBUTES:
1222 gtk_label_set_attributes (label, g_value_get_boxed (value));
1223 break;
1224 case PROP_USE_MARKUP:
1225 gtk_label_set_use_markup (label, g_value_get_boolean (value));
1226 break;
1227 case PROP_USE_UNDERLINE:
1228 gtk_label_set_use_underline (label, g_value_get_boolean (value));
1229 break;
1230 case PROP_JUSTIFY:
1231 gtk_label_set_justify (label, g_value_get_enum (value));
1232 break;
1233 case PROP_PATTERN:
1234 gtk_label_set_pattern (label, g_value_get_string (value));
1235 break;
1236 case PROP_WRAP:
1237 gtk_label_set_line_wrap (label, g_value_get_boolean (value));
1238 break;
1239 case PROP_WRAP_MODE:
1240 gtk_label_set_line_wrap_mode (label, g_value_get_enum (value));
1241 break;
1242 case PROP_SELECTABLE:
1243 gtk_label_set_selectable (label, g_value_get_boolean (value));
1244 break;
1245 case PROP_MNEMONIC_WIDGET:
1246 gtk_label_set_mnemonic_widget (label, (GtkWidget*) g_value_get_object (value));
1247 break;
1248 case PROP_ELLIPSIZE:
1249 gtk_label_set_ellipsize (label, g_value_get_enum (value));
1250 break;
1251 case PROP_WIDTH_CHARS:
1252 gtk_label_set_width_chars (label, g_value_get_int (value));
1253 break;
1254 case PROP_SINGLE_LINE_MODE:
1255 gtk_label_set_single_line_mode (label, g_value_get_boolean (value));
1256 break;
1257 case PROP_ANGLE:
1258 gtk_label_set_angle (label, g_value_get_double (value));
1259 break;
1260 case PROP_MAX_WIDTH_CHARS:
1261 gtk_label_set_max_width_chars (label, g_value_get_int (value));
1262 break;
1263 case PROP_TRACK_VISITED_LINKS:
1264 gtk_label_set_track_visited_links (label, g_value_get_boolean (value));
1265 break;
1266 case PROP_LINES:
1267 gtk_label_set_lines (label, g_value_get_int (value));
1268 break;
1269 case PROP_XALIGN:
1270 gtk_label_set_xalign (label, g_value_get_float (value));
1271 break;
1272 case PROP_YALIGN:
1273 gtk_label_set_yalign (label, g_value_get_float (value));
1274 break;
1275 default:
1276 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1277 break;
1278 }
1279 }
1280
1281 static void
gtk_label_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1282 gtk_label_get_property (GObject *object,
1283 guint prop_id,
1284 GValue *value,
1285 GParamSpec *pspec)
1286 {
1287 GtkLabel *label = GTK_LABEL (object);
1288 GtkLabelPrivate *priv = label->priv;
1289
1290 switch (prop_id)
1291 {
1292 case PROP_LABEL:
1293 g_value_set_string (value, priv->label);
1294 break;
1295 case PROP_ATTRIBUTES:
1296 g_value_set_boxed (value, priv->attrs);
1297 break;
1298 case PROP_USE_MARKUP:
1299 g_value_set_boolean (value, priv->use_markup);
1300 break;
1301 case PROP_USE_UNDERLINE:
1302 g_value_set_boolean (value, priv->use_underline);
1303 break;
1304 case PROP_JUSTIFY:
1305 g_value_set_enum (value, priv->jtype);
1306 break;
1307 case PROP_WRAP:
1308 g_value_set_boolean (value, priv->wrap);
1309 break;
1310 case PROP_WRAP_MODE:
1311 g_value_set_enum (value, priv->wrap_mode);
1312 break;
1313 case PROP_SELECTABLE:
1314 g_value_set_boolean (value, gtk_label_get_selectable (label));
1315 break;
1316 case PROP_MNEMONIC_KEYVAL:
1317 g_value_set_uint (value, priv->mnemonic_keyval);
1318 break;
1319 case PROP_MNEMONIC_WIDGET:
1320 g_value_set_object (value, (GObject*) priv->mnemonic_widget);
1321 break;
1322 case PROP_CURSOR_POSITION:
1323 g_value_set_int (value, _gtk_label_get_cursor_position (label));
1324 break;
1325 case PROP_SELECTION_BOUND:
1326 g_value_set_int (value, _gtk_label_get_selection_bound (label));
1327 break;
1328 case PROP_ELLIPSIZE:
1329 g_value_set_enum (value, priv->ellipsize);
1330 break;
1331 case PROP_WIDTH_CHARS:
1332 g_value_set_int (value, gtk_label_get_width_chars (label));
1333 break;
1334 case PROP_SINGLE_LINE_MODE:
1335 g_value_set_boolean (value, gtk_label_get_single_line_mode (label));
1336 break;
1337 case PROP_ANGLE:
1338 g_value_set_double (value, gtk_label_get_angle (label));
1339 break;
1340 case PROP_MAX_WIDTH_CHARS:
1341 g_value_set_int (value, gtk_label_get_max_width_chars (label));
1342 break;
1343 case PROP_TRACK_VISITED_LINKS:
1344 g_value_set_boolean (value, gtk_label_get_track_visited_links (label));
1345 break;
1346 case PROP_LINES:
1347 g_value_set_int (value, gtk_label_get_lines (label));
1348 break;
1349 case PROP_XALIGN:
1350 g_value_set_float (value, gtk_label_get_xalign (label));
1351 break;
1352 case PROP_YALIGN:
1353 g_value_set_float (value, gtk_label_get_yalign (label));
1354 break;
1355 default:
1356 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1357 break;
1358 }
1359 }
1360
1361 static void
gtk_label_init(GtkLabel * label)1362 gtk_label_init (GtkLabel *label)
1363 {
1364 GtkLabelPrivate *priv;
1365
1366 label->priv = gtk_label_get_instance_private (label);
1367 priv = label->priv;
1368
1369 gtk_widget_set_has_window (GTK_WIDGET (label), FALSE);
1370
1371 priv->width_chars = -1;
1372 priv->max_width_chars = -1;
1373 priv->label = g_strdup ("");
1374 priv->lines = -1;
1375
1376 priv->xalign = 0.5;
1377 priv->yalign = 0.5;
1378
1379 priv->jtype = GTK_JUSTIFY_LEFT;
1380 priv->wrap = FALSE;
1381 priv->wrap_mode = PANGO_WRAP_WORD;
1382 priv->ellipsize = PANGO_ELLIPSIZE_NONE;
1383
1384 priv->use_underline = FALSE;
1385 priv->use_markup = FALSE;
1386 priv->pattern_set = FALSE;
1387 priv->track_links = TRUE;
1388
1389 priv->mnemonic_keyval = GDK_KEY_VoidSymbol;
1390 priv->layout = NULL;
1391 priv->text = g_strdup ("");
1392 priv->attrs = NULL;
1393
1394 priv->mnemonic_widget = NULL;
1395 priv->mnemonic_window = NULL;
1396
1397 priv->mnemonics_visible = TRUE;
1398
1399 priv->gadget = gtk_css_custom_gadget_new_for_node (gtk_widget_get_css_node (GTK_WIDGET (label)),
1400 GTK_WIDGET (label),
1401 gtk_label_measure,
1402 NULL,
1403 gtk_label_render,
1404 NULL,
1405 NULL);
1406 }
1407
1408
1409 static void
gtk_label_buildable_interface_init(GtkBuildableIface * iface)1410 gtk_label_buildable_interface_init (GtkBuildableIface *iface)
1411 {
1412 buildable_parent_iface = g_type_interface_peek_parent (iface);
1413
1414 iface->custom_tag_start = gtk_label_buildable_custom_tag_start;
1415 iface->custom_finished = gtk_label_buildable_custom_finished;
1416 }
1417
1418 typedef struct {
1419 GtkBuilder *builder;
1420 GObject *object;
1421 PangoAttrList *attrs;
1422 } PangoParserData;
1423
1424 static PangoAttribute *
attribute_from_text(GtkBuilder * builder,const gchar * name,const gchar * value,GError ** error)1425 attribute_from_text (GtkBuilder *builder,
1426 const gchar *name,
1427 const gchar *value,
1428 GError **error)
1429 {
1430 PangoAttribute *attribute = NULL;
1431 PangoAttrType type;
1432 PangoLanguage *language;
1433 PangoFontDescription *font_desc;
1434 GdkColor *color;
1435 GValue val = G_VALUE_INIT;
1436
1437 if (!gtk_builder_value_from_string_type (builder, PANGO_TYPE_ATTR_TYPE, name, &val, error))
1438 return NULL;
1439
1440 type = g_value_get_enum (&val);
1441 g_value_unset (&val);
1442
1443 switch (type)
1444 {
1445 /* PangoAttrLanguage */
1446 case PANGO_ATTR_LANGUAGE:
1447 if ((language = pango_language_from_string (value)))
1448 {
1449 attribute = pango_attr_language_new (language);
1450 g_value_init (&val, G_TYPE_INT);
1451 }
1452 break;
1453 /* PangoAttrInt */
1454 case PANGO_ATTR_STYLE:
1455 if (gtk_builder_value_from_string_type (builder, PANGO_TYPE_STYLE, value, &val, error))
1456 attribute = pango_attr_style_new (g_value_get_enum (&val));
1457 break;
1458 case PANGO_ATTR_WEIGHT:
1459 if (gtk_builder_value_from_string_type (builder, PANGO_TYPE_WEIGHT, value, &val, error))
1460 attribute = pango_attr_weight_new (g_value_get_enum (&val));
1461 break;
1462 case PANGO_ATTR_VARIANT:
1463 if (gtk_builder_value_from_string_type (builder, PANGO_TYPE_VARIANT, value, &val, error))
1464 attribute = pango_attr_variant_new (g_value_get_enum (&val));
1465 break;
1466 case PANGO_ATTR_STRETCH:
1467 if (gtk_builder_value_from_string_type (builder, PANGO_TYPE_STRETCH, value, &val, error))
1468 attribute = pango_attr_stretch_new (g_value_get_enum (&val));
1469 break;
1470 case PANGO_ATTR_UNDERLINE:
1471 if (gtk_builder_value_from_string_type (builder, PANGO_TYPE_UNDERLINE, value, &val, NULL))
1472 attribute = pango_attr_underline_new (g_value_get_enum (&val));
1473 else
1474 {
1475 /* XXX: allow boolean for backwards compat, so ignore error */
1476 /* Deprecate this somehow */
1477 g_value_unset (&val);
1478 if (gtk_builder_value_from_string_type (builder, G_TYPE_BOOLEAN, value, &val, error))
1479 attribute = pango_attr_underline_new (g_value_get_boolean (&val));
1480 }
1481 break;
1482 case PANGO_ATTR_STRIKETHROUGH:
1483 if (gtk_builder_value_from_string_type (builder, G_TYPE_BOOLEAN, value, &val, error))
1484 attribute = pango_attr_strikethrough_new (g_value_get_boolean (&val));
1485 break;
1486 case PANGO_ATTR_GRAVITY:
1487 if (gtk_builder_value_from_string_type (builder, PANGO_TYPE_GRAVITY, value, &val, error))
1488 attribute = pango_attr_gravity_new (g_value_get_enum (&val));
1489 break;
1490 case PANGO_ATTR_GRAVITY_HINT:
1491 if (gtk_builder_value_from_string_type (builder, PANGO_TYPE_GRAVITY_HINT, value, &val, error))
1492 attribute = pango_attr_gravity_hint_new (g_value_get_enum (&val));
1493 break;
1494 /* PangoAttrString */
1495 case PANGO_ATTR_FAMILY:
1496 attribute = pango_attr_family_new (value);
1497 g_value_init (&val, G_TYPE_INT);
1498 break;
1499
1500 /* PangoAttrSize */
1501 case PANGO_ATTR_SIZE:
1502 if (gtk_builder_value_from_string_type (builder, G_TYPE_INT, value, &val, error))
1503 attribute = pango_attr_size_new (g_value_get_int (&val));
1504 break;
1505 case PANGO_ATTR_ABSOLUTE_SIZE:
1506 if (gtk_builder_value_from_string_type (builder, G_TYPE_INT, value, &val, error))
1507 attribute = pango_attr_size_new_absolute (g_value_get_int (&val));
1508 break;
1509
1510 /* PangoAttrFontDesc */
1511 case PANGO_ATTR_FONT_DESC:
1512 if ((font_desc = pango_font_description_from_string (value)))
1513 {
1514 attribute = pango_attr_font_desc_new (font_desc);
1515 pango_font_description_free (font_desc);
1516 g_value_init (&val, G_TYPE_INT);
1517 }
1518 break;
1519
1520 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
1521
1522 /* PangoAttrColor */
1523 case PANGO_ATTR_FOREGROUND:
1524 if (gtk_builder_value_from_string_type (builder, GDK_TYPE_COLOR, value, &val, error))
1525 {
1526 color = g_value_get_boxed (&val);
1527 attribute = pango_attr_foreground_new (color->red, color->green, color->blue);
1528 }
1529 break;
1530 case PANGO_ATTR_BACKGROUND:
1531 if (gtk_builder_value_from_string_type (builder, GDK_TYPE_COLOR, value, &val, error))
1532 {
1533 color = g_value_get_boxed (&val);
1534 attribute = pango_attr_background_new (color->red, color->green, color->blue);
1535 }
1536 break;
1537 case PANGO_ATTR_UNDERLINE_COLOR:
1538 if (gtk_builder_value_from_string_type (builder, GDK_TYPE_COLOR, value, &val, error))
1539 {
1540 color = g_value_get_boxed (&val);
1541 attribute = pango_attr_underline_color_new (color->red, color->green, color->blue);
1542 }
1543 break;
1544 case PANGO_ATTR_STRIKETHROUGH_COLOR:
1545 if (gtk_builder_value_from_string_type (builder, GDK_TYPE_COLOR, value, &val, error))
1546 {
1547 color = g_value_get_boxed (&val);
1548 attribute = pango_attr_strikethrough_color_new (color->red, color->green, color->blue);
1549 }
1550 break;
1551
1552 G_GNUC_END_IGNORE_DEPRECATIONS
1553
1554 /* PangoAttrShape */
1555 case PANGO_ATTR_SHAPE:
1556 /* Unsupported for now */
1557 break;
1558 /* PangoAttrFloat */
1559 case PANGO_ATTR_SCALE:
1560 if (gtk_builder_value_from_string_type (builder, G_TYPE_DOUBLE, value, &val, error))
1561 attribute = pango_attr_scale_new (g_value_get_double (&val));
1562 break;
1563 case PANGO_ATTR_LETTER_SPACING:
1564 if (gtk_builder_value_from_string_type (builder, G_TYPE_INT, value, &val, error))
1565 attribute = pango_attr_letter_spacing_new (g_value_get_int (&val));
1566 break;
1567 case PANGO_ATTR_RISE:
1568 if (gtk_builder_value_from_string_type (builder, G_TYPE_INT, value, &val, error))
1569 attribute = pango_attr_rise_new (g_value_get_int (&val));
1570 break;
1571 case PANGO_ATTR_FALLBACK:
1572 if (gtk_builder_value_from_string_type (builder, G_TYPE_BOOLEAN, value, &val, error))
1573 attribute = pango_attr_fallback_new (g_value_get_boolean (&val));
1574 break;
1575 case PANGO_ATTR_FONT_FEATURES:
1576 attribute = pango_attr_font_features_new (value);
1577 break;
1578 case PANGO_ATTR_FOREGROUND_ALPHA:
1579 if (gtk_builder_value_from_string_type (builder, G_TYPE_INT, value, &val, error))
1580 attribute = pango_attr_foreground_alpha_new ((guint16)g_value_get_int (&val));
1581 break;
1582 case PANGO_ATTR_BACKGROUND_ALPHA:
1583 if (gtk_builder_value_from_string_type (builder, G_TYPE_INT, value, &val, error))
1584 attribute = pango_attr_background_alpha_new ((guint16)g_value_get_int (&val));
1585 break;
1586 case PANGO_ATTR_INVALID:
1587 default:
1588 break;
1589 }
1590
1591 g_value_unset (&val);
1592
1593 return attribute;
1594 }
1595
1596
1597 static void
pango_start_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** names,const gchar ** values,gpointer user_data,GError ** error)1598 pango_start_element (GMarkupParseContext *context,
1599 const gchar *element_name,
1600 const gchar **names,
1601 const gchar **values,
1602 gpointer user_data,
1603 GError **error)
1604 {
1605 PangoParserData *data = (PangoParserData*)user_data;
1606
1607 if (strcmp (element_name, "attribute") == 0)
1608 {
1609 PangoAttribute *attr = NULL;
1610 const gchar *name = NULL;
1611 const gchar *value = NULL;
1612 const gchar *start = NULL;
1613 const gchar *end = NULL;
1614 guint start_val = 0;
1615 guint end_val = G_MAXUINT;
1616 GValue val = G_VALUE_INIT;
1617
1618 if (!_gtk_builder_check_parent (data->builder, context, "attributes", error))
1619 return;
1620
1621 if (!g_markup_collect_attributes (element_name, names, values, error,
1622 G_MARKUP_COLLECT_STRING, "name", &name,
1623 G_MARKUP_COLLECT_STRING, "value", &value,
1624 G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "start", &start,
1625 G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "end", &end,
1626 G_MARKUP_COLLECT_INVALID))
1627 {
1628 _gtk_builder_prefix_error (data->builder, context, error);
1629 return;
1630 }
1631
1632 if (start)
1633 {
1634 if (!gtk_builder_value_from_string_type (data->builder, G_TYPE_UINT, start, &val, error))
1635 {
1636 _gtk_builder_prefix_error (data->builder, context, error);
1637 return;
1638 }
1639 start_val = g_value_get_uint (&val);
1640 g_value_unset (&val);
1641 }
1642
1643 if (end)
1644 {
1645 if (!gtk_builder_value_from_string_type (data->builder, G_TYPE_UINT, end, &val, error))
1646 {
1647 _gtk_builder_prefix_error (data->builder, context, error);
1648 return;
1649 }
1650 end_val = g_value_get_uint (&val);
1651 g_value_unset (&val);
1652 }
1653
1654 attr = attribute_from_text (data->builder, name, value, error);
1655 if (!attr)
1656 {
1657 _gtk_builder_prefix_error (data->builder, context, error);
1658 return;
1659 }
1660
1661 attr->start_index = start_val;
1662 attr->end_index = end_val;
1663
1664 if (!data->attrs)
1665 data->attrs = pango_attr_list_new ();
1666
1667 pango_attr_list_insert (data->attrs, attr);
1668 }
1669 else if (strcmp (element_name, "attributes") == 0)
1670 {
1671 if (!_gtk_builder_check_parent (data->builder, context, "object", error))
1672 return;
1673
1674 if (!g_markup_collect_attributes (element_name, names, values, error,
1675 G_MARKUP_COLLECT_INVALID, NULL, NULL,
1676 G_MARKUP_COLLECT_INVALID))
1677 _gtk_builder_prefix_error (data->builder, context, error);
1678 }
1679 else
1680 {
1681 _gtk_builder_error_unhandled_tag (data->builder, context,
1682 "GtkContainer", element_name,
1683 error);
1684 }
1685 }
1686
1687 static const GMarkupParser pango_parser =
1688 {
1689 pango_start_element,
1690 };
1691
1692 static gboolean
gtk_label_buildable_custom_tag_start(GtkBuildable * buildable,GtkBuilder * builder,GObject * child,const gchar * tagname,GMarkupParser * parser,gpointer * data)1693 gtk_label_buildable_custom_tag_start (GtkBuildable *buildable,
1694 GtkBuilder *builder,
1695 GObject *child,
1696 const gchar *tagname,
1697 GMarkupParser *parser,
1698 gpointer *data)
1699 {
1700 if (buildable_parent_iface->custom_tag_start (buildable, builder, child,
1701 tagname, parser, data))
1702 return TRUE;
1703
1704 if (strcmp (tagname, "attributes") == 0)
1705 {
1706 PangoParserData *parser_data;
1707
1708 parser_data = g_slice_new0 (PangoParserData);
1709 parser_data->builder = g_object_ref (builder);
1710 parser_data->object = G_OBJECT (g_object_ref (buildable));
1711 *parser = pango_parser;
1712 *data = parser_data;
1713 return TRUE;
1714 }
1715 return FALSE;
1716 }
1717
1718 static void
gtk_label_buildable_custom_finished(GtkBuildable * buildable,GtkBuilder * builder,GObject * child,const gchar * tagname,gpointer user_data)1719 gtk_label_buildable_custom_finished (GtkBuildable *buildable,
1720 GtkBuilder *builder,
1721 GObject *child,
1722 const gchar *tagname,
1723 gpointer user_data)
1724 {
1725 PangoParserData *data;
1726
1727 buildable_parent_iface->custom_finished (buildable, builder, child,
1728 tagname, user_data);
1729
1730 if (strcmp (tagname, "attributes") == 0)
1731 {
1732 data = (PangoParserData*)user_data;
1733
1734 if (data->attrs)
1735 {
1736 gtk_label_set_attributes (GTK_LABEL (buildable), data->attrs);
1737 pango_attr_list_unref (data->attrs);
1738 }
1739
1740 g_object_unref (data->object);
1741 g_object_unref (data->builder);
1742 g_slice_free (PangoParserData, data);
1743 }
1744 }
1745
1746
1747 /**
1748 * gtk_label_new:
1749 * @str: (nullable): The text of the label
1750 *
1751 * Creates a new label with the given text inside it. You can
1752 * pass %NULL to get an empty label widget.
1753 *
1754 * Returns: the new #GtkLabel
1755 **/
1756 GtkWidget*
gtk_label_new(const gchar * str)1757 gtk_label_new (const gchar *str)
1758 {
1759 GtkLabel *label;
1760
1761 label = g_object_new (GTK_TYPE_LABEL, NULL);
1762
1763 if (str && *str)
1764 gtk_label_set_text (label, str);
1765
1766 return GTK_WIDGET (label);
1767 }
1768
1769 /**
1770 * gtk_label_new_with_mnemonic:
1771 * @str: (nullable): The text of the label, with an underscore in front of the
1772 * mnemonic character
1773 *
1774 * Creates a new #GtkLabel, containing the text in @str.
1775 *
1776 * If characters in @str are preceded by an underscore, they are
1777 * underlined. If you need a literal underscore character in a label, use
1778 * '__' (two underscores). The first underlined character represents a
1779 * keyboard accelerator called a mnemonic. The mnemonic key can be used
1780 * to activate another widget, chosen automatically, or explicitly using
1781 * gtk_label_set_mnemonic_widget().
1782 *
1783 * If gtk_label_set_mnemonic_widget() is not called, then the first
1784 * activatable ancestor of the #GtkLabel will be chosen as the mnemonic
1785 * widget. For instance, if the label is inside a button or menu item,
1786 * the button or menu item will automatically become the mnemonic widget
1787 * and be activated by the mnemonic.
1788 *
1789 * Returns: the new #GtkLabel
1790 **/
1791 GtkWidget*
gtk_label_new_with_mnemonic(const gchar * str)1792 gtk_label_new_with_mnemonic (const gchar *str)
1793 {
1794 GtkLabel *label;
1795
1796 label = g_object_new (GTK_TYPE_LABEL, NULL);
1797
1798 if (str && *str)
1799 gtk_label_set_text_with_mnemonic (label, str);
1800
1801 return GTK_WIDGET (label);
1802 }
1803
1804 static gboolean
gtk_label_mnemonic_activate(GtkWidget * widget,gboolean group_cycling)1805 gtk_label_mnemonic_activate (GtkWidget *widget,
1806 gboolean group_cycling)
1807 {
1808 GtkLabel *label = GTK_LABEL (widget);
1809 GtkLabelPrivate *priv = label->priv;
1810 GtkWidget *parent;
1811
1812 if (priv->mnemonic_widget)
1813 return gtk_widget_mnemonic_activate (priv->mnemonic_widget, group_cycling);
1814
1815 /* Try to find the widget to activate by traversing the
1816 * widget's ancestry.
1817 */
1818 parent = gtk_widget_get_parent (widget);
1819
1820 if (GTK_IS_NOTEBOOK (parent))
1821 return FALSE;
1822
1823 while (parent)
1824 {
1825 if (gtk_widget_get_can_focus (parent) ||
1826 (!group_cycling && GTK_WIDGET_GET_CLASS (parent)->activate_signal) ||
1827 GTK_IS_NOTEBOOK (gtk_widget_get_parent (parent)) ||
1828 GTK_IS_MENU_ITEM (parent))
1829 return gtk_widget_mnemonic_activate (parent, group_cycling);
1830 parent = gtk_widget_get_parent (parent);
1831 }
1832
1833 /* barf if there was nothing to activate */
1834 g_warning ("Couldn't find a target for a mnemonic activation.");
1835 gtk_widget_error_bell (widget);
1836
1837 return FALSE;
1838 }
1839
1840 static void
gtk_label_setup_mnemonic(GtkLabel * label,guint last_key)1841 gtk_label_setup_mnemonic (GtkLabel *label,
1842 guint last_key)
1843 {
1844 GtkLabelPrivate *priv = label->priv;
1845 GtkWidget *widget = GTK_WIDGET (label);
1846 GtkWidget *toplevel;
1847 GtkWidget *mnemonic_menu;
1848
1849 mnemonic_menu = g_object_get_qdata (G_OBJECT (label), quark_mnemonic_menu);
1850
1851 if (last_key != GDK_KEY_VoidSymbol)
1852 {
1853 if (priv->mnemonic_window)
1854 {
1855 gtk_window_remove_mnemonic (priv->mnemonic_window,
1856 last_key,
1857 widget);
1858 priv->mnemonic_window = NULL;
1859 }
1860 if (mnemonic_menu)
1861 {
1862 _gtk_menu_shell_remove_mnemonic (GTK_MENU_SHELL (mnemonic_menu),
1863 last_key,
1864 widget);
1865 mnemonic_menu = NULL;
1866 }
1867 }
1868
1869 if (priv->mnemonic_keyval == GDK_KEY_VoidSymbol)
1870 goto done;
1871
1872 connect_mnemonics_visible_notify (GTK_LABEL (widget));
1873
1874 toplevel = gtk_widget_get_toplevel (widget);
1875 if (gtk_widget_is_toplevel (toplevel))
1876 {
1877 GtkWidget *menu_shell;
1878
1879 menu_shell = gtk_widget_get_ancestor (widget,
1880 GTK_TYPE_MENU_SHELL);
1881
1882 if (menu_shell)
1883 {
1884 _gtk_menu_shell_add_mnemonic (GTK_MENU_SHELL (menu_shell),
1885 priv->mnemonic_keyval,
1886 widget);
1887 mnemonic_menu = menu_shell;
1888 }
1889
1890 if (!GTK_IS_MENU (menu_shell))
1891 {
1892 gtk_window_add_mnemonic (GTK_WINDOW (toplevel),
1893 priv->mnemonic_keyval,
1894 widget);
1895 priv->mnemonic_window = GTK_WINDOW (toplevel);
1896 }
1897 }
1898
1899 done:
1900 g_object_set_qdata (G_OBJECT (label), quark_mnemonic_menu, mnemonic_menu);
1901 }
1902
1903 static void
gtk_label_hierarchy_changed(GtkWidget * widget,GtkWidget * old_toplevel)1904 gtk_label_hierarchy_changed (GtkWidget *widget,
1905 GtkWidget *old_toplevel)
1906 {
1907 GtkLabel *label = GTK_LABEL (widget);
1908 GtkLabelPrivate *priv = label->priv;
1909
1910 gtk_label_setup_mnemonic (label, priv->mnemonic_keyval);
1911 }
1912
1913 static void
label_shortcut_setting_apply(GtkLabel * label)1914 label_shortcut_setting_apply (GtkLabel *label)
1915 {
1916 gtk_label_recalculate (label);
1917 if (GTK_IS_ACCEL_LABEL (label))
1918 gtk_accel_label_refetch (GTK_ACCEL_LABEL (label));
1919 }
1920
1921 static void
label_shortcut_setting_traverse_container(GtkWidget * widget,gpointer data)1922 label_shortcut_setting_traverse_container (GtkWidget *widget,
1923 gpointer data)
1924 {
1925 if (GTK_IS_LABEL (widget))
1926 label_shortcut_setting_apply (GTK_LABEL (widget));
1927 else if (GTK_IS_CONTAINER (widget))
1928 gtk_container_forall (GTK_CONTAINER (widget),
1929 label_shortcut_setting_traverse_container, data);
1930 }
1931
1932 static void
label_shortcut_setting_changed(GtkSettings * settings)1933 label_shortcut_setting_changed (GtkSettings *settings)
1934 {
1935 GList *list, *l;
1936
1937 list = gtk_window_list_toplevels ();
1938
1939 for (l = list; l ; l = l->next)
1940 {
1941 GtkWidget *widget = l->data;
1942
1943 if (gtk_widget_get_settings (widget) == settings)
1944 gtk_container_forall (GTK_CONTAINER (widget),
1945 label_shortcut_setting_traverse_container, NULL);
1946 }
1947
1948 g_list_free (list);
1949 }
1950
1951 static void
mnemonics_visible_apply(GtkWidget * widget,gboolean mnemonics_visible)1952 mnemonics_visible_apply (GtkWidget *widget,
1953 gboolean mnemonics_visible)
1954 {
1955 GtkLabel *label = GTK_LABEL (widget);
1956 GtkLabelPrivate *priv = label->priv;
1957
1958 mnemonics_visible = mnemonics_visible != FALSE;
1959
1960 if (priv->mnemonics_visible != mnemonics_visible)
1961 {
1962 priv->mnemonics_visible = mnemonics_visible;
1963
1964 gtk_label_recalculate (label);
1965 }
1966 }
1967
1968 static void
label_mnemonics_visible_traverse_container(GtkWidget * widget,gpointer data)1969 label_mnemonics_visible_traverse_container (GtkWidget *widget,
1970 gpointer data)
1971 {
1972 gboolean mnemonics_visible = GPOINTER_TO_INT (data);
1973
1974 _gtk_label_mnemonics_visible_apply_recursively (widget, mnemonics_visible);
1975 }
1976
1977 void
_gtk_label_mnemonics_visible_apply_recursively(GtkWidget * widget,gboolean mnemonics_visible)1978 _gtk_label_mnemonics_visible_apply_recursively (GtkWidget *widget,
1979 gboolean mnemonics_visible)
1980 {
1981 if (GTK_IS_LABEL (widget))
1982 mnemonics_visible_apply (widget, mnemonics_visible);
1983 else if (GTK_IS_CONTAINER (widget))
1984 gtk_container_forall (GTK_CONTAINER (widget),
1985 label_mnemonics_visible_traverse_container,
1986 GINT_TO_POINTER (mnemonics_visible));
1987 }
1988
1989 static void
label_mnemonics_visible_changed(GtkWindow * window,GParamSpec * pspec,gpointer data)1990 label_mnemonics_visible_changed (GtkWindow *window,
1991 GParamSpec *pspec,
1992 gpointer data)
1993 {
1994 gboolean mnemonics_visible;
1995
1996 g_object_get (window, "mnemonics-visible", &mnemonics_visible, NULL);
1997
1998 gtk_container_forall (GTK_CONTAINER (window),
1999 label_mnemonics_visible_traverse_container,
2000 GINT_TO_POINTER (mnemonics_visible));
2001 }
2002
2003 static void
gtk_label_screen_changed(GtkWidget * widget,GdkScreen * old_screen)2004 gtk_label_screen_changed (GtkWidget *widget,
2005 GdkScreen *old_screen)
2006 {
2007 GtkSettings *settings;
2008 gboolean shortcuts_connected;
2009
2010 /* The PangoContext is replaced when the screen changes, so clear the layouts */
2011 gtk_label_clear_layout (GTK_LABEL (widget));
2012
2013 if (!gtk_widget_has_screen (widget))
2014 return;
2015
2016 settings = gtk_widget_get_settings (widget);
2017
2018 shortcuts_connected =
2019 GPOINTER_TO_INT (g_object_get_qdata (G_OBJECT (settings), quark_shortcuts_connected));
2020
2021 if (! shortcuts_connected)
2022 {
2023 g_signal_connect (settings, "notify::gtk-enable-mnemonics",
2024 G_CALLBACK (label_shortcut_setting_changed),
2025 NULL);
2026 g_signal_connect (settings, "notify::gtk-enable-accels",
2027 G_CALLBACK (label_shortcut_setting_changed),
2028 NULL);
2029
2030 g_object_set_qdata (G_OBJECT (settings), quark_shortcuts_connected,
2031 GINT_TO_POINTER (TRUE));
2032 }
2033
2034 label_shortcut_setting_apply (GTK_LABEL (widget));
2035 }
2036
2037
2038 static void
label_mnemonic_widget_weak_notify(gpointer data,GObject * where_the_object_was)2039 label_mnemonic_widget_weak_notify (gpointer data,
2040 GObject *where_the_object_was)
2041 {
2042 GtkLabel *label = data;
2043 GtkLabelPrivate *priv = label->priv;
2044
2045 priv->mnemonic_widget = NULL;
2046 g_object_notify_by_pspec (G_OBJECT (label), label_props[PROP_MNEMONIC_WIDGET]);
2047 }
2048
2049 /**
2050 * gtk_label_set_mnemonic_widget:
2051 * @label: a #GtkLabel
2052 * @widget: (nullable): the target #GtkWidget, or %NULL to unset
2053 *
2054 * If the label has been set so that it has an mnemonic key (using
2055 * i.e. gtk_label_set_markup_with_mnemonic(),
2056 * gtk_label_set_text_with_mnemonic(), gtk_label_new_with_mnemonic()
2057 * or the “use_underline” property) the label can be associated with a
2058 * widget that is the target of the mnemonic. When the label is inside
2059 * a widget (like a #GtkButton or a #GtkNotebook tab) it is
2060 * automatically associated with the correct widget, but sometimes
2061 * (i.e. when the target is a #GtkEntry next to the label) you need to
2062 * set it explicitly using this function.
2063 *
2064 * The target widget will be accelerated by emitting the
2065 * GtkWidget::mnemonic-activate signal on it. The default handler for
2066 * this signal will activate the widget if there are no mnemonic collisions
2067 * and toggle focus between the colliding widgets otherwise.
2068 **/
2069 void
gtk_label_set_mnemonic_widget(GtkLabel * label,GtkWidget * widget)2070 gtk_label_set_mnemonic_widget (GtkLabel *label,
2071 GtkWidget *widget)
2072 {
2073 GtkLabelPrivate *priv;
2074
2075 g_return_if_fail (GTK_IS_LABEL (label));
2076
2077 priv = label->priv;
2078
2079 if (widget)
2080 g_return_if_fail (GTK_IS_WIDGET (widget));
2081
2082 if (priv->mnemonic_widget)
2083 {
2084 gtk_widget_remove_mnemonic_label (priv->mnemonic_widget, GTK_WIDGET (label));
2085 g_object_weak_unref (G_OBJECT (priv->mnemonic_widget),
2086 label_mnemonic_widget_weak_notify,
2087 label);
2088 }
2089 priv->mnemonic_widget = widget;
2090 if (priv->mnemonic_widget)
2091 {
2092 g_object_weak_ref (G_OBJECT (priv->mnemonic_widget),
2093 label_mnemonic_widget_weak_notify,
2094 label);
2095 gtk_widget_add_mnemonic_label (priv->mnemonic_widget, GTK_WIDGET (label));
2096 }
2097
2098 g_object_notify_by_pspec (G_OBJECT (label), label_props[PROP_MNEMONIC_WIDGET]);
2099 }
2100
2101 /**
2102 * gtk_label_get_mnemonic_widget:
2103 * @label: a #GtkLabel
2104 *
2105 * Retrieves the target of the mnemonic (keyboard shortcut) of this
2106 * label. See gtk_label_set_mnemonic_widget().
2107 *
2108 * Returns: (nullable) (transfer none): the target of the label’s mnemonic,
2109 * or %NULL if none has been set and the default algorithm will be used.
2110 **/
2111 GtkWidget *
gtk_label_get_mnemonic_widget(GtkLabel * label)2112 gtk_label_get_mnemonic_widget (GtkLabel *label)
2113 {
2114 g_return_val_if_fail (GTK_IS_LABEL (label), NULL);
2115
2116 return label->priv->mnemonic_widget;
2117 }
2118
2119 /**
2120 * gtk_label_get_mnemonic_keyval:
2121 * @label: a #GtkLabel
2122 *
2123 * If the label has been set so that it has an mnemonic key this function
2124 * returns the keyval used for the mnemonic accelerator. If there is no
2125 * mnemonic set up it returns #GDK_KEY_VoidSymbol.
2126 *
2127 * Returns: GDK keyval usable for accelerators, or #GDK_KEY_VoidSymbol
2128 **/
2129 guint
gtk_label_get_mnemonic_keyval(GtkLabel * label)2130 gtk_label_get_mnemonic_keyval (GtkLabel *label)
2131 {
2132 g_return_val_if_fail (GTK_IS_LABEL (label), GDK_KEY_VoidSymbol);
2133
2134 return label->priv->mnemonic_keyval;
2135 }
2136
2137 static void
gtk_label_set_text_internal(GtkLabel * label,gchar * str)2138 gtk_label_set_text_internal (GtkLabel *label,
2139 gchar *str)
2140 {
2141 GtkLabelPrivate *priv = label->priv;
2142
2143 if (g_strcmp0 (priv->text, str) == 0)
2144 {
2145 g_free (str);
2146 return;
2147 }
2148
2149 _gtk_label_accessible_text_deleted (label);
2150 g_free (priv->text);
2151 priv->text = str;
2152
2153 _gtk_label_accessible_text_inserted (label);
2154
2155 gtk_label_select_region_index (label, 0, 0);
2156 }
2157
2158 static void
gtk_label_set_label_internal(GtkLabel * label,gchar * str)2159 gtk_label_set_label_internal (GtkLabel *label,
2160 gchar *str)
2161 {
2162 GtkLabelPrivate *priv = label->priv;
2163
2164 g_free (priv->label);
2165
2166 priv->label = str;
2167
2168 g_object_notify_by_pspec (G_OBJECT (label), label_props[PROP_LABEL]);
2169 }
2170
2171 static gboolean
gtk_label_set_use_markup_internal(GtkLabel * label,gboolean val)2172 gtk_label_set_use_markup_internal (GtkLabel *label,
2173 gboolean val)
2174 {
2175 GtkLabelPrivate *priv = label->priv;
2176
2177 val = val != FALSE;
2178 if (priv->use_markup != val)
2179 {
2180 priv->use_markup = val;
2181
2182 g_object_notify_by_pspec (G_OBJECT (label), label_props[PROP_USE_MARKUP]);
2183
2184 return TRUE;
2185 }
2186
2187 return FALSE;
2188 }
2189
2190 static gboolean
gtk_label_set_use_underline_internal(GtkLabel * label,gboolean val)2191 gtk_label_set_use_underline_internal (GtkLabel *label,
2192 gboolean val)
2193 {
2194 GtkLabelPrivate *priv = label->priv;
2195
2196 val = val != FALSE;
2197 if (priv->use_underline != val)
2198 {
2199 priv->use_underline = val;
2200
2201 g_object_notify_by_pspec (G_OBJECT (label), label_props[PROP_USE_UNDERLINE]);
2202
2203 return TRUE;
2204 }
2205
2206 return FALSE;
2207 }
2208
2209 /* Calculates text, attrs and mnemonic_keyval from
2210 * label, use_underline and use_markup
2211 */
2212 static void
gtk_label_recalculate(GtkLabel * label)2213 gtk_label_recalculate (GtkLabel *label)
2214 {
2215 GtkLabelPrivate *priv = label->priv;
2216 guint keyval = priv->mnemonic_keyval;
2217
2218 gtk_label_clear_links (label);
2219
2220 if (priv->use_markup)
2221 gtk_label_set_markup_internal (label, priv->label, priv->use_underline);
2222 else if (priv->use_underline)
2223 gtk_label_set_uline_text_internal (label, priv->label);
2224 else
2225 {
2226 if (!priv->pattern_set)
2227 {
2228 if (priv->markup_attrs)
2229 pango_attr_list_unref (priv->markup_attrs);
2230 priv->markup_attrs = NULL;
2231 }
2232 gtk_label_set_text_internal (label, g_strdup (priv->label));
2233 }
2234
2235 if (!priv->use_underline)
2236 priv->mnemonic_keyval = GDK_KEY_VoidSymbol;
2237
2238 if (keyval != priv->mnemonic_keyval)
2239 {
2240 gtk_label_setup_mnemonic (label, keyval);
2241 g_object_notify_by_pspec (G_OBJECT (label), label_props[PROP_MNEMONIC_KEYVAL]);
2242 }
2243
2244 gtk_label_clear_layout (label);
2245 gtk_label_clear_select_info (label);
2246 gtk_widget_queue_resize (GTK_WIDGET (label));
2247 }
2248
2249 /**
2250 * gtk_label_set_text:
2251 * @label: a #GtkLabel
2252 * @str: The text you want to set
2253 *
2254 * Sets the text within the #GtkLabel widget. It overwrites any text that
2255 * was there before.
2256 *
2257 * This function will clear any previously set mnemonic accelerators, and
2258 * set the #GtkLabel:use-underline property to %FALSE as a side effect.
2259 *
2260 * This function will set the #GtkLabel:use-markup property to %FALSE
2261 * as a side effect.
2262 *
2263 * See also: gtk_label_set_markup()
2264 **/
2265 void
gtk_label_set_text(GtkLabel * label,const gchar * str)2266 gtk_label_set_text (GtkLabel *label,
2267 const gchar *str)
2268 {
2269 g_return_if_fail (GTK_IS_LABEL (label));
2270
2271 g_object_freeze_notify (G_OBJECT (label));
2272
2273 gtk_label_set_label_internal (label, g_strdup (str ? str : ""));
2274 gtk_label_set_use_markup_internal (label, FALSE);
2275 gtk_label_set_use_underline_internal (label, FALSE);
2276
2277 gtk_label_recalculate (label);
2278
2279 g_object_thaw_notify (G_OBJECT (label));
2280 }
2281
2282 /**
2283 * gtk_label_set_attributes:
2284 * @label: a #GtkLabel
2285 * @attrs: (nullable): a #PangoAttrList, or %NULL
2286 *
2287 * Sets a #PangoAttrList; the attributes in the list are applied to the
2288 * label text.
2289 *
2290 * The attributes set with this function will be applied
2291 * and merged with any other attributes previously effected by way
2292 * of the #GtkLabel:use-underline or #GtkLabel:use-markup properties.
2293 * While it is not recommended to mix markup strings with manually set
2294 * attributes, if you must; know that the attributes will be applied
2295 * to the label after the markup string is parsed.
2296 **/
2297 void
gtk_label_set_attributes(GtkLabel * label,PangoAttrList * attrs)2298 gtk_label_set_attributes (GtkLabel *label,
2299 PangoAttrList *attrs)
2300 {
2301 GtkLabelPrivate *priv = label->priv;
2302
2303 g_return_if_fail (GTK_IS_LABEL (label));
2304
2305 if (attrs)
2306 pango_attr_list_ref (attrs);
2307
2308 if (priv->attrs)
2309 pango_attr_list_unref (priv->attrs);
2310 priv->attrs = attrs;
2311
2312 g_object_notify_by_pspec (G_OBJECT (label), label_props[PROP_ATTRIBUTES]);
2313
2314 gtk_label_clear_layout (label);
2315 gtk_widget_queue_resize (GTK_WIDGET (label));
2316 }
2317
2318 /**
2319 * gtk_label_get_attributes:
2320 * @label: a #GtkLabel
2321 *
2322 * Gets the attribute list that was set on the label using
2323 * gtk_label_set_attributes(), if any. This function does
2324 * not reflect attributes that come from the labels markup
2325 * (see gtk_label_set_markup()). If you want to get the
2326 * effective attributes for the label, use
2327 * pango_layout_get_attribute (gtk_label_get_layout (label)).
2328 *
2329 * Returns: (nullable) (transfer none): the attribute list, or %NULL
2330 * if none was set.
2331 **/
2332 PangoAttrList *
gtk_label_get_attributes(GtkLabel * label)2333 gtk_label_get_attributes (GtkLabel *label)
2334 {
2335 g_return_val_if_fail (GTK_IS_LABEL (label), NULL);
2336
2337 return label->priv->attrs;
2338 }
2339
2340 /**
2341 * gtk_label_set_label:
2342 * @label: a #GtkLabel
2343 * @str: the new text to set for the label
2344 *
2345 * Sets the text of the label. The label is interpreted as
2346 * including embedded underlines and/or Pango markup depending
2347 * on the values of the #GtkLabel:use-underline and
2348 * #GtkLabel:use-markup properties.
2349 **/
2350 void
gtk_label_set_label(GtkLabel * label,const gchar * str)2351 gtk_label_set_label (GtkLabel *label,
2352 const gchar *str)
2353 {
2354 g_return_if_fail (GTK_IS_LABEL (label));
2355
2356 g_object_freeze_notify (G_OBJECT (label));
2357
2358 gtk_label_set_label_internal (label, g_strdup (str ? str : ""));
2359 gtk_label_recalculate (label);
2360
2361 g_object_thaw_notify (G_OBJECT (label));
2362 }
2363
2364 /**
2365 * gtk_label_get_label:
2366 * @label: a #GtkLabel
2367 *
2368 * Fetches the text from a label widget including any embedded
2369 * underlines indicating mnemonics and Pango markup. (See
2370 * gtk_label_get_text()).
2371 *
2372 * Returns: the text of the label widget. This string is
2373 * owned by the widget and must not be modified or freed.
2374 **/
2375 const gchar *
gtk_label_get_label(GtkLabel * label)2376 gtk_label_get_label (GtkLabel *label)
2377 {
2378 g_return_val_if_fail (GTK_IS_LABEL (label), NULL);
2379
2380 return label->priv->label;
2381 }
2382
2383 typedef struct
2384 {
2385 GtkLabel *label;
2386 GList *links;
2387 GString *new_str;
2388 gsize text_len;
2389 } UriParserData;
2390
2391 static void
start_element_handler(GMarkupParseContext * context,const gchar * element_name,const gchar ** attribute_names,const gchar ** attribute_values,gpointer user_data,GError ** error)2392 start_element_handler (GMarkupParseContext *context,
2393 const gchar *element_name,
2394 const gchar **attribute_names,
2395 const gchar **attribute_values,
2396 gpointer user_data,
2397 GError **error)
2398 {
2399 GtkLabelPrivate *priv;
2400 UriParserData *pdata = user_data;
2401
2402 if (strcmp (element_name, "a") == 0)
2403 {
2404 GtkLabelLink *link;
2405 const gchar *uri = NULL;
2406 const gchar *title = NULL;
2407 gboolean visited = FALSE;
2408 gint line_number;
2409 gint char_number;
2410 gint i;
2411 GtkCssNode *widget_node;
2412 GtkStateFlags state;
2413
2414 g_markup_parse_context_get_position (context, &line_number, &char_number);
2415
2416 for (i = 0; attribute_names[i] != NULL; i++)
2417 {
2418 const gchar *attr = attribute_names[i];
2419
2420 if (strcmp (attr, "href") == 0)
2421 uri = attribute_values[i];
2422 else if (strcmp (attr, "title") == 0)
2423 title = attribute_values[i];
2424 else
2425 {
2426 g_set_error (error,
2427 G_MARKUP_ERROR,
2428 G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
2429 "Attribute '%s' is not allowed on the <a> tag "
2430 "on line %d char %d",
2431 attr, line_number, char_number);
2432 return;
2433 }
2434 }
2435
2436 if (uri == NULL)
2437 {
2438 g_set_error (error,
2439 G_MARKUP_ERROR,
2440 G_MARKUP_ERROR_INVALID_CONTENT,
2441 "Attribute 'href' was missing on the <a> tag "
2442 "on line %d char %d",
2443 line_number, char_number);
2444 return;
2445 }
2446
2447 visited = FALSE;
2448 priv = pdata->label->priv;
2449 if (priv->track_links && priv->select_info)
2450 {
2451 GList *l;
2452 for (l = priv->select_info->links; l; l = l->next)
2453 {
2454 link = l->data;
2455 if (strcmp (uri, link->uri) == 0)
2456 {
2457 visited = link->visited;
2458 break;
2459 }
2460 }
2461 }
2462
2463 link = g_new0 (GtkLabelLink, 1);
2464 link->uri = g_strdup (uri);
2465 link->title = g_strdup (title);
2466
2467 widget_node = gtk_widget_get_css_node (GTK_WIDGET (pdata->label));
2468 link->cssnode = gtk_css_node_new ();
2469 gtk_css_node_set_name (link->cssnode, I_("link"));
2470 gtk_css_node_set_parent (link->cssnode, widget_node);
2471 state = gtk_css_node_get_state (widget_node);
2472 if (visited)
2473 state |= GTK_STATE_FLAG_VISITED;
2474 else
2475 state |= GTK_STATE_FLAG_LINK;
2476 gtk_css_node_set_state (link->cssnode, state);
2477 g_object_unref (link->cssnode);
2478
2479 link->visited = visited;
2480 link->start = pdata->text_len;
2481 pdata->links = g_list_prepend (pdata->links, link);
2482 }
2483 else
2484 {
2485 gint i;
2486
2487 g_string_append_c (pdata->new_str, '<');
2488 g_string_append (pdata->new_str, element_name);
2489
2490 for (i = 0; attribute_names[i] != NULL; i++)
2491 {
2492 const gchar *attr = attribute_names[i];
2493 const gchar *value = attribute_values[i];
2494 gchar *newvalue;
2495
2496 newvalue = g_markup_escape_text (value, -1);
2497
2498 g_string_append_c (pdata->new_str, ' ');
2499 g_string_append (pdata->new_str, attr);
2500 g_string_append (pdata->new_str, "=\"");
2501 g_string_append (pdata->new_str, newvalue);
2502 g_string_append_c (pdata->new_str, '\"');
2503
2504 g_free (newvalue);
2505 }
2506 g_string_append_c (pdata->new_str, '>');
2507 }
2508 }
2509
2510 static void
end_element_handler(GMarkupParseContext * context,const gchar * element_name,gpointer user_data,GError ** error)2511 end_element_handler (GMarkupParseContext *context,
2512 const gchar *element_name,
2513 gpointer user_data,
2514 GError **error)
2515 {
2516 UriParserData *pdata = user_data;
2517
2518 if (!strcmp (element_name, "a"))
2519 {
2520 GtkLabelLink *link = pdata->links->data;
2521 link->end = pdata->text_len;
2522 }
2523 else
2524 {
2525 g_string_append (pdata->new_str, "</");
2526 g_string_append (pdata->new_str, element_name);
2527 g_string_append_c (pdata->new_str, '>');
2528 }
2529 }
2530
2531 static void
text_handler(GMarkupParseContext * context,const gchar * text,gsize text_len,gpointer user_data,GError ** error)2532 text_handler (GMarkupParseContext *context,
2533 const gchar *text,
2534 gsize text_len,
2535 gpointer user_data,
2536 GError **error)
2537 {
2538 UriParserData *pdata = user_data;
2539 gchar *newtext;
2540
2541 newtext = g_markup_escape_text (text, text_len);
2542 g_string_append (pdata->new_str, newtext);
2543 pdata->text_len += text_len;
2544 g_free (newtext);
2545 }
2546
2547 static const GMarkupParser markup_parser =
2548 {
2549 start_element_handler,
2550 end_element_handler,
2551 text_handler,
2552 NULL,
2553 NULL
2554 };
2555
2556 static gboolean
xml_isspace(gchar c)2557 xml_isspace (gchar c)
2558 {
2559 return (c == ' ' || c == '\t' || c == '\n' || c == '\r');
2560 }
2561
2562 static void
link_free(GtkLabelLink * link)2563 link_free (GtkLabelLink *link)
2564 {
2565 gtk_css_node_set_parent (link->cssnode, NULL);
2566 g_free (link->uri);
2567 g_free (link->title);
2568 g_free (link);
2569 }
2570
2571
2572 static gboolean
parse_uri_markup(GtkLabel * label,const gchar * str,gchar ** new_str,GList ** links,GError ** error)2573 parse_uri_markup (GtkLabel *label,
2574 const gchar *str,
2575 gchar **new_str,
2576 GList **links,
2577 GError **error)
2578 {
2579 GMarkupParseContext *context = NULL;
2580 const gchar *p, *end;
2581 gboolean needs_root = TRUE;
2582 gsize length;
2583 UriParserData pdata;
2584
2585 length = strlen (str);
2586 p = str;
2587 end = str + length;
2588
2589 pdata.label = label;
2590 pdata.links = NULL;
2591 pdata.new_str = g_string_sized_new (length);
2592 pdata.text_len = 0;
2593
2594 while (p != end && xml_isspace (*p))
2595 p++;
2596
2597 if (end - p >= 8 && strncmp (p, "<markup>", 8) == 0)
2598 needs_root = FALSE;
2599
2600 context = g_markup_parse_context_new (&markup_parser, 0, &pdata, NULL);
2601
2602 if (needs_root)
2603 {
2604 if (!g_markup_parse_context_parse (context, "<markup>", -1, error))
2605 goto failed;
2606 }
2607
2608 if (!g_markup_parse_context_parse (context, str, length, error))
2609 goto failed;
2610
2611 if (needs_root)
2612 {
2613 if (!g_markup_parse_context_parse (context, "</markup>", -1, error))
2614 goto failed;
2615 }
2616
2617 if (!g_markup_parse_context_end_parse (context, error))
2618 goto failed;
2619
2620 g_markup_parse_context_free (context);
2621
2622 *new_str = g_string_free (pdata.new_str, FALSE);
2623 *links = pdata.links;
2624
2625 return TRUE;
2626
2627 failed:
2628 g_markup_parse_context_free (context);
2629 g_string_free (pdata.new_str, TRUE);
2630 g_list_free_full (pdata.links, (GDestroyNotify) link_free);
2631
2632 return FALSE;
2633 }
2634
2635 static void
gtk_label_ensure_has_tooltip(GtkLabel * label)2636 gtk_label_ensure_has_tooltip (GtkLabel *label)
2637 {
2638 GtkLabelPrivate *priv = label->priv;
2639 GList *l;
2640 gboolean has_tooltip = FALSE;
2641
2642 for (l = priv->select_info->links; l; l = l->next)
2643 {
2644 GtkLabelLink *link = l->data;
2645 if (link->title)
2646 {
2647 has_tooltip = TRUE;
2648 break;
2649 }
2650 }
2651
2652 gtk_widget_set_has_tooltip (GTK_WIDGET (label), has_tooltip);
2653 }
2654
2655 static void
gtk_label_set_markup_internal(GtkLabel * label,const gchar * str,gboolean with_uline)2656 gtk_label_set_markup_internal (GtkLabel *label,
2657 const gchar *str,
2658 gboolean with_uline)
2659 {
2660 GtkLabelPrivate *priv = label->priv;
2661 gchar *text = NULL;
2662 GError *error = NULL;
2663 PangoAttrList *attrs = NULL;
2664 gunichar accel_char = 0;
2665 gchar *str_for_display = NULL;
2666 gchar *str_for_accel = NULL;
2667 GList *links = NULL;
2668
2669 if (!parse_uri_markup (label, str, &str_for_display, &links, &error))
2670 {
2671 g_warning ("Failed to set text '%s' from markup due to error parsing markup: %s",
2672 str, error->message);
2673 g_error_free (error);
2674 return;
2675 }
2676
2677 str_for_accel = g_strdup (str_for_display);
2678
2679 if (links)
2680 {
2681 gtk_label_ensure_select_info (label);
2682 priv->select_info->links = g_list_reverse (links);
2683 _gtk_label_accessible_update_links (label);
2684 gtk_label_ensure_has_tooltip (label);
2685 }
2686
2687 if (with_uline)
2688 {
2689 gboolean enable_mnemonics = TRUE;
2690 gboolean auto_mnemonics = TRUE;
2691
2692 g_object_get (gtk_widget_get_settings (GTK_WIDGET (label)),
2693 "gtk-enable-mnemonics", &enable_mnemonics,
2694 NULL);
2695
2696 if (!(enable_mnemonics && priv->mnemonics_visible &&
2697 (!auto_mnemonics ||
2698 (gtk_widget_is_sensitive (GTK_WIDGET (label)) &&
2699 (!priv->mnemonic_widget ||
2700 gtk_widget_is_sensitive (priv->mnemonic_widget))))))
2701 {
2702 gchar *tmp;
2703 gchar *pattern;
2704 guint key;
2705
2706 if (separate_uline_pattern (str_for_display, &key, &tmp, &pattern))
2707 {
2708 g_free (str_for_display);
2709 str_for_display = tmp;
2710 g_free (pattern);
2711 }
2712 }
2713 }
2714
2715 /* Extract the text to display */
2716 if (!pango_parse_markup (str_for_display,
2717 -1,
2718 with_uline ? '_' : 0,
2719 &attrs,
2720 &text,
2721 NULL,
2722 &error))
2723 {
2724 g_warning ("Failed to set text '%s' from markup due to error parsing markup: %s",
2725 str_for_display, error->message);
2726 g_free (str_for_display);
2727 g_free (str_for_accel);
2728 g_error_free (error);
2729 return;
2730 }
2731
2732 /* Extract the accelerator character */
2733 if (with_uline && !pango_parse_markup (str_for_accel,
2734 -1,
2735 '_',
2736 NULL,
2737 NULL,
2738 &accel_char,
2739 &error))
2740 {
2741 g_warning ("Failed to set text from markup due to error parsing markup: %s",
2742 error->message);
2743 g_free (str_for_display);
2744 g_free (str_for_accel);
2745 g_error_free (error);
2746 return;
2747 }
2748
2749 g_free (str_for_display);
2750 g_free (str_for_accel);
2751
2752 if (text)
2753 gtk_label_set_text_internal (label, text);
2754
2755 if (attrs)
2756 {
2757 if (priv->markup_attrs)
2758 pango_attr_list_unref (priv->markup_attrs);
2759 priv->markup_attrs = attrs;
2760 }
2761
2762 if (accel_char != 0)
2763 priv->mnemonic_keyval = gdk_keyval_to_lower (gdk_unicode_to_keyval (accel_char));
2764 else
2765 priv->mnemonic_keyval = GDK_KEY_VoidSymbol;
2766 }
2767
2768 /**
2769 * gtk_label_set_markup:
2770 * @label: a #GtkLabel
2771 * @str: a markup string (see [Pango markup format][PangoMarkupFormat])
2772 *
2773 * Parses @str which is marked up with the
2774 * [Pango text markup language][PangoMarkupFormat], setting the
2775 * label’s text and attribute list based on the parse results.
2776 *
2777 * If the @str is external data, you may need to escape it with
2778 * g_markup_escape_text() or g_markup_printf_escaped():
2779 *
2780 * |[<!-- language="C" -->
2781 * GtkWidget *label = gtk_label_new (NULL);
2782 * const char *str = "some text";
2783 * const char *format = "<span style=\"italic\">\%s</span>";
2784 * char *markup;
2785 *
2786 * markup = g_markup_printf_escaped (format, str);
2787 * gtk_label_set_markup (GTK_LABEL (label), markup);
2788 * g_free (markup);
2789 * ]|
2790 *
2791 * This function will set the #GtkLabel:use-markup property to %TRUE as
2792 * a side effect.
2793 *
2794 * If you set the label contents using the #GtkLabel:label property you
2795 * should also ensure that you set the #GtkLabel:use-markup property
2796 * accordingly.
2797 *
2798 * See also: gtk_label_set_text()
2799 **/
2800 void
gtk_label_set_markup(GtkLabel * label,const gchar * str)2801 gtk_label_set_markup (GtkLabel *label,
2802 const gchar *str)
2803 {
2804 g_return_if_fail (GTK_IS_LABEL (label));
2805
2806 g_object_freeze_notify (G_OBJECT (label));
2807
2808 gtk_label_set_label_internal (label, g_strdup (str ? str : ""));
2809 gtk_label_set_use_markup_internal (label, TRUE);
2810 gtk_label_set_use_underline_internal (label, FALSE);
2811
2812 gtk_label_recalculate (label);
2813
2814 g_object_thaw_notify (G_OBJECT (label));
2815 }
2816
2817 /**
2818 * gtk_label_set_markup_with_mnemonic:
2819 * @label: a #GtkLabel
2820 * @str: a markup string (see
2821 * [Pango markup format][PangoMarkupFormat])
2822 *
2823 * Parses @str which is marked up with the
2824 * [Pango text markup language][PangoMarkupFormat],
2825 * setting the label’s text and attribute list based on the parse results.
2826 * If characters in @str are preceded by an underscore, they are underlined
2827 * indicating that they represent a keyboard accelerator called a mnemonic.
2828 *
2829 * The mnemonic key can be used to activate another widget, chosen
2830 * automatically, or explicitly using gtk_label_set_mnemonic_widget().
2831 */
2832 void
gtk_label_set_markup_with_mnemonic(GtkLabel * label,const gchar * str)2833 gtk_label_set_markup_with_mnemonic (GtkLabel *label,
2834 const gchar *str)
2835 {
2836 g_return_if_fail (GTK_IS_LABEL (label));
2837
2838 g_object_freeze_notify (G_OBJECT (label));
2839
2840 gtk_label_set_label_internal (label, g_strdup (str ? str : ""));
2841 gtk_label_set_use_markup_internal (label, TRUE);
2842 gtk_label_set_use_underline_internal (label, TRUE);
2843
2844 gtk_label_recalculate (label);
2845
2846 g_object_thaw_notify (G_OBJECT (label));
2847 }
2848
2849 /**
2850 * gtk_label_get_text:
2851 * @label: a #GtkLabel
2852 *
2853 * Fetches the text from a label widget, as displayed on the
2854 * screen. This does not include any embedded underlines
2855 * indicating mnemonics or Pango markup. (See gtk_label_get_label())
2856 *
2857 * Returns: the text in the label widget. This is the internal
2858 * string used by the label, and must not be modified.
2859 **/
2860 const gchar *
gtk_label_get_text(GtkLabel * label)2861 gtk_label_get_text (GtkLabel *label)
2862 {
2863 g_return_val_if_fail (GTK_IS_LABEL (label), NULL);
2864
2865 return label->priv->text;
2866 }
2867
2868 static PangoAttrList *
gtk_label_pattern_to_attrs(GtkLabel * label,const gchar * pattern)2869 gtk_label_pattern_to_attrs (GtkLabel *label,
2870 const gchar *pattern)
2871 {
2872 GtkLabelPrivate *priv = label->priv;
2873 const char *start;
2874 const char *p = priv->text;
2875 const char *q = pattern;
2876 PangoAttrList *attrs;
2877
2878 attrs = pango_attr_list_new ();
2879
2880 while (1)
2881 {
2882 while (*p && *q && *q != '_')
2883 {
2884 p = g_utf8_next_char (p);
2885 q++;
2886 }
2887 start = p;
2888 while (*p && *q && *q == '_')
2889 {
2890 p = g_utf8_next_char (p);
2891 q++;
2892 }
2893
2894 if (p > start)
2895 {
2896 PangoAttribute *attr = pango_attr_underline_new (PANGO_UNDERLINE_LOW);
2897 attr->start_index = start - priv->text;
2898 attr->end_index = p - priv->text;
2899
2900 pango_attr_list_insert (attrs, attr);
2901 }
2902 else
2903 break;
2904 }
2905
2906 return attrs;
2907 }
2908
2909 static void
gtk_label_set_pattern_internal(GtkLabel * label,const gchar * pattern,gboolean is_mnemonic)2910 gtk_label_set_pattern_internal (GtkLabel *label,
2911 const gchar *pattern,
2912 gboolean is_mnemonic)
2913 {
2914 GtkLabelPrivate *priv = label->priv;
2915 PangoAttrList *attrs;
2916 gboolean enable_mnemonics = TRUE;
2917 gboolean auto_mnemonics = TRUE;
2918
2919 if (priv->pattern_set)
2920 return;
2921
2922 if (is_mnemonic)
2923 {
2924 g_object_get (gtk_widget_get_settings (GTK_WIDGET (label)),
2925 "gtk-enable-mnemonics", &enable_mnemonics,
2926 NULL);
2927
2928 if (enable_mnemonics && priv->mnemonics_visible && pattern &&
2929 (!auto_mnemonics ||
2930 (gtk_widget_is_sensitive (GTK_WIDGET (label)) &&
2931 (!priv->mnemonic_widget ||
2932 gtk_widget_is_sensitive (priv->mnemonic_widget)))))
2933 attrs = gtk_label_pattern_to_attrs (label, pattern);
2934 else
2935 attrs = NULL;
2936 }
2937 else
2938 attrs = gtk_label_pattern_to_attrs (label, pattern);
2939
2940 if (priv->markup_attrs)
2941 pango_attr_list_unref (priv->markup_attrs);
2942 priv->markup_attrs = attrs;
2943 }
2944
2945 /**
2946 * gtk_label_set_pattern:
2947 * @label: The #GtkLabel you want to set the pattern to.
2948 * @pattern: The pattern as described above.
2949 *
2950 * The pattern of underlines you want under the existing text within the
2951 * #GtkLabel widget. For example if the current text of the label says
2952 * “FooBarBaz” passing a pattern of “___ ___” will underline
2953 * “Foo” and “Baz” but not “Bar”.
2954 */
2955 void
gtk_label_set_pattern(GtkLabel * label,const gchar * pattern)2956 gtk_label_set_pattern (GtkLabel *label,
2957 const gchar *pattern)
2958 {
2959 GtkLabelPrivate *priv;
2960
2961 g_return_if_fail (GTK_IS_LABEL (label));
2962
2963 priv = label->priv;
2964
2965 priv->pattern_set = FALSE;
2966
2967 if (pattern)
2968 {
2969 gtk_label_set_pattern_internal (label, pattern, FALSE);
2970 priv->pattern_set = TRUE;
2971 }
2972 else
2973 gtk_label_recalculate (label);
2974
2975 gtk_label_clear_layout (label);
2976 gtk_widget_queue_resize (GTK_WIDGET (label));
2977 }
2978
2979
2980 /**
2981 * gtk_label_set_justify:
2982 * @label: a #GtkLabel
2983 * @jtype: a #GtkJustification
2984 *
2985 * Sets the alignment of the lines in the text of the label relative to
2986 * each other. %GTK_JUSTIFY_LEFT is the default value when the widget is
2987 * first created with gtk_label_new(). If you instead want to set the
2988 * alignment of the label as a whole, use gtk_widget_set_halign() instead.
2989 * gtk_label_set_justify() has no effect on labels containing only a
2990 * single line.
2991 */
2992 void
gtk_label_set_justify(GtkLabel * label,GtkJustification jtype)2993 gtk_label_set_justify (GtkLabel *label,
2994 GtkJustification jtype)
2995 {
2996 GtkLabelPrivate *priv;
2997
2998 g_return_if_fail (GTK_IS_LABEL (label));
2999 g_return_if_fail (jtype >= GTK_JUSTIFY_LEFT && jtype <= GTK_JUSTIFY_FILL);
3000
3001 priv = label->priv;
3002
3003 if ((GtkJustification) priv->jtype != jtype)
3004 {
3005 priv->jtype = jtype;
3006
3007 /* No real need to be this drastic, but easier than duplicating the code */
3008 gtk_label_clear_layout (label);
3009
3010 g_object_notify_by_pspec (G_OBJECT (label), label_props[PROP_JUSTIFY]);
3011 gtk_widget_queue_resize (GTK_WIDGET (label));
3012 }
3013 }
3014
3015 /**
3016 * gtk_label_get_justify:
3017 * @label: a #GtkLabel
3018 *
3019 * Returns the justification of the label. See gtk_label_set_justify().
3020 *
3021 * Returns: #GtkJustification
3022 **/
3023 GtkJustification
gtk_label_get_justify(GtkLabel * label)3024 gtk_label_get_justify (GtkLabel *label)
3025 {
3026 g_return_val_if_fail (GTK_IS_LABEL (label), 0);
3027
3028 return label->priv->jtype;
3029 }
3030
3031 /**
3032 * gtk_label_set_ellipsize:
3033 * @label: a #GtkLabel
3034 * @mode: a #PangoEllipsizeMode
3035 *
3036 * Sets the mode used to ellipsize (add an ellipsis: "...") to the text
3037 * if there is not enough space to render the entire string.
3038 *
3039 * Since: 2.6
3040 **/
3041 void
gtk_label_set_ellipsize(GtkLabel * label,PangoEllipsizeMode mode)3042 gtk_label_set_ellipsize (GtkLabel *label,
3043 PangoEllipsizeMode mode)
3044 {
3045 GtkLabelPrivate *priv;
3046
3047 g_return_if_fail (GTK_IS_LABEL (label));
3048 g_return_if_fail (mode >= PANGO_ELLIPSIZE_NONE && mode <= PANGO_ELLIPSIZE_END);
3049
3050 priv = label->priv;
3051
3052 if ((PangoEllipsizeMode) priv->ellipsize != mode)
3053 {
3054 priv->ellipsize = mode;
3055
3056 /* No real need to be this drastic, but easier than duplicating the code */
3057 gtk_label_clear_layout (label);
3058
3059 g_object_notify_by_pspec (G_OBJECT (label), label_props[PROP_ELLIPSIZE]);
3060 gtk_widget_queue_resize (GTK_WIDGET (label));
3061 }
3062 }
3063
3064 /**
3065 * gtk_label_get_ellipsize:
3066 * @label: a #GtkLabel
3067 *
3068 * Returns the ellipsizing position of the label. See gtk_label_set_ellipsize().
3069 *
3070 * Returns: #PangoEllipsizeMode
3071 *
3072 * Since: 2.6
3073 **/
3074 PangoEllipsizeMode
gtk_label_get_ellipsize(GtkLabel * label)3075 gtk_label_get_ellipsize (GtkLabel *label)
3076 {
3077 g_return_val_if_fail (GTK_IS_LABEL (label), PANGO_ELLIPSIZE_NONE);
3078
3079 return label->priv->ellipsize;
3080 }
3081
3082 /**
3083 * gtk_label_set_width_chars:
3084 * @label: a #GtkLabel
3085 * @n_chars: the new desired width, in characters.
3086 *
3087 * Sets the desired width in characters of @label to @n_chars.
3088 *
3089 * Since: 2.6
3090 **/
3091 void
gtk_label_set_width_chars(GtkLabel * label,gint n_chars)3092 gtk_label_set_width_chars (GtkLabel *label,
3093 gint n_chars)
3094 {
3095 GtkLabelPrivate *priv;
3096
3097 g_return_if_fail (GTK_IS_LABEL (label));
3098
3099 priv = label->priv;
3100
3101 if (priv->width_chars != n_chars)
3102 {
3103 priv->width_chars = n_chars;
3104 g_object_notify_by_pspec (G_OBJECT (label), label_props[PROP_WIDTH_CHARS]);
3105 gtk_widget_queue_resize (GTK_WIDGET (label));
3106 }
3107 }
3108
3109 /**
3110 * gtk_label_get_width_chars:
3111 * @label: a #GtkLabel
3112 *
3113 * Retrieves the desired width of @label, in characters. See
3114 * gtk_label_set_width_chars().
3115 *
3116 * Returns: the width of the label in characters.
3117 *
3118 * Since: 2.6
3119 **/
3120 gint
gtk_label_get_width_chars(GtkLabel * label)3121 gtk_label_get_width_chars (GtkLabel *label)
3122 {
3123 g_return_val_if_fail (GTK_IS_LABEL (label), -1);
3124
3125 return label->priv->width_chars;
3126 }
3127
3128 /**
3129 * gtk_label_set_max_width_chars:
3130 * @label: a #GtkLabel
3131 * @n_chars: the new desired maximum width, in characters.
3132 *
3133 * Sets the desired maximum width in characters of @label to @n_chars.
3134 *
3135 * Since: 2.6
3136 **/
3137 void
gtk_label_set_max_width_chars(GtkLabel * label,gint n_chars)3138 gtk_label_set_max_width_chars (GtkLabel *label,
3139 gint n_chars)
3140 {
3141 GtkLabelPrivate *priv;
3142
3143 g_return_if_fail (GTK_IS_LABEL (label));
3144
3145 priv = label->priv;
3146
3147 if (priv->max_width_chars != n_chars)
3148 {
3149 priv->max_width_chars = n_chars;
3150
3151 g_object_notify_by_pspec (G_OBJECT (label), label_props[PROP_MAX_WIDTH_CHARS]);
3152 gtk_widget_queue_resize (GTK_WIDGET (label));
3153 }
3154 }
3155
3156 /**
3157 * gtk_label_get_max_width_chars:
3158 * @label: a #GtkLabel
3159 *
3160 * Retrieves the desired maximum width of @label, in characters. See
3161 * gtk_label_set_width_chars().
3162 *
3163 * Returns: the maximum width of the label in characters.
3164 *
3165 * Since: 2.6
3166 **/
3167 gint
gtk_label_get_max_width_chars(GtkLabel * label)3168 gtk_label_get_max_width_chars (GtkLabel *label)
3169 {
3170 g_return_val_if_fail (GTK_IS_LABEL (label), -1);
3171
3172 return label->priv->max_width_chars;
3173 }
3174
3175 /**
3176 * gtk_label_set_line_wrap:
3177 * @label: a #GtkLabel
3178 * @wrap: the setting
3179 *
3180 * Toggles line wrapping within the #GtkLabel widget. %TRUE makes it break
3181 * lines if text exceeds the widget’s size. %FALSE lets the text get cut off
3182 * by the edge of the widget if it exceeds the widget size.
3183 *
3184 * Note that setting line wrapping to %TRUE does not make the label
3185 * wrap at its parent container’s width, because GTK+ widgets
3186 * conceptually can’t make their requisition depend on the parent
3187 * container’s size. For a label that wraps at a specific position,
3188 * set the label’s width using gtk_widget_set_size_request().
3189 **/
3190 void
gtk_label_set_line_wrap(GtkLabel * label,gboolean wrap)3191 gtk_label_set_line_wrap (GtkLabel *label,
3192 gboolean wrap)
3193 {
3194 GtkLabelPrivate *priv;
3195
3196 g_return_if_fail (GTK_IS_LABEL (label));
3197
3198 priv = label->priv;
3199
3200 wrap = wrap != FALSE;
3201
3202 if (priv->wrap != wrap)
3203 {
3204 priv->wrap = wrap;
3205
3206 gtk_label_clear_layout (label);
3207 gtk_widget_queue_resize (GTK_WIDGET (label));
3208 g_object_notify_by_pspec (G_OBJECT (label), label_props[PROP_WRAP]);
3209 }
3210 }
3211
3212 /**
3213 * gtk_label_get_line_wrap:
3214 * @label: a #GtkLabel
3215 *
3216 * Returns whether lines in the label are automatically wrapped.
3217 * See gtk_label_set_line_wrap().
3218 *
3219 * Returns: %TRUE if the lines of the label are automatically wrapped.
3220 */
3221 gboolean
gtk_label_get_line_wrap(GtkLabel * label)3222 gtk_label_get_line_wrap (GtkLabel *label)
3223 {
3224 g_return_val_if_fail (GTK_IS_LABEL (label), FALSE);
3225
3226 return label->priv->wrap;
3227 }
3228
3229 /**
3230 * gtk_label_set_line_wrap_mode:
3231 * @label: a #GtkLabel
3232 * @wrap_mode: the line wrapping mode
3233 *
3234 * If line wrapping is on (see gtk_label_set_line_wrap()) this controls how
3235 * the line wrapping is done. The default is %PANGO_WRAP_WORD which means
3236 * wrap on word boundaries.
3237 *
3238 * Since: 2.10
3239 **/
3240 void
gtk_label_set_line_wrap_mode(GtkLabel * label,PangoWrapMode wrap_mode)3241 gtk_label_set_line_wrap_mode (GtkLabel *label,
3242 PangoWrapMode wrap_mode)
3243 {
3244 GtkLabelPrivate *priv;
3245
3246 g_return_if_fail (GTK_IS_LABEL (label));
3247
3248 priv = label->priv;
3249
3250 if (priv->wrap_mode != wrap_mode)
3251 {
3252 priv->wrap_mode = wrap_mode;
3253 g_object_notify_by_pspec (G_OBJECT (label), label_props[PROP_WRAP_MODE]);
3254
3255 gtk_widget_queue_resize (GTK_WIDGET (label));
3256 }
3257 }
3258
3259 /**
3260 * gtk_label_get_line_wrap_mode:
3261 * @label: a #GtkLabel
3262 *
3263 * Returns line wrap mode used by the label. See gtk_label_set_line_wrap_mode().
3264 *
3265 * Returns: %TRUE if the lines of the label are automatically wrapped.
3266 *
3267 * Since: 2.10
3268 */
3269 PangoWrapMode
gtk_label_get_line_wrap_mode(GtkLabel * label)3270 gtk_label_get_line_wrap_mode (GtkLabel *label)
3271 {
3272 g_return_val_if_fail (GTK_IS_LABEL (label), FALSE);
3273
3274 return label->priv->wrap_mode;
3275 }
3276
3277 static void
gtk_label_destroy(GtkWidget * widget)3278 gtk_label_destroy (GtkWidget *widget)
3279 {
3280 GtkLabel *label = GTK_LABEL (widget);
3281
3282 gtk_label_set_mnemonic_widget (label, NULL);
3283
3284 GTK_WIDGET_CLASS (gtk_label_parent_class)->destroy (widget);
3285 }
3286
3287 static void
gtk_label_finalize(GObject * object)3288 gtk_label_finalize (GObject *object)
3289 {
3290 GtkLabel *label = GTK_LABEL (object);
3291 GtkLabelPrivate *priv = label->priv;
3292
3293 g_free (priv->label);
3294 g_free (priv->text);
3295
3296 g_clear_object (&priv->layout);
3297 g_clear_pointer (&priv->attrs, pango_attr_list_unref);
3298 g_clear_pointer (&priv->markup_attrs, pango_attr_list_unref);
3299
3300 if (priv->select_info)
3301 {
3302 g_object_unref (priv->select_info->drag_gesture);
3303 g_object_unref (priv->select_info->multipress_gesture);
3304 }
3305
3306 gtk_label_clear_links (label);
3307 g_free (priv->select_info);
3308
3309 g_clear_object (&priv->gadget);
3310
3311 G_OBJECT_CLASS (gtk_label_parent_class)->finalize (object);
3312 }
3313
3314 static void
gtk_label_clear_layout(GtkLabel * label)3315 gtk_label_clear_layout (GtkLabel *label)
3316 {
3317 g_clear_object (&label->priv->layout);
3318 }
3319
3320 /**
3321 * gtk_label_get_measuring_layout:
3322 * @label: the label
3323 * @existing_layout: %NULL or an existing layout already in use.
3324 * @width: the width to measure with in pango units, or -1 for infinite
3325 *
3326 * Gets a layout that can be used for measuring sizes. The returned
3327 * layout will be identical to the label’s layout except for the
3328 * layout’s width, which will be set to @width. Do not modify the returned
3329 * layout.
3330 *
3331 * Returns: a new reference to a pango layout
3332 **/
3333 static PangoLayout *
gtk_label_get_measuring_layout(GtkLabel * label,PangoLayout * existing_layout,int width)3334 gtk_label_get_measuring_layout (GtkLabel * label,
3335 PangoLayout *existing_layout,
3336 int width)
3337 {
3338 GtkLabelPrivate *priv = label->priv;
3339 PangoRectangle rect;
3340 PangoLayout *copy;
3341
3342 if (existing_layout != NULL)
3343 {
3344 if (existing_layout != priv->layout)
3345 {
3346 pango_layout_set_width (existing_layout, width);
3347 return existing_layout;
3348 }
3349
3350 g_object_unref (existing_layout);
3351 }
3352
3353 gtk_label_ensure_layout (label);
3354
3355 if (pango_layout_get_width (priv->layout) == width)
3356 {
3357 g_object_ref (priv->layout);
3358 return priv->layout;
3359 }
3360
3361 /* We can use the label's own layout if we're not allocated a size yet,
3362 * because we don't need it to be properly setup at that point.
3363 * This way we can make use of caching upon the label's creation.
3364 */
3365 if (gtk_widget_get_allocated_width (GTK_WIDGET (label)) <= 1)
3366 {
3367 g_object_ref (priv->layout);
3368 pango_layout_set_width (priv->layout, width);
3369 return priv->layout;
3370 }
3371
3372 /* oftentimes we want to measure a width that is far wider than the current width,
3373 * even though the layout would not change if we made it wider. In that case, we
3374 * can just return the current layout, because for measuring purposes, it will be
3375 * identical.
3376 */
3377 pango_layout_get_extents (priv->layout, NULL, &rect);
3378 if ((width == -1 || rect.width <= width) &&
3379 !pango_layout_is_wrapped (priv->layout) &&
3380 !pango_layout_is_ellipsized (priv->layout))
3381 {
3382 g_object_ref (priv->layout);
3383 return priv->layout;
3384 }
3385
3386 copy = pango_layout_copy (priv->layout);
3387 pango_layout_set_width (copy, width);
3388 return copy;
3389 }
3390
3391 static void
gtk_label_update_layout_width(GtkLabel * label)3392 gtk_label_update_layout_width (GtkLabel *label)
3393 {
3394 GtkLabelPrivate *priv = label->priv;
3395 GtkWidget *widget = GTK_WIDGET (label);
3396
3397 g_assert (priv->layout);
3398
3399 if (priv->ellipsize || priv->wrap)
3400 {
3401 GtkAllocation allocation;
3402 int xpad, ypad;
3403 PangoRectangle logical;
3404 gint width, height;
3405
3406 gtk_css_gadget_get_content_allocation (priv->gadget, &allocation, NULL);
3407 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
3408 gtk_misc_get_padding (GTK_MISC (label), &xpad, &ypad);
3409 G_GNUC_END_IGNORE_DEPRECATIONS
3410
3411 width = allocation.width - 2 * xpad;
3412 height = allocation.height - 2 * ypad;
3413
3414 if (priv->have_transform)
3415 {
3416 PangoContext *context = gtk_widget_get_pango_context (widget);
3417 const PangoMatrix *matrix = pango_context_get_matrix (context);
3418 const gdouble dx = matrix->xx; /* cos (M_PI * angle / 180) */
3419 const gdouble dy = matrix->xy; /* sin (M_PI * angle / 180) */
3420
3421 pango_layout_set_width (priv->layout, -1);
3422 pango_layout_get_pixel_extents (priv->layout, NULL, &logical);
3423
3424 if (fabs (dy) < 0.01)
3425 {
3426 if (logical.width > width)
3427 pango_layout_set_width (priv->layout, width * PANGO_SCALE);
3428 }
3429 else if (fabs (dx) < 0.01)
3430 {
3431 if (logical.width > height)
3432 pango_layout_set_width (priv->layout, height * PANGO_SCALE);
3433 }
3434 else
3435 {
3436 gdouble x0, y0, x1, y1, length;
3437 gboolean vertical;
3438 gint cy;
3439
3440 x0 = width / 2;
3441 y0 = dx ? x0 * dy / dx : G_MAXDOUBLE;
3442 vertical = fabs (y0) > height / 2;
3443
3444 if (vertical)
3445 {
3446 y0 = height/2;
3447 x0 = dy ? y0 * dx / dy : G_MAXDOUBLE;
3448 }
3449
3450 length = 2 * sqrt (x0 * x0 + y0 * y0);
3451 pango_layout_set_width (priv->layout, rint (length * PANGO_SCALE));
3452 pango_layout_get_pixel_size (priv->layout, NULL, &cy);
3453
3454 x1 = +dy * cy/2;
3455 y1 = -dx * cy/2;
3456
3457 if (vertical)
3458 {
3459 y0 = height/2 + y1 - y0;
3460 x0 = -y0 * dx/dy;
3461 }
3462 else
3463 {
3464 x0 = width/2 + x1 - x0;
3465 y0 = -x0 * dy/dx;
3466 }
3467
3468 length = length - sqrt (x0 * x0 + y0 * y0) * 2;
3469 pango_layout_set_width (priv->layout, rint (length * PANGO_SCALE));
3470 }
3471 }
3472 else
3473 {
3474 pango_layout_set_width (priv->layout, width * PANGO_SCALE);
3475 }
3476 }
3477 else
3478 {
3479 pango_layout_set_width (priv->layout, -1);
3480 }
3481 }
3482
3483 static void
gtk_label_update_layout_attributes(GtkLabel * label)3484 gtk_label_update_layout_attributes (GtkLabel *label)
3485 {
3486 GtkLabelPrivate *priv = label->priv;
3487 GtkWidget *widget = GTK_WIDGET (label);
3488 GtkStyleContext *context;
3489 PangoAttrList *attrs;
3490 PangoAttrList *style_attrs;
3491
3492 if (priv->layout == NULL)
3493 return;
3494
3495 context = gtk_widget_get_style_context (widget);
3496
3497 if (priv->select_info && priv->select_info->links)
3498 {
3499 GdkRGBA link_color;
3500 PangoAttribute *attribute;
3501 GList *list;
3502
3503 attrs = pango_attr_list_new ();
3504
3505 for (list = priv->select_info->links; list; list = list->next)
3506 {
3507 GtkLabelLink *link = list->data;
3508
3509 attribute = pango_attr_underline_new (TRUE);
3510 attribute->start_index = link->start;
3511 attribute->end_index = link->end;
3512 pango_attr_list_insert (attrs, attribute);
3513
3514 gtk_style_context_save_to_node (context, link->cssnode);
3515 gtk_style_context_get_color (context, gtk_style_context_get_state (context), &link_color);
3516 gtk_style_context_restore (context);
3517
3518 attribute = pango_attr_foreground_new (link_color.red * 65535,
3519 link_color.green * 65535,
3520 link_color.blue * 65535);
3521 attribute->start_index = link->start;
3522 attribute->end_index = link->end;
3523 pango_attr_list_insert (attrs, attribute);
3524 }
3525 }
3526 else if (priv->markup_attrs && priv->attrs)
3527 attrs = pango_attr_list_new ();
3528 else
3529 attrs = NULL;
3530
3531 style_attrs = _gtk_style_context_get_pango_attributes (context);
3532
3533 attrs = _gtk_pango_attr_list_merge (attrs, style_attrs);
3534 attrs = _gtk_pango_attr_list_merge (attrs, priv->markup_attrs);
3535 attrs = _gtk_pango_attr_list_merge (attrs, priv->attrs);
3536
3537 pango_layout_set_attributes (priv->layout, attrs);
3538
3539 if (attrs)
3540 pango_attr_list_unref (attrs);
3541 if (style_attrs)
3542 pango_attr_list_unref (style_attrs);
3543 }
3544
3545 static void
gtk_label_ensure_layout(GtkLabel * label)3546 gtk_label_ensure_layout (GtkLabel *label)
3547 {
3548 GtkLabelPrivate *priv = label->priv;
3549 GtkWidget *widget;
3550 gboolean rtl;
3551
3552 widget = GTK_WIDGET (label);
3553
3554 rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL;
3555
3556 if (!priv->layout)
3557 {
3558 PangoAlignment align = PANGO_ALIGN_LEFT; /* Quiet gcc */
3559 gdouble angle = gtk_label_get_angle (label);
3560
3561 if (angle != 0.0 && !priv->select_info)
3562 {
3563 PangoMatrix matrix = PANGO_MATRIX_INIT;
3564
3565 /* We rotate the standard singleton PangoContext for the widget,
3566 * depending on the fact that it's meant pretty much exclusively
3567 * for our use.
3568 */
3569 pango_matrix_rotate (&matrix, angle);
3570
3571 pango_context_set_matrix (gtk_widget_get_pango_context (widget), &matrix);
3572
3573 priv->have_transform = TRUE;
3574 }
3575 else
3576 {
3577 if (priv->have_transform)
3578 pango_context_set_matrix (gtk_widget_get_pango_context (widget), NULL);
3579
3580 priv->have_transform = FALSE;
3581 }
3582
3583 priv->layout = gtk_widget_create_pango_layout (widget, priv->text);
3584
3585 gtk_label_update_layout_attributes (label);
3586
3587 switch (priv->jtype)
3588 {
3589 case GTK_JUSTIFY_LEFT:
3590 align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
3591 break;
3592 case GTK_JUSTIFY_RIGHT:
3593 align = rtl ? PANGO_ALIGN_LEFT : PANGO_ALIGN_RIGHT;
3594 break;
3595 case GTK_JUSTIFY_CENTER:
3596 align = PANGO_ALIGN_CENTER;
3597 break;
3598 case GTK_JUSTIFY_FILL:
3599 align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
3600 pango_layout_set_justify (priv->layout, TRUE);
3601 break;
3602 default:
3603 g_assert_not_reached();
3604 }
3605
3606 pango_layout_set_alignment (priv->layout, align);
3607 pango_layout_set_ellipsize (priv->layout, priv->ellipsize);
3608 pango_layout_set_wrap (priv->layout, priv->wrap_mode);
3609 pango_layout_set_single_paragraph_mode (priv->layout, priv->single_line_mode);
3610 if (priv->lines > 0)
3611 pango_layout_set_height (priv->layout, - priv->lines);
3612
3613 gtk_label_update_layout_width (label);
3614 }
3615 }
3616
3617 static GtkSizeRequestMode
gtk_label_get_request_mode(GtkWidget * widget)3618 gtk_label_get_request_mode (GtkWidget *widget)
3619 {
3620 GtkLabel *label = GTK_LABEL (widget);
3621 gdouble angle;
3622
3623 angle = gtk_label_get_angle (label);
3624
3625 if (label->priv->wrap)
3626 return (angle == 90 || angle == 270)
3627 ? GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT
3628 : GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
3629
3630 return GTK_SIZE_REQUEST_CONSTANT_SIZE;
3631 }
3632
3633
3634 static void
get_size_for_allocation(GtkLabel * label,gint allocation,gint * minimum_size,gint * natural_size,gint * minimum_baseline,gint * natural_baseline)3635 get_size_for_allocation (GtkLabel *label,
3636 gint allocation,
3637 gint *minimum_size,
3638 gint *natural_size,
3639 gint *minimum_baseline,
3640 gint *natural_baseline)
3641 {
3642 PangoLayout *layout;
3643 gint text_height, baseline;
3644
3645 layout = gtk_label_get_measuring_layout (label, NULL, allocation * PANGO_SCALE);
3646
3647 pango_layout_get_pixel_size (layout, NULL, &text_height);
3648
3649 *minimum_size = text_height;
3650 *natural_size = text_height;
3651
3652 if (minimum_baseline || natural_baseline)
3653 {
3654 baseline = pango_layout_get_baseline (layout) / PANGO_SCALE;
3655 *minimum_baseline = baseline;
3656 *natural_baseline = baseline;
3657 }
3658
3659 g_object_unref (layout);
3660 }
3661
3662 static gint
get_char_pixels(GtkWidget * label,PangoLayout * layout)3663 get_char_pixels (GtkWidget *label,
3664 PangoLayout *layout)
3665 {
3666 PangoContext *context;
3667 PangoFontMetrics *metrics;
3668 gint char_width, digit_width;
3669
3670 context = pango_layout_get_context (layout);
3671 metrics = pango_context_get_metrics (context,
3672 pango_context_get_font_description (context),
3673 pango_context_get_language (context));
3674 char_width = pango_font_metrics_get_approximate_char_width (metrics);
3675 digit_width = pango_font_metrics_get_approximate_digit_width (metrics);
3676 pango_font_metrics_unref (metrics);
3677
3678 return MAX (char_width, digit_width);;
3679 }
3680
3681 static void
gtk_label_get_preferred_layout_size(GtkLabel * label,PangoRectangle * smallest,PangoRectangle * widest)3682 gtk_label_get_preferred_layout_size (GtkLabel *label,
3683 PangoRectangle *smallest,
3684 PangoRectangle *widest)
3685 {
3686 GtkLabelPrivate *priv = label->priv;
3687 PangoLayout *layout;
3688 gint char_pixels;
3689
3690 /* "width-chars" Hard-coded minimum width:
3691 * - minimum size should be MAX (width-chars, strlen ("..."));
3692 * - natural size should be MAX (width-chars, strlen (priv->text));
3693 *
3694 * "max-width-chars" User specified maximum size requisition
3695 * - minimum size should be MAX (width-chars, 0)
3696 * - natural size should be MIN (max-width-chars, strlen (priv->text))
3697 *
3698 * For ellipsizing labels; if max-width-chars is specified: either it is used as
3699 * a minimum size or the label text as a minimum size (natural size still overflows).
3700 *
3701 * For wrapping labels; A reasonable minimum size is useful to naturally layout
3702 * interfaces automatically. In this case if no "width-chars" is specified, the minimum
3703 * width will default to the wrap guess that gtk_label_ensure_layout() does.
3704 */
3705
3706 /* Start off with the pixel extents of an as-wide-as-possible layout */
3707 layout = gtk_label_get_measuring_layout (label, NULL, -1);
3708
3709 if (priv->width_chars > -1 || priv->max_width_chars > -1)
3710 char_pixels = get_char_pixels (GTK_WIDGET (label), layout);
3711 else
3712 char_pixels = 0;
3713
3714 pango_layout_get_extents (layout, NULL, widest);
3715 widest->width = MAX (widest->width, char_pixels * priv->width_chars);
3716 widest->x = widest->y = 0;
3717
3718 if (priv->ellipsize || priv->wrap)
3719 {
3720 /* a layout with width 0 will be as small as humanly possible */
3721 layout = gtk_label_get_measuring_layout (label,
3722 layout,
3723 priv->width_chars > -1 ? char_pixels * priv->width_chars
3724 : 0);
3725
3726 pango_layout_get_extents (layout, NULL, smallest);
3727 smallest->width = MAX (smallest->width, char_pixels * priv->width_chars);
3728 smallest->x = smallest->y = 0;
3729
3730 if (priv->max_width_chars > -1 && widest->width > char_pixels * priv->max_width_chars)
3731 {
3732 layout = gtk_label_get_measuring_layout (label,
3733 layout,
3734 MAX (smallest->width, char_pixels * priv->max_width_chars));
3735 pango_layout_get_extents (layout, NULL, widest);
3736 widest->width = MAX (widest->width, char_pixels * priv->width_chars);
3737 widest->x = widest->y = 0;
3738 }
3739 }
3740 else
3741 {
3742 *smallest = *widest;
3743 }
3744
3745 if (widest->width < smallest->width)
3746 *smallest = *widest;
3747
3748 g_object_unref (layout);
3749 }
3750
3751 static void
gtk_label_get_preferred_size(GtkWidget * widget,GtkOrientation orientation,gint * minimum_size,gint * natural_size,gint * minimum_baseline,gint * natural_baseline)3752 gtk_label_get_preferred_size (GtkWidget *widget,
3753 GtkOrientation orientation,
3754 gint *minimum_size,
3755 gint *natural_size,
3756 gint *minimum_baseline,
3757 gint *natural_baseline)
3758 {
3759 GtkLabel *label = GTK_LABEL (widget);
3760 GtkLabelPrivate *priv = label->priv;
3761 gint xpad, ypad;
3762 PangoRectangle widest_rect;
3763 PangoRectangle smallest_rect;
3764
3765 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
3766 gtk_misc_get_padding (GTK_MISC (label), &xpad, &ypad);
3767 G_GNUC_END_IGNORE_DEPRECATIONS
3768
3769 gtk_label_get_preferred_layout_size (label, &smallest_rect, &widest_rect);
3770
3771 /* Now that we have minimum and natural sizes in pango extents, apply a possible transform */
3772 if (priv->have_transform)
3773 {
3774 PangoContext *context;
3775 const PangoMatrix *matrix;
3776
3777 context = pango_layout_get_context (priv->layout);
3778 matrix = pango_context_get_matrix (context);
3779
3780 pango_matrix_transform_rectangle (matrix, &widest_rect);
3781 pango_matrix_transform_rectangle (matrix, &smallest_rect);
3782
3783 /* Bump the size in case of ellipsize to ensure pango has
3784 * enough space in the angles (note, we could alternatively set the
3785 * layout to not ellipsize when we know we have been allocated our
3786 * full size, or it may be that pango needs a fix here).
3787 */
3788 if (priv->ellipsize && priv->angle != 0 && priv->angle != 90 &&
3789 priv->angle != 180 && priv->angle != 270 && priv->angle != 360)
3790 {
3791 /* For some reason we only need this at about 110 degrees, and only
3792 * when gaining in height
3793 */
3794 widest_rect.height += ROTATION_ELLIPSIZE_PADDING * 2 * PANGO_SCALE;
3795 widest_rect.width += ROTATION_ELLIPSIZE_PADDING * 2 * PANGO_SCALE;
3796 smallest_rect.height += ROTATION_ELLIPSIZE_PADDING * 2 * PANGO_SCALE;
3797 smallest_rect.width += ROTATION_ELLIPSIZE_PADDING * 2 * PANGO_SCALE;
3798 }
3799 }
3800
3801 widest_rect.width = PANGO_PIXELS_CEIL (widest_rect.width);
3802 widest_rect.height = PANGO_PIXELS_CEIL (widest_rect.height);
3803
3804 smallest_rect.width = PANGO_PIXELS_CEIL (smallest_rect.width);
3805 smallest_rect.height = PANGO_PIXELS_CEIL (smallest_rect.height);
3806
3807 if (orientation == GTK_ORIENTATION_HORIZONTAL)
3808 {
3809 /* Note, we cant use get_size_for_allocation() when rotating
3810 * ellipsized labels.
3811 */
3812 if (!(priv->ellipsize && priv->have_transform) &&
3813 (priv->angle == 90 || priv->angle == 270))
3814 {
3815 /* Doing a h4w request on a rotated label here, return the
3816 * required width for the minimum height.
3817 */
3818 get_size_for_allocation (label,
3819 smallest_rect.height,
3820 minimum_size, natural_size,
3821 NULL, NULL);
3822
3823 }
3824 else
3825 {
3826 /* Normal desired width */
3827 *minimum_size = smallest_rect.width;
3828 *natural_size = widest_rect.width;
3829 }
3830
3831 *minimum_size += xpad * 2;
3832 *natural_size += xpad * 2;
3833
3834 if (minimum_baseline)
3835 *minimum_baseline = -1;
3836
3837 if (natural_baseline)
3838 *natural_baseline = -1;
3839 }
3840 else /* GTK_ORIENTATION_VERTICAL */
3841 {
3842 /* Note, we cant use get_size_for_allocation() when rotating
3843 * ellipsized labels.
3844 */
3845 if (!(priv->ellipsize && priv->have_transform) &&
3846 (priv->angle == 0 || priv->angle == 180 || priv->angle == 360))
3847 {
3848 /* Doing a w4h request on a label here, return the required
3849 * height for the minimum width.
3850 */
3851 get_size_for_allocation (label,
3852 widest_rect.width,
3853 minimum_size, natural_size,
3854 minimum_baseline, natural_baseline);
3855
3856 if (priv->angle == 180)
3857 {
3858 if (minimum_baseline)
3859 *minimum_baseline = *minimum_size - *minimum_baseline;
3860 if (natural_baseline)
3861 *natural_baseline = *natural_size - *natural_baseline;
3862 }
3863 }
3864 else
3865 {
3866 /* A vertically rotated label does w4h, so return the base
3867 * desired height (text length)
3868 */
3869 *minimum_size = MIN (smallest_rect.height, widest_rect.height);
3870 *natural_size = MAX (smallest_rect.height, widest_rect.height);
3871 }
3872
3873 *minimum_size += ypad * 2;
3874 *natural_size += ypad * 2;
3875 }
3876 }
3877
3878 static void
gtk_label_measure(GtkCssGadget * gadget,GtkOrientation orientation,int for_size,int * minimum,int * natural,int * minimum_baseline,int * natural_baseline,gpointer unused)3879 gtk_label_measure (GtkCssGadget *gadget,
3880 GtkOrientation orientation,
3881 int for_size,
3882 int *minimum,
3883 int *natural,
3884 int *minimum_baseline,
3885 int *natural_baseline,
3886 gpointer unused)
3887 {
3888 GtkWidget *widget;
3889 GtkLabel *label;
3890 GtkLabelPrivate *priv;
3891 gint xpad, ypad;
3892
3893 widget = gtk_css_gadget_get_owner (gadget);
3894 label = GTK_LABEL (widget);
3895 priv = label->priv;
3896
3897 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
3898 gtk_misc_get_padding (GTK_MISC (label), &xpad, &ypad);
3899 G_GNUC_END_IGNORE_DEPRECATIONS
3900
3901 if ((orientation == GTK_ORIENTATION_VERTICAL && for_size != -1 && priv->wrap && (priv->angle == 0 || priv->angle == 180 || priv->angle == 360)) ||
3902 (orientation == GTK_ORIENTATION_HORIZONTAL && priv->wrap && (priv->angle == 90 || priv->angle == 270)))
3903 {
3904 gint size;
3905
3906 if (priv->wrap)
3907 gtk_label_clear_layout (label);
3908
3909 if (orientation == GTK_ORIENTATION_HORIZONTAL)
3910 size = MAX (1, for_size) - 2 * ypad;
3911 else
3912 size = MAX (1, for_size) - 2 * xpad;
3913
3914 get_size_for_allocation (label, size, minimum, natural, minimum_baseline, natural_baseline);
3915
3916 if (orientation == GTK_ORIENTATION_HORIZONTAL)
3917 {
3918 *minimum += 2 * xpad;
3919 *natural += 2 * xpad;
3920 }
3921 else
3922 {
3923 *minimum += 2 * ypad;
3924 *natural += 2 * ypad;
3925 }
3926 }
3927 else
3928 gtk_label_get_preferred_size (widget, orientation, minimum, natural, minimum_baseline, natural_baseline);
3929 }
3930
3931 static void
gtk_label_get_preferred_width(GtkWidget * widget,gint * minimum_size,gint * natural_size)3932 gtk_label_get_preferred_width (GtkWidget *widget,
3933 gint *minimum_size,
3934 gint *natural_size)
3935 {
3936 gtk_css_gadget_get_preferred_size (GTK_LABEL (widget)->priv->gadget,
3937 GTK_ORIENTATION_HORIZONTAL,
3938 -1,
3939 minimum_size, natural_size,
3940 NULL, NULL);
3941 }
3942
3943 static void
gtk_label_get_preferred_height(GtkWidget * widget,gint * minimum_size,gint * natural_size)3944 gtk_label_get_preferred_height (GtkWidget *widget,
3945 gint *minimum_size,
3946 gint *natural_size)
3947 {
3948 gtk_css_gadget_get_preferred_size (GTK_LABEL (widget)->priv->gadget,
3949 GTK_ORIENTATION_VERTICAL,
3950 -1,
3951 minimum_size, natural_size,
3952 NULL, NULL);
3953 }
3954
3955 static void
gtk_label_get_preferred_width_for_height(GtkWidget * widget,gint height,gint * minimum_width,gint * natural_width)3956 gtk_label_get_preferred_width_for_height (GtkWidget *widget,
3957 gint height,
3958 gint *minimum_width,
3959 gint *natural_width)
3960 {
3961 gtk_css_gadget_get_preferred_size (GTK_LABEL (widget)->priv->gadget,
3962 GTK_ORIENTATION_HORIZONTAL,
3963 height,
3964 minimum_width, natural_width,
3965 NULL, NULL);
3966 }
3967
3968 static void
gtk_label_get_preferred_height_for_width(GtkWidget * widget,gint width,gint * minimum_height,gint * natural_height)3969 gtk_label_get_preferred_height_for_width (GtkWidget *widget,
3970 gint width,
3971 gint *minimum_height,
3972 gint *natural_height)
3973 {
3974 gtk_css_gadget_get_preferred_size (GTK_LABEL (widget)->priv->gadget,
3975 GTK_ORIENTATION_VERTICAL,
3976 width,
3977 minimum_height, natural_height,
3978 NULL, NULL);
3979 }
3980
3981 static void
gtk_label_get_preferred_height_and_baseline_for_width(GtkWidget * widget,gint width,gint * minimum_height,gint * natural_height,gint * minimum_baseline,gint * natural_baseline)3982 gtk_label_get_preferred_height_and_baseline_for_width (GtkWidget *widget,
3983 gint width,
3984 gint *minimum_height,
3985 gint *natural_height,
3986 gint *minimum_baseline,
3987 gint *natural_baseline)
3988 {
3989 gtk_css_gadget_get_preferred_size (GTK_LABEL (widget)->priv->gadget,
3990 GTK_ORIENTATION_VERTICAL,
3991 width,
3992 minimum_height, natural_height,
3993 minimum_baseline, natural_baseline);
3994 }
3995
3996 static void
get_layout_location(GtkLabel * label,gint * xp,gint * yp)3997 get_layout_location (GtkLabel *label,
3998 gint *xp,
3999 gint *yp)
4000 {
4001 GtkAllocation allocation;
4002 GtkWidget *widget;
4003 GtkLabelPrivate *priv;
4004 gint xpad, ypad;
4005 gint req_width, x, y;
4006 gint req_height;
4007 gfloat xalign, yalign;
4008 PangoRectangle logical;
4009 gint baseline, layout_baseline, baseline_offset;
4010
4011 widget = GTK_WIDGET (label);
4012 priv = label->priv;
4013
4014 xalign = priv->xalign;
4015 yalign = priv->yalign;
4016
4017 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
4018 gtk_misc_get_padding (GTK_MISC (label), &xpad, &ypad);
4019 G_GNUC_END_IGNORE_DEPRECATIONS
4020
4021 if (gtk_widget_get_direction (widget) != GTK_TEXT_DIR_LTR)
4022 xalign = 1.0 - xalign;
4023
4024 pango_layout_get_extents (priv->layout, NULL, &logical);
4025
4026 if (priv->have_transform)
4027 {
4028 PangoContext *context = gtk_widget_get_pango_context (widget);
4029 const PangoMatrix *matrix = pango_context_get_matrix (context);
4030 pango_matrix_transform_rectangle (matrix, &logical);
4031 }
4032
4033 pango_extents_to_pixels (&logical, NULL);
4034
4035 req_width = logical.width;
4036 req_height = logical.height;
4037
4038 req_width += 2 * xpad;
4039 req_height += 2 * ypad;
4040
4041 gtk_css_gadget_get_content_allocation (priv->gadget,
4042 &allocation,
4043 &baseline);
4044
4045 x = floor (allocation.x + xpad + xalign * (allocation.width - req_width) - logical.x);
4046
4047 baseline_offset = 0;
4048 if (baseline != -1 && !priv->have_transform)
4049 {
4050 layout_baseline = pango_layout_get_baseline (priv->layout) / PANGO_SCALE;
4051 baseline_offset = baseline - layout_baseline;
4052 yalign = 0.0; /* Can't support yalign while baseline aligning */
4053 }
4054
4055 /* bgo#315462 - For single-line labels, *do* align the requisition with
4056 * respect to the allocation, even if we are under-allocated. For multi-line
4057 * labels, always show the top of the text when they are under-allocated. The
4058 * rationale is this:
4059 *
4060 * - Single-line labels appear in GtkButtons, and it is very easy to get them
4061 * to be smaller than their requisition. The button may clip the label, but
4062 * the label will still be able to show most of itself and the focus
4063 * rectangle. Also, it is fairly easy to read a single line of clipped text.
4064 *
4065 * - Multi-line labels should not be clipped to showing "something in the
4066 * middle". You want to read the first line, at least, to get some context.
4067 */
4068 if (pango_layout_get_line_count (priv->layout) == 1)
4069 y = floor (allocation.y + ypad + (allocation.height - req_height) * yalign) - logical.y + baseline_offset;
4070 else
4071 y = floor (allocation.y + ypad + MAX ((allocation.height - req_height) * yalign, 0)) - logical.y + baseline_offset;
4072
4073 if (xp)
4074 *xp = x;
4075
4076 if (yp)
4077 *yp = y;
4078 }
4079
4080 static void
gtk_label_get_ink_rect(GtkLabel * label,GdkRectangle * rect)4081 gtk_label_get_ink_rect (GtkLabel *label,
4082 GdkRectangle *rect)
4083 {
4084 GtkLabelPrivate *priv = label->priv;
4085 GtkStyleContext *context;
4086 PangoRectangle ink_rect;
4087 GtkBorder extents;
4088 int x, y;
4089
4090 gtk_label_ensure_layout (label);
4091 get_layout_location (label, &x, &y);
4092 pango_layout_get_pixel_extents (priv->layout, &ink_rect, NULL);
4093 context = gtk_widget_get_style_context (GTK_WIDGET (label));
4094 _gtk_css_shadows_value_get_extents (_gtk_style_context_peek_property (context, GTK_CSS_PROPERTY_TEXT_SHADOW), &extents);
4095
4096 rect->x = x + ink_rect.x - extents.left;
4097 rect->width = ink_rect.width + extents.left + extents.right;
4098 rect->y = y + ink_rect.y - extents.top;
4099 rect->height = ink_rect.height + extents.top + extents.bottom;
4100 }
4101
4102 static void
gtk_label_size_allocate(GtkWidget * widget,GtkAllocation * allocation)4103 gtk_label_size_allocate (GtkWidget *widget,
4104 GtkAllocation *allocation)
4105 {
4106 GtkLabel *label = GTK_LABEL (widget);
4107 GtkLabelPrivate *priv = label->priv;
4108 GdkRectangle clip_rect, clip;
4109
4110 GTK_WIDGET_CLASS (gtk_label_parent_class)->size_allocate (widget, allocation);
4111
4112 gtk_css_gadget_allocate (priv->gadget,
4113 allocation,
4114 gtk_widget_get_allocated_baseline (widget),
4115 &clip);
4116
4117 if (priv->layout)
4118 gtk_label_update_layout_width (label);
4119
4120 if (priv->select_info && priv->select_info->window)
4121 gdk_window_move_resize (priv->select_info->window,
4122 allocation->x,
4123 allocation->y,
4124 allocation->width,
4125 allocation->height);
4126
4127 gtk_label_get_ink_rect (label, &clip_rect);
4128 gdk_rectangle_union (&clip_rect, &clip, &clip_rect);
4129 _gtk_widget_set_simple_clip (widget, &clip_rect);
4130 }
4131
4132 static void
gtk_label_update_cursor(GtkLabel * label)4133 gtk_label_update_cursor (GtkLabel *label)
4134 {
4135 GtkLabelPrivate *priv = label->priv;
4136 GtkWidget *widget;
4137
4138 if (!priv->select_info)
4139 return;
4140
4141 widget = GTK_WIDGET (label);
4142
4143 if (gtk_widget_get_realized (widget))
4144 {
4145 GdkDisplay *display;
4146 GdkCursor *cursor;
4147
4148 if (gtk_widget_is_sensitive (widget))
4149 {
4150 display = gtk_widget_get_display (widget);
4151
4152 if (priv->select_info->active_link)
4153 cursor = gdk_cursor_new_from_name (display, "pointer");
4154 else if (priv->select_info->selectable)
4155 cursor = gdk_cursor_new_from_name (display, "text");
4156 else
4157 cursor = NULL;
4158 }
4159 else
4160 cursor = NULL;
4161
4162 gdk_window_set_cursor (priv->select_info->window, cursor);
4163
4164 if (cursor)
4165 g_object_unref (cursor);
4166 }
4167 }
4168
4169 static void
update_link_state(GtkLabel * label)4170 update_link_state (GtkLabel *label)
4171 {
4172 GtkLabelPrivate *priv = label->priv;
4173 GList *l;
4174 GtkStateFlags state;
4175
4176 if (!priv->select_info)
4177 return;
4178
4179 for (l = priv->select_info->links; l; l = l->next)
4180 {
4181 GtkLabelLink *link = l->data;
4182
4183 state = gtk_widget_get_state_flags (GTK_WIDGET (label));
4184 if (link->visited)
4185 state |= GTK_STATE_FLAG_VISITED;
4186 else
4187 state |= GTK_STATE_FLAG_LINK;
4188 if (link == priv->select_info->active_link)
4189 {
4190 if (priv->select_info->link_clicked)
4191 state |= GTK_STATE_FLAG_ACTIVE;
4192 else
4193 state |= GTK_STATE_FLAG_PRELIGHT;
4194 }
4195 gtk_css_node_set_state (link->cssnode, state);
4196 }
4197 }
4198
4199 static void
gtk_label_state_flags_changed(GtkWidget * widget,GtkStateFlags prev_state)4200 gtk_label_state_flags_changed (GtkWidget *widget,
4201 GtkStateFlags prev_state)
4202 {
4203 GtkLabel *label = GTK_LABEL (widget);
4204 GtkLabelPrivate *priv = label->priv;
4205
4206 if (priv->select_info)
4207 {
4208 if (!gtk_widget_is_sensitive (widget))
4209 gtk_label_select_region (label, 0, 0);
4210
4211 gtk_label_update_cursor (label);
4212 update_link_state (label);
4213 }
4214
4215 if (GTK_WIDGET_CLASS (gtk_label_parent_class)->state_flags_changed)
4216 GTK_WIDGET_CLASS (gtk_label_parent_class)->state_flags_changed (widget, prev_state);
4217 }
4218
4219 static void
gtk_label_style_updated(GtkWidget * widget)4220 gtk_label_style_updated (GtkWidget *widget)
4221 {
4222 GtkLabel *label = GTK_LABEL (widget);
4223 GtkLabelPrivate *priv = label->priv;
4224 GtkStyleContext *context;
4225 GtkCssStyleChange *change;
4226
4227 GTK_WIDGET_CLASS (gtk_label_parent_class)->style_updated (widget);
4228
4229 context = gtk_widget_get_style_context (widget);
4230 change = gtk_style_context_get_change (context);
4231
4232 if (change == NULL || gtk_css_style_change_affects (change, GTK_CSS_AFFECTS_FONT))
4233 {
4234 gtk_label_clear_layout (GTK_LABEL (widget));
4235 gtk_widget_queue_resize (label);
4236 }
4237
4238 if (change == NULL || gtk_css_style_change_affects (change, GTK_CSS_AFFECTS_TEXT_ATTRS) ||
4239 (priv->select_info && priv->select_info->links))
4240 gtk_label_update_layout_attributes (label);
4241 }
4242
4243 static PangoDirection
get_cursor_direction(GtkLabel * label)4244 get_cursor_direction (GtkLabel *label)
4245 {
4246 GtkLabelPrivate *priv = label->priv;
4247 GSList *l;
4248
4249 g_assert (priv->select_info);
4250
4251 gtk_label_ensure_layout (label);
4252
4253 for (l = pango_layout_get_lines_readonly (priv->layout); l; l = l->next)
4254 {
4255 PangoLayoutLine *line = l->data;
4256
4257 /* If priv->select_info->selection_end is at the very end of
4258 * the line, we don't know if the cursor is on this line or
4259 * the next without looking ahead at the next line. (End
4260 * of paragraph is different from line break.) But it's
4261 * definitely in this paragraph, which is good enough
4262 * to figure out the resolved direction.
4263 */
4264 if (line->start_index + line->length >= priv->select_info->selection_end)
4265 return line->resolved_dir;
4266 }
4267
4268 return PANGO_DIRECTION_LTR;
4269 }
4270
4271 static GtkLabelLink *
gtk_label_get_focus_link(GtkLabel * label)4272 gtk_label_get_focus_link (GtkLabel *label)
4273 {
4274 GtkLabelPrivate *priv = label->priv;
4275 GtkLabelSelectionInfo *info = priv->select_info;
4276 GList *l;
4277
4278 if (!info)
4279 return NULL;
4280
4281 if (info->selection_anchor != info->selection_end)
4282 return NULL;
4283
4284 for (l = info->links; l; l = l->next)
4285 {
4286 GtkLabelLink *link = l->data;
4287 if (link->start <= info->selection_anchor &&
4288 info->selection_anchor <= link->end)
4289 return link;
4290 }
4291
4292 return NULL;
4293 }
4294
4295 static gboolean
gtk_label_draw(GtkWidget * widget,cairo_t * cr)4296 gtk_label_draw (GtkWidget *widget,
4297 cairo_t *cr)
4298 {
4299 gtk_css_gadget_draw (GTK_LABEL (widget)->priv->gadget, cr);
4300
4301 return FALSE;
4302 }
4303
4304 static void layout_to_window_coords (GtkLabel *label,
4305 gint *x,
4306 gint *y);
4307
4308 static gboolean
gtk_label_render(GtkCssGadget * gadget,cairo_t * cr,int x,int y,int width,int height,gpointer data)4309 gtk_label_render (GtkCssGadget *gadget,
4310 cairo_t *cr,
4311 int x,
4312 int y,
4313 int width,
4314 int height,
4315 gpointer data)
4316 {
4317 GtkWidget *widget;
4318 GtkLabel *label;
4319 GtkLabelPrivate *priv;
4320 GtkLabelSelectionInfo *info;
4321 GtkStyleContext *context;
4322 gint lx, ly;
4323
4324 widget = gtk_css_gadget_get_owner (gadget);
4325 label = GTK_LABEL (widget);
4326 priv = label->priv;
4327 info = priv->select_info;
4328
4329 gtk_label_ensure_layout (label);
4330
4331 context = gtk_widget_get_style_context (widget);
4332
4333 if (GTK_IS_ACCEL_LABEL (widget))
4334 {
4335 guint ac_width = gtk_accel_label_get_accel_width (GTK_ACCEL_LABEL (widget));
4336 width -= ac_width;
4337 if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
4338 x += ac_width;
4339 }
4340
4341 if (priv->text && (*priv->text != '\0'))
4342 {
4343 lx = ly = 0;
4344 layout_to_window_coords (label, &lx, &ly);
4345
4346 gtk_render_layout (context, cr, lx, ly, priv->layout);
4347
4348 if (info && (info->selection_anchor != info->selection_end))
4349 {
4350 gint range[2];
4351 cairo_region_t *clip;
4352
4353 range[0] = info->selection_anchor;
4354 range[1] = info->selection_end;
4355
4356 if (range[0] > range[1])
4357 {
4358 gint tmp = range[0];
4359 range[0] = range[1];
4360 range[1] = tmp;
4361 }
4362
4363 clip = gdk_pango_layout_get_clip_region (priv->layout, lx, ly, range, 1);
4364
4365 cairo_save (cr);
4366 gtk_style_context_save_to_node (context, info->selection_node);
4367
4368 gdk_cairo_region (cr, clip);
4369 cairo_clip (cr);
4370
4371 gtk_render_background (context, cr, x, y, width, height);
4372 gtk_render_layout (context, cr, lx, ly, priv->layout);
4373
4374 gtk_style_context_restore (context);
4375 cairo_restore (cr);
4376 cairo_region_destroy (clip);
4377 }
4378 else if (info)
4379 {
4380 GtkLabelLink *focus_link;
4381 GtkLabelLink *active_link;
4382 gint range[2];
4383 cairo_region_t *clip;
4384 GdkRectangle rect;
4385
4386 if (info->selectable &&
4387 gtk_widget_has_focus (widget) &&
4388 gtk_widget_is_drawable (widget))
4389 {
4390 PangoDirection cursor_direction;
4391
4392 cursor_direction = get_cursor_direction (label);
4393 gtk_render_insertion_cursor (context, cr,
4394 lx, ly,
4395 priv->layout, priv->select_info->selection_end,
4396 cursor_direction);
4397 }
4398
4399 focus_link = gtk_label_get_focus_link (label);
4400 active_link = info->active_link;
4401
4402 if (active_link)
4403 {
4404 range[0] = active_link->start;
4405 range[1] = active_link->end;
4406
4407 cairo_save (cr);
4408 gtk_style_context_save_to_node (context, active_link->cssnode);
4409
4410 clip = gdk_pango_layout_get_clip_region (priv->layout, lx, ly, range, 1);
4411
4412 gdk_cairo_region (cr, clip);
4413 cairo_clip (cr);
4414 cairo_region_destroy (clip);
4415
4416 gtk_render_background (context, cr, x, y, width, height);
4417 gtk_render_layout (context, cr, lx, ly, priv->layout);
4418
4419 gtk_style_context_restore (context);
4420 cairo_restore (cr);
4421 }
4422
4423 if (focus_link && gtk_widget_has_visible_focus (widget))
4424 {
4425 range[0] = focus_link->start;
4426 range[1] = focus_link->end;
4427
4428 clip = gdk_pango_layout_get_clip_region (priv->layout, lx, ly, range, 1);
4429 cairo_region_get_extents (clip, &rect);
4430
4431 gtk_render_focus (context, cr, rect.x, rect.y, rect.width, rect.height);
4432
4433 cairo_region_destroy (clip);
4434 }
4435 }
4436 }
4437
4438 return FALSE;
4439 }
4440
4441 static gboolean
separate_uline_pattern(const gchar * str,guint * accel_key,gchar ** new_str,gchar ** pattern)4442 separate_uline_pattern (const gchar *str,
4443 guint *accel_key,
4444 gchar **new_str,
4445 gchar **pattern)
4446 {
4447 gboolean underscore;
4448 const gchar *src;
4449 gchar *dest;
4450 gchar *pattern_dest;
4451
4452 *accel_key = GDK_KEY_VoidSymbol;
4453 *new_str = g_new (gchar, strlen (str) + 1);
4454 *pattern = g_new (gchar, g_utf8_strlen (str, -1) + 1);
4455
4456 underscore = FALSE;
4457
4458 src = str;
4459 dest = *new_str;
4460 pattern_dest = *pattern;
4461
4462 while (*src)
4463 {
4464 gunichar c;
4465 const gchar *next_src;
4466
4467 c = g_utf8_get_char (src);
4468 if (c == (gunichar)-1)
4469 {
4470 g_warning ("Invalid input string");
4471 g_free (*new_str);
4472 g_free (*pattern);
4473
4474 return FALSE;
4475 }
4476 next_src = g_utf8_next_char (src);
4477
4478 if (underscore)
4479 {
4480 if (c == '_')
4481 *pattern_dest++ = ' ';
4482 else
4483 {
4484 *pattern_dest++ = '_';
4485 if (*accel_key == GDK_KEY_VoidSymbol)
4486 *accel_key = gdk_keyval_to_lower (gdk_unicode_to_keyval (c));
4487 }
4488
4489 while (src < next_src)
4490 *dest++ = *src++;
4491
4492 underscore = FALSE;
4493 }
4494 else
4495 {
4496 if (c == '_')
4497 {
4498 underscore = TRUE;
4499 src = next_src;
4500 }
4501 else
4502 {
4503 while (src < next_src)
4504 *dest++ = *src++;
4505
4506 *pattern_dest++ = ' ';
4507 }
4508 }
4509 }
4510
4511 *dest = 0;
4512 *pattern_dest = 0;
4513
4514 return TRUE;
4515 }
4516
4517 static void
gtk_label_set_uline_text_internal(GtkLabel * label,const gchar * str)4518 gtk_label_set_uline_text_internal (GtkLabel *label,
4519 const gchar *str)
4520 {
4521 GtkLabelPrivate *priv = label->priv;
4522 guint accel_key = GDK_KEY_VoidSymbol;
4523 gchar *new_str;
4524 gchar *pattern;
4525
4526 g_return_if_fail (GTK_IS_LABEL (label));
4527 g_return_if_fail (str != NULL);
4528
4529 /* Split text into the base text and a separate pattern
4530 * of underscores.
4531 */
4532 if (!separate_uline_pattern (str, &accel_key, &new_str, &pattern))
4533 return;
4534
4535 gtk_label_set_text_internal (label, new_str);
4536 gtk_label_set_pattern_internal (label, pattern, TRUE);
4537 priv->mnemonic_keyval = accel_key;
4538
4539 g_free (pattern);
4540 }
4541
4542 /**
4543 * gtk_label_set_text_with_mnemonic:
4544 * @label: a #GtkLabel
4545 * @str: a string
4546 *
4547 * Sets the label’s text from the string @str.
4548 * If characters in @str are preceded by an underscore, they are underlined
4549 * indicating that they represent a keyboard accelerator called a mnemonic.
4550 * The mnemonic key can be used to activate another widget, chosen
4551 * automatically, or explicitly using gtk_label_set_mnemonic_widget().
4552 **/
4553 void
gtk_label_set_text_with_mnemonic(GtkLabel * label,const gchar * str)4554 gtk_label_set_text_with_mnemonic (GtkLabel *label,
4555 const gchar *str)
4556 {
4557 g_return_if_fail (GTK_IS_LABEL (label));
4558 g_return_if_fail (str != NULL);
4559
4560 g_object_freeze_notify (G_OBJECT (label));
4561
4562 gtk_label_set_label_internal (label, g_strdup (str));
4563 gtk_label_set_use_markup_internal (label, FALSE);
4564 gtk_label_set_use_underline_internal (label, TRUE);
4565
4566 gtk_label_recalculate (label);
4567
4568 g_object_thaw_notify (G_OBJECT (label));
4569 }
4570
4571 static void
gtk_label_realize(GtkWidget * widget)4572 gtk_label_realize (GtkWidget *widget)
4573 {
4574 GtkLabel *label = GTK_LABEL (widget);
4575 GtkLabelPrivate *priv = label->priv;
4576
4577 GTK_WIDGET_CLASS (gtk_label_parent_class)->realize (widget);
4578
4579 if (priv->select_info)
4580 gtk_label_create_window (label);
4581 }
4582
4583 static void
gtk_label_unrealize(GtkWidget * widget)4584 gtk_label_unrealize (GtkWidget *widget)
4585 {
4586 GtkLabel *label = GTK_LABEL (widget);
4587 GtkLabelPrivate *priv = label->priv;
4588
4589 if (priv->select_info)
4590 gtk_label_destroy_window (label);
4591
4592 GTK_WIDGET_CLASS (gtk_label_parent_class)->unrealize (widget);
4593 }
4594
4595 static void
gtk_label_map(GtkWidget * widget)4596 gtk_label_map (GtkWidget *widget)
4597 {
4598 GtkLabel *label = GTK_LABEL (widget);
4599 GtkLabelPrivate *priv = label->priv;
4600
4601 GTK_WIDGET_CLASS (gtk_label_parent_class)->map (widget);
4602
4603 if (priv->select_info)
4604 gdk_window_show (priv->select_info->window);
4605 }
4606
4607 static void
gtk_label_unmap(GtkWidget * widget)4608 gtk_label_unmap (GtkWidget *widget)
4609 {
4610 GtkLabel *label = GTK_LABEL (widget);
4611 GtkLabelPrivate *priv = label->priv;
4612
4613 if (priv->select_info)
4614 {
4615 gdk_window_hide (priv->select_info->window);
4616
4617 if (priv->select_info->popup_menu)
4618 {
4619 gtk_widget_destroy (priv->select_info->popup_menu);
4620 priv->select_info->popup_menu = NULL;
4621 }
4622 }
4623
4624 GTK_WIDGET_CLASS (gtk_label_parent_class)->unmap (widget);
4625 }
4626
4627 static void
window_to_layout_coords(GtkLabel * label,gint * x,gint * y)4628 window_to_layout_coords (GtkLabel *label,
4629 gint *x,
4630 gint *y)
4631 {
4632 GtkAllocation allocation;
4633 gint lx, ly;
4634
4635 /* get layout location in widget->window coords */
4636 get_layout_location (label, &lx, &ly);
4637 gtk_widget_get_allocation (GTK_WIDGET (label), &allocation);
4638
4639 *x += allocation.x; /* go to widget->window */
4640 *x -= lx; /* go to layout */
4641
4642 *y += allocation.y; /* go to widget->window */
4643 *y -= ly; /* go to layout */
4644 }
4645
4646 static void
layout_to_window_coords(GtkLabel * label,gint * x,gint * y)4647 layout_to_window_coords (GtkLabel *label,
4648 gint *x,
4649 gint *y)
4650 {
4651 gint lx, ly;
4652 GtkAllocation allocation;
4653
4654 /* get layout location in widget->window coords */
4655 get_layout_location (label, &lx, &ly);
4656 gtk_widget_get_allocation (GTK_WIDGET (label), &allocation);
4657
4658 *x += lx; /* go to widget->window */
4659 *x -= allocation.x; /* go to selection window */
4660
4661 *y += ly; /* go to widget->window */
4662 *y -= allocation.y; /* go to selection window */
4663 }
4664
4665 static gboolean
get_layout_index(GtkLabel * label,gint x,gint y,gint * index)4666 get_layout_index (GtkLabel *label,
4667 gint x,
4668 gint y,
4669 gint *index)
4670 {
4671 GtkLabelPrivate *priv = label->priv;
4672 gint trailing = 0;
4673 const gchar *cluster;
4674 const gchar *cluster_end;
4675 gboolean inside;
4676
4677 *index = 0;
4678
4679 gtk_label_ensure_layout (label);
4680
4681 window_to_layout_coords (label, &x, &y);
4682
4683 x *= PANGO_SCALE;
4684 y *= PANGO_SCALE;
4685
4686 inside = pango_layout_xy_to_index (priv->layout,
4687 x, y,
4688 index, &trailing);
4689
4690 cluster = priv->text + *index;
4691 cluster_end = cluster;
4692 while (trailing)
4693 {
4694 cluster_end = g_utf8_next_char (cluster_end);
4695 --trailing;
4696 }
4697
4698 *index += (cluster_end - cluster);
4699
4700 return inside;
4701 }
4702
4703 static gboolean
range_is_in_ellipsis_full(GtkLabel * label,gint range_start,gint range_end,gint * ellipsis_start,gint * ellipsis_end)4704 range_is_in_ellipsis_full (GtkLabel *label,
4705 gint range_start,
4706 gint range_end,
4707 gint *ellipsis_start,
4708 gint *ellipsis_end)
4709 {
4710 GtkLabelPrivate *priv = label->priv;
4711 PangoLayoutIter *iter;
4712 gboolean in_ellipsis;
4713
4714 if (!priv->ellipsize)
4715 return FALSE;
4716
4717 gtk_label_ensure_layout (label);
4718
4719 if (!pango_layout_is_ellipsized (priv->layout))
4720 return FALSE;
4721
4722 iter = pango_layout_get_iter (priv->layout);
4723
4724 in_ellipsis = FALSE;
4725
4726 do {
4727 PangoLayoutRun *run;
4728
4729 run = pango_layout_iter_get_run_readonly (iter);
4730 if (run)
4731 {
4732 PangoItem *item;
4733
4734 item = ((PangoGlyphItem*)run)->item;
4735
4736 if (item->offset <= range_start && range_end <= item->offset + item->length)
4737 {
4738 if (item->analysis.flags & PANGO_ANALYSIS_FLAG_IS_ELLIPSIS)
4739 {
4740 if (ellipsis_start)
4741 *ellipsis_start = item->offset;
4742 if (ellipsis_end)
4743 *ellipsis_end = item->offset + item->length;
4744 in_ellipsis = TRUE;
4745 }
4746 break;
4747 }
4748 else if (item->offset + item->length >= range_end)
4749 break;
4750 }
4751 } while (pango_layout_iter_next_run (iter));
4752
4753 pango_layout_iter_free (iter);
4754
4755 return in_ellipsis;
4756 }
4757
4758 static gboolean
range_is_in_ellipsis(GtkLabel * label,gint range_start,gint range_end)4759 range_is_in_ellipsis (GtkLabel *label,
4760 gint range_start,
4761 gint range_end)
4762 {
4763 return range_is_in_ellipsis_full (label, range_start, range_end, NULL, NULL);
4764 }
4765
4766 static void
gtk_label_select_word(GtkLabel * label)4767 gtk_label_select_word (GtkLabel *label)
4768 {
4769 GtkLabelPrivate *priv = label->priv;
4770 gint min, max;
4771
4772 gint start_index = gtk_label_move_backward_word (label, priv->select_info->selection_end);
4773 gint end_index = gtk_label_move_forward_word (label, priv->select_info->selection_end);
4774
4775 min = MIN (priv->select_info->selection_anchor,
4776 priv->select_info->selection_end);
4777 max = MAX (priv->select_info->selection_anchor,
4778 priv->select_info->selection_end);
4779
4780 min = MIN (min, start_index);
4781 max = MAX (max, end_index);
4782
4783 gtk_label_select_region_index (label, min, max);
4784 }
4785
4786 static void
gtk_label_grab_focus(GtkWidget * widget)4787 gtk_label_grab_focus (GtkWidget *widget)
4788 {
4789 GtkLabel *label = GTK_LABEL (widget);
4790 GtkLabelPrivate *priv = label->priv;
4791 gboolean select_on_focus;
4792 GtkLabelLink *link;
4793 GList *l;
4794
4795 if (priv->select_info == NULL)
4796 return;
4797
4798 GTK_WIDGET_CLASS (gtk_label_parent_class)->grab_focus (widget);
4799
4800 if (priv->select_info->selectable)
4801 {
4802 g_object_get (gtk_widget_get_settings (widget),
4803 "gtk-label-select-on-focus",
4804 &select_on_focus,
4805 NULL);
4806
4807 if (select_on_focus && !priv->in_click)
4808 gtk_label_select_region (label, 0, -1);
4809 }
4810 else
4811 {
4812 if (priv->select_info->links && !priv->in_click)
4813 {
4814 for (l = priv->select_info->links; l; l = l->next)
4815 {
4816 link = l->data;
4817 if (!range_is_in_ellipsis (label, link->start, link->end))
4818 {
4819 priv->select_info->selection_anchor = link->start;
4820 priv->select_info->selection_end = link->start;
4821 _gtk_label_accessible_focus_link_changed (label);
4822 break;
4823 }
4824 }
4825 }
4826 }
4827 }
4828
4829 static gboolean
gtk_label_focus(GtkWidget * widget,GtkDirectionType direction)4830 gtk_label_focus (GtkWidget *widget,
4831 GtkDirectionType direction)
4832 {
4833 GtkLabel *label = GTK_LABEL (widget);
4834 GtkLabelPrivate *priv = label->priv;
4835 GtkLabelSelectionInfo *info = priv->select_info;
4836 GtkLabelLink *focus_link;
4837 GList *l;
4838
4839 if (!gtk_widget_is_focus (widget))
4840 {
4841 gtk_widget_grab_focus (widget);
4842 if (info)
4843 {
4844 focus_link = gtk_label_get_focus_link (label);
4845 if (focus_link && direction == GTK_DIR_TAB_BACKWARD)
4846 {
4847 for (l = g_list_last (info->links); l; l = l->prev)
4848 {
4849 focus_link = l->data;
4850 if (!range_is_in_ellipsis (label, focus_link->start, focus_link->end))
4851 {
4852 info->selection_anchor = focus_link->start;
4853 info->selection_end = focus_link->start;
4854 _gtk_label_accessible_focus_link_changed (label);
4855 }
4856 }
4857 }
4858 }
4859
4860 return TRUE;
4861 }
4862
4863 if (!info)
4864 return FALSE;
4865
4866 if (info->selectable)
4867 {
4868 gint index;
4869
4870 if (info->selection_anchor != info->selection_end)
4871 goto out;
4872
4873 index = info->selection_anchor;
4874
4875 if (direction == GTK_DIR_TAB_FORWARD)
4876 for (l = info->links; l; l = l->next)
4877 {
4878 GtkLabelLink *link = l->data;
4879
4880 if (link->start > index)
4881 {
4882 if (!range_is_in_ellipsis (label, link->start, link->end))
4883 {
4884 gtk_label_select_region_index (label, link->start, link->start);
4885 _gtk_label_accessible_focus_link_changed (label);
4886 return TRUE;
4887 }
4888 }
4889 }
4890 else if (direction == GTK_DIR_TAB_BACKWARD)
4891 for (l = g_list_last (info->links); l; l = l->prev)
4892 {
4893 GtkLabelLink *link = l->data;
4894
4895 if (link->end < index)
4896 {
4897 if (!range_is_in_ellipsis (label, link->start, link->end))
4898 {
4899 gtk_label_select_region_index (label, link->start, link->start);
4900 _gtk_label_accessible_focus_link_changed (label);
4901 return TRUE;
4902 }
4903 }
4904 }
4905
4906 goto out;
4907 }
4908 else
4909 {
4910 focus_link = gtk_label_get_focus_link (label);
4911 switch (direction)
4912 {
4913 case GTK_DIR_TAB_FORWARD:
4914 if (focus_link)
4915 {
4916 l = g_list_find (info->links, focus_link);
4917 l = l->next;
4918 }
4919 else
4920 l = info->links;
4921 for (; l; l = l->next)
4922 {
4923 GtkLabelLink *link = l->data;
4924 if (!range_is_in_ellipsis (label, link->start, link->end))
4925 break;
4926 }
4927 break;
4928
4929 case GTK_DIR_TAB_BACKWARD:
4930 if (focus_link)
4931 {
4932 l = g_list_find (info->links, focus_link);
4933 l = l->prev;
4934 }
4935 else
4936 l = g_list_last (info->links);
4937 for (; l; l = l->prev)
4938 {
4939 GtkLabelLink *link = l->data;
4940 if (!range_is_in_ellipsis (label, link->start, link->end))
4941 break;
4942 }
4943 break;
4944
4945 default:
4946 goto out;
4947 }
4948
4949 if (l)
4950 {
4951 focus_link = l->data;
4952 info->selection_anchor = focus_link->start;
4953 info->selection_end = focus_link->start;
4954 _gtk_label_accessible_focus_link_changed (label);
4955 gtk_widget_queue_draw (widget);
4956
4957 return TRUE;
4958 }
4959 }
4960
4961 out:
4962
4963 return FALSE;
4964 }
4965
4966 static void
gtk_label_multipress_gesture_pressed(GtkGestureMultiPress * gesture,gint n_press,gdouble widget_x,gdouble widget_y,GtkLabel * label)4967 gtk_label_multipress_gesture_pressed (GtkGestureMultiPress *gesture,
4968 gint n_press,
4969 gdouble widget_x,
4970 gdouble widget_y,
4971 GtkLabel *label)
4972 {
4973 GtkLabelPrivate *priv = label->priv;
4974 GtkLabelSelectionInfo *info = priv->select_info;
4975 GtkWidget *widget = GTK_WIDGET (label);
4976 GdkEventSequence *sequence;
4977 const GdkEvent *event;
4978 guint button;
4979
4980 if (info == NULL)
4981 {
4982 gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
4983 return;
4984 }
4985
4986 button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
4987 sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
4988 event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
4989 gtk_label_update_active_link (widget, widget_x, widget_y);
4990
4991 gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
4992
4993 if (info->active_link)
4994 {
4995 if (gdk_event_triggers_context_menu (event))
4996 {
4997 info->link_clicked = 1;
4998 update_link_state (label);
4999 gtk_label_do_popup (label, event);
5000 return;
5001 }
5002 else if (button == GDK_BUTTON_PRIMARY)
5003 {
5004 info->link_clicked = 1;
5005 update_link_state (label);
5006 gtk_widget_queue_draw (widget);
5007 if (!info->selectable)
5008 return;
5009 }
5010 }
5011
5012 if (!info->selectable)
5013 {
5014 gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
5015 return;
5016 }
5017
5018 info->in_drag = FALSE;
5019 info->select_words = FALSE;
5020
5021 if (gdk_event_triggers_context_menu (event))
5022 gtk_label_do_popup (label, event);
5023 else if (button == GDK_BUTTON_PRIMARY)
5024 {
5025 if (!gtk_widget_has_focus (widget))
5026 {
5027 priv->in_click = TRUE;
5028 gtk_widget_grab_focus (widget);
5029 priv->in_click = FALSE;
5030 }
5031
5032 if (n_press == 3)
5033 gtk_label_select_region_index (label, 0, strlen (priv->text));
5034 else if (n_press == 2)
5035 {
5036 info->select_words = TRUE;
5037 gtk_label_select_word (label);
5038 }
5039 }
5040 else
5041 {
5042 gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
5043 return;
5044 }
5045
5046 if (n_press >= 3)
5047 gtk_event_controller_reset (GTK_EVENT_CONTROLLER (gesture));
5048 }
5049
5050 static void
gtk_label_multipress_gesture_released(GtkGestureMultiPress * gesture,gint n_press,gdouble x,gdouble y,GtkLabel * label)5051 gtk_label_multipress_gesture_released (GtkGestureMultiPress *gesture,
5052 gint n_press,
5053 gdouble x,
5054 gdouble y,
5055 GtkLabel *label)
5056 {
5057 GtkLabelPrivate *priv = label->priv;
5058 GtkLabelSelectionInfo *info = priv->select_info;
5059 GdkEventSequence *sequence;
5060 gint index;
5061
5062 if (info == NULL)
5063 return;
5064
5065 sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
5066
5067 if (!gtk_gesture_handles_sequence (GTK_GESTURE (gesture), sequence))
5068 return;
5069
5070 if (n_press != 1)
5071 return;
5072
5073 if (info->in_drag)
5074 {
5075 info->in_drag = 0;
5076 get_layout_index (label, x, y, &index);
5077 gtk_label_select_region_index (label, index, index);
5078 }
5079 else if (info->active_link &&
5080 info->selection_anchor == info->selection_end &&
5081 info->link_clicked)
5082 {
5083 emit_activate_link (label, info->active_link);
5084 info->link_clicked = 0;
5085 }
5086 }
5087
5088 static void
connect_mnemonics_visible_notify(GtkLabel * label)5089 connect_mnemonics_visible_notify (GtkLabel *label)
5090 {
5091 GtkLabelPrivate *priv = label->priv;
5092 GtkWidget *toplevel;
5093 gboolean connected;
5094
5095 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (label));
5096
5097 if (!GTK_IS_WINDOW (toplevel))
5098 return;
5099
5100 /* always set up this widgets initial value */
5101 priv->mnemonics_visible =
5102 gtk_window_get_mnemonics_visible (GTK_WINDOW (toplevel));
5103
5104 connected =
5105 GPOINTER_TO_INT (g_object_get_qdata (G_OBJECT (toplevel), quark_mnemonics_visible_connected));
5106
5107 if (!connected)
5108 {
5109 g_signal_connect (toplevel,
5110 "notify::mnemonics-visible",
5111 G_CALLBACK (label_mnemonics_visible_changed),
5112 label);
5113 g_object_set_qdata (G_OBJECT (toplevel),
5114 quark_mnemonics_visible_connected,
5115 GINT_TO_POINTER (1));
5116 }
5117 }
5118
5119 static void
drag_begin_cb(GtkWidget * widget,GdkDragContext * context,gpointer data)5120 drag_begin_cb (GtkWidget *widget,
5121 GdkDragContext *context,
5122 gpointer data)
5123 {
5124 GtkLabel *label = GTK_LABEL (widget);
5125 GtkLabelPrivate *priv = label->priv;
5126 cairo_surface_t *surface = NULL;
5127
5128 g_signal_handlers_disconnect_by_func (widget, drag_begin_cb, NULL);
5129
5130 if ((priv->select_info->selection_anchor !=
5131 priv->select_info->selection_end) &&
5132 priv->text)
5133 {
5134 gint start, end;
5135 gint len;
5136
5137 start = MIN (priv->select_info->selection_anchor,
5138 priv->select_info->selection_end);
5139 end = MAX (priv->select_info->selection_anchor,
5140 priv->select_info->selection_end);
5141
5142 len = strlen (priv->text);
5143
5144 if (end > len)
5145 end = len;
5146
5147 if (start > len)
5148 start = len;
5149
5150 surface = _gtk_text_util_create_drag_icon (widget,
5151 priv->text + start,
5152 end - start);
5153 }
5154
5155 if (surface)
5156 {
5157 gtk_drag_set_icon_surface (context, surface);
5158 cairo_surface_destroy (surface);
5159 }
5160 else
5161 {
5162 gtk_drag_set_icon_default (context);
5163 }
5164 }
5165
5166 static void
gtk_label_drag_gesture_begin(GtkGestureDrag * gesture,gdouble start_x,gdouble start_y,GtkLabel * label)5167 gtk_label_drag_gesture_begin (GtkGestureDrag *gesture,
5168 gdouble start_x,
5169 gdouble start_y,
5170 GtkLabel *label)
5171 {
5172 GtkLabelPrivate *priv = label->priv;
5173 GtkLabelSelectionInfo *info = priv->select_info;
5174 GdkModifierType state_mask;
5175 GdkEventSequence *sequence;
5176 const GdkEvent *event;
5177 gint min, max, index;
5178
5179 if (!info || !info->selectable)
5180 {
5181 gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
5182 return;
5183 }
5184
5185 get_layout_index (label, start_x, start_y, &index);
5186 min = MIN (info->selection_anchor, info->selection_end);
5187 max = MAX (info->selection_anchor, info->selection_end);
5188
5189 sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
5190 event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
5191 gdk_event_get_state (event, &state_mask);
5192
5193 if ((info->selection_anchor != info->selection_end) &&
5194 (state_mask & GDK_SHIFT_MASK))
5195 {
5196 if (index > min && index < max)
5197 {
5198 /* truncate selection, but keep it as big as possible */
5199 if (index - min > max - index)
5200 max = index;
5201 else
5202 min = index;
5203 }
5204 else
5205 {
5206 /* extend (same as motion) */
5207 min = MIN (min, index);
5208 max = MAX (max, index);
5209 }
5210
5211 /* ensure the anchor is opposite index */
5212 if (index == min)
5213 {
5214 gint tmp = min;
5215 min = max;
5216 max = tmp;
5217 }
5218
5219 gtk_label_select_region_index (label, min, max);
5220 }
5221 else
5222 {
5223 if (min < max && min <= index && index <= max)
5224 {
5225 info->in_drag = TRUE;
5226 info->drag_start_x = start_x;
5227 info->drag_start_y = start_y;
5228 }
5229 else
5230 /* start a replacement */
5231 gtk_label_select_region_index (label, index, index);
5232 }
5233 }
5234
5235 static void
gtk_label_drag_gesture_update(GtkGestureDrag * gesture,gdouble offset_x,gdouble offset_y,GtkLabel * label)5236 gtk_label_drag_gesture_update (GtkGestureDrag *gesture,
5237 gdouble offset_x,
5238 gdouble offset_y,
5239 GtkLabel *label)
5240 {
5241 GtkLabelPrivate *priv = label->priv;
5242 GtkLabelSelectionInfo *info = priv->select_info;
5243 GtkWidget *widget = GTK_WIDGET (label);
5244 GdkEventSequence *sequence;
5245 gdouble x, y;
5246 gint index;
5247
5248 if (info == NULL || !info->selectable)
5249 return;
5250
5251 sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
5252 gtk_gesture_get_point (GTK_GESTURE (gesture), sequence, &x, &y);
5253
5254 if (info->in_drag)
5255 {
5256 if (gtk_drag_check_threshold (widget,
5257 info->drag_start_x,
5258 info->drag_start_y,
5259 x, y))
5260 {
5261 GtkTargetList *target_list = gtk_target_list_new (NULL, 0);
5262 const GdkEvent *event;
5263
5264 event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
5265 gtk_target_list_add_text_targets (target_list, 0);
5266
5267 g_signal_connect (widget, "drag-begin",
5268 G_CALLBACK (drag_begin_cb), NULL);
5269 gtk_drag_begin_with_coordinates (widget, target_list,
5270 GDK_ACTION_COPY,
5271 1, (GdkEvent*) event,
5272 info->drag_start_x,
5273 info->drag_start_y);
5274
5275 info->in_drag = FALSE;
5276
5277 gtk_target_list_unref (target_list);
5278 }
5279 }
5280 else
5281 {
5282 get_layout_index (label, x, y, &index);
5283
5284 if (index != info->selection_anchor)
5285 gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
5286
5287 if (info->select_words)
5288 {
5289 gint min, max;
5290 gint old_min, old_max;
5291 gint anchor, end;
5292
5293 min = gtk_label_move_backward_word (label, index);
5294 max = gtk_label_move_forward_word (label, index);
5295
5296 anchor = info->selection_anchor;
5297 end = info->selection_end;
5298
5299 old_min = MIN (anchor, end);
5300 old_max = MAX (anchor, end);
5301
5302 if (min < old_min)
5303 {
5304 anchor = min;
5305 end = old_max;
5306 }
5307 else if (old_max < max)
5308 {
5309 anchor = max;
5310 end = old_min;
5311 }
5312 else if (anchor == old_min)
5313 {
5314 if (anchor != min)
5315 anchor = max;
5316 }
5317 else
5318 {
5319 if (anchor != max)
5320 anchor = min;
5321 }
5322
5323 gtk_label_select_region_index (label, anchor, end);
5324 }
5325 else
5326 gtk_label_select_region_index (label, info->selection_anchor, index);
5327 }
5328 }
5329
5330 static void
gtk_label_update_active_link(GtkWidget * widget,gdouble x,gdouble y)5331 gtk_label_update_active_link (GtkWidget *widget,
5332 gdouble x,
5333 gdouble y)
5334 {
5335 GtkLabel *label = GTK_LABEL (widget);
5336 GtkLabelPrivate *priv = label->priv;
5337 GtkLabelSelectionInfo *info = priv->select_info;
5338 gint index;
5339
5340 if (info == NULL)
5341 return;
5342
5343 if (info->links && !info->in_drag)
5344 {
5345 GList *l;
5346 GtkLabelLink *link;
5347 gboolean found = FALSE;
5348
5349 if (info->selection_anchor == info->selection_end)
5350 {
5351 if (get_layout_index (label, x, y, &index))
5352 {
5353 for (l = info->links; l != NULL; l = l->next)
5354 {
5355 link = l->data;
5356 if (index >= link->start && index <= link->end)
5357 {
5358 if (!range_is_in_ellipsis (label, link->start, link->end))
5359 found = TRUE;
5360 break;
5361 }
5362 }
5363 }
5364 }
5365
5366 if (found)
5367 {
5368 if (info->active_link != link)
5369 {
5370 info->link_clicked = 0;
5371 info->active_link = link;
5372 update_link_state (label);
5373 gtk_label_update_cursor (label);
5374 gtk_widget_queue_draw (widget);
5375 }
5376 }
5377 else
5378 {
5379 if (info->active_link != NULL)
5380 {
5381 info->link_clicked = 0;
5382 info->active_link = NULL;
5383 update_link_state (label);
5384 gtk_label_update_cursor (label);
5385 gtk_widget_queue_draw (widget);
5386 }
5387 }
5388 }
5389 }
5390
5391 static gboolean
gtk_label_motion(GtkWidget * widget,GdkEventMotion * event)5392 gtk_label_motion (GtkWidget *widget,
5393 GdkEventMotion *event)
5394 {
5395 gdouble x, y;
5396
5397 gdk_event_get_coords ((GdkEvent *) event, &x, &y);
5398 gtk_label_update_active_link (widget, x, y);
5399
5400 return GTK_WIDGET_CLASS (gtk_label_parent_class)->motion_notify_event (widget, event);
5401 }
5402
5403 static gboolean
gtk_label_leave_notify(GtkWidget * widget,GdkEventCrossing * event)5404 gtk_label_leave_notify (GtkWidget *widget,
5405 GdkEventCrossing *event)
5406 {
5407 GtkLabel *label = GTK_LABEL (widget);
5408 GtkLabelPrivate *priv = label->priv;
5409
5410 if (priv->select_info)
5411 {
5412 priv->select_info->active_link = NULL;
5413 gtk_label_update_cursor (label);
5414 gtk_widget_queue_draw (widget);
5415 }
5416
5417 if (GTK_WIDGET_CLASS (gtk_label_parent_class)->leave_notify_event)
5418 return GTK_WIDGET_CLASS (gtk_label_parent_class)->leave_notify_event (widget, event);
5419
5420 return FALSE;
5421 }
5422
5423 static void
gtk_label_create_window(GtkLabel * label)5424 gtk_label_create_window (GtkLabel *label)
5425 {
5426 GtkLabelPrivate *priv = label->priv;
5427 GtkAllocation allocation;
5428 GtkWidget *widget;
5429 GdkWindowAttr attributes;
5430 gint attributes_mask;
5431
5432 g_assert (priv->select_info);
5433 widget = GTK_WIDGET (label);
5434 g_assert (gtk_widget_get_realized (widget));
5435
5436 if (priv->select_info->window)
5437 return;
5438
5439 gtk_widget_get_allocation (widget, &allocation);
5440
5441 attributes.x = allocation.x;
5442 attributes.y = allocation.y;
5443 attributes.width = allocation.width;
5444 attributes.height = allocation.height;
5445 attributes.window_type = GDK_WINDOW_CHILD;
5446 attributes.wclass = GDK_INPUT_ONLY;
5447 attributes.override_redirect = TRUE;
5448 attributes.event_mask = gtk_widget_get_events (widget) |
5449 GDK_BUTTON_PRESS_MASK |
5450 GDK_BUTTON_RELEASE_MASK |
5451 GDK_LEAVE_NOTIFY_MASK |
5452 GDK_BUTTON_MOTION_MASK |
5453 GDK_POINTER_MOTION_MASK;
5454 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_NOREDIR;
5455 if (gtk_widget_is_sensitive (widget) && priv->select_info->selectable)
5456 {
5457 attributes.cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget),
5458 GDK_XTERM);
5459 attributes_mask |= GDK_WA_CURSOR;
5460 }
5461
5462
5463 priv->select_info->window = gdk_window_new (gtk_widget_get_window (widget),
5464 &attributes, attributes_mask);
5465 gtk_widget_register_window (widget, priv->select_info->window);
5466
5467 if (attributes_mask & GDK_WA_CURSOR)
5468 g_object_unref (attributes.cursor);
5469 }
5470
5471 static void
gtk_label_destroy_window(GtkLabel * label)5472 gtk_label_destroy_window (GtkLabel *label)
5473 {
5474 GtkLabelPrivate *priv = label->priv;
5475
5476 g_assert (priv->select_info);
5477
5478 if (priv->select_info->window == NULL)
5479 return;
5480
5481 gtk_widget_unregister_window (GTK_WIDGET (label), priv->select_info->window);
5482 gdk_window_destroy (priv->select_info->window);
5483 priv->select_info->window = NULL;
5484 }
5485
5486 static void
gtk_label_ensure_select_info(GtkLabel * label)5487 gtk_label_ensure_select_info (GtkLabel *label)
5488 {
5489 GtkLabelPrivate *priv = label->priv;
5490
5491 if (priv->select_info == NULL)
5492 {
5493 priv->select_info = g_new0 (GtkLabelSelectionInfo, 1);
5494
5495 gtk_widget_set_can_focus (GTK_WIDGET (label), TRUE);
5496
5497 if (gtk_widget_get_realized (GTK_WIDGET (label)))
5498 gtk_label_create_window (label);
5499
5500 if (gtk_widget_get_mapped (GTK_WIDGET (label)))
5501 gdk_window_show (priv->select_info->window);
5502
5503 priv->select_info->drag_gesture = gtk_gesture_drag_new (GTK_WIDGET (label));
5504 g_signal_connect (priv->select_info->drag_gesture, "drag-begin",
5505 G_CALLBACK (gtk_label_drag_gesture_begin), label);
5506 g_signal_connect (priv->select_info->drag_gesture, "drag-update",
5507 G_CALLBACK (gtk_label_drag_gesture_update), label);
5508 gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (priv->select_info->drag_gesture), TRUE);
5509
5510 priv->select_info->multipress_gesture = gtk_gesture_multi_press_new (GTK_WIDGET (label));
5511 g_signal_connect (priv->select_info->multipress_gesture, "pressed",
5512 G_CALLBACK (gtk_label_multipress_gesture_pressed), label);
5513 g_signal_connect (priv->select_info->multipress_gesture, "released",
5514 G_CALLBACK (gtk_label_multipress_gesture_released), label);
5515 gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (priv->select_info->multipress_gesture), 0);
5516 gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (priv->select_info->multipress_gesture), TRUE);
5517 }
5518 }
5519
5520 static void
gtk_label_clear_select_info(GtkLabel * label)5521 gtk_label_clear_select_info (GtkLabel *label)
5522 {
5523 GtkLabelPrivate *priv = label->priv;
5524
5525 if (priv->select_info == NULL)
5526 return;
5527
5528 if (!priv->select_info->selectable && !priv->select_info->links)
5529 {
5530 gtk_label_destroy_window (label);
5531
5532 g_object_unref (priv->select_info->drag_gesture);
5533 g_object_unref (priv->select_info->multipress_gesture);
5534
5535 g_free (priv->select_info);
5536 priv->select_info = NULL;
5537
5538 gtk_widget_set_can_focus (GTK_WIDGET (label), FALSE);
5539 }
5540 }
5541
5542 /**
5543 * gtk_label_set_selectable:
5544 * @label: a #GtkLabel
5545 * @setting: %TRUE to allow selecting text in the label
5546 *
5547 * Selectable labels allow the user to select text from the label, for
5548 * copy-and-paste.
5549 **/
5550 void
gtk_label_set_selectable(GtkLabel * label,gboolean setting)5551 gtk_label_set_selectable (GtkLabel *label,
5552 gboolean setting)
5553 {
5554 GtkLabelPrivate *priv;
5555 gboolean old_setting;
5556
5557 g_return_if_fail (GTK_IS_LABEL (label));
5558
5559 priv = label->priv;
5560
5561 setting = setting != FALSE;
5562 old_setting = priv->select_info && priv->select_info->selectable;
5563
5564 if (setting)
5565 {
5566 gtk_label_ensure_select_info (label);
5567 priv->select_info->selectable = TRUE;
5568 gtk_label_update_cursor (label);
5569 }
5570 else
5571 {
5572 if (old_setting)
5573 {
5574 /* unselect, to give up the selection */
5575 gtk_label_select_region (label, 0, 0);
5576
5577 priv->select_info->selectable = FALSE;
5578 gtk_label_clear_select_info (label);
5579 gtk_label_update_cursor (label);
5580 }
5581 }
5582 if (setting != old_setting)
5583 {
5584 g_object_freeze_notify (G_OBJECT (label));
5585 g_object_notify_by_pspec (G_OBJECT (label), label_props[PROP_SELECTABLE]);
5586 g_object_notify_by_pspec (G_OBJECT (label), label_props[PROP_CURSOR_POSITION]);
5587 g_object_notify_by_pspec (G_OBJECT (label), label_props[PROP_SELECTION_BOUND]);
5588 g_object_thaw_notify (G_OBJECT (label));
5589 gtk_widget_queue_draw (GTK_WIDGET (label));
5590 }
5591 }
5592
5593 /**
5594 * gtk_label_get_selectable:
5595 * @label: a #GtkLabel
5596 *
5597 * Gets the value set by gtk_label_set_selectable().
5598 *
5599 * Returns: %TRUE if the user can copy text from the label
5600 **/
5601 gboolean
gtk_label_get_selectable(GtkLabel * label)5602 gtk_label_get_selectable (GtkLabel *label)
5603 {
5604 GtkLabelPrivate *priv;
5605
5606 g_return_val_if_fail (GTK_IS_LABEL (label), FALSE);
5607
5608 priv = label->priv;
5609
5610 return priv->select_info && priv->select_info->selectable;
5611 }
5612
5613 /**
5614 * gtk_label_set_angle:
5615 * @label: a #GtkLabel
5616 * @angle: the angle that the baseline of the label makes with
5617 * the horizontal, in degrees, measured counterclockwise
5618 *
5619 * Sets the angle of rotation for the label. An angle of 90 reads from
5620 * from bottom to top, an angle of 270, from top to bottom. The angle
5621 * setting for the label is ignored if the label is selectable,
5622 * wrapped, or ellipsized.
5623 *
5624 * Since: 2.6
5625 **/
5626 void
gtk_label_set_angle(GtkLabel * label,gdouble angle)5627 gtk_label_set_angle (GtkLabel *label,
5628 gdouble angle)
5629 {
5630 GtkLabelPrivate *priv;
5631
5632 g_return_if_fail (GTK_IS_LABEL (label));
5633
5634 priv = label->priv;
5635
5636 /* Canonicalize to [0,360]. We don't canonicalize 360 to 0, because
5637 * double property ranges are inclusive, and changing 360 to 0 would
5638 * make a property editor behave strangely.
5639 */
5640 if (angle < 0 || angle > 360.0)
5641 angle = angle - 360. * floor (angle / 360.);
5642
5643 if (priv->angle != angle)
5644 {
5645 priv->angle = angle;
5646
5647 gtk_label_clear_layout (label);
5648 gtk_widget_queue_resize (GTK_WIDGET (label));
5649
5650 g_object_notify_by_pspec (G_OBJECT (label), label_props[PROP_ANGLE]);
5651 }
5652 }
5653
5654 /**
5655 * gtk_label_get_angle:
5656 * @label: a #GtkLabel
5657 *
5658 * Gets the angle of rotation for the label. See
5659 * gtk_label_set_angle().
5660 *
5661 * Returns: the angle of rotation for the label
5662 *
5663 * Since: 2.6
5664 **/
5665 gdouble
gtk_label_get_angle(GtkLabel * label)5666 gtk_label_get_angle (GtkLabel *label)
5667 {
5668 g_return_val_if_fail (GTK_IS_LABEL (label), 0.0);
5669
5670 return label->priv->angle;
5671 }
5672
5673 static void
gtk_label_set_selection_text(GtkLabel * label,GtkSelectionData * selection_data)5674 gtk_label_set_selection_text (GtkLabel *label,
5675 GtkSelectionData *selection_data)
5676 {
5677 GtkLabelPrivate *priv = label->priv;
5678
5679 if (priv->select_info &&
5680 (priv->select_info->selection_anchor !=
5681 priv->select_info->selection_end) &&
5682 priv->text)
5683 {
5684 gint start, end;
5685 gint len;
5686
5687 start = MIN (priv->select_info->selection_anchor,
5688 priv->select_info->selection_end);
5689 end = MAX (priv->select_info->selection_anchor,
5690 priv->select_info->selection_end);
5691
5692 len = strlen (priv->text);
5693
5694 if (end > len)
5695 end = len;
5696
5697 if (start > len)
5698 start = len;
5699
5700 gtk_selection_data_set_text (selection_data,
5701 priv->text + start,
5702 end - start);
5703 }
5704 }
5705
5706 static void
gtk_label_drag_data_get(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection_data,guint info,guint time)5707 gtk_label_drag_data_get (GtkWidget *widget,
5708 GdkDragContext *context,
5709 GtkSelectionData *selection_data,
5710 guint info,
5711 guint time)
5712 {
5713 gtk_label_set_selection_text (GTK_LABEL (widget), selection_data);
5714 }
5715
5716 static void
get_text_callback(GtkClipboard * clipboard,GtkSelectionData * selection_data,guint info,gpointer user_data_or_owner)5717 get_text_callback (GtkClipboard *clipboard,
5718 GtkSelectionData *selection_data,
5719 guint info,
5720 gpointer user_data_or_owner)
5721 {
5722 gtk_label_set_selection_text (GTK_LABEL (user_data_or_owner), selection_data);
5723 }
5724
5725 static void
clear_text_callback(GtkClipboard * clipboard,gpointer user_data_or_owner)5726 clear_text_callback (GtkClipboard *clipboard,
5727 gpointer user_data_or_owner)
5728 {
5729 GtkLabel *label;
5730 GtkLabelPrivate *priv;
5731
5732 label = GTK_LABEL (user_data_or_owner);
5733 priv = label->priv;
5734
5735 if (priv->select_info)
5736 {
5737 priv->select_info->selection_anchor = priv->select_info->selection_end;
5738
5739 gtk_widget_queue_draw (GTK_WIDGET (label));
5740 }
5741 }
5742
5743 static void
gtk_label_select_region_index(GtkLabel * label,gint anchor_index,gint end_index)5744 gtk_label_select_region_index (GtkLabel *label,
5745 gint anchor_index,
5746 gint end_index)
5747 {
5748 GtkLabelPrivate *priv;
5749
5750 g_return_if_fail (GTK_IS_LABEL (label));
5751
5752 priv = label->priv;
5753
5754 if (priv->select_info && priv->select_info->selectable)
5755 {
5756 GtkClipboard *clipboard;
5757 gint s, e;
5758
5759 /* Ensure that we treat an ellipsized region like a single
5760 * character with respect to selection.
5761 */
5762 if (anchor_index < end_index)
5763 {
5764 if (range_is_in_ellipsis_full (label, anchor_index, anchor_index + 1, &s, &e))
5765 {
5766 if (priv->select_info->selection_anchor == s)
5767 anchor_index = e;
5768 else
5769 anchor_index = s;
5770 }
5771 if (range_is_in_ellipsis_full (label, end_index - 1, end_index, &s, &e))
5772 {
5773 if (priv->select_info->selection_end == e)
5774 end_index = s;
5775 else
5776 end_index = e;
5777 }
5778 }
5779 else if (end_index < anchor_index)
5780 {
5781 if (range_is_in_ellipsis_full (label, end_index, end_index + 1, &s, &e))
5782 {
5783 if (priv->select_info->selection_end == s)
5784 end_index = e;
5785 else
5786 end_index = s;
5787 }
5788 if (range_is_in_ellipsis_full (label, anchor_index - 1, anchor_index, &s, &e))
5789 {
5790 if (priv->select_info->selection_anchor == e)
5791 anchor_index = s;
5792 else
5793 anchor_index = e;
5794 }
5795 }
5796 else
5797 {
5798 if (range_is_in_ellipsis_full (label, anchor_index, anchor_index, &s, &e))
5799 {
5800 if (priv->select_info->selection_anchor == s)
5801 anchor_index = e;
5802 else if (priv->select_info->selection_anchor == e)
5803 anchor_index = s;
5804 else if (anchor_index - s < e - anchor_index)
5805 anchor_index = s;
5806 else
5807 anchor_index = e;
5808 end_index = anchor_index;
5809 }
5810 }
5811
5812 if (priv->select_info->selection_anchor == anchor_index &&
5813 priv->select_info->selection_end == end_index)
5814 return;
5815
5816 g_object_freeze_notify (G_OBJECT (label));
5817
5818 if (priv->select_info->selection_anchor != anchor_index)
5819 g_object_notify_by_pspec (G_OBJECT (label), label_props[PROP_SELECTION_BOUND]);
5820 if (priv->select_info->selection_end != end_index)
5821 g_object_notify_by_pspec (G_OBJECT (label), label_props[PROP_CURSOR_POSITION]);
5822
5823 priv->select_info->selection_anchor = anchor_index;
5824 priv->select_info->selection_end = end_index;
5825
5826 if (gtk_widget_has_screen (GTK_WIDGET (label)))
5827 clipboard = gtk_widget_get_clipboard (GTK_WIDGET (label),
5828 GDK_SELECTION_PRIMARY);
5829 else
5830 clipboard = NULL;
5831
5832 if (anchor_index != end_index)
5833 {
5834 GtkTargetList *list;
5835 GtkTargetEntry *targets;
5836 gint n_targets;
5837
5838 list = gtk_target_list_new (NULL, 0);
5839 gtk_target_list_add_text_targets (list, 0);
5840 targets = gtk_target_table_new_from_list (list, &n_targets);
5841
5842 if (clipboard)
5843 gtk_clipboard_set_with_owner (clipboard,
5844 targets, n_targets,
5845 get_text_callback,
5846 clear_text_callback,
5847 G_OBJECT (label));
5848
5849 gtk_target_table_free (targets, n_targets);
5850 gtk_target_list_unref (list);
5851
5852 if (!priv->select_info->selection_node)
5853 {
5854 GtkCssNode *widget_node;
5855
5856 widget_node = gtk_widget_get_css_node (GTK_WIDGET (label));
5857 priv->select_info->selection_node = gtk_css_node_new ();
5858 gtk_css_node_set_name (priv->select_info->selection_node, I_("selection"));
5859 gtk_css_node_set_parent (priv->select_info->selection_node, widget_node);
5860 gtk_css_node_set_state (priv->select_info->selection_node, gtk_css_node_get_state (widget_node));
5861 g_object_unref (priv->select_info->selection_node);
5862 }
5863 }
5864 else
5865 {
5866 if (clipboard &&
5867 gtk_clipboard_get_owner (clipboard) == G_OBJECT (label))
5868 gtk_clipboard_clear (clipboard);
5869
5870 if (priv->select_info->selection_node)
5871 {
5872 gtk_css_node_set_parent (priv->select_info->selection_node, NULL);
5873 priv->select_info->selection_node = NULL;
5874 }
5875 }
5876
5877 gtk_widget_queue_draw (GTK_WIDGET (label));
5878
5879 g_object_thaw_notify (G_OBJECT (label));
5880 }
5881 }
5882
5883 /**
5884 * gtk_label_select_region:
5885 * @label: a #GtkLabel
5886 * @start_offset: start offset (in characters not bytes)
5887 * @end_offset: end offset (in characters not bytes)
5888 *
5889 * Selects a range of characters in the label, if the label is selectable.
5890 * See gtk_label_set_selectable(). If the label is not selectable,
5891 * this function has no effect. If @start_offset or
5892 * @end_offset are -1, then the end of the label will be substituted.
5893 **/
5894 void
gtk_label_select_region(GtkLabel * label,gint start_offset,gint end_offset)5895 gtk_label_select_region (GtkLabel *label,
5896 gint start_offset,
5897 gint end_offset)
5898 {
5899 GtkLabelPrivate *priv;
5900
5901 g_return_if_fail (GTK_IS_LABEL (label));
5902
5903 priv = label->priv;
5904
5905 if (priv->text && priv->select_info)
5906 {
5907 if (start_offset < 0)
5908 start_offset = g_utf8_strlen (priv->text, -1);
5909
5910 if (end_offset < 0)
5911 end_offset = g_utf8_strlen (priv->text, -1);
5912
5913 gtk_label_select_region_index (label,
5914 g_utf8_offset_to_pointer (priv->text, start_offset) - priv->text,
5915 g_utf8_offset_to_pointer (priv->text, end_offset) - priv->text);
5916 }
5917 }
5918
5919 /**
5920 * gtk_label_get_selection_bounds:
5921 * @label: a #GtkLabel
5922 * @start: (out): return location for start of selection, as a character offset
5923 * @end: (out): return location for end of selection, as a character offset
5924 *
5925 * Gets the selected range of characters in the label, returning %TRUE
5926 * if there’s a selection.
5927 *
5928 * Returns: %TRUE if selection is non-empty
5929 **/
5930 gboolean
gtk_label_get_selection_bounds(GtkLabel * label,gint * start,gint * end)5931 gtk_label_get_selection_bounds (GtkLabel *label,
5932 gint *start,
5933 gint *end)
5934 {
5935 GtkLabelPrivate *priv;
5936
5937 g_return_val_if_fail (GTK_IS_LABEL (label), FALSE);
5938
5939 priv = label->priv;
5940
5941 if (priv->select_info == NULL)
5942 {
5943 /* not a selectable label */
5944 if (start)
5945 *start = 0;
5946 if (end)
5947 *end = 0;
5948
5949 return FALSE;
5950 }
5951 else
5952 {
5953 gint start_index, end_index;
5954 gint start_offset, end_offset;
5955 gint len;
5956
5957 start_index = MIN (priv->select_info->selection_anchor,
5958 priv->select_info->selection_end);
5959 end_index = MAX (priv->select_info->selection_anchor,
5960 priv->select_info->selection_end);
5961
5962 len = strlen (priv->text);
5963
5964 if (end_index > len)
5965 end_index = len;
5966
5967 if (start_index > len)
5968 start_index = len;
5969
5970 start_offset = g_utf8_strlen (priv->text, start_index);
5971 end_offset = g_utf8_strlen (priv->text, end_index);
5972
5973 if (start_offset > end_offset)
5974 {
5975 gint tmp = start_offset;
5976 start_offset = end_offset;
5977 end_offset = tmp;
5978 }
5979
5980 if (start)
5981 *start = start_offset;
5982
5983 if (end)
5984 *end = end_offset;
5985
5986 return start_offset != end_offset;
5987 }
5988 }
5989
5990
5991 /**
5992 * gtk_label_get_layout:
5993 * @label: a #GtkLabel
5994 *
5995 * Gets the #PangoLayout used to display the label.
5996 * The layout is useful to e.g. convert text positions to
5997 * pixel positions, in combination with gtk_label_get_layout_offsets().
5998 * The returned layout is owned by the @label so need not be
5999 * freed by the caller. The @label is free to recreate its layout at
6000 * any time, so it should be considered read-only.
6001 *
6002 * Returns: (transfer none): the #PangoLayout for this label
6003 **/
6004 PangoLayout*
gtk_label_get_layout(GtkLabel * label)6005 gtk_label_get_layout (GtkLabel *label)
6006 {
6007 GtkLabelPrivate *priv;
6008
6009 g_return_val_if_fail (GTK_IS_LABEL (label), NULL);
6010
6011 priv = label->priv;
6012
6013 gtk_label_ensure_layout (label);
6014
6015 return priv->layout;
6016 }
6017
6018 /**
6019 * gtk_label_get_layout_offsets:
6020 * @label: a #GtkLabel
6021 * @x: (out) (optional): location to store X offset of layout, or %NULL
6022 * @y: (out) (optional): location to store Y offset of layout, or %NULL
6023 *
6024 * Obtains the coordinates where the label will draw the #PangoLayout
6025 * representing the text in the label; useful to convert mouse events
6026 * into coordinates inside the #PangoLayout, e.g. to take some action
6027 * if some part of the label is clicked. Of course you will need to
6028 * create a #GtkEventBox to receive the events, and pack the label
6029 * inside it, since labels are windowless (they return %FALSE from
6030 * gtk_widget_get_has_window()). Remember
6031 * when using the #PangoLayout functions you need to convert to
6032 * and from pixels using PANGO_PIXELS() or #PANGO_SCALE.
6033 **/
6034 void
gtk_label_get_layout_offsets(GtkLabel * label,gint * x,gint * y)6035 gtk_label_get_layout_offsets (GtkLabel *label,
6036 gint *x,
6037 gint *y)
6038 {
6039 g_return_if_fail (GTK_IS_LABEL (label));
6040
6041 gtk_label_ensure_layout (label);
6042
6043 get_layout_location (label, x, y);
6044 }
6045
6046 /**
6047 * gtk_label_set_use_markup:
6048 * @label: a #GtkLabel
6049 * @setting: %TRUE if the label’s text should be parsed for markup.
6050 *
6051 * Sets whether the text of the label contains markup in
6052 * [Pango’s text markup language][PangoMarkupFormat].
6053 * See gtk_label_set_markup().
6054 **/
6055 void
gtk_label_set_use_markup(GtkLabel * label,gboolean setting)6056 gtk_label_set_use_markup (GtkLabel *label,
6057 gboolean setting)
6058 {
6059 g_return_if_fail (GTK_IS_LABEL (label));
6060
6061 g_object_freeze_notify (G_OBJECT (label));
6062
6063 if (gtk_label_set_use_markup_internal (label, setting))
6064 gtk_label_recalculate (label);
6065
6066 g_object_thaw_notify (G_OBJECT (label));
6067 }
6068
6069 /**
6070 * gtk_label_get_use_markup:
6071 * @label: a #GtkLabel
6072 *
6073 * Returns whether the label’s text is interpreted as marked up with
6074 * the [Pango text markup language][PangoMarkupFormat].
6075 * See gtk_label_set_use_markup ().
6076 *
6077 * Returns: %TRUE if the label’s text will be parsed for markup.
6078 **/
6079 gboolean
gtk_label_get_use_markup(GtkLabel * label)6080 gtk_label_get_use_markup (GtkLabel *label)
6081 {
6082 g_return_val_if_fail (GTK_IS_LABEL (label), FALSE);
6083
6084 return label->priv->use_markup;
6085 }
6086
6087 /**
6088 * gtk_label_set_use_underline:
6089 * @label: a #GtkLabel
6090 * @setting: %TRUE if underlines in the text indicate mnemonics
6091 *
6092 * If true, an underline in the text indicates the next character should be
6093 * used for the mnemonic accelerator key.
6094 */
6095 void
gtk_label_set_use_underline(GtkLabel * label,gboolean setting)6096 gtk_label_set_use_underline (GtkLabel *label,
6097 gboolean setting)
6098 {
6099 g_return_if_fail (GTK_IS_LABEL (label));
6100
6101 g_object_freeze_notify (G_OBJECT (label));
6102
6103 if (gtk_label_set_use_underline_internal (label, setting))
6104 gtk_label_recalculate (label);
6105
6106 g_object_thaw_notify (G_OBJECT (label));
6107 }
6108
6109 /**
6110 * gtk_label_get_use_underline:
6111 * @label: a #GtkLabel
6112 *
6113 * Returns whether an embedded underline in the label indicates a
6114 * mnemonic. See gtk_label_set_use_underline().
6115 *
6116 * Returns: %TRUE whether an embedded underline in the label indicates
6117 * the mnemonic accelerator keys.
6118 **/
6119 gboolean
gtk_label_get_use_underline(GtkLabel * label)6120 gtk_label_get_use_underline (GtkLabel *label)
6121 {
6122 g_return_val_if_fail (GTK_IS_LABEL (label), FALSE);
6123
6124 return label->priv->use_underline;
6125 }
6126
6127 /**
6128 * gtk_label_set_single_line_mode:
6129 * @label: a #GtkLabel
6130 * @single_line_mode: %TRUE if the label should be in single line mode
6131 *
6132 * Sets whether the label is in single line mode.
6133 *
6134 * Since: 2.6
6135 */
6136 void
gtk_label_set_single_line_mode(GtkLabel * label,gboolean single_line_mode)6137 gtk_label_set_single_line_mode (GtkLabel *label,
6138 gboolean single_line_mode)
6139 {
6140 GtkLabelPrivate *priv;
6141
6142 g_return_if_fail (GTK_IS_LABEL (label));
6143
6144 priv = label->priv;
6145
6146 single_line_mode = single_line_mode != FALSE;
6147
6148 if (priv->single_line_mode != single_line_mode)
6149 {
6150 priv->single_line_mode = single_line_mode;
6151
6152 gtk_label_clear_layout (label);
6153 gtk_widget_queue_resize (GTK_WIDGET (label));
6154
6155 g_object_notify_by_pspec (G_OBJECT (label), label_props[PROP_SINGLE_LINE_MODE]);
6156 }
6157 }
6158
6159 /**
6160 * gtk_label_get_single_line_mode:
6161 * @label: a #GtkLabel
6162 *
6163 * Returns whether the label is in single line mode.
6164 *
6165 * Returns: %TRUE when the label is in single line mode.
6166 *
6167 * Since: 2.6
6168 **/
6169 gboolean
gtk_label_get_single_line_mode(GtkLabel * label)6170 gtk_label_get_single_line_mode (GtkLabel *label)
6171 {
6172 g_return_val_if_fail (GTK_IS_LABEL (label), FALSE);
6173
6174 return label->priv->single_line_mode;
6175 }
6176
6177 /* Compute the X position for an offset that corresponds to the "more important
6178 * cursor position for that offset. We use this when trying to guess to which
6179 * end of the selection we should go to when the user hits the left or
6180 * right arrow key.
6181 */
6182 static void
get_better_cursor(GtkLabel * label,gint index,gint * x,gint * y)6183 get_better_cursor (GtkLabel *label,
6184 gint index,
6185 gint *x,
6186 gint *y)
6187 {
6188 GtkLabelPrivate *priv = label->priv;
6189 GdkKeymap *keymap = gdk_keymap_get_for_display (gtk_widget_get_display (GTK_WIDGET (label)));
6190 PangoDirection keymap_direction = gdk_keymap_get_direction (keymap);
6191 PangoDirection cursor_direction = get_cursor_direction (label);
6192 gboolean split_cursor;
6193 PangoRectangle strong_pos, weak_pos;
6194
6195 g_object_get (gtk_widget_get_settings (GTK_WIDGET (label)),
6196 "gtk-split-cursor", &split_cursor,
6197 NULL);
6198
6199 gtk_label_ensure_layout (label);
6200
6201 pango_layout_get_cursor_pos (priv->layout, index,
6202 &strong_pos, &weak_pos);
6203
6204 if (split_cursor)
6205 {
6206 *x = strong_pos.x / PANGO_SCALE;
6207 *y = strong_pos.y / PANGO_SCALE;
6208 }
6209 else
6210 {
6211 if (keymap_direction == cursor_direction)
6212 {
6213 *x = strong_pos.x / PANGO_SCALE;
6214 *y = strong_pos.y / PANGO_SCALE;
6215 }
6216 else
6217 {
6218 *x = weak_pos.x / PANGO_SCALE;
6219 *y = weak_pos.y / PANGO_SCALE;
6220 }
6221 }
6222 }
6223
6224
6225 static gint
gtk_label_move_logically(GtkLabel * label,gint start,gint count)6226 gtk_label_move_logically (GtkLabel *label,
6227 gint start,
6228 gint count)
6229 {
6230 GtkLabelPrivate *priv = label->priv;
6231 gint offset = g_utf8_pointer_to_offset (priv->text,
6232 priv->text + start);
6233
6234 if (priv->text)
6235 {
6236 PangoLogAttr *log_attrs;
6237 gint n_attrs;
6238 gint length;
6239
6240 gtk_label_ensure_layout (label);
6241
6242 length = g_utf8_strlen (priv->text, -1);
6243
6244 pango_layout_get_log_attrs (priv->layout, &log_attrs, &n_attrs);
6245
6246 while (count > 0 && offset < length)
6247 {
6248 do
6249 offset++;
6250 while (offset < length && !log_attrs[offset].is_cursor_position);
6251
6252 count--;
6253 }
6254 while (count < 0 && offset > 0)
6255 {
6256 do
6257 offset--;
6258 while (offset > 0 && !log_attrs[offset].is_cursor_position);
6259
6260 count++;
6261 }
6262
6263 g_free (log_attrs);
6264 }
6265
6266 return g_utf8_offset_to_pointer (priv->text, offset) - priv->text;
6267 }
6268
6269 static gint
gtk_label_move_visually(GtkLabel * label,gint start,gint count)6270 gtk_label_move_visually (GtkLabel *label,
6271 gint start,
6272 gint count)
6273 {
6274 GtkLabelPrivate *priv = label->priv;
6275 gint index;
6276
6277 index = start;
6278
6279 while (count != 0)
6280 {
6281 int new_index, new_trailing;
6282 gboolean split_cursor;
6283 gboolean strong;
6284
6285 gtk_label_ensure_layout (label);
6286
6287 g_object_get (gtk_widget_get_settings (GTK_WIDGET (label)),
6288 "gtk-split-cursor", &split_cursor,
6289 NULL);
6290
6291 if (split_cursor)
6292 strong = TRUE;
6293 else
6294 {
6295 GdkKeymap *keymap = gdk_keymap_get_for_display (gtk_widget_get_display (GTK_WIDGET (label)));
6296 PangoDirection keymap_direction = gdk_keymap_get_direction (keymap);
6297
6298 strong = keymap_direction == get_cursor_direction (label);
6299 }
6300
6301 if (count > 0)
6302 {
6303 pango_layout_move_cursor_visually (priv->layout, strong, index, 0, 1, &new_index, &new_trailing);
6304 count--;
6305 }
6306 else
6307 {
6308 pango_layout_move_cursor_visually (priv->layout, strong, index, 0, -1, &new_index, &new_trailing);
6309 count++;
6310 }
6311
6312 if (new_index < 0 || new_index == G_MAXINT)
6313 break;
6314
6315 index = new_index;
6316
6317 while (new_trailing--)
6318 index = g_utf8_next_char (priv->text + new_index) - priv->text;
6319 }
6320
6321 return index;
6322 }
6323
6324 static gint
gtk_label_move_forward_word(GtkLabel * label,gint start)6325 gtk_label_move_forward_word (GtkLabel *label,
6326 gint start)
6327 {
6328 GtkLabelPrivate *priv = label->priv;
6329 gint new_pos = g_utf8_pointer_to_offset (priv->text,
6330 priv->text + start);
6331 gint length;
6332
6333 length = g_utf8_strlen (priv->text, -1);
6334 if (new_pos < length)
6335 {
6336 PangoLogAttr *log_attrs;
6337 gint n_attrs;
6338
6339 gtk_label_ensure_layout (label);
6340
6341 pango_layout_get_log_attrs (priv->layout, &log_attrs, &n_attrs);
6342
6343 /* Find the next word end */
6344 new_pos++;
6345 while (new_pos < n_attrs && !log_attrs[new_pos].is_word_end)
6346 new_pos++;
6347
6348 g_free (log_attrs);
6349 }
6350
6351 return g_utf8_offset_to_pointer (priv->text, new_pos) - priv->text;
6352 }
6353
6354
6355 static gint
gtk_label_move_backward_word(GtkLabel * label,gint start)6356 gtk_label_move_backward_word (GtkLabel *label,
6357 gint start)
6358 {
6359 GtkLabelPrivate *priv = label->priv;
6360 gint new_pos = g_utf8_pointer_to_offset (priv->text,
6361 priv->text + start);
6362
6363 if (new_pos > 0)
6364 {
6365 PangoLogAttr *log_attrs;
6366 gint n_attrs;
6367
6368 gtk_label_ensure_layout (label);
6369
6370 pango_layout_get_log_attrs (priv->layout, &log_attrs, &n_attrs);
6371
6372 new_pos -= 1;
6373
6374 /* Find the previous word beginning */
6375 while (new_pos > 0 && !log_attrs[new_pos].is_word_start)
6376 new_pos--;
6377
6378 g_free (log_attrs);
6379 }
6380
6381 return g_utf8_offset_to_pointer (priv->text, new_pos) - priv->text;
6382 }
6383
6384 static void
gtk_label_move_cursor(GtkLabel * label,GtkMovementStep step,gint count,gboolean extend_selection)6385 gtk_label_move_cursor (GtkLabel *label,
6386 GtkMovementStep step,
6387 gint count,
6388 gboolean extend_selection)
6389 {
6390 GtkLabelPrivate *priv = label->priv;
6391 gint old_pos;
6392 gint new_pos;
6393
6394 if (priv->select_info == NULL)
6395 return;
6396
6397 old_pos = new_pos = priv->select_info->selection_end;
6398
6399 if (priv->select_info->selection_end != priv->select_info->selection_anchor &&
6400 !extend_selection)
6401 {
6402 /* If we have a current selection and aren't extending it, move to the
6403 * start/or end of the selection as appropriate
6404 */
6405 switch (step)
6406 {
6407 case GTK_MOVEMENT_VISUAL_POSITIONS:
6408 {
6409 gint end_x, end_y;
6410 gint anchor_x, anchor_y;
6411 gboolean end_is_left;
6412
6413 get_better_cursor (label, priv->select_info->selection_end, &end_x, &end_y);
6414 get_better_cursor (label, priv->select_info->selection_anchor, &anchor_x, &anchor_y);
6415
6416 end_is_left = (end_y < anchor_y) || (end_y == anchor_y && end_x < anchor_x);
6417
6418 if (count < 0)
6419 new_pos = end_is_left ? priv->select_info->selection_end : priv->select_info->selection_anchor;
6420 else
6421 new_pos = !end_is_left ? priv->select_info->selection_end : priv->select_info->selection_anchor;
6422 break;
6423 }
6424 case GTK_MOVEMENT_LOGICAL_POSITIONS:
6425 case GTK_MOVEMENT_WORDS:
6426 if (count < 0)
6427 new_pos = MIN (priv->select_info->selection_end, priv->select_info->selection_anchor);
6428 else
6429 new_pos = MAX (priv->select_info->selection_end, priv->select_info->selection_anchor);
6430 break;
6431 case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
6432 case GTK_MOVEMENT_PARAGRAPH_ENDS:
6433 case GTK_MOVEMENT_BUFFER_ENDS:
6434 /* FIXME: Can do better here */
6435 new_pos = count < 0 ? 0 : strlen (priv->text);
6436 break;
6437 case GTK_MOVEMENT_DISPLAY_LINES:
6438 case GTK_MOVEMENT_PARAGRAPHS:
6439 case GTK_MOVEMENT_PAGES:
6440 case GTK_MOVEMENT_HORIZONTAL_PAGES:
6441 break;
6442 }
6443 }
6444 else
6445 {
6446 switch (step)
6447 {
6448 case GTK_MOVEMENT_LOGICAL_POSITIONS:
6449 new_pos = gtk_label_move_logically (label, new_pos, count);
6450 break;
6451 case GTK_MOVEMENT_VISUAL_POSITIONS:
6452 new_pos = gtk_label_move_visually (label, new_pos, count);
6453 if (new_pos == old_pos)
6454 {
6455 if (!extend_selection)
6456 {
6457 if (!gtk_widget_keynav_failed (GTK_WIDGET (label),
6458 count > 0 ?
6459 GTK_DIR_RIGHT : GTK_DIR_LEFT))
6460 {
6461 GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (label));
6462
6463 if (toplevel)
6464 gtk_widget_child_focus (toplevel,
6465 count > 0 ?
6466 GTK_DIR_RIGHT : GTK_DIR_LEFT);
6467 }
6468 }
6469 else
6470 {
6471 gtk_widget_error_bell (GTK_WIDGET (label));
6472 }
6473 }
6474 break;
6475 case GTK_MOVEMENT_WORDS:
6476 while (count > 0)
6477 {
6478 new_pos = gtk_label_move_forward_word (label, new_pos);
6479 count--;
6480 }
6481 while (count < 0)
6482 {
6483 new_pos = gtk_label_move_backward_word (label, new_pos);
6484 count++;
6485 }
6486 if (new_pos == old_pos)
6487 gtk_widget_error_bell (GTK_WIDGET (label));
6488 break;
6489 case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
6490 case GTK_MOVEMENT_PARAGRAPH_ENDS:
6491 case GTK_MOVEMENT_BUFFER_ENDS:
6492 /* FIXME: Can do better here */
6493 new_pos = count < 0 ? 0 : strlen (priv->text);
6494 if (new_pos == old_pos)
6495 gtk_widget_error_bell (GTK_WIDGET (label));
6496 break;
6497 case GTK_MOVEMENT_DISPLAY_LINES:
6498 case GTK_MOVEMENT_PARAGRAPHS:
6499 case GTK_MOVEMENT_PAGES:
6500 case GTK_MOVEMENT_HORIZONTAL_PAGES:
6501 break;
6502 }
6503 }
6504
6505 if (extend_selection)
6506 gtk_label_select_region_index (label,
6507 priv->select_info->selection_anchor,
6508 new_pos);
6509 else
6510 gtk_label_select_region_index (label, new_pos, new_pos);
6511 }
6512
6513 static void
gtk_label_copy_clipboard(GtkLabel * label)6514 gtk_label_copy_clipboard (GtkLabel *label)
6515 {
6516 GtkLabelPrivate *priv = label->priv;
6517
6518 if (priv->text && priv->select_info)
6519 {
6520 gint start, end;
6521 gint len;
6522 GtkClipboard *clipboard;
6523
6524 start = MIN (priv->select_info->selection_anchor,
6525 priv->select_info->selection_end);
6526 end = MAX (priv->select_info->selection_anchor,
6527 priv->select_info->selection_end);
6528
6529 len = strlen (priv->text);
6530
6531 if (end > len)
6532 end = len;
6533
6534 if (start > len)
6535 start = len;
6536
6537 clipboard = gtk_widget_get_clipboard (GTK_WIDGET (label), GDK_SELECTION_CLIPBOARD);
6538
6539 if (start != end)
6540 gtk_clipboard_set_text (clipboard, priv->text + start, end - start);
6541 else
6542 {
6543 GtkLabelLink *link;
6544
6545 link = gtk_label_get_focus_link (label);
6546 if (link)
6547 gtk_clipboard_set_text (clipboard, link->uri, -1);
6548 }
6549 }
6550 }
6551
6552 static void
gtk_label_select_all(GtkLabel * label)6553 gtk_label_select_all (GtkLabel *label)
6554 {
6555 GtkLabelPrivate *priv = label->priv;
6556
6557 gtk_label_select_region_index (label, 0, strlen (priv->text));
6558 }
6559
6560 /* Quick hack of a popup menu
6561 */
6562 static void
activate_cb(GtkWidget * menuitem,GtkLabel * label)6563 activate_cb (GtkWidget *menuitem,
6564 GtkLabel *label)
6565 {
6566 const gchar *signal = g_object_get_qdata (G_OBJECT (menuitem), quark_gtk_signal);
6567 g_signal_emit_by_name (label, signal);
6568 }
6569
6570 static void
append_action_signal(GtkLabel * label,GtkWidget * menu,const gchar * text,const gchar * signal,gboolean sensitive)6571 append_action_signal (GtkLabel *label,
6572 GtkWidget *menu,
6573 const gchar *text,
6574 const gchar *signal,
6575 gboolean sensitive)
6576 {
6577 GtkWidget *menuitem = gtk_menu_item_new_with_mnemonic (text);
6578
6579 g_object_set_qdata (G_OBJECT (menuitem), quark_gtk_signal, (char *)signal);
6580 g_signal_connect (menuitem, "activate",
6581 G_CALLBACK (activate_cb), label);
6582
6583 gtk_widget_set_sensitive (menuitem, sensitive);
6584
6585 gtk_widget_show (menuitem);
6586 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
6587 }
6588
6589 static void
popup_menu_detach(GtkWidget * attach_widget,GtkMenu * menu)6590 popup_menu_detach (GtkWidget *attach_widget,
6591 GtkMenu *menu)
6592 {
6593 GtkLabel *label = GTK_LABEL (attach_widget);
6594 GtkLabelPrivate *priv = label->priv;
6595
6596 if (priv->select_info)
6597 priv->select_info->popup_menu = NULL;
6598 }
6599
6600 static void
open_link_activate_cb(GtkMenuItem * menuitem,GtkLabel * label)6601 open_link_activate_cb (GtkMenuItem *menuitem,
6602 GtkLabel *label)
6603 {
6604 GtkLabelLink *link;
6605
6606 link = g_object_get_qdata (G_OBJECT (menuitem), quark_link);
6607 emit_activate_link (label, link);
6608 }
6609
6610 static void
copy_link_activate_cb(GtkMenuItem * menuitem,GtkLabel * label)6611 copy_link_activate_cb (GtkMenuItem *menuitem,
6612 GtkLabel *label)
6613 {
6614 GtkLabelLink *link;
6615 GtkClipboard *clipboard;
6616
6617 link = g_object_get_qdata (G_OBJECT (menuitem), quark_link);
6618 clipboard = gtk_widget_get_clipboard (GTK_WIDGET (label), GDK_SELECTION_CLIPBOARD);
6619 gtk_clipboard_set_text (clipboard, link->uri, -1);
6620 }
6621
6622 static gboolean
gtk_label_popup_menu(GtkWidget * widget)6623 gtk_label_popup_menu (GtkWidget *widget)
6624 {
6625 gtk_label_do_popup (GTK_LABEL (widget), NULL);
6626
6627 return TRUE;
6628 }
6629
6630 static void
gtk_label_do_popup(GtkLabel * label,const GdkEvent * event)6631 gtk_label_do_popup (GtkLabel *label,
6632 const GdkEvent *event)
6633 {
6634 GtkLabelPrivate *priv = label->priv;
6635 GtkWidget *menuitem;
6636 GtkWidget *menu;
6637 gboolean have_selection;
6638 GtkLabelLink *link;
6639
6640 if (!priv->select_info)
6641 return;
6642
6643 if (priv->select_info->popup_menu)
6644 gtk_widget_destroy (priv->select_info->popup_menu);
6645
6646 priv->select_info->popup_menu = menu = gtk_menu_new ();
6647 gtk_style_context_add_class (gtk_widget_get_style_context (menu),
6648 GTK_STYLE_CLASS_CONTEXT_MENU);
6649
6650 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (label), popup_menu_detach);
6651
6652 have_selection =
6653 priv->select_info->selection_anchor != priv->select_info->selection_end;
6654
6655 if (event)
6656 {
6657 if (priv->select_info->link_clicked)
6658 link = priv->select_info->active_link;
6659 else
6660 link = NULL;
6661 }
6662 else
6663 link = gtk_label_get_focus_link (label);
6664
6665 if (!have_selection && link)
6666 {
6667 /* Open Link */
6668 menuitem = gtk_menu_item_new_with_mnemonic (_("_Open Link"));
6669 g_object_set_qdata (G_OBJECT (menuitem), quark_link, link);
6670 gtk_widget_show (menuitem);
6671 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
6672
6673 g_signal_connect (G_OBJECT (menuitem), "activate",
6674 G_CALLBACK (open_link_activate_cb), label);
6675
6676 /* Copy Link Address */
6677 menuitem = gtk_menu_item_new_with_mnemonic (_("Copy _Link Address"));
6678 g_object_set_qdata (G_OBJECT (menuitem), quark_link, link);
6679 gtk_widget_show (menuitem);
6680 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
6681
6682 g_signal_connect (G_OBJECT (menuitem), "activate",
6683 G_CALLBACK (copy_link_activate_cb), label);
6684 }
6685 else
6686 {
6687 append_action_signal (label, menu, _("Cu_t"), "cut-clipboard", FALSE);
6688 append_action_signal (label, menu, _("_Copy"), "copy-clipboard", have_selection);
6689 append_action_signal (label, menu, _("_Paste"), "paste-clipboard", FALSE);
6690
6691 menuitem = gtk_menu_item_new_with_mnemonic (_("_Delete"));
6692 gtk_widget_set_sensitive (menuitem, FALSE);
6693 gtk_widget_show (menuitem);
6694 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
6695
6696 menuitem = gtk_separator_menu_item_new ();
6697 gtk_widget_show (menuitem);
6698 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
6699
6700 menuitem = gtk_menu_item_new_with_mnemonic (_("Select _All"));
6701 g_signal_connect_swapped (menuitem, "activate",
6702 G_CALLBACK (gtk_label_select_all), label);
6703 gtk_widget_show (menuitem);
6704 gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
6705 }
6706
6707 g_signal_emit (label, signals[POPULATE_POPUP], 0, menu);
6708
6709 if (event && gdk_event_triggers_context_menu (event))
6710 gtk_menu_popup_at_pointer (GTK_MENU (menu), event);
6711 else
6712 {
6713 gtk_menu_popup_at_widget (GTK_MENU (menu),
6714 GTK_WIDGET (label),
6715 GDK_GRAVITY_SOUTH,
6716 GDK_GRAVITY_NORTH_WEST,
6717 event);
6718
6719 gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
6720 }
6721 }
6722
6723 static void
gtk_label_clear_links(GtkLabel * label)6724 gtk_label_clear_links (GtkLabel *label)
6725 {
6726 GtkLabelPrivate *priv = label->priv;
6727
6728 if (!priv->select_info)
6729 return;
6730
6731 g_list_free_full (priv->select_info->links, (GDestroyNotify) link_free);
6732 priv->select_info->links = NULL;
6733 priv->select_info->active_link = NULL;
6734
6735 _gtk_label_accessible_update_links (label);
6736 }
6737
6738 static gboolean
gtk_label_activate_link(GtkLabel * label,const gchar * uri)6739 gtk_label_activate_link (GtkLabel *label,
6740 const gchar *uri)
6741 {
6742 GtkWidget *widget = GTK_WIDGET (label);
6743 GtkWidget *top_level = gtk_widget_get_toplevel (widget);
6744 guint32 timestamp = gtk_get_current_event_time ();
6745 GError *error = NULL;
6746
6747 if (!gtk_show_uri_on_window (GTK_WINDOW (top_level), uri, timestamp, &error))
6748 {
6749 g_warning ("Unable to show '%s': %s", uri, error->message);
6750 g_error_free (error);
6751 }
6752
6753 return TRUE;
6754 }
6755
6756 static void
emit_activate_link(GtkLabel * label,GtkLabelLink * link)6757 emit_activate_link (GtkLabel *label,
6758 GtkLabelLink *link)
6759 {
6760 GtkLabelPrivate *priv = label->priv;
6761 gboolean handled;
6762 GtkStateFlags state;
6763
6764 g_signal_emit (label, signals[ACTIVATE_LINK], 0, link->uri, &handled);
6765
6766 /* signal handler might have invalidated the layout */
6767 if (!priv->layout)
6768 return;
6769
6770 if (handled && priv->track_links && !link->visited &&
6771 priv->select_info && priv->select_info->links)
6772 {
6773 link->visited = TRUE;
6774 state = gtk_css_node_get_state (link->cssnode);
6775 gtk_css_node_set_state (link->cssnode, (state & ~GTK_STATE_FLAG_LINK) | GTK_STATE_FLAG_VISITED);
6776 /* FIXME: shouldn't have to redo everything here */
6777 gtk_label_clear_layout (label);
6778 }
6779 }
6780
6781 static void
gtk_label_activate_current_link(GtkLabel * label)6782 gtk_label_activate_current_link (GtkLabel *label)
6783 {
6784 GtkLabelLink *link;
6785 GtkWidget *widget = GTK_WIDGET (label);
6786
6787 link = gtk_label_get_focus_link (label);
6788
6789 if (link)
6790 {
6791 emit_activate_link (label, link);
6792 }
6793 else
6794 {
6795 GtkWidget *toplevel;
6796 GtkWindow *window;
6797 GtkWidget *default_widget, *focus_widget;
6798
6799 toplevel = gtk_widget_get_toplevel (widget);
6800 if (GTK_IS_WINDOW (toplevel))
6801 {
6802 window = GTK_WINDOW (toplevel);
6803
6804 if (window)
6805 {
6806 default_widget = gtk_window_get_default_widget (window);
6807 focus_widget = gtk_window_get_focus (window);
6808
6809 if (default_widget != widget &&
6810 !(widget == focus_widget && (!default_widget || !gtk_widget_is_sensitive (default_widget))))
6811 gtk_window_activate_default (window);
6812 }
6813 }
6814 }
6815 }
6816
6817 static GtkLabelLink *
gtk_label_get_current_link(GtkLabel * label)6818 gtk_label_get_current_link (GtkLabel *label)
6819 {
6820 GtkLabelPrivate *priv = label->priv;
6821 GtkLabelLink *link;
6822
6823 if (!priv->select_info)
6824 return NULL;
6825
6826 if (priv->select_info->link_clicked)
6827 link = priv->select_info->active_link;
6828 else
6829 link = gtk_label_get_focus_link (label);
6830
6831 return link;
6832 }
6833
6834 /**
6835 * gtk_label_get_current_uri:
6836 * @label: a #GtkLabel
6837 *
6838 * Returns the URI for the currently active link in the label.
6839 * The active link is the one under the mouse pointer or, in a
6840 * selectable label, the link in which the text cursor is currently
6841 * positioned.
6842 *
6843 * This function is intended for use in a #GtkLabel::activate-link handler
6844 * or for use in a #GtkWidget::query-tooltip handler.
6845 *
6846 * Returns: the currently active URI. The string is owned by GTK+ and must
6847 * not be freed or modified.
6848 *
6849 * Since: 2.18
6850 */
6851 const gchar *
gtk_label_get_current_uri(GtkLabel * label)6852 gtk_label_get_current_uri (GtkLabel *label)
6853 {
6854 GtkLabelLink *link;
6855
6856 g_return_val_if_fail (GTK_IS_LABEL (label), NULL);
6857
6858 link = gtk_label_get_current_link (label);
6859
6860 if (link)
6861 return link->uri;
6862
6863 return NULL;
6864 }
6865
6866 /**
6867 * gtk_label_set_track_visited_links:
6868 * @label: a #GtkLabel
6869 * @track_links: %TRUE to track visited links
6870 *
6871 * Sets whether the label should keep track of clicked
6872 * links (and use a different color for them).
6873 *
6874 * Since: 2.18
6875 */
6876 void
gtk_label_set_track_visited_links(GtkLabel * label,gboolean track_links)6877 gtk_label_set_track_visited_links (GtkLabel *label,
6878 gboolean track_links)
6879 {
6880 GtkLabelPrivate *priv;
6881
6882 g_return_if_fail (GTK_IS_LABEL (label));
6883
6884 priv = label->priv;
6885
6886 track_links = track_links != FALSE;
6887
6888 if (priv->track_links != track_links)
6889 {
6890 priv->track_links = track_links;
6891
6892 /* FIXME: shouldn't have to redo everything here */
6893 gtk_label_recalculate (label);
6894
6895 g_object_notify_by_pspec (G_OBJECT (label), label_props[PROP_TRACK_VISITED_LINKS]);
6896 }
6897 }
6898
6899 /**
6900 * gtk_label_get_track_visited_links:
6901 * @label: a #GtkLabel
6902 *
6903 * Returns whether the label is currently keeping track
6904 * of clicked links.
6905 *
6906 * Returns: %TRUE if clicked links are remembered
6907 *
6908 * Since: 2.18
6909 */
6910 gboolean
gtk_label_get_track_visited_links(GtkLabel * label)6911 gtk_label_get_track_visited_links (GtkLabel *label)
6912 {
6913 g_return_val_if_fail (GTK_IS_LABEL (label), FALSE);
6914
6915 return label->priv->track_links;
6916 }
6917
6918 static gboolean
gtk_label_query_tooltip(GtkWidget * widget,gint x,gint y,gboolean keyboard_tip,GtkTooltip * tooltip)6919 gtk_label_query_tooltip (GtkWidget *widget,
6920 gint x,
6921 gint y,
6922 gboolean keyboard_tip,
6923 GtkTooltip *tooltip)
6924 {
6925 GtkLabel *label = GTK_LABEL (widget);
6926 GtkLabelPrivate *priv = label->priv;
6927 GtkLabelSelectionInfo *info = priv->select_info;
6928 gint index = -1;
6929 GList *l;
6930
6931 if (info && info->links)
6932 {
6933 if (keyboard_tip)
6934 {
6935 if (info->selection_anchor == info->selection_end)
6936 index = info->selection_anchor;
6937 }
6938 else
6939 {
6940 if (!get_layout_index (label, x, y, &index))
6941 index = -1;
6942 }
6943
6944 if (index != -1)
6945 {
6946 for (l = info->links; l != NULL; l = l->next)
6947 {
6948 GtkLabelLink *link = l->data;
6949 if (index >= link->start && index <= link->end)
6950 {
6951 if (link->title)
6952 {
6953 gtk_tooltip_set_markup (tooltip, link->title);
6954 return TRUE;
6955 }
6956 break;
6957 }
6958 }
6959 }
6960 }
6961
6962 return GTK_WIDGET_CLASS (gtk_label_parent_class)->query_tooltip (widget,
6963 x, y,
6964 keyboard_tip,
6965 tooltip);
6966 }
6967
6968 gint
_gtk_label_get_cursor_position(GtkLabel * label)6969 _gtk_label_get_cursor_position (GtkLabel *label)
6970 {
6971 GtkLabelPrivate *priv = label->priv;
6972
6973 if (priv->select_info && priv->select_info->selectable)
6974 return g_utf8_pointer_to_offset (priv->text,
6975 priv->text + priv->select_info->selection_end);
6976
6977 return 0;
6978 }
6979
6980 gint
_gtk_label_get_selection_bound(GtkLabel * label)6981 _gtk_label_get_selection_bound (GtkLabel *label)
6982 {
6983 GtkLabelPrivate *priv = label->priv;
6984
6985 if (priv->select_info && priv->select_info->selectable)
6986 return g_utf8_pointer_to_offset (priv->text,
6987 priv->text + priv->select_info->selection_anchor);
6988
6989 return 0;
6990 }
6991
6992 /**
6993 * gtk_label_set_lines:
6994 * @label: a #GtkLabel
6995 * @lines: the desired number of lines, or -1
6996 *
6997 * Sets the number of lines to which an ellipsized, wrapping label
6998 * should be limited. This has no effect if the label is not wrapping
6999 * or ellipsized. Set this to -1 if you don’t want to limit the
7000 * number of lines.
7001 *
7002 * Since: 3.10
7003 */
7004 void
gtk_label_set_lines(GtkLabel * label,gint lines)7005 gtk_label_set_lines (GtkLabel *label,
7006 gint lines)
7007 {
7008 GtkLabelPrivate *priv;
7009
7010 g_return_if_fail (GTK_IS_LABEL (label));
7011
7012 priv = label->priv;
7013
7014 if (priv->lines != lines)
7015 {
7016 priv->lines = lines;
7017 gtk_label_clear_layout (label);
7018 g_object_notify_by_pspec (G_OBJECT (label), label_props[PROP_LINES]);
7019 gtk_widget_queue_resize (GTK_WIDGET (label));
7020 }
7021 }
7022
7023 /**
7024 * gtk_label_get_lines:
7025 * @label: a #GtkLabel
7026 *
7027 * Gets the number of lines to which an ellipsized, wrapping
7028 * label should be limited. See gtk_label_set_lines().
7029 *
7030 * Returns: The number of lines
7031 *
7032 * Since: 3.10
7033 */
7034 gint
gtk_label_get_lines(GtkLabel * label)7035 gtk_label_get_lines (GtkLabel *label)
7036 {
7037 g_return_val_if_fail (GTK_IS_LABEL (label), -1);
7038
7039 return label->priv->lines;
7040 }
7041
7042 gint
_gtk_label_get_n_links(GtkLabel * label)7043 _gtk_label_get_n_links (GtkLabel *label)
7044 {
7045 GtkLabelPrivate *priv = label->priv;
7046
7047 if (priv->select_info)
7048 return g_list_length (priv->select_info->links);
7049
7050 return 0;
7051 }
7052
7053 const gchar *
_gtk_label_get_link_uri(GtkLabel * label,gint idx)7054 _gtk_label_get_link_uri (GtkLabel *label,
7055 gint idx)
7056 {
7057 GtkLabelPrivate *priv = label->priv;
7058
7059 if (priv->select_info)
7060 {
7061 GtkLabelLink *link = g_list_nth_data (priv->select_info->links, idx);
7062 if (link)
7063 return link->uri;
7064 }
7065
7066 return NULL;
7067 }
7068
7069 void
_gtk_label_get_link_extent(GtkLabel * label,gint idx,gint * start,gint * end)7070 _gtk_label_get_link_extent (GtkLabel *label,
7071 gint idx,
7072 gint *start,
7073 gint *end)
7074 {
7075 GtkLabelPrivate *priv = label->priv;
7076 gint i;
7077 GList *l;
7078 GtkLabelLink *link;
7079
7080 if (priv->select_info)
7081 for (l = priv->select_info->links, i = 0; l; l = l->next, i++)
7082 {
7083 if (i == idx)
7084 {
7085 link = l->data;
7086 *start = link->start;
7087 *end = link->end;
7088 return;
7089 }
7090 }
7091
7092 *start = -1;
7093 *end = -1;
7094 }
7095
7096 gint
_gtk_label_get_link_at(GtkLabel * label,gint pos)7097 _gtk_label_get_link_at (GtkLabel *label,
7098 gint pos)
7099 {
7100 GtkLabelPrivate *priv = label->priv;
7101 gint i;
7102 GList *l;
7103 GtkLabelLink *link;
7104
7105 if (priv->select_info)
7106 for (l = priv->select_info->links, i = 0; l; l = l->next, i++)
7107 {
7108 link = l->data;
7109 if (link->start <= pos && pos < link->end)
7110 return i;
7111 }
7112
7113 return -1;
7114 }
7115
7116 void
_gtk_label_activate_link(GtkLabel * label,gint idx)7117 _gtk_label_activate_link (GtkLabel *label,
7118 gint idx)
7119 {
7120 GtkLabelPrivate *priv = label->priv;
7121
7122 if (priv->select_info)
7123 {
7124 GtkLabelLink *link = g_list_nth_data (priv->select_info->links, idx);
7125
7126 if (link)
7127 emit_activate_link (label, link);
7128 }
7129 }
7130
7131 gboolean
_gtk_label_get_link_visited(GtkLabel * label,gint idx)7132 _gtk_label_get_link_visited (GtkLabel *label,
7133 gint idx)
7134 {
7135 GtkLabelPrivate *priv = label->priv;
7136
7137 if (priv->select_info)
7138 {
7139 GtkLabelLink *link = g_list_nth_data (priv->select_info->links, idx);
7140 return link ? link->visited : FALSE;
7141 }
7142
7143 return FALSE;
7144 }
7145
7146 gboolean
_gtk_label_get_link_focused(GtkLabel * label,gint idx)7147 _gtk_label_get_link_focused (GtkLabel *label,
7148 gint idx)
7149 {
7150 GtkLabelPrivate *priv = label->priv;
7151 gint i;
7152 GList *l;
7153 GtkLabelLink *link;
7154 GtkLabelSelectionInfo *info = priv->select_info;
7155
7156 if (!info)
7157 return FALSE;
7158
7159 if (info->selection_anchor != info->selection_end)
7160 return FALSE;
7161
7162 for (l = info->links, i = 0; l; l = l->next, i++)
7163 {
7164 if (i == idx)
7165 {
7166 link = l->data;
7167 if (link->start <= info->selection_anchor &&
7168 info->selection_anchor <= link->end)
7169 return TRUE;
7170 }
7171 }
7172
7173 return FALSE;
7174 }
7175
7176 /**
7177 * gtk_label_set_xalign:
7178 * @label: a #GtkLabel
7179 * @xalign: the new xalign value, between 0 and 1
7180 *
7181 * Sets the #GtkLabel:xalign property for @label.
7182 *
7183 * Since: 3.16
7184 */
7185 void
gtk_label_set_xalign(GtkLabel * label,gfloat xalign)7186 gtk_label_set_xalign (GtkLabel *label,
7187 gfloat xalign)
7188 {
7189 g_return_if_fail (GTK_IS_LABEL (label));
7190
7191 xalign = CLAMP (xalign, 0.0, 1.0);
7192
7193 if (label->priv->xalign == xalign)
7194 return;
7195
7196 label->priv->xalign = xalign;
7197
7198 gtk_widget_queue_draw (GTK_WIDGET (label));
7199 g_object_notify_by_pspec (G_OBJECT (label), label_props[PROP_XALIGN]);
7200 }
7201
7202 /**
7203 * gtk_label_get_xalign:
7204 * @label: a #GtkLabel
7205 *
7206 * Gets the #GtkLabel:xalign property for @label.
7207 *
7208 * Returns: the xalign property
7209 *
7210 * Since: 3.16
7211 */
7212 gfloat
gtk_label_get_xalign(GtkLabel * label)7213 gtk_label_get_xalign (GtkLabel *label)
7214 {
7215 g_return_val_if_fail (GTK_IS_LABEL (label), 0.5);
7216
7217 return label->priv->xalign;
7218 }
7219
7220 /**
7221 * gtk_label_set_yalign:
7222 * @label: a #GtkLabel
7223 * @yalign: the new yalign value, between 0 and 1
7224 *
7225 * Sets the #GtkLabel:yalign property for @label.
7226 *
7227 * Since: 3.16
7228 */
7229 void
gtk_label_set_yalign(GtkLabel * label,gfloat yalign)7230 gtk_label_set_yalign (GtkLabel *label,
7231 gfloat yalign)
7232 {
7233 g_return_if_fail (GTK_IS_LABEL (label));
7234
7235 yalign = CLAMP (yalign, 0.0, 1.0);
7236
7237 if (label->priv->yalign == yalign)
7238 return;
7239
7240 label->priv->yalign = yalign;
7241
7242 gtk_widget_queue_draw (GTK_WIDGET (label));
7243 g_object_notify_by_pspec (G_OBJECT (label), label_props[PROP_YALIGN]);
7244 }
7245
7246 /**
7247 * gtk_label_get_yalign:
7248 * @label: a #GtkLabel
7249 *
7250 * Gets the #GtkLabel:yalign property for @label.
7251 *
7252 * Returns: the yalign property
7253 *
7254 * Since: 3.16
7255 */
7256 gfloat
gtk_label_get_yalign(GtkLabel * label)7257 gtk_label_get_yalign (GtkLabel *label)
7258 {
7259 g_return_val_if_fail (GTK_IS_LABEL (label), 0.5);
7260
7261 return label->priv->yalign;
7262 }
7263