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

source code of gtk/gtk/gtklabel.c