1/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2/* GTK - The GIMP Toolkit
3 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
4 * Copyright (C) 2004-2006 Christian Hammond
5 * Copyright (C) 2008 Cody Russell
6 * Copyright (C) 2008 Red Hat, Inc.
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22#include "config.h"
23
24#include "gtktextprivate.h"
25
26#include "gtkactionable.h"
27#include "gtkadjustment.h"
28#include "gtkbox.h"
29#include "gtkbutton.h"
30#include "gtkdebug.h"
31#include "gtkdragicon.h"
32#include "gtkdragsourceprivate.h"
33#include "gtkdroptarget.h"
34#include "gtkeditable.h"
35#include "gtkemojichooser.h"
36#include "gtkemojicompletion.h"
37#include "gtkentrybuffer.h"
38#include "gtkeventcontrollerfocus.h"
39#include "gtkeventcontrollerkey.h"
40#include "gtkeventcontrollermotion.h"
41#include "gtkgesturedrag.h"
42#include "gtkgestureclick.h"
43#include "gtkgesturesingle.h"
44#include "gtkimageprivate.h"
45#include "gtkimcontextsimple.h"
46#include "gtkimmulticontext.h"
47#include "gtkintl.h"
48#include "gtklabel.h"
49#include "gtkmagnifierprivate.h"
50#include "gtkmain.h"
51#include "gtkmarshalers.h"
52#include "gtkpango.h"
53#include "gtkpopovermenu.h"
54#include "gtkprivate.h"
55#include "gtksettings.h"
56#include "gtksnapshot.h"
57#include "gtkstylecontextprivate.h"
58#include "gtktexthandleprivate.h"
59#include "gtktexthistoryprivate.h"
60#include "gtktextutil.h"
61#include "gtktooltip.h"
62#include "gtktypebuiltins.h"
63#include "gtkwidgetprivate.h"
64#include "gtkwindow.h"
65#include "gtknative.h"
66#include "gtkactionmuxerprivate.h"
67#include "gtkjoinedmenuprivate.h"
68
69#include <cairo-gobject.h>
70#include <string.h>
71
72/**
73 * GtkText:
74 *
75 * The `GtkText` widget is a single-line text entry widget.
76 *
77 * `GtkText` is the common implementation of single-line text editing
78 * that is shared between `GtkEntry`, `GtkPasswordEntry`, `GtkSpinButton`
79 * and other widgets. In all of these, `GtkText` is used as the delegate
80 * for the [iface@Gtk.Editable] implementation.
81 *
82 * A fairly large set of key bindings are supported by default. If the
83 * entered text is longer than the allocation of the widget, the widget
84 * will scroll so that the cursor position is visible.
85 *
86 * When using an entry for passwords and other sensitive information,
87 * it can be put into “password mode” using [method@Gtk.Text.set_visibility].
88 * In this mode, entered text is displayed using a “invisible” character.
89 * By default, GTK picks the best invisible character that is available
90 * in the current font, but it can be changed with
91 * [method@Gtk.Text.set_invisible_char].
92 *
93 * If you are looking to add icons or progress display in an entry, look
94 * at `GtkEntry`. There other alternatives for more specialized use cases,
95 * such as `GtkSearchEntry`.
96 *
97 * If you need multi-line editable text, look at `GtkTextView`.
98 *
99 * # CSS nodes
100 *
101 * ```
102 * text[.read-only]
103 * ├── placeholder
104 * ├── undershoot.left
105 * ├── undershoot.right
106 * ├── [selection]
107 * ├── [block-cursor]
108 * ╰── [window.popup]
109 * ```
110 *
111 * `GtkText` has a main node with the name text. Depending on the properties
112 * of the widget, the .read-only style class may appear.
113 *
114 * When the entry has a selection, it adds a subnode with the name selection.
115 *
116 * When the entry is in overwrite mode, it adds a subnode with the name
117 * block-cursor that determines how the block cursor is drawn.
118 *
119 * The CSS node for a context menu is added as a subnode below text as well.
120 *
121 * The undershoot nodes are used to draw the underflow indication when content
122 * is scrolled out of view. These nodes get the .left and .right style classes
123 * added depending on where the indication is drawn.
124 *
125 * When touch is used and touch selection handles are shown, they are using
126 * CSS nodes with name cursor-handle. They get the .top or .bottom style class
127 * depending on where they are shown in relation to the selection. If there is
128 * just a single handle for the text cursor, it gets the style class
129 * .insertion-cursor.
130 *
131 * # Accessibility
132 *
133 * `GtkText` uses the %GTK_ACCESSIBLE_ROLE_NONE role, which causes it to be
134 * skipped for accessibility. This is because `GtkText` is expected to be used
135 * as a delegate for a `GtkEditable` implementation that will be represented
136 * to accessibility.
137 */
138
139#define NAT_ENTRY_WIDTH 150
140
141#define UNDERSHOOT_SIZE 20
142
143#define DEFAULT_MAX_UNDO 200
144
145static GQuark quark_password_hint = 0;
146
147enum
148{
149 TEXT_HANDLE_CURSOR,
150 TEXT_HANDLE_SELECTION_BOUND,
151 TEXT_HANDLE_N_HANDLES
152};
153
154typedef struct _GtkTextPasswordHint GtkTextPasswordHint;
155
156typedef struct _GtkTextPrivate GtkTextPrivate;
157struct _GtkTextPrivate
158{
159 GtkEntryBuffer *buffer;
160 GtkIMContext *im_context;
161
162 int text_baseline;
163
164 PangoLayout *cached_layout;
165 PangoAttrList *attrs;
166 PangoTabArray *tabs;
167
168 GdkContentProvider *selection_content;
169
170 char *im_module;
171
172 GtkWidget *emoji_completion;
173 GtkTextHandle *text_handles[TEXT_HANDLE_N_HANDLES];
174 GtkWidget *selection_bubble;
175 guint selection_bubble_timeout_id;
176
177 GtkWidget *magnifier_popover;
178 GtkWidget *magnifier;
179
180 GtkWidget *placeholder;
181
182 GtkGesture *drag_gesture;
183 GtkEventController *key_controller;
184 GtkEventController *focus_controller;
185
186 GtkCssNode *selection_node;
187 GtkCssNode *block_cursor_node;
188 GtkCssNode *undershoot_node[2];
189
190 GtkWidget *popup_menu;
191 GMenuModel *extra_menu;
192
193 GtkTextHistory *history;
194
195 GdkDrag *drag;
196
197 float xalign;
198
199 int ascent; /* font ascent in pango units */
200 int current_pos;
201 int descent; /* font descent in pango units */
202 int dnd_position; /* In chars, -1 == no DND cursor */
203 int drag_start_x;
204 int drag_start_y;
205 int insert_pos;
206 int selection_bound;
207 int scroll_offset;
208 int width_chars;
209 int max_width_chars;
210 guint32 obscured_cursor_timestamp;
211
212 gunichar invisible_char;
213
214 guint64 blink_start_time;
215 guint blink_tick;
216 float cursor_alpha;
217
218 guint16 preedit_length; /* length of preedit string, in bytes */
219 guint16 preedit_cursor; /* offset of cursor within preedit string, in chars */
220
221 gint64 handle_place_time;
222
223 guint editable : 1;
224 guint enable_emoji_completion : 1;
225 guint in_drag : 1;
226 guint overwrite_mode : 1;
227 guint visible : 1;
228
229 guint activates_default : 1;
230 guint cache_includes_preedit : 1;
231 guint change_count : 8;
232 guint in_click : 1; /* Flag so we don't select all when clicking in entry to focus in */
233 guint invisible_char_set : 1;
234 guint mouse_cursor_obscured : 1;
235 guint need_im_reset : 1;
236 guint real_changed : 1;
237 guint resolved_dir : 4; /* PangoDirection */
238 guint select_words : 1;
239 guint select_lines : 1;
240 guint truncate_multiline : 1;
241 guint cursor_handle_dragged : 1;
242 guint selection_handle_dragged : 1;
243 guint populate_all : 1;
244 guint propagate_text_width : 1;
245 guint text_handles_enabled : 1;
246};
247
248struct _GtkTextPasswordHint
249{
250 int position; /* Position (in text) of the last password hint */
251 guint source_id; /* Timeout source id */
252};
253
254enum {
255 ACTIVATE,
256 MOVE_CURSOR,
257 INSERT_AT_CURSOR,
258 DELETE_FROM_CURSOR,
259 BACKSPACE,
260 CUT_CLIPBOARD,
261 COPY_CLIPBOARD,
262 PASTE_CLIPBOARD,
263 TOGGLE_OVERWRITE,
264 PREEDIT_CHANGED,
265 INSERT_EMOJI,
266 LAST_SIGNAL
267};
268
269enum {
270 PROP_0,
271 PROP_BUFFER,
272 PROP_MAX_LENGTH,
273 PROP_VISIBILITY,
274 PROP_INVISIBLE_CHAR,
275 PROP_INVISIBLE_CHAR_SET,
276 PROP_ACTIVATES_DEFAULT,
277 PROP_SCROLL_OFFSET,
278 PROP_TRUNCATE_MULTILINE,
279 PROP_OVERWRITE_MODE,
280 PROP_IM_MODULE,
281 PROP_PLACEHOLDER_TEXT,
282 PROP_INPUT_PURPOSE,
283 PROP_INPUT_HINTS,
284 PROP_ATTRIBUTES,
285 PROP_TABS,
286 PROP_ENABLE_EMOJI_COMPLETION,
287 PROP_PROPAGATE_TEXT_WIDTH,
288 PROP_EXTRA_MENU,
289 NUM_PROPERTIES
290};
291
292static GParamSpec *text_props[NUM_PROPERTIES] = { NULL, };
293
294static guint signals[LAST_SIGNAL] = { 0 };
295
296typedef enum {
297 CURSOR_STANDARD,
298 CURSOR_DND
299} CursorType;
300
301typedef enum
302{
303 DISPLAY_NORMAL, /* The text is being shown */
304 DISPLAY_INVISIBLE, /* In invisible mode, text replaced by (eg) bullets */
305 DISPLAY_BLANK /* In invisible mode, nothing shown at all */
306} DisplayMode;
307
308/* GObject methods
309 */
310static void gtk_text_set_property (GObject *object,
311 guint prop_id,
312 const GValue *value,
313 GParamSpec *pspec);
314static void gtk_text_get_property (GObject *object,
315 guint prop_id,
316 GValue *value,
317 GParamSpec *pspec);
318static void gtk_text_finalize (GObject *object);
319static void gtk_text_dispose (GObject *object);
320
321/* GtkWidget methods
322 */
323static void gtk_text_realize (GtkWidget *widget);
324static void gtk_text_unrealize (GtkWidget *widget);
325static void gtk_text_map (GtkWidget *widget);
326static void gtk_text_unmap (GtkWidget *widget);
327static void gtk_text_measure (GtkWidget *widget,
328 GtkOrientation orientation,
329 int for_size,
330 int *minimum,
331 int *natural,
332 int *minimum_baseline,
333 int *natural_baseline);
334static void gtk_text_size_allocate (GtkWidget *widget,
335 int width,
336 int height,
337 int baseline);
338static void gtk_text_snapshot (GtkWidget *widget,
339 GtkSnapshot *snapshot);
340static void gtk_text_focus_changed (GtkEventControllerFocus *focus,
341 GParamSpec *pspec,
342 GtkWidget *widget);
343static gboolean gtk_text_grab_focus (GtkWidget *widget);
344static void gtk_text_css_changed (GtkWidget *widget,
345 GtkCssStyleChange *change);
346static void gtk_text_direction_changed (GtkWidget *widget,
347 GtkTextDirection previous_dir);
348static void gtk_text_state_flags_changed (GtkWidget *widget,
349 GtkStateFlags previous_state);
350
351static gboolean gtk_text_drag_drop (GtkDropTarget *dest,
352 const GValue *value,
353 double x,
354 double y,
355 GtkText *text);
356static gboolean gtk_text_drag_accept (GtkDropTarget *dest,
357 GdkDrop *drop,
358 GtkText *self);
359static GdkDragAction gtk_text_drag_motion (GtkDropTarget *dest,
360 double x,
361 double y,
362 GtkText *text);
363static void gtk_text_drag_leave (GtkDropTarget *dest,
364 GtkText *text);
365
366
367/* GtkEditable method implementations
368 */
369static void gtk_text_editable_init (GtkEditableInterface *iface);
370static void gtk_text_insert_text (GtkText *self,
371 const char *text,
372 int length,
373 int *position);
374static void gtk_text_delete_text (GtkText *self,
375 int start_pos,
376 int end_pos);
377static void gtk_text_delete_selection (GtkText *self);
378static void gtk_text_set_selection_bounds (GtkText *self,
379 int start,
380 int end);
381static gboolean gtk_text_get_selection_bounds (GtkText *self,
382 int *start,
383 int *end);
384
385static void gtk_text_set_editable (GtkText *self,
386 gboolean is_editable);
387static void gtk_text_set_text (GtkText *self,
388 const char *text);
389static void gtk_text_set_width_chars (GtkText *self,
390 int n_chars);
391static void gtk_text_set_max_width_chars (GtkText *self,
392 int n_chars);
393static void gtk_text_set_alignment (GtkText *self,
394 float xalign);
395
396/* Default signal handlers
397 */
398static GMenuModel *gtk_text_get_menu_model (GtkText *self);
399static void gtk_text_popup_menu (GtkWidget *widget,
400 const char *action_name,
401 GVariant *parameters);
402static void gtk_text_move_cursor (GtkText *self,
403 GtkMovementStep step,
404 int count,
405 gboolean extend);
406static void gtk_text_insert_at_cursor (GtkText *self,
407 const char *str);
408static void gtk_text_delete_from_cursor (GtkText *self,
409 GtkDeleteType type,
410 int count);
411static void gtk_text_backspace (GtkText *self);
412static void gtk_text_cut_clipboard (GtkText *self);
413static void gtk_text_copy_clipboard (GtkText *self);
414static void gtk_text_paste_clipboard (GtkText *self);
415static void gtk_text_toggle_overwrite (GtkText *self);
416static void gtk_text_insert_emoji (GtkText *self);
417static void gtk_text_select_all (GtkText *self);
418static void gtk_text_real_activate (GtkText *self);
419
420static void direction_changed (GdkDevice *keyboard,
421 GParamSpec *pspec,
422 GtkText *self);
423
424/* IM Context Callbacks
425 */
426static void gtk_text_commit_cb (GtkIMContext *context,
427 const char *str,
428 GtkText *self);
429static void gtk_text_preedit_start_cb (GtkIMContext *context,
430 GtkText *self);
431static void gtk_text_preedit_changed_cb (GtkIMContext *context,
432 GtkText *self);
433static gboolean gtk_text_retrieve_surrounding_cb (GtkIMContext *context,
434 GtkText *self);
435static gboolean gtk_text_delete_surrounding_cb (GtkIMContext *context,
436 int offset,
437 int n_chars,
438 GtkText *self);
439
440/* Entry buffer signal handlers
441 */
442static void buffer_inserted_text (GtkEntryBuffer *buffer,
443 guint position,
444 const char *chars,
445 guint n_chars,
446 GtkText *self);
447static void buffer_deleted_text (GtkEntryBuffer *buffer,
448 guint position,
449 guint n_chars,
450 GtkText *self);
451static void buffer_notify_text (GtkEntryBuffer *buffer,
452 GParamSpec *spec,
453 GtkText *self);
454static void buffer_notify_max_length (GtkEntryBuffer *buffer,
455 GParamSpec *spec,
456 GtkText *self);
457
458/* Event controller callbacks
459 */
460static void gtk_text_motion_controller_motion (GtkEventControllerMotion *controller,
461 double x,
462 double y,
463 GtkText *self);
464static void gtk_text_click_gesture_pressed (GtkGestureClick *gesture,
465 int n_press,
466 double x,
467 double y,
468 GtkText *self);
469static void gtk_text_drag_gesture_update (GtkGestureDrag *gesture,
470 double offset_x,
471 double offset_y,
472 GtkText *self);
473static void gtk_text_drag_gesture_end (GtkGestureDrag *gesture,
474 double offset_x,
475 double offset_y,
476 GtkText *self);
477static gboolean gtk_text_key_controller_key_pressed (GtkEventControllerKey *controller,
478 guint keyval,
479 guint keycode,
480 GdkModifierType state,
481 GtkText *self);
482
483
484/* GtkTextHandle handlers */
485static void gtk_text_handle_drag_started (GtkTextHandle *handle,
486 GtkText *self);
487static void gtk_text_handle_dragged (GtkTextHandle *handle,
488 int x,
489 int y,
490 GtkText *self);
491static void gtk_text_handle_drag_finished (GtkTextHandle *handle,
492 GtkText *self);
493
494/* Internal routines
495 */
496static void gtk_text_draw_text (GtkText *self,
497 GtkSnapshot *snapshot);
498static void gtk_text_draw_cursor (GtkText *self,
499 GtkSnapshot *snapshot,
500 CursorType type);
501static PangoLayout *gtk_text_ensure_layout (GtkText *self,
502 gboolean include_preedit);
503static void gtk_text_reset_layout (GtkText *self);
504static void gtk_text_recompute (GtkText *self);
505static int gtk_text_find_position (GtkText *self,
506 int x);
507static void gtk_text_get_cursor_locations (GtkText *self,
508 int *strong_x,
509 int *weak_x);
510static void gtk_text_adjust_scroll (GtkText *self);
511static int gtk_text_move_visually (GtkText *editable,
512 int start,
513 int count);
514static int gtk_text_move_logically (GtkText *self,
515 int start,
516 int count);
517static int gtk_text_move_forward_word (GtkText *self,
518 int start,
519 gboolean allow_whitespace);
520static int gtk_text_move_backward_word (GtkText *self,
521 int start,
522 gboolean allow_whitespace);
523static void gtk_text_delete_whitespace (GtkText *self);
524static void gtk_text_select_word (GtkText *self);
525static void gtk_text_select_line (GtkText *self);
526static void gtk_text_paste (GtkText *self,
527 GdkClipboard *clipboard);
528static void gtk_text_update_primary_selection (GtkText *self);
529static void gtk_text_schedule_im_reset (GtkText *self);
530static gboolean gtk_text_mnemonic_activate (GtkWidget *widget,
531 gboolean group_cycling);
532static void gtk_text_check_cursor_blink (GtkText *self);
533static void remove_blink_timeout (GtkText *self);
534static void gtk_text_pend_cursor_blink (GtkText *self);
535static void gtk_text_reset_blink_time (GtkText *self);
536static void gtk_text_update_cached_style_values(GtkText *self);
537static gboolean get_middle_click_paste (GtkText *self);
538static void gtk_text_get_scroll_limits (GtkText *self,
539 int *min_offset,
540 int *max_offset);
541static GtkEntryBuffer *get_buffer (GtkText *self);
542static void set_text_cursor (GtkWidget *widget);
543static void update_placeholder_visibility (GtkText *self);
544
545static void buffer_connect_signals (GtkText *self);
546static void buffer_disconnect_signals (GtkText *self);
547
548static void gtk_text_selection_bubble_popup_set (GtkText *self);
549static void gtk_text_selection_bubble_popup_unset (GtkText *self);
550
551static void begin_change (GtkText *self);
552static void end_change (GtkText *self);
553static void emit_changed (GtkText *self);
554
555static void gtk_text_update_clipboard_actions (GtkText *self);
556static void gtk_text_update_emoji_action (GtkText *self);
557static void gtk_text_update_handles (GtkText *self);
558
559static void gtk_text_activate_clipboard_cut (GtkWidget *widget,
560 const char *action_name,
561 GVariant *parameter);
562static void gtk_text_activate_clipboard_copy (GtkWidget *widget,
563 const char *action_name,
564 GVariant *parameter);
565static void gtk_text_activate_clipboard_paste (GtkWidget *widget,
566 const char *action_name,
567 GVariant *parameter);
568static void gtk_text_activate_selection_delete (GtkWidget *widget,
569 const char *action_name,
570 GVariant *parameter);
571static void gtk_text_activate_selection_select_all (GtkWidget *widget,
572 const char *action_name,
573 GVariant *parameter);
574static void gtk_text_activate_misc_insert_emoji (GtkWidget *widget,
575 const char *action_name,
576 GVariant *parameter);
577static void gtk_text_real_undo (GtkWidget *widget,
578 const char *action_name,
579 GVariant *parameters);
580static void gtk_text_real_redo (GtkWidget *widget,
581 const char *action_name,
582 GVariant *parameters);
583static void gtk_text_history_change_state_cb (gpointer funcs_data,
584 gboolean is_modified,
585 gboolean can_undo,
586 gboolean can_redo);
587static void gtk_text_history_insert_cb (gpointer funcs_data,
588 guint begin,
589 guint end,
590 const char *text,
591 guint len);
592static void gtk_text_history_delete_cb (gpointer funcs_data,
593 guint begin,
594 guint end,
595 const char *expected_text,
596 guint len);
597static void gtk_text_history_select_cb (gpointer funcs_data,
598 int selection_insert,
599 int selection_bound);
600
601/* GtkTextContent implementation
602 */
603#define GTK_TYPE_TEXT_CONTENT (gtk_text_content_get_type ())
604#define GTK_TEXT_CONTENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_TEXT_CONTENT, GtkTextContent))
605#define GTK_IS_TEXT_CONTENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_TEXT_CONTENT))
606#define GTK_TEXT_CONTENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_TEXT_CONTENT, GtkTextContentClass))
607#define GTK_IS_TEXT_CONTENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_TEXT_CONTENT))
608#define GTK_TEXT_CONTENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_TEXT_CONTENT, GtkTextContentClass))
609
610typedef struct _GtkTextContent GtkTextContent;
611typedef struct _GtkTextContentClass GtkTextContentClass;
612
613struct _GtkTextContent
614{
615 GdkContentProvider parent;
616
617 GtkText *self;
618};
619
620struct _GtkTextContentClass
621{
622 GdkContentProviderClass parent_class;
623};
624
625GType gtk_text_content_get_type (void) G_GNUC_CONST;
626
627G_DEFINE_TYPE (GtkTextContent, gtk_text_content, GDK_TYPE_CONTENT_PROVIDER)
628
629static GdkContentFormats *
630gtk_text_content_ref_formats (GdkContentProvider *provider)
631{
632 return gdk_content_formats_new_for_gtype (G_TYPE_STRING);
633}
634
635static gboolean
636gtk_text_content_get_value (GdkContentProvider *provider,
637 GValue *value,
638 GError **error)
639{
640 GtkTextContent *content = GTK_TEXT_CONTENT (provider);
641
642 if (G_VALUE_HOLDS (value, G_TYPE_STRING))
643 {
644 int start, end;
645
646 if (gtk_text_get_selection_bounds (self: content->self, start: &start, end: &end))
647 {
648 char *str = gtk_text_get_display_text (entry: content->self, start_pos: start, end_pos: end);
649 g_value_take_string (value, v_string: str);
650 }
651 return TRUE;
652 }
653
654 return GDK_CONTENT_PROVIDER_CLASS (gtk_text_content_parent_class)->get_value (provider, value, error);
655}
656
657static void
658gtk_text_content_detach (GdkContentProvider *provider,
659 GdkClipboard *clipboard)
660{
661 GtkTextContent *content = GTK_TEXT_CONTENT (provider);
662 int current_pos, selection_bound;
663
664 gtk_text_get_selection_bounds (self: content->self, start: &current_pos, end: &selection_bound);
665 gtk_text_set_selection_bounds (self: content->self, start: current_pos, end: current_pos);
666}
667
668static void
669gtk_text_content_class_init (GtkTextContentClass *class)
670{
671 GdkContentProviderClass *provider_class = GDK_CONTENT_PROVIDER_CLASS (class);
672
673 provider_class->ref_formats = gtk_text_content_ref_formats;
674 provider_class->get_value = gtk_text_content_get_value;
675 provider_class->detach_clipboard = gtk_text_content_detach;
676}
677
678static void
679gtk_text_content_init (GtkTextContent *content)
680{
681}
682
683/* GtkText
684 */
685
686static const GtkTextHistoryFuncs history_funcs = {
687 gtk_text_history_change_state_cb,
688 gtk_text_history_insert_cb,
689 gtk_text_history_delete_cb,
690 gtk_text_history_select_cb,
691};
692
693G_DEFINE_TYPE_WITH_CODE (GtkText, gtk_text, GTK_TYPE_WIDGET,
694 G_ADD_PRIVATE (GtkText)
695 G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, gtk_text_editable_init))
696
697static void
698add_move_binding (GtkWidgetClass *widget_class,
699 guint keyval,
700 guint modmask,
701 GtkMovementStep step,
702 int count)
703{
704 g_return_if_fail ((modmask & GDK_SHIFT_MASK) == 0);
705
706 gtk_widget_class_add_binding_signal (widget_class,
707 keyval, mods: modmask,
708 signal: "move-cursor",
709 format_string: "(iib)", step, count, FALSE);
710 /* Selection-extending version */
711 gtk_widget_class_add_binding_signal (widget_class,
712 keyval, mods: modmask | GDK_SHIFT_MASK,
713 signal: "move-cursor",
714 format_string: "(iib)", step, count, TRUE);
715}
716
717static void
718gtk_text_class_init (GtkTextClass *class)
719{
720 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
721 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
722
723 gobject_class->dispose = gtk_text_dispose;
724 gobject_class->finalize = gtk_text_finalize;
725 gobject_class->set_property = gtk_text_set_property;
726 gobject_class->get_property = gtk_text_get_property;
727
728 widget_class->map = gtk_text_map;
729 widget_class->unmap = gtk_text_unmap;
730 widget_class->realize = gtk_text_realize;
731 widget_class->unrealize = gtk_text_unrealize;
732 widget_class->measure = gtk_text_measure;
733 widget_class->size_allocate = gtk_text_size_allocate;
734 widget_class->snapshot = gtk_text_snapshot;
735 widget_class->grab_focus = gtk_text_grab_focus;
736 widget_class->css_changed = gtk_text_css_changed;
737 widget_class->direction_changed = gtk_text_direction_changed;
738 widget_class->state_flags_changed = gtk_text_state_flags_changed;
739 widget_class->mnemonic_activate = gtk_text_mnemonic_activate;
740
741 class->move_cursor = gtk_text_move_cursor;
742 class->insert_at_cursor = gtk_text_insert_at_cursor;
743 class->delete_from_cursor = gtk_text_delete_from_cursor;
744 class->backspace = gtk_text_backspace;
745 class->cut_clipboard = gtk_text_cut_clipboard;
746 class->copy_clipboard = gtk_text_copy_clipboard;
747 class->paste_clipboard = gtk_text_paste_clipboard;
748 class->toggle_overwrite = gtk_text_toggle_overwrite;
749 class->insert_emoji = gtk_text_insert_emoji;
750 class->activate = gtk_text_real_activate;
751
752 quark_password_hint = g_quark_from_static_string (string: "gtk-entry-password-hint");
753
754 /**
755 * GtkText:buffer: (attributes org.gtk.Property.get=gtk_text_get_buffer org.gtk.Property.set=gtk_text_set_buffer)
756 *
757 * The `GtkEntryBuffer` object which stores the text.
758 */
759 text_props[PROP_BUFFER] =
760 g_param_spec_object (name: "buffer",
761 P_("Text Buffer"),
762 P_("Text buffer object which actually stores self text"),
763 GTK_TYPE_ENTRY_BUFFER,
764 GTK_PARAM_READWRITE|G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY);
765
766 /**
767 * GtkText:max-length: (attributes org.gtk.Property.get=gtk_text_get_max_length org.gtk.Property.set=gtk_text_set_max_length)
768 *
769 * Maximum number of characters that are allowed.
770 *
771 * Zero indicates no limit.
772 */
773 text_props[PROP_MAX_LENGTH] =
774 g_param_spec_int (name: "max-length",
775 P_("Maximum length"),
776 P_("Maximum number of characters for this self. Zero if no maximum"),
777 minimum: 0, GTK_ENTRY_BUFFER_MAX_SIZE,
778 default_value: 0,
779 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
780
781 /**
782 * GtkText:invisible-char: (attributes org.gtk.Property.get=gtk_text_set_invisible_char org.gtk.Property.set=gtk_text_set_invisible_char)
783 *
784 * The character to used when masking contents (in “password mode”).
785 */
786 text_props[PROP_INVISIBLE_CHAR] =
787 g_param_spec_unichar (name: "invisible-char",
788 P_("Invisible character"),
789 P_("The character to use when masking self contents (in “password mode”)"),
790 default_value: '*',
791 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
792
793 /**
794 * GtkText:activates-default: (attributes org.gtk.Property.get=gtk_text_get_activates_default org.gtk.Property.set=gtk_text_set_activates_default)
795 *
796 * Whether to activate the default widget when Enter is pressed.
797 */
798 text_props[PROP_ACTIVATES_DEFAULT] =
799 g_param_spec_boolean (name: "activates-default",
800 P_("Activates default"),
801 P_("Whether to activate the default widget (such as the default button in a dialog) when Enter is pressed"),
802 FALSE,
803 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
804
805 /**
806 * GtkText:scroll-offset:
807 *
808 * Number of pixels scrolled of the screen to the left.
809 */
810 text_props[PROP_SCROLL_OFFSET] =
811 g_param_spec_int (name: "scroll-offset",
812 P_("Scroll offset"),
813 P_("Number of pixels of the text scrolled off the screen to the left"),
814 minimum: 0, G_MAXINT,
815 default_value: 0,
816 GTK_PARAM_READABLE|G_PARAM_EXPLICIT_NOTIFY);
817
818 /**
819 * GtkText:truncate-multiline: (attributes org.gtk.Property.get=gtk_text_get_truncate_multiline org.gtk.Property.set=gtk_text_set_truncate_multiline)
820 *
821 * When %TRUE, pasted multi-line text is truncated to the first line.
822 */
823 text_props[PROP_TRUNCATE_MULTILINE] =
824 g_param_spec_boolean (name: "truncate-multiline",
825 P_("Truncate multiline"),
826 P_("Whether to truncate multiline pastes to one line."),
827 FALSE,
828 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
829
830 /**
831 * GtkText:overwrite-mode: (attributes org.gtk.Property.get=gtk_text_get_overwrite_mode org.gtk.Property.set=gtk_text_set_overwrite_mode)
832 *
833 * If text is overwritten when typing in the `GtkText`.
834 */
835 text_props[PROP_OVERWRITE_MODE] =
836 g_param_spec_boolean (name: "overwrite-mode",
837 P_("Overwrite mode"),
838 P_("Whether new text overwrites existing text"),
839 FALSE,
840 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
841
842 /**
843 * GtkText:invisible-char-set:
844 *
845 * Whether the invisible char has been set for the `GtkText`.
846 */
847 text_props[PROP_INVISIBLE_CHAR_SET] =
848 g_param_spec_boolean (name: "invisible-char-set",
849 P_("Invisible character set"),
850 P_("Whether the invisible character has been set"),
851 FALSE,
852 GTK_PARAM_READWRITE);
853
854 /**
855 * GtkText:placeholder-text: (attributes org.gtk.Property.get=gtk_text_get_placeholder_text org.gtk.Property.set=gtk_text_set_placeholder_text)
856 *
857 * The text that will be displayed in the `GtkText` when it is empty
858 * and unfocused.
859 */
860 text_props[PROP_PLACEHOLDER_TEXT] =
861 g_param_spec_string (name: "placeholder-text",
862 P_("Placeholder text"),
863 P_("Show text in the GtkText when it’s empty and unfocused"),
864 NULL,
865 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
866
867 /**
868 * GtkText:im-module:
869 *
870 * Which IM (input method) module should be used for this self.
871 *
872 * See [class@Gtk.IMMulticontext].
873 *
874 * Setting this to a non-%NULL value overrides the system-wide
875 * IM module setting. See the [property@Gtk.Settings:gtk-im-module]
876 * property.
877 */
878 text_props[PROP_IM_MODULE] =
879 g_param_spec_string (name: "im-module",
880 P_("IM module"),
881 P_("Which IM module should be used"),
882 NULL,
883 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
884
885 /**
886 * GtkText:input-purpose: (attributes org.gtk.Property.get=gtk_text_get_input_purpose org.gtk.Property.set=gtk_text_set_input_purpose)
887 *
888 * The purpose of this text field.
889 *
890 * This property can be used by on-screen keyboards and other input
891 * methods to adjust their behaviour.
892 *
893 * Note that setting the purpose to %GTK_INPUT_PURPOSE_PASSWORD or
894 * %GTK_INPUT_PURPOSE_PIN is independent from setting
895 * [property@Gtk.Text:visibility].
896 */
897 text_props[PROP_INPUT_PURPOSE] =
898 g_param_spec_enum (name: "input-purpose",
899 P_("Purpose"),
900 P_("Purpose of the text field"),
901 enum_type: GTK_TYPE_INPUT_PURPOSE,
902 default_value: GTK_INPUT_PURPOSE_FREE_FORM,
903 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
904
905 /**
906 * GtkText:input-hints: (attributes org.gtk.Property.get=gtk_text_get_input_hints org.gtk.Property.set=gtk_text_set_input_hints)
907 *
908 * Additional hints that allow input methods to fine-tune
909 * their behaviour.
910 */
911 text_props[PROP_INPUT_HINTS] =
912 g_param_spec_flags (name: "input-hints",
913 P_("hints"),
914 P_("Hints for the text field behaviour"),
915 flags_type: GTK_TYPE_INPUT_HINTS,
916 default_value: GTK_INPUT_HINT_NONE,
917 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
918
919 /**
920 * GtkText:attributes: (attributes org.gtk.Property.get=gtk_text_get_attributes org.gtk.Property.set=gtk_text_set_attributes)
921 *
922 * A list of Pango attributes to apply to the text of the `GtkText`.
923 *
924 * This is mainly useful to change the size or weight of the text.
925 *
926 * The `PangoAttribute`'s @start_index and @end_index must refer to the
927 * `GtkEntryBuffer` text, i.e. without the preedit string.
928 */
929 text_props[PROP_ATTRIBUTES] =
930 g_param_spec_boxed (name: "attributes",
931 P_("Attributes"),
932 P_("A list of style attributes to apply to the text of the GtkText"),
933 PANGO_TYPE_ATTR_LIST,
934 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
935
936 /**
937 * GtkText:tabs: (attributes org.gtk.Property.get=gtk_text_get_tabs org.gtk.Property.set=gtk_text_set_tabs)
938 *
939 * A list of tabstops to apply to the text of the `GtkText`.
940 */
941 text_props[PROP_TABS] =
942 g_param_spec_boxed (name: "tabs",
943 P_("Tabs"),
944 P_("A list of tabstop locations to apply to the text of the GtkText"),
945 PANGO_TYPE_TAB_ARRAY,
946 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
947
948 /**
949 * GtkText:enable-emoji-completion: (attributes org.gtk.Property.get=gtk_text_get_enable_emoji_completion org.gtk.Property.set=gtk_text_set_enable_emoji_completion)
950 *
951 * Whether to suggest Emoji replacements.
952 */
953 text_props[PROP_ENABLE_EMOJI_COMPLETION] =
954 g_param_spec_boolean (name: "enable-emoji-completion",
955 P_("Enable Emoji completion"),
956 P_("Whether to suggest Emoji replacements"),
957 FALSE,
958 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
959
960 /**
961 * GtkText:visibility: (attributes org.gtk.Property.get=gtk_text_get_visibility org.gtk.Property.set=gtk_text_set_visibility)
962 *
963 * If %FALSE, the text is masked with the “invisible char”.
964 */
965 text_props[PROP_VISIBILITY] =
966 g_param_spec_boolean (name: "visibility",
967 P_("Visibility"),
968 P_("FALSE displays the “invisible char” instead of the actual text (password mode)"),
969 TRUE,
970 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
971
972 /**
973 * GtkText:propagate-text-width: (attributes org.gtk.Property.get=gtk_text_get_propagate_text_width org.gtk.Property.set=gtk_text_set_propagate_text_width)
974 *
975 * Whether the widget should grow and shrink with the content.
976 */
977 text_props[PROP_PROPAGATE_TEXT_WIDTH] =
978 g_param_spec_boolean (name: "propagate-text-width",
979 P_("Propagate text width"),
980 P_("Whether the entry should grow and shrink with the content"),
981 FALSE,
982 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
983
984 /**
985 * GtkText:extra-menu: (attributes org.gtk.Property.get=gtk_text_get_extra_menu org.gtk.Property.set=gtk_text_set_extra_menu)
986 *
987 * A menu model whose contents will be appended to
988 * the context menu.
989 */
990 text_props[PROP_EXTRA_MENU] =
991 g_param_spec_object (name: "extra-menu",
992 P_("Extra menu"),
993 P_("Menu model to append to the context menu"),
994 G_TYPE_MENU_MODEL,
995 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
996
997 g_object_class_install_properties (oclass: gobject_class, n_pspecs: NUM_PROPERTIES, pspecs: text_props);
998
999 gtk_editable_install_properties (object_class: gobject_class, first_prop: NUM_PROPERTIES);
1000
1001 /* Action signals */
1002
1003 /**
1004 * GtkText::activate:
1005 * @self: The widget on which the signal is emitted
1006 *
1007 * Emitted when the user hits the Enter key.
1008 *
1009 * The default bindings for this signal are all forms
1010 * of the <kbd>Enter</kbd> key.
1011 */
1012 signals[ACTIVATE] =
1013 g_signal_new (I_("activate"),
1014 G_OBJECT_CLASS_TYPE (gobject_class),
1015 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1016 G_STRUCT_OFFSET (GtkTextClass, activate),
1017 NULL, NULL,
1018 NULL,
1019 G_TYPE_NONE, n_params: 0);
1020
1021 /**
1022 * GtkText::move-cursor:
1023 * @self: the object which received the signal
1024 * @step: the granularity of the move, as a `GtkMovementStep`
1025 * @count: the number of @step units to move
1026 * @extend: %TRUE if the move should extend the selection
1027 *
1028 * Emitted when the user initiates a cursor movement.
1029 *
1030 * If the cursor is not visible in @self, this signal causes
1031 * the viewport to be moved instead.
1032 *
1033 * This is a [keybinding signal](class.SignalAction.html).
1034 *
1035 * Applications should not connect to it, but may emit it with
1036 * g_signal_emit_by_name() if they need to control the cursor
1037 * programmatically.
1038 *
1039 * The default bindings for this signal come in two variants,
1040 * the variant with the <kbd>Shift</kbd> modifier extends the
1041 * selection, the variant without it does not.
1042 * There are too many key combinations to list them all here.
1043 *
1044 * - <kbd>←</kbd>, <kbd>→</kbd>, <kbd>↑</kbd>, <kbd>↓</kbd>
1045 * move by individual characters/lines
1046 * - <kbd>Ctrl</kbd>-<kbd>→</kbd>, etc. move by words/paragraphs
1047 * - <kbd>Home</kbd>, <kbd>End</kbd> move to the ends of the buffer
1048 */
1049 signals[MOVE_CURSOR] =
1050 g_signal_new (I_("move-cursor"),
1051 G_OBJECT_CLASS_TYPE (gobject_class),
1052 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1053 G_STRUCT_OFFSET (GtkTextClass, move_cursor),
1054 NULL, NULL,
1055 c_marshaller: _gtk_marshal_VOID__ENUM_INT_BOOLEAN,
1056 G_TYPE_NONE, n_params: 3,
1057 GTK_TYPE_MOVEMENT_STEP,
1058 G_TYPE_INT,
1059 G_TYPE_BOOLEAN);
1060
1061 /**
1062 * GtkText::insert-at-cursor:
1063 * @self: the object which received the signal
1064 * @string: the string to insert
1065 *
1066 * Emitted when the user initiates the insertion of a
1067 * fixed string at the cursor.
1068 *
1069 * This is a [keybinding signal](class.SignalAction.html).
1070 *
1071 * This signal has no default bindings.
1072 */
1073 signals[INSERT_AT_CURSOR] =
1074 g_signal_new (I_("insert-at-cursor"),
1075 G_OBJECT_CLASS_TYPE (gobject_class),
1076 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1077 G_STRUCT_OFFSET (GtkTextClass, insert_at_cursor),
1078 NULL, NULL,
1079 NULL,
1080 G_TYPE_NONE, n_params: 1,
1081 G_TYPE_STRING);
1082
1083 /**
1084 * GtkText::delete-from-cursor:
1085 * @self: the object which received the signal
1086 * @type: the granularity of the deletion, as a `GtkDeleteType`
1087 * @count: the number of @type units to delete
1088 *
1089 * Emitted when the user initiates a text deletion.
1090 *
1091 * This is a [keybinding signal](class.SignalAction.html).
1092 *
1093 * If the @type is %GTK_DELETE_CHARS, GTK deletes the selection
1094 * if there is one, otherwise it deletes the requested number
1095 * of characters.
1096 *
1097 * The default bindings for this signal are <kbd>Delete</kbd>
1098 * for deleting a character and <kbd>Ctrl</kbd>-<kbd>Delete</kbd>
1099 * for deleting a word.
1100 */
1101 signals[DELETE_FROM_CURSOR] =
1102 g_signal_new (I_("delete-from-cursor"),
1103 G_OBJECT_CLASS_TYPE (gobject_class),
1104 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1105 G_STRUCT_OFFSET (GtkTextClass, delete_from_cursor),
1106 NULL, NULL,
1107 c_marshaller: _gtk_marshal_VOID__ENUM_INT,
1108 G_TYPE_NONE, n_params: 2,
1109 GTK_TYPE_DELETE_TYPE,
1110 G_TYPE_INT);
1111
1112 /**
1113 * GtkText::backspace:
1114 * @self: the object which received the signal
1115 *
1116 * Emitted when the user asks for it.
1117 *
1118 * This is a [keybinding signal](class.SignalAction.html).
1119 *
1120 * The default bindings for this signal are
1121 * <kbd>Backspace</kbd> and <kbd>Shift</kbd>-<kbd>Backspace</kbd>.
1122 */
1123 signals[BACKSPACE] =
1124 g_signal_new (I_("backspace"),
1125 G_OBJECT_CLASS_TYPE (gobject_class),
1126 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1127 G_STRUCT_OFFSET (GtkTextClass, backspace),
1128 NULL, NULL,
1129 NULL,
1130 G_TYPE_NONE, n_params: 0);
1131
1132 /**
1133 * GtkText::cut-clipboard:
1134 * @self: the object which received the signal
1135 *
1136 * Emitted to cut the selection to the clipboard.
1137 *
1138 * This is a [keybinding signal](class.SignalAction.html).
1139 *
1140 * The default bindings for this signal are
1141 * <kbd>Ctrl</kbd>-<kbd>x</kbd> and
1142 * <kbd>Shift</kbd>-<kbd>Delete</kbd>.
1143 */
1144 signals[CUT_CLIPBOARD] =
1145 g_signal_new (I_("cut-clipboard"),
1146 G_OBJECT_CLASS_TYPE (gobject_class),
1147 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1148 G_STRUCT_OFFSET (GtkTextClass, cut_clipboard),
1149 NULL, NULL,
1150 NULL,
1151 G_TYPE_NONE, n_params: 0);
1152
1153 /**
1154 * GtkText::copy-clipboard:
1155 * @self: the object which received the signal
1156 *
1157 * Emitted to copy the selection to the clipboard.
1158 *
1159 * This is a [keybinding signal](class.SignalAction.html).
1160 *
1161 * The default bindings for this signal are
1162 * <kbd>Ctrl</kbd>-<kbd>c</kbd> and
1163 * <kbd>Ctrl</kbd>-<kbd>Insert</kbd>.
1164 */
1165 signals[COPY_CLIPBOARD] =
1166 g_signal_new (I_("copy-clipboard"),
1167 G_OBJECT_CLASS_TYPE (gobject_class),
1168 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1169 G_STRUCT_OFFSET (GtkTextClass, copy_clipboard),
1170 NULL, NULL,
1171 NULL,
1172 G_TYPE_NONE, n_params: 0);
1173
1174 /**
1175 * GtkText::paste-clipboard:
1176 * @self: the object which received the signal
1177 *
1178 * Emitted to paste the contents of the clipboard.
1179 *
1180 * This is a [keybinding signal](class.SignalAction.html).
1181 *
1182 * The default bindings for this signal are
1183 * <kbd>Ctrl</kbd>-<kbd>v</kbd> and <kbd>Shift</kbd>-<kbd>Insert</kbd>.
1184 */
1185 signals[PASTE_CLIPBOARD] =
1186 g_signal_new (I_("paste-clipboard"),
1187 G_OBJECT_CLASS_TYPE (gobject_class),
1188 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1189 G_STRUCT_OFFSET (GtkTextClass, paste_clipboard),
1190 NULL, NULL,
1191 NULL,
1192 G_TYPE_NONE, n_params: 0);
1193
1194 /**
1195 * GtkText::toggle-overwrite:
1196 * @self: the object which received the signal
1197 *
1198 * Emitted to toggle the overwrite mode of the `GtkText`.
1199 *
1200 * This is a [keybinding signal](class.SignalAction.html).
1201 *
1202 * The default bindings for this signal is <kbd>Insert</kbd>.
1203 */
1204 signals[TOGGLE_OVERWRITE] =
1205 g_signal_new (I_("toggle-overwrite"),
1206 G_OBJECT_CLASS_TYPE (gobject_class),
1207 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1208 G_STRUCT_OFFSET (GtkTextClass, toggle_overwrite),
1209 NULL, NULL,
1210 NULL,
1211 G_TYPE_NONE, n_params: 0);
1212
1213 /**
1214 * GtkText::preedit-changed:
1215 * @self: the object which received the signal
1216 * @preedit: the current preedit string
1217 *
1218 * Emitted when the preedit text changes.
1219 *
1220 * If an input method is used, the typed text will not immediately
1221 * be committed to the buffer. So if you are interested in the text,
1222 * connect to this signal.
1223 */
1224 signals[PREEDIT_CHANGED] =
1225 g_signal_new_class_handler (I_("preedit-changed"),
1226 G_OBJECT_CLASS_TYPE (gobject_class),
1227 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1228 NULL,
1229 NULL, NULL,
1230 NULL,
1231 G_TYPE_NONE, n_params: 1,
1232 G_TYPE_STRING);
1233
1234 /**
1235 * GtkText::insert-emoji:
1236 * @self: the object which received the signal
1237 *
1238 * Emitted to present the Emoji chooser for the widget.
1239 *
1240 * This is a [keybinding signal](class.SignalAction.html).
1241 *
1242 * The default bindings for this signal are
1243 * <kbd>Ctrl</kbd>-<kbd>.</kbd> and
1244 * <kbd>Ctrl</kbd>-<kbd>;</kbd>
1245 */
1246 signals[INSERT_EMOJI] =
1247 g_signal_new (I_("insert-emoji"),
1248 G_OBJECT_CLASS_TYPE (gobject_class),
1249 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1250 G_STRUCT_OFFSET (GtkTextClass, insert_emoji),
1251 NULL, NULL,
1252 NULL,
1253 G_TYPE_NONE, n_params: 0);
1254
1255 /*
1256 * Actions
1257 */
1258
1259 /**
1260 * GtkText|clipboard.cut:
1261 *
1262 * Copies the contents to the clipboard and deletes it from the widget.
1263 */
1264 gtk_widget_class_install_action (widget_class, action_name: "clipboard.cut", NULL,
1265 activate: gtk_text_activate_clipboard_cut);
1266
1267 /**
1268 * GtkText|clipboard.copy:
1269 *
1270 * Copies the contents to the clipboard.
1271 */
1272 gtk_widget_class_install_action (widget_class, action_name: "clipboard.copy", NULL,
1273 activate: gtk_text_activate_clipboard_copy);
1274
1275 /**
1276 * GtkText|clipboard.paste:
1277 *
1278 * Inserts the contents of the clipboard into the widget.
1279 */
1280 gtk_widget_class_install_action (widget_class, action_name: "clipboard.paste", NULL,
1281 activate: gtk_text_activate_clipboard_paste);
1282
1283 /**
1284 * GtkText|selection.delete:
1285 *
1286 * Deletes the current selection.
1287 */
1288 gtk_widget_class_install_action (widget_class, action_name: "selection.delete", NULL,
1289 activate: gtk_text_activate_selection_delete);
1290
1291 /**
1292 * GtkText|selection.select-all:
1293 *
1294 * Selects all of the widgets content.
1295 */
1296 gtk_widget_class_install_action (widget_class, action_name: "selection.select-all", NULL,
1297 activate: gtk_text_activate_selection_select_all);
1298
1299 /**
1300 * GtkText|misc.insert-emoji:
1301 *
1302 * Opens the Emoji chooser.
1303 */
1304 gtk_widget_class_install_action (widget_class, action_name: "misc.insert-emoji", NULL,
1305 activate: gtk_text_activate_misc_insert_emoji);
1306
1307 /**
1308 * GtkText|misc.toggle-visibility:
1309 *
1310 * Toggles the `GtkText`:visibility property.
1311 */
1312 gtk_widget_class_install_property_action (widget_class,
1313 action_name: "misc.toggle-visibility",
1314 property_name: "visibility");
1315
1316 /**
1317 * GtkText|text.undo:
1318 *
1319 * Undoes the last change to the contents.
1320 */
1321 gtk_widget_class_install_action (widget_class, action_name: "text.undo", NULL, activate: gtk_text_real_undo);
1322
1323 /**
1324 * GtkText|text.redo:
1325 *
1326 * Redoes the last change to the contents.
1327 */
1328 gtk_widget_class_install_action (widget_class, action_name: "text.redo", NULL, activate: gtk_text_real_redo);
1329
1330 /**
1331 * GtkText|menu.popup:
1332 *
1333 * Opens the context menu.
1334 */
1335 gtk_widget_class_install_action (widget_class, action_name: "menu.popup", NULL, activate: gtk_text_popup_menu);
1336
1337 /*
1338 * Key bindings
1339 */
1340
1341 gtk_widget_class_add_binding_action (widget_class,
1342 GDK_KEY_F10, mods: GDK_SHIFT_MASK,
1343 action_name: "menu.popup",
1344 NULL);
1345 gtk_widget_class_add_binding_action (widget_class,
1346 GDK_KEY_Menu, mods: 0,
1347 action_name: "menu.popup",
1348 NULL);
1349
1350 /* Moving the insertion point */
1351 add_move_binding (widget_class, GDK_KEY_Right, modmask: 0,
1352 step: GTK_MOVEMENT_VISUAL_POSITIONS, count: 1);
1353
1354 add_move_binding (widget_class, GDK_KEY_Left, modmask: 0,
1355 step: GTK_MOVEMENT_VISUAL_POSITIONS, count: -1);
1356
1357 add_move_binding (widget_class, GDK_KEY_KP_Right, modmask: 0,
1358 step: GTK_MOVEMENT_VISUAL_POSITIONS, count: 1);
1359
1360 add_move_binding (widget_class, GDK_KEY_KP_Left, modmask: 0,
1361 step: GTK_MOVEMENT_VISUAL_POSITIONS, count: -1);
1362
1363 add_move_binding (widget_class, GDK_KEY_Right, modmask: GDK_CONTROL_MASK,
1364 step: GTK_MOVEMENT_WORDS, count: 1);
1365
1366 add_move_binding (widget_class, GDK_KEY_Left, modmask: GDK_CONTROL_MASK,
1367 step: GTK_MOVEMENT_WORDS, count: -1);
1368
1369 add_move_binding (widget_class, GDK_KEY_KP_Right, modmask: GDK_CONTROL_MASK,
1370 step: GTK_MOVEMENT_WORDS, count: 1);
1371
1372 add_move_binding (widget_class, GDK_KEY_KP_Left, modmask: GDK_CONTROL_MASK,
1373 step: GTK_MOVEMENT_WORDS, count: -1);
1374
1375 add_move_binding (widget_class, GDK_KEY_Home, modmask: 0,
1376 step: GTK_MOVEMENT_DISPLAY_LINE_ENDS, count: -1);
1377
1378 add_move_binding (widget_class, GDK_KEY_End, modmask: 0,
1379 step: GTK_MOVEMENT_DISPLAY_LINE_ENDS, count: 1);
1380
1381 add_move_binding (widget_class, GDK_KEY_KP_Home, modmask: 0,
1382 step: GTK_MOVEMENT_DISPLAY_LINE_ENDS, count: -1);
1383
1384 add_move_binding (widget_class, GDK_KEY_KP_End, modmask: 0,
1385 step: GTK_MOVEMENT_DISPLAY_LINE_ENDS, count: 1);
1386
1387 add_move_binding (widget_class, GDK_KEY_Home, modmask: GDK_CONTROL_MASK,
1388 step: GTK_MOVEMENT_BUFFER_ENDS, count: -1);
1389
1390 add_move_binding (widget_class, GDK_KEY_End, modmask: GDK_CONTROL_MASK,
1391 step: GTK_MOVEMENT_BUFFER_ENDS, count: 1);
1392
1393 add_move_binding (widget_class, GDK_KEY_KP_Home, modmask: GDK_CONTROL_MASK,
1394 step: GTK_MOVEMENT_BUFFER_ENDS, count: -1);
1395
1396 add_move_binding (widget_class, GDK_KEY_KP_End, modmask: GDK_CONTROL_MASK,
1397 step: GTK_MOVEMENT_BUFFER_ENDS, count: 1);
1398
1399 /* Select all
1400 */
1401 gtk_widget_class_add_binding (widget_class,
1402 GDK_KEY_a, mods: GDK_CONTROL_MASK,
1403 callback: (GtkShortcutFunc) gtk_text_select_all,
1404 NULL);
1405
1406 gtk_widget_class_add_binding (widget_class,
1407 GDK_KEY_slash, mods: GDK_CONTROL_MASK,
1408 callback: (GtkShortcutFunc) gtk_text_select_all,
1409 NULL);
1410 /* Unselect all
1411 */
1412 gtk_widget_class_add_binding_signal (widget_class,
1413 GDK_KEY_backslash, mods: GDK_CONTROL_MASK,
1414 signal: "move-cursor",
1415 format_string: "(iib)", GTK_MOVEMENT_VISUAL_POSITIONS, 0, FALSE);
1416 gtk_widget_class_add_binding_signal (widget_class,
1417 GDK_KEY_a, mods: GDK_SHIFT_MASK | GDK_CONTROL_MASK,
1418 signal: "move-cursor",
1419 format_string: "(iib)", GTK_MOVEMENT_VISUAL_POSITIONS, 0, FALSE);
1420
1421 /* Activate
1422 */
1423 gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_Return, mods: 0,
1424 signal: "activate",
1425 NULL);
1426 gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_ISO_Enter, mods: 0,
1427 signal: "activate",
1428 NULL);
1429 gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_KP_Enter, mods: 0,
1430 signal: "activate",
1431 NULL);
1432
1433 /* Deleting text */
1434 gtk_widget_class_add_binding_signal (widget_class,
1435 GDK_KEY_Delete, mods: 0,
1436 signal: "delete-from-cursor",
1437 format_string: "(ii)", GTK_DELETE_CHARS, 1);
1438
1439 gtk_widget_class_add_binding_signal (widget_class,
1440 GDK_KEY_KP_Delete, mods: 0,
1441 signal: "delete-from-cursor",
1442 format_string: "(ii)", GTK_DELETE_CHARS, 1);
1443
1444 gtk_widget_class_add_binding_signal (widget_class,
1445 GDK_KEY_BackSpace, mods: 0,
1446 signal: "backspace",
1447 NULL);
1448
1449 gtk_widget_class_add_binding_signal (widget_class,
1450 GDK_KEY_u, mods: GDK_CONTROL_MASK,
1451 signal: "delete-from-cursor",
1452 format_string: "(ii)", GTK_DELETE_PARAGRAPH_ENDS, -1);
1453
1454 /* Make this do the same as Backspace, to help with mis-typing */
1455 gtk_widget_class_add_binding_signal (widget_class,
1456 GDK_KEY_BackSpace, mods: GDK_SHIFT_MASK,
1457 signal: "backspace",
1458 NULL);
1459
1460 gtk_widget_class_add_binding_signal (widget_class,
1461 GDK_KEY_Delete, mods: GDK_CONTROL_MASK,
1462 signal: "delete-from-cursor",
1463 format_string: "(ii)", GTK_DELETE_WORD_ENDS, 1);
1464
1465 gtk_widget_class_add_binding_signal (widget_class,
1466 GDK_KEY_KP_Delete, mods: GDK_CONTROL_MASK,
1467 signal: "delete-from-cursor",
1468 format_string: "(ii)", GTK_DELETE_WORD_ENDS, 1);
1469
1470 gtk_widget_class_add_binding_signal (widget_class,
1471 GDK_KEY_BackSpace, mods: GDK_CONTROL_MASK,
1472 signal: "delete-from-cursor",
1473 format_string: "(ii)", GTK_DELETE_WORD_ENDS, -1);
1474
1475 /* Cut/copy/paste */
1476 gtk_widget_class_add_binding_signal (widget_class,
1477 GDK_KEY_x, mods: GDK_CONTROL_MASK,
1478 signal: "cut-clipboard",
1479 NULL);
1480 gtk_widget_class_add_binding_signal (widget_class,
1481 GDK_KEY_c, mods: GDK_CONTROL_MASK,
1482 signal: "copy-clipboard",
1483 NULL);
1484 gtk_widget_class_add_binding_signal (widget_class,
1485 GDK_KEY_v, mods: GDK_CONTROL_MASK,
1486 signal: "paste-clipboard",
1487 NULL);
1488
1489 gtk_widget_class_add_binding_signal (widget_class,
1490 GDK_KEY_Delete, mods: GDK_SHIFT_MASK,
1491 signal: "cut-clipboard",
1492 NULL);
1493 gtk_widget_class_add_binding_signal (widget_class,
1494 GDK_KEY_Insert, mods: GDK_CONTROL_MASK,
1495 signal: "copy-clipboard",
1496 NULL);
1497 gtk_widget_class_add_binding_signal (widget_class,
1498 GDK_KEY_Insert, mods: GDK_SHIFT_MASK,
1499 signal: "paste-clipboard",
1500 NULL);
1501
1502 gtk_widget_class_add_binding_signal (widget_class,
1503 GDK_KEY_KP_Delete, mods: GDK_SHIFT_MASK,
1504 signal: "cut-clipboard",
1505 NULL);
1506 gtk_widget_class_add_binding_signal (widget_class,
1507 GDK_KEY_KP_Insert, mods: GDK_CONTROL_MASK,
1508 signal: "copy-clipboard",
1509 NULL);
1510 gtk_widget_class_add_binding_signal (widget_class,
1511 GDK_KEY_KP_Insert, mods: GDK_SHIFT_MASK,
1512 signal: "paste-clipboard",
1513 NULL);
1514
1515 /* Overwrite */
1516 gtk_widget_class_add_binding_signal (widget_class,
1517 GDK_KEY_Insert, mods: 0,
1518 signal: "toggle-overwrite",
1519 NULL);
1520 gtk_widget_class_add_binding_signal (widget_class,
1521 GDK_KEY_KP_Insert, mods: 0,
1522 signal: "toggle-overwrite",
1523 NULL);
1524
1525 /* Emoji */
1526 gtk_widget_class_add_binding_action (widget_class,
1527 GDK_KEY_period, mods: GDK_CONTROL_MASK,
1528 action_name: "misc.insert-emoji",
1529 NULL);
1530 gtk_widget_class_add_binding_action (widget_class,
1531 GDK_KEY_semicolon, mods: GDK_CONTROL_MASK,
1532 action_name: "misc.insert-emoji",
1533 NULL);
1534
1535 /* Undo/Redo */
1536 gtk_widget_class_add_binding_action (widget_class,
1537 GDK_KEY_z, mods: GDK_CONTROL_MASK,
1538 action_name: "text.undo", NULL);
1539 gtk_widget_class_add_binding_action (widget_class,
1540 GDK_KEY_y, mods: GDK_CONTROL_MASK,
1541 action_name: "text.redo", NULL);
1542 gtk_widget_class_add_binding_action (widget_class,
1543 GDK_KEY_z, mods: GDK_CONTROL_MASK | GDK_SHIFT_MASK,
1544 action_name: "text.redo", NULL);
1545
1546 gtk_widget_class_set_css_name (widget_class, I_("text"));
1547 gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_NONE);
1548}
1549
1550static void
1551editable_insert_text (GtkEditable *editable,
1552 const char *text,
1553 int length,
1554 int *position)
1555{
1556 gtk_text_insert_text (GTK_TEXT (editable), text, length, position);
1557}
1558
1559static void
1560editable_delete_text (GtkEditable *editable,
1561 int start_pos,
1562 int end_pos)
1563{
1564 gtk_text_delete_text (GTK_TEXT (editable), start_pos, end_pos);
1565}
1566
1567static const char *
1568editable_get_text (GtkEditable *editable)
1569{
1570 return gtk_entry_buffer_get_text (buffer: get_buffer (GTK_TEXT (editable)));
1571}
1572
1573static void
1574editable_set_selection_bounds (GtkEditable *editable,
1575 int start_pos,
1576 int end_pos)
1577{
1578 gtk_text_set_selection_bounds (GTK_TEXT (editable), start: start_pos, end: end_pos);
1579}
1580
1581static gboolean
1582editable_get_selection_bounds (GtkEditable *editable,
1583 int *start_pos,
1584 int *end_pos)
1585{
1586 return gtk_text_get_selection_bounds (GTK_TEXT (editable), start: start_pos, end: end_pos);
1587}
1588
1589static void
1590gtk_text_editable_init (GtkEditableInterface *iface)
1591{
1592 iface->insert_text = editable_insert_text;
1593 iface->delete_text = editable_delete_text;
1594 iface->get_text = editable_get_text;
1595 iface->set_selection_bounds = editable_set_selection_bounds;
1596 iface->get_selection_bounds = editable_get_selection_bounds;
1597}
1598
1599static void
1600gtk_text_set_property (GObject *object,
1601 guint prop_id,
1602 const GValue *value,
1603 GParamSpec *pspec)
1604{
1605 GtkText *self = GTK_TEXT (object);
1606 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
1607
1608 switch (prop_id)
1609 {
1610 /* GtkEditable properties */
1611 case NUM_PROPERTIES + GTK_EDITABLE_PROP_EDITABLE:
1612 gtk_text_set_editable (self, is_editable: g_value_get_boolean (value));
1613 break;
1614
1615 case NUM_PROPERTIES + GTK_EDITABLE_PROP_WIDTH_CHARS:
1616 gtk_text_set_width_chars (self, n_chars: g_value_get_int (value));
1617 break;
1618
1619 case NUM_PROPERTIES + GTK_EDITABLE_PROP_MAX_WIDTH_CHARS:
1620 gtk_text_set_max_width_chars (self, n_chars: g_value_get_int (value));
1621 break;
1622
1623 case NUM_PROPERTIES + GTK_EDITABLE_PROP_TEXT:
1624 gtk_text_set_text (self, text: g_value_get_string (value));
1625 break;
1626
1627 case NUM_PROPERTIES + GTK_EDITABLE_PROP_XALIGN:
1628 gtk_text_set_alignment (self, xalign: g_value_get_float (value));
1629 break;
1630
1631 case NUM_PROPERTIES + GTK_EDITABLE_PROP_ENABLE_UNDO:
1632 if (g_value_get_boolean (value) != gtk_text_history_get_enabled (self: priv->history))
1633 {
1634 gtk_text_history_set_enabled (self: priv->history, enabled: g_value_get_boolean (value));
1635 g_object_notify_by_pspec (object, pspec);
1636 }
1637 break;
1638
1639 /* GtkText properties */
1640 case PROP_BUFFER:
1641 gtk_text_set_buffer (self, buffer: g_value_get_object (value));
1642 break;
1643
1644 case PROP_MAX_LENGTH:
1645 gtk_text_set_max_length (self, length: g_value_get_int (value));
1646 break;
1647
1648 case PROP_VISIBILITY:
1649 gtk_text_set_visibility (self, visible: g_value_get_boolean (value));
1650 break;
1651
1652 case PROP_INVISIBLE_CHAR:
1653 gtk_text_set_invisible_char (self, ch: g_value_get_uint (value));
1654 break;
1655
1656 case PROP_ACTIVATES_DEFAULT:
1657 gtk_text_set_activates_default (self, activates: g_value_get_boolean (value));
1658 break;
1659
1660 case PROP_TRUNCATE_MULTILINE:
1661 gtk_text_set_truncate_multiline (self, truncate_multiline: g_value_get_boolean (value));
1662 break;
1663
1664 case PROP_OVERWRITE_MODE:
1665 gtk_text_set_overwrite_mode (self, overwrite: g_value_get_boolean (value));
1666 break;
1667
1668 case PROP_INVISIBLE_CHAR_SET:
1669 if (g_value_get_boolean (value))
1670 priv->invisible_char_set = TRUE;
1671 else
1672 gtk_text_unset_invisible_char (self);
1673 break;
1674
1675 case PROP_PLACEHOLDER_TEXT:
1676 gtk_text_set_placeholder_text (self, text: g_value_get_string (value));
1677 break;
1678
1679 case PROP_IM_MODULE:
1680 g_free (mem: priv->im_module);
1681 priv->im_module = g_value_dup_string (value);
1682 if (GTK_IS_IM_MULTICONTEXT (priv->im_context))
1683 gtk_im_multicontext_set_context_id (GTK_IM_MULTICONTEXT (priv->im_context), context_id: priv->im_module);
1684 g_object_notify_by_pspec (object, pspec);
1685 break;
1686
1687 case PROP_INPUT_PURPOSE:
1688 gtk_text_set_input_purpose (self, purpose: g_value_get_enum (value));
1689 break;
1690
1691 case PROP_INPUT_HINTS:
1692 gtk_text_set_input_hints (self, hints: g_value_get_flags (value));
1693 break;
1694
1695 case PROP_ATTRIBUTES:
1696 gtk_text_set_attributes (self, attrs: g_value_get_boxed (value));
1697 break;
1698
1699 case PROP_TABS:
1700 gtk_text_set_tabs (self, tabs: g_value_get_boxed (value));
1701 break;
1702
1703 case PROP_ENABLE_EMOJI_COMPLETION:
1704 gtk_text_set_enable_emoji_completion (self, enable_emoji_completion: g_value_get_boolean (value));
1705 break;
1706
1707 case PROP_PROPAGATE_TEXT_WIDTH:
1708 gtk_text_set_propagate_text_width (self, propagate_text_width: g_value_get_boolean (value));
1709 break;
1710
1711 case PROP_EXTRA_MENU:
1712 gtk_text_set_extra_menu (self, model: g_value_get_object (value));
1713 break;
1714
1715 default:
1716 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1717 break;
1718 }
1719}
1720
1721static void
1722gtk_text_get_property (GObject *object,
1723 guint prop_id,
1724 GValue *value,
1725 GParamSpec *pspec)
1726{
1727 GtkText *self = GTK_TEXT (object);
1728 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
1729
1730 switch (prop_id)
1731 {
1732 /* GtkEditable properties */
1733 case NUM_PROPERTIES + GTK_EDITABLE_PROP_CURSOR_POSITION:
1734 g_value_set_int (value, v_int: priv->current_pos);
1735 break;
1736
1737 case NUM_PROPERTIES + GTK_EDITABLE_PROP_SELECTION_BOUND:
1738 g_value_set_int (value, v_int: priv->selection_bound);
1739 break;
1740
1741 case NUM_PROPERTIES + GTK_EDITABLE_PROP_EDITABLE:
1742 g_value_set_boolean (value, v_boolean: priv->editable);
1743 break;
1744
1745 case NUM_PROPERTIES + GTK_EDITABLE_PROP_WIDTH_CHARS:
1746 g_value_set_int (value, v_int: priv->width_chars);
1747 break;
1748
1749 case NUM_PROPERTIES + GTK_EDITABLE_PROP_MAX_WIDTH_CHARS:
1750 g_value_set_int (value, v_int: priv->max_width_chars);
1751 break;
1752
1753 case NUM_PROPERTIES + GTK_EDITABLE_PROP_TEXT:
1754 g_value_set_string (value, v_string: gtk_entry_buffer_get_text (buffer: get_buffer (self)));
1755 break;
1756
1757 case NUM_PROPERTIES + GTK_EDITABLE_PROP_XALIGN:
1758 g_value_set_float (value, v_float: priv->xalign);
1759 break;
1760
1761 case NUM_PROPERTIES + GTK_EDITABLE_PROP_ENABLE_UNDO:
1762 g_value_set_boolean (value, v_boolean: gtk_text_history_get_enabled (self: priv->history));
1763 break;
1764
1765 /* GtkText properties */
1766 case PROP_BUFFER:
1767 g_value_set_object (value, v_object: get_buffer (self));
1768 break;
1769
1770 case PROP_MAX_LENGTH:
1771 g_value_set_int (value, v_int: gtk_entry_buffer_get_max_length (buffer: get_buffer (self)));
1772 break;
1773
1774 case PROP_VISIBILITY:
1775 g_value_set_boolean (value, v_boolean: priv->visible);
1776 break;
1777
1778 case PROP_INVISIBLE_CHAR:
1779 g_value_set_uint (value, v_uint: priv->invisible_char);
1780 break;
1781
1782 case PROP_ACTIVATES_DEFAULT:
1783 g_value_set_boolean (value, v_boolean: priv->activates_default);
1784 break;
1785
1786 case PROP_SCROLL_OFFSET:
1787 g_value_set_int (value, v_int: priv->scroll_offset);
1788 break;
1789
1790 case PROP_TRUNCATE_MULTILINE:
1791 g_value_set_boolean (value, v_boolean: priv->truncate_multiline);
1792 break;
1793
1794 case PROP_OVERWRITE_MODE:
1795 g_value_set_boolean (value, v_boolean: priv->overwrite_mode);
1796 break;
1797
1798 case PROP_INVISIBLE_CHAR_SET:
1799 g_value_set_boolean (value, v_boolean: priv->invisible_char_set);
1800 break;
1801
1802 case PROP_IM_MODULE:
1803 g_value_set_string (value, v_string: priv->im_module);
1804 break;
1805
1806 case PROP_PLACEHOLDER_TEXT:
1807 g_value_set_string (value, v_string: gtk_text_get_placeholder_text (self));
1808 break;
1809
1810 case PROP_INPUT_PURPOSE:
1811 g_value_set_enum (value, v_enum: gtk_text_get_input_purpose (self));
1812 break;
1813
1814 case PROP_INPUT_HINTS:
1815 g_value_set_flags (value, v_flags: gtk_text_get_input_hints (self));
1816 break;
1817
1818 case PROP_ATTRIBUTES:
1819 g_value_set_boxed (value, v_boxed: priv->attrs);
1820 break;
1821
1822 case PROP_TABS:
1823 g_value_set_boxed (value, v_boxed: priv->tabs);
1824 break;
1825
1826 case PROP_ENABLE_EMOJI_COMPLETION:
1827 g_value_set_boolean (value, v_boolean: priv->enable_emoji_completion);
1828 break;
1829
1830 case PROP_PROPAGATE_TEXT_WIDTH:
1831 g_value_set_boolean (value, v_boolean: priv->propagate_text_width);
1832 break;
1833
1834 case PROP_EXTRA_MENU:
1835 g_value_set_object (value, v_object: priv->extra_menu);
1836 break;
1837
1838 default:
1839 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1840 break;
1841 }
1842}
1843
1844static void
1845gtk_text_ensure_text_handles (GtkText *self)
1846{
1847 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
1848 int i;
1849
1850 for (i = 0; i < TEXT_HANDLE_N_HANDLES; i++)
1851 {
1852 if (priv->text_handles[i])
1853 continue;
1854 priv->text_handles[i] = gtk_text_handle_new (GTK_WIDGET (self));
1855 g_signal_connect (priv->text_handles[i], "drag-started",
1856 G_CALLBACK (gtk_text_handle_drag_started), self);
1857 g_signal_connect (priv->text_handles[i], "handle-dragged",
1858 G_CALLBACK (gtk_text_handle_dragged), self);
1859 g_signal_connect (priv->text_handles[i], "drag-finished",
1860 G_CALLBACK (gtk_text_handle_drag_finished), self);
1861 }
1862}
1863
1864static void
1865gtk_text_init (GtkText *self)
1866{
1867 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
1868 GtkCssNode *widget_node;
1869 GtkGesture *gesture;
1870 GtkEventController *controller;
1871 int i;
1872 GtkDropTarget *target;
1873
1874 gtk_widget_set_focusable (GTK_WIDGET (self), TRUE);
1875 gtk_widget_set_overflow (GTK_WIDGET (self), overflow: GTK_OVERFLOW_HIDDEN);
1876
1877 priv->editable = TRUE;
1878 priv->visible = TRUE;
1879 priv->dnd_position = -1;
1880 priv->width_chars = -1;
1881 priv->max_width_chars = -1;
1882 priv->truncate_multiline = FALSE;
1883 priv->xalign = 0.0;
1884 priv->insert_pos = -1;
1885 priv->cursor_alpha = 1.0;
1886 priv->invisible_char = 0;
1887 priv->history = gtk_text_history_new (funcs: &history_funcs, funcs_data: self);
1888
1889 gtk_text_history_set_max_undo_levels (self: priv->history, DEFAULT_MAX_UNDO);
1890
1891 priv->selection_content = g_object_new (GTK_TYPE_TEXT_CONTENT, NULL);
1892 GTK_TEXT_CONTENT (priv->selection_content)->self = self;
1893
1894 target = gtk_drop_target_new (G_TYPE_STRING, actions: GDK_ACTION_COPY | GDK_ACTION_MOVE);
1895 gtk_event_controller_set_name (GTK_EVENT_CONTROLLER (target), name: "gtk-text-drop-target");
1896 g_signal_connect (target, "accept", G_CALLBACK (gtk_text_drag_accept), self);
1897 g_signal_connect (target, "enter", G_CALLBACK (gtk_text_drag_motion), self);
1898 g_signal_connect (target, "motion", G_CALLBACK (gtk_text_drag_motion), self);
1899 g_signal_connect (target, "leave", G_CALLBACK (gtk_text_drag_leave), self);
1900 g_signal_connect (target, "drop", G_CALLBACK (gtk_text_drag_drop), self);
1901 gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (target));
1902
1903 /* This object is completely private. No external entity can gain a reference
1904 * to it; so we create it here and destroy it in finalize().
1905 */
1906 priv->im_context = gtk_im_multicontext_new ();
1907
1908 g_signal_connect (priv->im_context, "preedit-start",
1909 G_CALLBACK (gtk_text_preedit_start_cb), self);
1910 g_signal_connect (priv->im_context, "commit",
1911 G_CALLBACK (gtk_text_commit_cb), self);
1912 g_signal_connect (priv->im_context, "preedit-changed",
1913 G_CALLBACK (gtk_text_preedit_changed_cb), self);
1914 g_signal_connect (priv->im_context, "retrieve-surrounding",
1915 G_CALLBACK (gtk_text_retrieve_surrounding_cb), self);
1916 g_signal_connect (priv->im_context, "delete-surrounding",
1917 G_CALLBACK (gtk_text_delete_surrounding_cb), self);
1918
1919 priv->drag_gesture = gtk_gesture_drag_new ();
1920 gtk_event_controller_set_name (GTK_EVENT_CONTROLLER (priv->drag_gesture), name: "gtk-text-drag-gesture");
1921 g_signal_connect (priv->drag_gesture, "drag-update",
1922 G_CALLBACK (gtk_text_drag_gesture_update), self);
1923 g_signal_connect (priv->drag_gesture, "drag-end",
1924 G_CALLBACK (gtk_text_drag_gesture_end), self);
1925 gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (priv->drag_gesture), button: 0);
1926 gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (priv->drag_gesture), TRUE);
1927 gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (priv->drag_gesture));
1928
1929 gesture = gtk_gesture_click_new ();
1930 gtk_event_controller_set_name (GTK_EVENT_CONTROLLER (gesture), name: "gtk-text-click-gesture");
1931 g_signal_connect (gesture, "pressed",
1932 G_CALLBACK (gtk_text_click_gesture_pressed), self);
1933 gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), button: 0);
1934 gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (gesture), TRUE);
1935 gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture));
1936
1937 controller = gtk_event_controller_motion_new ();
1938 gtk_event_controller_set_name (controller, name: "gtk-text-motion-controller");
1939 g_signal_connect (controller, "motion",
1940 G_CALLBACK (gtk_text_motion_controller_motion), self);
1941 gtk_widget_add_controller (GTK_WIDGET (self), controller);
1942
1943 priv->key_controller = gtk_event_controller_key_new ();
1944 gtk_event_controller_set_propagation_phase (controller: priv->key_controller, phase: GTK_PHASE_TARGET);
1945 gtk_event_controller_set_name (controller: priv->key_controller, name: "gtk-text-key-controller");
1946 g_signal_connect (priv->key_controller, "key-pressed",
1947 G_CALLBACK (gtk_text_key_controller_key_pressed), self);
1948 g_signal_connect_swapped (priv->key_controller, "im-update",
1949 G_CALLBACK (gtk_text_schedule_im_reset), self);
1950 gtk_event_controller_key_set_im_context (GTK_EVENT_CONTROLLER_KEY (priv->key_controller),
1951 im_context: priv->im_context);
1952 gtk_widget_add_controller (GTK_WIDGET (self), controller: priv->key_controller);
1953
1954 priv->focus_controller = gtk_event_controller_focus_new ();
1955 gtk_event_controller_set_name (controller: priv->focus_controller, name: "gtk-text-focus-controller");
1956 g_signal_connect (priv->focus_controller, "notify::is-focus",
1957 G_CALLBACK (gtk_text_focus_changed), self);
1958 gtk_widget_add_controller (GTK_WIDGET (self), controller: priv->focus_controller);
1959
1960 widget_node = gtk_widget_get_css_node (GTK_WIDGET (self));
1961 for (i = 0; i < 2; i++)
1962 {
1963 priv->undershoot_node[i] = gtk_css_node_new ();
1964 gtk_css_node_set_name (cssnode: priv->undershoot_node[i], name: g_quark_from_static_string (string: "undershoot"));
1965 gtk_css_node_add_class (cssnode: priv->undershoot_node[i], style_class: g_quark_from_static_string (string: i == 0 ? "left" : "right"));
1966 gtk_css_node_set_parent (cssnode: priv->undershoot_node[i], parent: widget_node);
1967 gtk_css_node_set_state (cssnode: priv->undershoot_node[i], state_flags: gtk_css_node_get_state (cssnode: widget_node) & ~GTK_STATE_FLAG_DROP_ACTIVE);
1968 g_object_unref (object: priv->undershoot_node[i]);
1969 }
1970
1971 set_text_cursor (GTK_WIDGET (self));
1972}
1973
1974static void
1975gtk_text_dispose (GObject *object)
1976{
1977 GtkText *self = GTK_TEXT (object);
1978 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
1979 GdkSeat *seat;
1980 GdkDevice *keyboard = NULL;
1981 GtkWidget *chooser;
1982
1983 priv->current_pos = priv->selection_bound = 0;
1984 gtk_text_reset_im_context (entry: self);
1985 gtk_text_reset_layout (self);
1986
1987 if (priv->blink_tick)
1988 {
1989 gtk_widget_remove_tick_callback (GTK_WIDGET (object), id: priv->blink_tick);
1990 priv->blink_tick = 0;
1991 }
1992
1993 if (priv->magnifier)
1994 _gtk_magnifier_set_inspected (GTK_MAGNIFIER (priv->magnifier), NULL);
1995
1996 if (priv->buffer)
1997 {
1998 buffer_disconnect_signals (self);
1999 g_object_unref (object: priv->buffer);
2000 priv->buffer = NULL;
2001 }
2002
2003 g_clear_pointer (&priv->emoji_completion, gtk_widget_unparent);
2004 chooser = g_object_get_data (object, key: "gtk-emoji-chooser");
2005 if (chooser)
2006 gtk_widget_unparent (widget: chooser);
2007
2008 seat = gdk_display_get_default_seat (display: gtk_widget_get_display (GTK_WIDGET (object)));
2009 if (seat)
2010 keyboard = gdk_seat_get_keyboard (seat);
2011 if (keyboard)
2012 g_signal_handlers_disconnect_by_func (keyboard, direction_changed, self);
2013
2014 g_clear_pointer (&priv->selection_bubble, gtk_widget_unparent);
2015 g_clear_pointer (&priv->popup_menu, gtk_widget_unparent);
2016 g_clear_pointer ((GtkWidget **) &priv->text_handles[TEXT_HANDLE_CURSOR], gtk_widget_unparent);
2017 g_clear_pointer ((GtkWidget **) &priv->text_handles[TEXT_HANDLE_SELECTION_BOUND], gtk_widget_unparent);
2018 g_clear_object (&priv->extra_menu);
2019
2020 g_clear_pointer (&priv->magnifier_popover, gtk_widget_unparent);
2021 g_clear_pointer (&priv->placeholder, gtk_widget_unparent);
2022
2023 G_OBJECT_CLASS (gtk_text_parent_class)->dispose (object);
2024}
2025
2026static void
2027gtk_text_finalize (GObject *object)
2028{
2029 GtkText *self = GTK_TEXT (object);
2030 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
2031
2032 g_clear_object (&priv->selection_content);
2033
2034 g_clear_object (&priv->history);
2035 g_clear_object (&priv->cached_layout);
2036 g_clear_object (&priv->im_context);
2037 g_free (mem: priv->im_module);
2038
2039 if (priv->tabs)
2040 pango_tab_array_free (tab_array: priv->tabs);
2041
2042 if (priv->attrs)
2043 pango_attr_list_unref (list: priv->attrs);
2044
2045
2046 G_OBJECT_CLASS (gtk_text_parent_class)->finalize (object);
2047}
2048
2049static void
2050gtk_text_ensure_magnifier (GtkText *self)
2051{
2052 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
2053
2054 if (priv->magnifier_popover)
2055 return;
2056
2057 priv->magnifier = _gtk_magnifier_new (GTK_WIDGET (self));
2058 gtk_widget_set_size_request (widget: priv->magnifier, width: 100, height: 60);
2059 _gtk_magnifier_set_magnification (GTK_MAGNIFIER (priv->magnifier), magnification: 2.0);
2060 priv->magnifier_popover = gtk_popover_new ();
2061 gtk_popover_set_position (GTK_POPOVER (priv->magnifier_popover), position: GTK_POS_TOP);
2062 gtk_widget_set_parent (widget: priv->magnifier_popover, GTK_WIDGET (self));
2063 gtk_widget_add_css_class (widget: priv->magnifier_popover, css_class: "magnifier");
2064 gtk_popover_set_autohide (GTK_POPOVER (priv->magnifier_popover), FALSE);
2065 gtk_popover_set_child (GTK_POPOVER (priv->magnifier_popover), child: priv->magnifier);
2066 gtk_widget_show (widget: priv->magnifier);
2067}
2068
2069static void
2070begin_change (GtkText *self)
2071{
2072 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
2073
2074 priv->change_count++;
2075
2076 g_object_freeze_notify (G_OBJECT (self));
2077}
2078
2079static void
2080end_change (GtkText *self)
2081{
2082 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
2083
2084 g_return_if_fail (priv->change_count > 0);
2085
2086 g_object_thaw_notify (G_OBJECT (self));
2087
2088 priv->change_count--;
2089
2090 if (priv->change_count == 0)
2091 {
2092 if (priv->real_changed)
2093 {
2094 g_signal_emit_by_name (instance: self, detailed_signal: "changed");
2095 priv->real_changed = FALSE;
2096 }
2097 }
2098}
2099
2100static void
2101emit_changed (GtkText *self)
2102{
2103 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
2104
2105 if (priv->change_count == 0)
2106 g_signal_emit_by_name (instance: self, detailed_signal: "changed");
2107 else
2108 priv->real_changed = TRUE;
2109}
2110
2111static DisplayMode
2112gtk_text_get_display_mode (GtkText *self)
2113{
2114 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
2115
2116 if (priv->visible)
2117 return DISPLAY_NORMAL;
2118
2119 if (priv->invisible_char == 0 && priv->invisible_char_set)
2120 return DISPLAY_BLANK;
2121
2122 return DISPLAY_INVISIBLE;
2123}
2124
2125char *
2126gtk_text_get_display_text (GtkText *self,
2127 int start_pos,
2128 int end_pos)
2129{
2130 GtkTextPasswordHint *password_hint;
2131 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
2132 gunichar invisible_char;
2133 const char *start;
2134 const char *end;
2135 const char *text;
2136 char char_str[7];
2137 int char_len;
2138 GString *str;
2139 guint length;
2140 int i;
2141
2142 text = gtk_entry_buffer_get_text (buffer: get_buffer (self));
2143 length = gtk_entry_buffer_get_length (buffer: get_buffer (self));
2144
2145 if (end_pos < 0 || end_pos > length)
2146 end_pos = length;
2147 if (start_pos > length)
2148 start_pos = length;
2149
2150 if (end_pos <= start_pos)
2151 return g_strdup (str: "");
2152 else if (priv->visible)
2153 {
2154 start = g_utf8_offset_to_pointer (str: text, offset: start_pos);
2155 end = g_utf8_offset_to_pointer (str: start, offset: end_pos - start_pos);
2156 return g_strndup (str: start, n: end - start);
2157 }
2158 else
2159 {
2160 str = g_string_sized_new (dfl_size: length * 2);
2161
2162 /* Figure out what our invisible char is and encode it */
2163 if (!priv->invisible_char)
2164 invisible_char = priv->invisible_char_set ? ' ' : '*';
2165 else
2166 invisible_char = priv->invisible_char;
2167 char_len = g_unichar_to_utf8 (c: invisible_char, outbuf: char_str);
2168
2169 /*
2170 * Add hidden characters for each character in the text
2171 * buffer. If there is a password hint, then keep that
2172 * character visible.
2173 */
2174
2175 password_hint = g_object_get_qdata (G_OBJECT (self), quark: quark_password_hint);
2176 for (i = start_pos; i < end_pos; ++i)
2177 {
2178 if (password_hint && i == password_hint->position)
2179 {
2180 start = g_utf8_offset_to_pointer (str: text, offset: i);
2181 g_string_append_len (string: str, val: start, g_utf8_next_char (start) - start);
2182 }
2183 else
2184 {
2185 g_string_append_len (string: str, val: char_str, len: char_len);
2186 }
2187 }
2188
2189 return g_string_free (string: str, FALSE);
2190 }
2191}
2192
2193static void
2194gtk_text_map (GtkWidget *widget)
2195{
2196 GtkText *self = GTK_TEXT (widget);
2197
2198 GTK_WIDGET_CLASS (gtk_text_parent_class)->map (widget);
2199
2200 gtk_text_recompute (self);
2201}
2202
2203static void
2204gtk_text_unmap (GtkWidget *widget)
2205{
2206 GtkText *self = GTK_TEXT (widget);
2207 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
2208
2209 priv->text_handles_enabled = FALSE;
2210 gtk_text_update_handles (self);
2211 priv->cursor_alpha = 1.0;
2212
2213 GTK_WIDGET_CLASS (gtk_text_parent_class)->unmap (widget);
2214}
2215
2216static void
2217gtk_text_im_set_focus_in (GtkText *self)
2218{
2219 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
2220
2221 if (!priv->editable)
2222 return;
2223
2224 gtk_text_schedule_im_reset (self);
2225 gtk_im_context_focus_in (context: priv->im_context);
2226}
2227
2228static void
2229gtk_text_realize (GtkWidget *widget)
2230{
2231 GtkText *self = GTK_TEXT (widget);
2232 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
2233
2234 GTK_WIDGET_CLASS (gtk_text_parent_class)->realize (widget);
2235
2236 gtk_im_context_set_client_widget (context: priv->im_context, widget);
2237 if (gtk_widget_is_focus (GTK_WIDGET (self)))
2238 gtk_text_im_set_focus_in (self);
2239
2240 gtk_text_adjust_scroll (self);
2241 gtk_text_update_primary_selection (self);
2242}
2243
2244static void
2245gtk_text_unrealize (GtkWidget *widget)
2246{
2247 GtkText *self = GTK_TEXT (widget);
2248 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
2249 GdkClipboard *clipboard;
2250
2251 gtk_text_reset_layout (self);
2252
2253 gtk_im_context_set_client_widget (context: priv->im_context, NULL);
2254
2255 clipboard = gtk_widget_get_primary_clipboard (widget);
2256 if (gdk_clipboard_get_content (clipboard) == priv->selection_content)
2257 gdk_clipboard_set_content (clipboard, NULL);
2258
2259 GTK_WIDGET_CLASS (gtk_text_parent_class)->unrealize (widget);
2260}
2261
2262static void
2263update_im_cursor_location (GtkText *self)
2264{
2265 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
2266 const int text_width = gtk_widget_get_width (GTK_WIDGET (self));
2267 GdkRectangle area;
2268 int strong_x;
2269 int strong_xoffset;
2270
2271 gtk_text_get_cursor_locations (self, strong_x: &strong_x, NULL);
2272
2273 strong_xoffset = strong_x - priv->scroll_offset;
2274 if (strong_xoffset < 0)
2275 strong_xoffset = 0;
2276 else if (strong_xoffset > text_width)
2277 strong_xoffset = text_width;
2278
2279 area.x = strong_xoffset;
2280 area.y = 0;
2281 area.width = 0;
2282 area.height = gtk_widget_get_height (GTK_WIDGET (self));
2283
2284 gtk_im_context_set_cursor_location (context: priv->im_context, area: &area);
2285}
2286
2287static void
2288gtk_text_move_handle (GtkText *self,
2289 GtkTextHandle *handle,
2290 int x,
2291 int y,
2292 int height)
2293{
2294 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
2295
2296 if (!gtk_text_handle_get_is_dragged (handle) &&
2297 (x < 0 || x > gtk_widget_get_width (GTK_WIDGET (self))))
2298 {
2299 /* Hide the handle if it's not being manipulated
2300 * and fell outside of the visible text area.
2301 */
2302 gtk_widget_hide (GTK_WIDGET (handle));
2303 }
2304 else
2305 {
2306 GdkRectangle rect;
2307
2308 rect.x = x;
2309 rect.y = y;
2310 rect.width = 1;
2311 rect.height = height;
2312
2313 gtk_text_handle_set_position (handle, rect: &rect);
2314 gtk_widget_set_direction (GTK_WIDGET (handle), dir: priv->resolved_dir);
2315 gtk_widget_show (GTK_WIDGET (handle));
2316 }
2317}
2318
2319static int
2320gtk_text_get_selection_bound_location (GtkText *self)
2321{
2322 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
2323 PangoLayout *layout;
2324 PangoRectangle pos;
2325 int x;
2326 const char *text;
2327 int index;
2328
2329 layout = gtk_text_ensure_layout (self, FALSE);
2330 text = pango_layout_get_text (layout);
2331 index = g_utf8_offset_to_pointer (str: text, offset: priv->selection_bound) - text;
2332 pango_layout_index_to_pos (layout, index_: index, pos: &pos);
2333
2334 if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
2335 x = (pos.x + pos.width) / PANGO_SCALE;
2336 else
2337 x = pos.x / PANGO_SCALE;
2338
2339 return x;
2340}
2341
2342static void
2343gtk_text_update_handles (GtkText *self)
2344{
2345 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
2346 const int text_height = gtk_widget_get_height (GTK_WIDGET (self));
2347 int strong_x;
2348 int cursor, bound;
2349
2350 if (!priv->text_handles_enabled)
2351 {
2352 if (priv->text_handles[TEXT_HANDLE_CURSOR])
2353 gtk_widget_hide (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_CURSOR]));
2354 if (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])
2355 gtk_widget_hide (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]));
2356 }
2357 else
2358 {
2359 gtk_text_ensure_text_handles (self);
2360 gtk_text_get_cursor_locations (self, strong_x: &strong_x, NULL);
2361 cursor = strong_x - priv->scroll_offset;
2362
2363 if (priv->selection_bound != priv->current_pos)
2364 {
2365 int start, end;
2366
2367 bound = gtk_text_get_selection_bound_location (self) - priv->scroll_offset;
2368
2369 if (priv->selection_bound > priv->current_pos)
2370 {
2371 start = cursor;
2372 end = bound;
2373 }
2374 else
2375 {
2376 start = bound;
2377 end = cursor;
2378 }
2379
2380 /* Update start selection bound */
2381 gtk_text_handle_set_role (handle: priv->text_handles[TEXT_HANDLE_SELECTION_BOUND],
2382 role: GTK_TEXT_HANDLE_ROLE_SELECTION_END);
2383 gtk_text_move_handle (self,
2384 handle: priv->text_handles[TEXT_HANDLE_SELECTION_BOUND],
2385 x: end, y: 0, height: text_height);
2386 gtk_text_handle_set_role (handle: priv->text_handles[TEXT_HANDLE_CURSOR],
2387 role: GTK_TEXT_HANDLE_ROLE_SELECTION_START);
2388 gtk_text_move_handle (self,
2389 handle: priv->text_handles[TEXT_HANDLE_CURSOR],
2390 x: start, y: 0, height: text_height);
2391 }
2392 else
2393 {
2394 gtk_widget_hide (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]));
2395 gtk_text_handle_set_role (handle: priv->text_handles[TEXT_HANDLE_CURSOR],
2396 role: GTK_TEXT_HANDLE_ROLE_CURSOR);
2397 gtk_text_move_handle (self,
2398 handle: priv->text_handles[TEXT_HANDLE_CURSOR],
2399 x: cursor, y: 0, height: text_height);
2400 }
2401 }
2402}
2403
2404static void
2405gtk_text_measure (GtkWidget *widget,
2406 GtkOrientation orientation,
2407 int for_size,
2408 int *minimum,
2409 int *natural,
2410 int *minimum_baseline,
2411 int *natural_baseline)
2412{
2413 GtkText *self = GTK_TEXT (widget);
2414 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
2415 PangoContext *context;
2416 PangoFontMetrics *metrics;
2417
2418 context = gtk_widget_get_pango_context (widget);
2419 metrics = pango_context_get_metrics (context, NULL, NULL);
2420
2421 if (orientation == GTK_ORIENTATION_HORIZONTAL)
2422 {
2423 int min, nat;
2424 int char_width;
2425 int digit_width;
2426 int char_pixels;
2427
2428 char_width = pango_font_metrics_get_approximate_char_width (metrics);
2429 digit_width = pango_font_metrics_get_approximate_digit_width (metrics);
2430 char_pixels = (MAX (char_width, digit_width) + PANGO_SCALE - 1) / PANGO_SCALE;
2431
2432 if (priv->width_chars >= 0)
2433 min = char_pixels * priv->width_chars;
2434 else
2435 min = 0;
2436
2437 if (priv->max_width_chars < 0)
2438 nat = NAT_ENTRY_WIDTH;
2439 else
2440 nat = char_pixels * priv->max_width_chars;
2441
2442 if (priv->propagate_text_width)
2443 {
2444 PangoLayout *layout;
2445 int act;
2446
2447 layout = gtk_text_ensure_layout (self, TRUE);
2448 pango_layout_get_pixel_size (layout, width: &act, NULL);
2449
2450 nat = MIN (act, nat);
2451 }
2452
2453 nat = MAX (min, nat);
2454
2455 if (priv->placeholder)
2456 {
2457 int pmin, pnat;
2458
2459 gtk_widget_measure (widget: priv->placeholder, orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1,
2460 minimum: &pmin, natural: &pnat, NULL, NULL);
2461 min = MAX (min, pmin);
2462 nat = MAX (nat, pnat);
2463 }
2464
2465 *minimum = min;
2466 *natural = nat;
2467 }
2468 else
2469 {
2470 int height, baseline;
2471 PangoLayout *layout;
2472
2473 layout = gtk_text_ensure_layout (self, TRUE);
2474
2475 priv->ascent = pango_font_metrics_get_ascent (metrics);
2476 priv->descent = pango_font_metrics_get_descent (metrics);
2477
2478 pango_layout_get_pixel_size (layout, NULL, height: &height);
2479
2480 height = MAX (height, PANGO_PIXELS (priv->ascent + priv->descent));
2481
2482 baseline = pango_layout_get_baseline (layout) / PANGO_SCALE;
2483
2484 *minimum = *natural = height;
2485
2486 if (priv->placeholder)
2487 {
2488 int min, nat;
2489
2490 gtk_widget_measure (widget: priv->placeholder, orientation: GTK_ORIENTATION_VERTICAL, for_size: -1,
2491 minimum: &min, natural: &nat, NULL, NULL);
2492 *minimum = MAX (*minimum, min);
2493 *natural = MAX (*natural, nat);
2494 }
2495
2496 if (minimum_baseline)
2497 *minimum_baseline = baseline;
2498 if (natural_baseline)
2499 *natural_baseline = baseline;
2500 }
2501
2502 pango_font_metrics_unref (metrics);
2503}
2504
2505static void
2506gtk_text_size_allocate (GtkWidget *widget,
2507 int width,
2508 int height,
2509 int baseline)
2510{
2511 GtkText *self = GTK_TEXT (widget);
2512 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
2513 GtkEmojiChooser *chooser;
2514
2515 priv->text_baseline = baseline;
2516
2517 if (priv->placeholder)
2518 {
2519 gtk_widget_size_allocate (widget: priv->placeholder,
2520 allocation: &(GtkAllocation) { 0, 0, width, height },
2521 baseline: -1);
2522 }
2523
2524 gtk_text_adjust_scroll (self);
2525 gtk_text_check_cursor_blink (self);
2526 update_im_cursor_location (self);
2527
2528 chooser = g_object_get_data (G_OBJECT (self), key: "gtk-emoji-chooser");
2529 if (chooser)
2530 gtk_popover_present (GTK_POPOVER (chooser));
2531
2532 gtk_text_update_handles (self);
2533
2534 if (priv->emoji_completion)
2535 gtk_popover_present (GTK_POPOVER (priv->emoji_completion));
2536
2537 if (priv->magnifier_popover)
2538 gtk_popover_present (GTK_POPOVER (priv->magnifier_popover));
2539
2540 if (priv->popup_menu)
2541 gtk_popover_present (GTK_POPOVER (priv->popup_menu));
2542
2543 if (priv->selection_bubble)
2544 gtk_popover_present (GTK_POPOVER (priv->selection_bubble));
2545
2546 if (priv->text_handles[TEXT_HANDLE_CURSOR])
2547 gtk_text_handle_present (handle: priv->text_handles[TEXT_HANDLE_CURSOR]);
2548
2549 if (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])
2550 gtk_text_handle_present (handle: priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]);
2551}
2552
2553static void
2554gtk_text_draw_undershoot (GtkText *self,
2555 GtkSnapshot *snapshot)
2556{
2557 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
2558 const int text_width = gtk_widget_get_width (GTK_WIDGET (self));
2559 const int text_height = gtk_widget_get_height (GTK_WIDGET (self));
2560 GtkStyleContext *context;
2561 int min_offset, max_offset;
2562
2563 context = gtk_widget_get_style_context (GTK_WIDGET (self));
2564
2565 gtk_text_get_scroll_limits (self, min_offset: &min_offset, max_offset: &max_offset);
2566
2567 if (priv->scroll_offset > min_offset)
2568 {
2569 gtk_style_context_save_to_node (context, node: priv->undershoot_node[0]);
2570 gtk_snapshot_render_background (snapshot, context, x: 0, y: 0, UNDERSHOOT_SIZE, height: text_height);
2571 gtk_snapshot_render_frame (snapshot, context, x: 0, y: 0, UNDERSHOOT_SIZE, height: text_height);
2572 gtk_style_context_restore (context);
2573 }
2574
2575 if (priv->scroll_offset < max_offset)
2576 {
2577 gtk_style_context_save_to_node (context, node: priv->undershoot_node[1]);
2578 gtk_snapshot_render_background (snapshot, context, x: text_width - UNDERSHOOT_SIZE, y: 0, UNDERSHOOT_SIZE, height: text_height);
2579 gtk_snapshot_render_frame (snapshot, context, x: text_width - UNDERSHOOT_SIZE, y: 0, UNDERSHOOT_SIZE, height: text_height);
2580 gtk_style_context_restore (context);
2581 }
2582}
2583
2584static void
2585gtk_text_snapshot (GtkWidget *widget,
2586 GtkSnapshot *snapshot)
2587{
2588 GtkText *self = GTK_TEXT (widget);
2589 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
2590
2591 /* Draw text and cursor */
2592 if (priv->dnd_position != -1)
2593 gtk_text_draw_cursor (self, snapshot, type: CURSOR_DND);
2594
2595 if (priv->placeholder)
2596 gtk_widget_snapshot_child (widget, child: priv->placeholder, snapshot);
2597
2598 gtk_text_draw_text (self, snapshot);
2599
2600 /* When no text is being displayed at all, don't show the cursor */
2601 if (gtk_text_get_display_mode (self) != DISPLAY_BLANK &&
2602 gtk_widget_has_focus (widget) &&
2603 priv->selection_bound == priv->current_pos)
2604 {
2605 gtk_snapshot_push_opacity (snapshot, opacity: priv->cursor_alpha);
2606 gtk_text_draw_cursor (self, snapshot, type: CURSOR_STANDARD);
2607 gtk_snapshot_pop (snapshot);
2608 }
2609
2610 gtk_text_draw_undershoot (self, snapshot);
2611}
2612
2613static void
2614gtk_text_get_pixel_ranges (GtkText *self,
2615 int **ranges,
2616 int *n_ranges)
2617{
2618 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
2619
2620 if (priv->selection_bound != priv->current_pos)
2621 {
2622 PangoLayout *layout = gtk_text_ensure_layout (self, TRUE);
2623 PangoLayoutLine *line = pango_layout_get_lines_readonly (layout)->data;
2624 const char *text = pango_layout_get_text (layout);
2625 int start_index = g_utf8_offset_to_pointer (str: text, offset: priv->selection_bound) - text;
2626 int end_index = g_utf8_offset_to_pointer (str: text, offset: priv->current_pos) - text;
2627 int real_n_ranges, i;
2628
2629 pango_layout_line_get_x_ranges (line,
2630 MIN (start_index, end_index),
2631 MAX (start_index, end_index),
2632 ranges,
2633 n_ranges: &real_n_ranges);
2634
2635 if (ranges)
2636 {
2637 int *r = *ranges;
2638
2639 for (i = 0; i < real_n_ranges; ++i)
2640 {
2641 r[2 * i + 1] = (r[2 * i + 1] - r[2 * i]) / PANGO_SCALE;
2642 r[2 * i] = r[2 * i] / PANGO_SCALE;
2643 }
2644 }
2645
2646 if (n_ranges)
2647 *n_ranges = real_n_ranges;
2648 }
2649 else
2650 {
2651 if (n_ranges)
2652 *n_ranges = 0;
2653 if (ranges)
2654 *ranges = NULL;
2655 }
2656}
2657
2658static gboolean
2659in_selection (GtkText *self,
2660 int x)
2661{
2662 int *ranges;
2663 int n_ranges, i;
2664 int retval = FALSE;
2665
2666 gtk_text_get_pixel_ranges (self, ranges: &ranges, n_ranges: &n_ranges);
2667
2668 for (i = 0; i < n_ranges; ++i)
2669 {
2670 if (x >= ranges[2 * i] && x < ranges[2 * i] + ranges[2 * i + 1])
2671 {
2672 retval = TRUE;
2673 break;
2674 }
2675 }
2676
2677 g_free (mem: ranges);
2678 return retval;
2679}
2680
2681static int
2682gesture_get_current_point_in_layout (GtkGestureSingle *gesture,
2683 GtkText *self)
2684{
2685 int tx;
2686 GdkEventSequence *sequence;
2687 double px;
2688
2689 sequence = gtk_gesture_single_get_current_sequence (gesture);
2690 gtk_gesture_get_point (GTK_GESTURE (gesture), sequence, x: &px, NULL);
2691 gtk_text_get_layout_offsets (entry: self, x: &tx, NULL);
2692
2693 return px - tx;
2694}
2695
2696static void
2697gtk_text_do_popup (GtkText *self,
2698 double x,
2699 double y)
2700{
2701 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
2702
2703 gtk_text_update_clipboard_actions (self);
2704 gtk_text_update_emoji_action (self);
2705
2706 if (!priv->popup_menu)
2707 {
2708 GMenuModel *model;
2709
2710 model = gtk_text_get_menu_model (self);
2711 priv->popup_menu = gtk_popover_menu_new_from_model (model);
2712 gtk_widget_set_parent (widget: priv->popup_menu, GTK_WIDGET (self));
2713 gtk_popover_set_position (GTK_POPOVER (priv->popup_menu), position: GTK_POS_BOTTOM);
2714
2715 gtk_popover_set_has_arrow (GTK_POPOVER (priv->popup_menu), FALSE);
2716 gtk_widget_set_halign (widget: priv->popup_menu, align: GTK_ALIGN_START);
2717
2718 g_object_unref (object: model);
2719 }
2720
2721 if (x != -1 && y != -1)
2722 {
2723 GdkRectangle rect = { x, y, 1, 1 };
2724 gtk_popover_set_pointing_to (GTK_POPOVER (priv->popup_menu), rect: &rect);
2725 }
2726 else
2727 gtk_popover_set_pointing_to (GTK_POPOVER (priv->popup_menu), NULL);
2728
2729 gtk_popover_popup (GTK_POPOVER (priv->popup_menu));
2730}
2731
2732static void
2733gtk_text_click_gesture_pressed (GtkGestureClick *gesture,
2734 int n_press,
2735 double widget_x,
2736 double widget_y,
2737 GtkText *self)
2738{
2739 GtkWidget *widget = GTK_WIDGET (self);
2740 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
2741 GdkEventSequence *current;
2742 GdkEvent *event;
2743 int x, y, sel_start, sel_end;
2744 guint button;
2745 int tmp_pos;
2746
2747 button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
2748 current = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
2749 event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence: current);
2750
2751 x = gesture_get_current_point_in_layout (GTK_GESTURE_SINGLE (gesture), self);
2752 y = widget_y;
2753 gtk_text_reset_blink_time (self);
2754
2755 if (!gtk_widget_has_focus (widget))
2756 {
2757 priv->in_click = TRUE;
2758 gtk_widget_grab_focus (widget);
2759 gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_CLAIMED);
2760 priv->in_click = FALSE;
2761 }
2762
2763 tmp_pos = gtk_text_find_position (self, x);
2764
2765 if (gdk_event_triggers_context_menu (event))
2766 {
2767 gtk_text_do_popup (self, x: widget_x, y: widget_y);
2768 gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_CLAIMED);
2769 }
2770 else if (n_press == 1 && button == GDK_BUTTON_MIDDLE &&
2771 get_middle_click_paste (self))
2772 {
2773 if (priv->editable)
2774 {
2775 priv->insert_pos = tmp_pos;
2776 gtk_text_paste (self, clipboard: gtk_widget_get_primary_clipboard (widget));
2777 }
2778 else
2779 {
2780 gtk_widget_error_bell (widget);
2781 }
2782
2783 gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_CLAIMED);
2784 }
2785 else if (button == GDK_BUTTON_PRIMARY)
2786 {
2787 gboolean have_selection;
2788 gboolean is_touchscreen, extend_selection;
2789 GdkDevice *source;
2790 guint state;
2791
2792 gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_CLAIMED);
2793
2794 sel_start = priv->selection_bound;
2795 sel_end = priv->current_pos;
2796 have_selection = sel_start != sel_end;
2797
2798 source = gdk_event_get_device (event);
2799 is_touchscreen = gtk_simulate_touchscreen () ||
2800 gdk_device_get_source (device: source) == GDK_SOURCE_TOUCHSCREEN;
2801
2802 priv->text_handles_enabled = is_touchscreen;
2803
2804 priv->in_drag = FALSE;
2805 priv->select_words = FALSE;
2806 priv->select_lines = FALSE;
2807
2808 state = gdk_event_get_modifier_state (event);
2809
2810 extend_selection = (state & GDK_SHIFT_MASK) != 0;
2811
2812 /* Always emit reset when preedit is shown */
2813 priv->need_im_reset = TRUE;
2814 gtk_text_reset_im_context (entry: self);
2815
2816 switch (n_press)
2817 {
2818 case 1:
2819 if (in_selection (self, x))
2820 {
2821 if (is_touchscreen)
2822 {
2823 if (priv->selection_bubble &&
2824 gtk_widget_get_visible (widget: priv->selection_bubble))
2825 gtk_text_selection_bubble_popup_unset (self);
2826 else
2827 gtk_text_selection_bubble_popup_set (self);
2828 }
2829 else if (extend_selection)
2830 {
2831 /* Truncate current selection, but keep it as big as possible */
2832 if (tmp_pos - sel_start > sel_end - tmp_pos)
2833 gtk_text_set_positions (entry: self, current_pos: sel_start, selection_bound: tmp_pos);
2834 else
2835 gtk_text_set_positions (entry: self, current_pos: tmp_pos, selection_bound: sel_end);
2836
2837 /* all done, so skip the extend_to_left stuff later */
2838 extend_selection = FALSE;
2839 }
2840 else
2841 {
2842 /* We'll either start a drag, or clear the selection */
2843 priv->in_drag = TRUE;
2844 priv->drag_start_x = x;
2845 priv->drag_start_y = y;
2846 }
2847 }
2848 else
2849 {
2850 gtk_text_selection_bubble_popup_unset (self);
2851
2852 if (!extend_selection)
2853 {
2854 gtk_text_set_selection_bounds (self, start: tmp_pos, end: tmp_pos);
2855 priv->handle_place_time = g_get_monotonic_time ();
2856 }
2857 else
2858 {
2859 /* select from the current position to the clicked position */
2860 if (!have_selection)
2861 sel_start = sel_end = priv->current_pos;
2862
2863 gtk_text_set_positions (entry: self, current_pos: tmp_pos, selection_bound: tmp_pos);
2864 }
2865 }
2866
2867 break;
2868
2869 case 2:
2870 priv->select_words = TRUE;
2871 gtk_text_select_word (self);
2872 break;
2873
2874 case 3:
2875 priv->select_lines = TRUE;
2876 gtk_text_select_line (self);
2877 break;
2878
2879 default:
2880 break;
2881 }
2882
2883 if (extend_selection)
2884 {
2885 gboolean extend_to_left;
2886 int start, end;
2887
2888 start = MIN (priv->current_pos, priv->selection_bound);
2889 start = MIN (sel_start, start);
2890
2891 end = MAX (priv->current_pos, priv->selection_bound);
2892 end = MAX (sel_end, end);
2893
2894 if (tmp_pos == sel_start || tmp_pos == sel_end)
2895 extend_to_left = (tmp_pos == start);
2896 else
2897 extend_to_left = (end == sel_end);
2898
2899 if (extend_to_left)
2900 gtk_text_set_positions (entry: self, current_pos: start, selection_bound: end);
2901 else
2902 gtk_text_set_positions (entry: self, current_pos: end, selection_bound: start);
2903 }
2904
2905 gtk_text_update_handles (self);
2906 }
2907
2908 if (n_press >= 3)
2909 gtk_event_controller_reset (GTK_EVENT_CONTROLLER (gesture));
2910}
2911
2912static char *
2913_gtk_text_get_selected_text (GtkText *self)
2914{
2915 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
2916
2917 if (priv->selection_bound != priv->current_pos)
2918 {
2919 const int start = MIN (priv->selection_bound, priv->current_pos);
2920 const int end = MAX (priv->selection_bound, priv->current_pos);
2921 const char *text = gtk_entry_buffer_get_text (buffer: get_buffer (self));
2922 const int start_index = g_utf8_offset_to_pointer (str: text, offset: start) - text;
2923 const int end_index = g_utf8_offset_to_pointer (str: text, offset: end) - text;
2924
2925 return g_strndup (str: text + start_index, n: end_index - start_index);
2926 }
2927
2928 return NULL;
2929}
2930
2931static void
2932gtk_text_show_magnifier (GtkText *self,
2933 int x,
2934 int y)
2935{
2936 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
2937 const int text_height = gtk_widget_get_height (GTK_WIDGET (self));
2938 cairo_rectangle_int_t rect;
2939
2940 gtk_text_ensure_magnifier (self);
2941
2942 rect.x = x;
2943 rect.width = 1;
2944 rect.y = 0;
2945 rect.height = text_height;
2946
2947 _gtk_magnifier_set_coords (GTK_MAGNIFIER (priv->magnifier), x: rect.x,
2948 y: rect.y + rect.height / 2);
2949 gtk_popover_set_pointing_to (GTK_POPOVER (priv->magnifier_popover),
2950 rect: &rect);
2951 gtk_popover_popup (GTK_POPOVER (priv->magnifier_popover));
2952}
2953
2954static void
2955gtk_text_motion_controller_motion (GtkEventControllerMotion *controller,
2956 double x,
2957 double y,
2958 GtkText *self)
2959{
2960 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
2961 GdkDevice *device;
2962
2963 device = gtk_event_controller_get_current_event_device (GTK_EVENT_CONTROLLER (controller));
2964
2965 if (priv->mouse_cursor_obscured &&
2966 gdk_device_get_timestamp (device) != priv->obscured_cursor_timestamp)
2967 {
2968 set_text_cursor (GTK_WIDGET (self));
2969 priv->mouse_cursor_obscured = FALSE;
2970 }
2971}
2972
2973static void
2974dnd_finished_cb (GdkDrag *drag,
2975 GtkText *self)
2976{
2977 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
2978
2979 if (gdk_drag_get_selected_action (drag) == GDK_ACTION_MOVE)
2980 gtk_text_delete_selection (self);
2981
2982 priv->drag = NULL;
2983}
2984
2985static void
2986dnd_cancel_cb (GdkDrag *drag,
2987 GdkDragCancelReason reason,
2988 GtkText *self)
2989{
2990 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
2991
2992 priv->drag = NULL;
2993}
2994
2995static void
2996gtk_text_drag_gesture_update (GtkGestureDrag *gesture,
2997 double offset_x,
2998 double offset_y,
2999 GtkText *self)
3000{
3001 GtkWidget *widget = GTK_WIDGET (self);
3002 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
3003 GdkEventSequence *sequence;
3004 GdkEvent *event;
3005 int x, y;
3006 double start_y;
3007
3008 gtk_text_selection_bubble_popup_unset (self);
3009
3010 x = gesture_get_current_point_in_layout (GTK_GESTURE_SINGLE (gesture), self);
3011 gtk_gesture_drag_get_start_point (gesture, NULL, y: &start_y);
3012 y = start_y + offset_y;
3013
3014 sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
3015 event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
3016
3017 if (priv->mouse_cursor_obscured)
3018 {
3019 set_text_cursor (widget);
3020 priv->mouse_cursor_obscured = FALSE;
3021 }
3022
3023 if (priv->select_lines)
3024 return;
3025
3026 if (priv->in_drag)
3027 {
3028 if (gtk_text_get_display_mode (self) == DISPLAY_NORMAL &&
3029 gtk_drag_check_threshold_double (widget, start_x: 0, start_y: 0, current_x: offset_x, current_y: offset_y))
3030 {
3031 int *ranges;
3032 int n_ranges;
3033 char *text;
3034 GdkDragAction actions;
3035 GdkDrag *drag;
3036 GdkPaintable *paintable;
3037 GdkContentProvider *content;
3038
3039 text = _gtk_text_get_selected_text (self);
3040 gtk_text_get_pixel_ranges (self, ranges: &ranges, n_ranges: &n_ranges);
3041
3042 g_assert (n_ranges > 0);
3043
3044 if (priv->editable)
3045 actions = GDK_ACTION_COPY|GDK_ACTION_MOVE;
3046 else
3047 actions = GDK_ACTION_COPY;
3048
3049 content = gdk_content_provider_new_typed (G_TYPE_STRING, text);
3050
3051 drag = gdk_drag_begin (surface: gdk_event_get_surface (event: (GdkEvent*) event),
3052 device: gdk_event_get_device (event: (GdkEvent*) event),
3053 content,
3054 actions,
3055 dx: priv->drag_start_x,
3056 dy: priv->drag_start_y);
3057 g_object_unref (object: content);
3058
3059 g_signal_connect (drag, "dnd-finished", G_CALLBACK (dnd_finished_cb), self);
3060 g_signal_connect (drag, "cancel", G_CALLBACK (dnd_cancel_cb), self);
3061
3062 paintable = gtk_text_util_create_drag_icon (widget, text, len: -1);
3063 gtk_drag_icon_set_from_paintable (drag, paintable,
3064 hot_x: (priv->drag_start_x - ranges[0]),
3065 hot_y: priv->drag_start_y);
3066 g_clear_object (&paintable);
3067
3068 priv->drag = drag;
3069
3070 g_object_unref (object: drag);
3071
3072 g_free (mem: ranges);
3073 g_free (mem: text);
3074
3075 priv->in_drag = FALSE;
3076
3077 /* Deny the gesture so we don't get further updates */
3078 gtk_gesture_set_state (gesture: priv->drag_gesture, state: GTK_EVENT_SEQUENCE_DENIED);
3079 }
3080 }
3081 else
3082 {
3083 GdkInputSource input_source;
3084 GdkDevice *source;
3085 guint length;
3086 int tmp_pos;
3087
3088 gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_CLAIMED);
3089
3090 length = gtk_entry_buffer_get_length (buffer: get_buffer (self));
3091
3092 if (y < 0)
3093 tmp_pos = 0;
3094 else if (y >= gtk_widget_get_height (GTK_WIDGET (self)))
3095 tmp_pos = length;
3096 else
3097 tmp_pos = gtk_text_find_position (self, x);
3098
3099 source = gdk_event_get_device (event);
3100 input_source = gdk_device_get_source (device: source);
3101
3102 if (priv->select_words)
3103 {
3104 int min, max;
3105 int old_min, old_max;
3106 int pos, bound;
3107
3108 min = gtk_text_move_backward_word (self, start: tmp_pos, TRUE);
3109 max = gtk_text_move_forward_word (self, start: tmp_pos, TRUE);
3110
3111 pos = priv->current_pos;
3112 bound = priv->selection_bound;
3113
3114 old_min = MIN (priv->current_pos, priv->selection_bound);
3115 old_max = MAX (priv->current_pos, priv->selection_bound);
3116
3117 if (min < old_min)
3118 {
3119 pos = min;
3120 bound = old_max;
3121 }
3122 else if (old_max < max)
3123 {
3124 pos = max;
3125 bound = old_min;
3126 }
3127 else if (pos == old_min)
3128 {
3129 if (priv->current_pos != min)
3130 pos = max;
3131 }
3132 else
3133 {
3134 if (priv->current_pos != max)
3135 pos = min;
3136 }
3137
3138 gtk_text_set_positions (entry: self, current_pos: pos, selection_bound: bound);
3139 }
3140 else
3141 gtk_text_set_positions (entry: self, current_pos: tmp_pos, selection_bound: -1);
3142
3143 /* Update touch handles' position */
3144 if (gtk_simulate_touchscreen () ||
3145 input_source == GDK_SOURCE_TOUCHSCREEN)
3146 {
3147 priv->text_handles_enabled = TRUE;
3148 gtk_text_update_handles (self);
3149 gtk_text_show_magnifier (self, x: x - priv->scroll_offset, y);
3150 }
3151 }
3152}
3153
3154static void
3155gtk_text_drag_gesture_end (GtkGestureDrag *gesture,
3156 double offset_x,
3157 double offset_y,
3158 GtkText *self)
3159{
3160 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
3161 gboolean in_drag;
3162 GdkEventSequence *sequence;
3163
3164 sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
3165 in_drag = priv->in_drag;
3166 priv->in_drag = FALSE;
3167
3168 if (priv->magnifier_popover)
3169 gtk_popover_popdown (GTK_POPOVER (priv->magnifier_popover));
3170
3171 /* Check whether the drag was cancelled rather than finished */
3172 if (!gtk_gesture_handles_sequence (GTK_GESTURE (gesture), sequence))
3173 return;
3174
3175 if (in_drag)
3176 {
3177 int tmp_pos = gtk_text_find_position (self, x: priv->drag_start_x);
3178
3179 gtk_text_set_selection_bounds (self, start: tmp_pos, end: tmp_pos);
3180 }
3181
3182 gtk_text_update_handles (self);
3183
3184 gtk_text_update_primary_selection (self);
3185}
3186
3187static void
3188gtk_text_obscure_mouse_cursor (GtkText *self)
3189{
3190 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
3191 GdkDisplay *display;
3192 GdkSeat *seat;
3193 GdkDevice *device;
3194
3195 if (priv->mouse_cursor_obscured)
3196 return;
3197
3198 gtk_widget_set_cursor_from_name (GTK_WIDGET (self), name: "none");
3199
3200 display = gtk_widget_get_display (GTK_WIDGET (self));
3201 seat = gdk_display_get_default_seat (display);
3202 device = gdk_seat_get_pointer (seat);
3203
3204 priv->obscured_cursor_timestamp = gdk_device_get_timestamp (device);
3205 priv->mouse_cursor_obscured = TRUE;
3206}
3207
3208static gboolean
3209gtk_text_key_controller_key_pressed (GtkEventControllerKey *controller,
3210 guint keyval,
3211 guint keycode,
3212 GdkModifierType state,
3213 GtkText *self)
3214{
3215 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
3216 gunichar unichar;
3217
3218 gtk_text_reset_blink_time (self);
3219 gtk_text_pend_cursor_blink (self);
3220
3221 gtk_text_selection_bubble_popup_unset (self);
3222
3223 priv->text_handles_enabled = FALSE;
3224 gtk_text_update_handles (self);
3225
3226 if (keyval == GDK_KEY_Return ||
3227 keyval == GDK_KEY_KP_Enter ||
3228 keyval == GDK_KEY_ISO_Enter ||
3229 keyval == GDK_KEY_Escape)
3230 gtk_text_reset_im_context (entry: self);
3231
3232 unichar = gdk_keyval_to_unicode (keyval);
3233
3234 if (!priv->editable && unichar != 0)
3235 gtk_widget_error_bell (GTK_WIDGET (self));
3236
3237 gtk_text_obscure_mouse_cursor (self);
3238
3239 return FALSE;
3240}
3241
3242static void
3243gtk_text_focus_changed (GtkEventControllerFocus *controller,
3244 GParamSpec *pspec,
3245 GtkWidget *widget)
3246{
3247 GtkText *self = GTK_TEXT (widget);
3248 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
3249 GdkSeat *seat = NULL;
3250 GdkDevice *keyboard = NULL;
3251
3252 seat = gdk_display_get_default_seat (display: gtk_widget_get_display (widget));
3253 if (seat)
3254 keyboard = gdk_seat_get_keyboard (seat);
3255
3256 gtk_widget_queue_draw (widget);
3257
3258 if (gtk_event_controller_focus_is_focus (self: controller))
3259 {
3260 if (keyboard)
3261 {
3262 /* Work around unexpected notify::direction emissions */
3263 gdk_device_get_direction (device: keyboard);
3264 g_signal_connect (keyboard, "notify::direction",
3265 G_CALLBACK (direction_changed), self);
3266 }
3267
3268 gtk_text_im_set_focus_in (self);
3269 gtk_text_reset_blink_time (self);
3270 gtk_text_check_cursor_blink (self);
3271 }
3272 else /* Focus out */
3273 {
3274 gtk_text_selection_bubble_popup_unset (self);
3275
3276 priv->text_handles_enabled = FALSE;
3277 gtk_text_update_handles (self);
3278
3279 if (keyboard)
3280 g_signal_handlers_disconnect_by_func (keyboard, direction_changed, self);
3281
3282 if (priv->editable)
3283 {
3284 gtk_text_schedule_im_reset (self);
3285 gtk_im_context_focus_out (context: priv->im_context);
3286 }
3287
3288 if (priv->blink_tick)
3289 remove_blink_timeout (self);
3290 }
3291}
3292
3293static gboolean
3294gtk_text_grab_focus (GtkWidget *widget)
3295{
3296 GtkText *self = GTK_TEXT (widget);
3297 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
3298 gboolean select_on_focus;
3299 GtkWidget *prev_focus;
3300 gboolean prev_focus_was_child;
3301
3302 prev_focus = gtk_root_get_focus (self: gtk_widget_get_root (widget));
3303 prev_focus_was_child = prev_focus && gtk_widget_is_ancestor (widget: prev_focus, ancestor: widget);
3304
3305 if (!GTK_WIDGET_CLASS (gtk_text_parent_class)->grab_focus (GTK_WIDGET (self)))
3306 return FALSE;
3307
3308 if (priv->editable && !priv->in_click && !prev_focus_was_child)
3309 {
3310 g_object_get (object: gtk_widget_get_settings (widget),
3311 first_property_name: "gtk-entry-select-on-focus",
3312 &select_on_focus,
3313 NULL);
3314
3315 if (select_on_focus)
3316 gtk_text_set_selection_bounds (self, start: 0, end: -1);
3317 }
3318
3319 return TRUE;
3320}
3321
3322/**
3323 * gtk_text_grab_focus_without_selecting:
3324 * @self: a `GtkText`
3325 *
3326 * Causes @self to have keyboard focus.
3327 *
3328 * It behaves like [method@Gtk.Widget.grab_focus],
3329 * except that it doesn't select the contents of @self.
3330 * You only want to call this on some special entries
3331 * which the user usually doesn't want to replace all text in,
3332 * such as search-as-you-type entries.
3333 *
3334 * Returns: %TRUE if focus is now inside @self
3335 */
3336gboolean
3337gtk_text_grab_focus_without_selecting (GtkText *self)
3338{
3339 g_return_val_if_fail (GTK_IS_TEXT (self), FALSE);
3340
3341 return GTK_WIDGET_CLASS (gtk_text_parent_class)->grab_focus (GTK_WIDGET (self));
3342}
3343
3344static void
3345gtk_text_direction_changed (GtkWidget *widget,
3346 GtkTextDirection previous_dir)
3347{
3348 GtkText *self = GTK_TEXT (widget);
3349
3350 gtk_text_recompute (self);
3351
3352 GTK_WIDGET_CLASS (gtk_text_parent_class)->direction_changed (widget, previous_dir);
3353}
3354
3355static void
3356gtk_text_state_flags_changed (GtkWidget *widget,
3357 GtkStateFlags previous_state)
3358{
3359 GtkText *self = GTK_TEXT (widget);
3360 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
3361 GtkStateFlags state;
3362
3363 state = gtk_widget_get_state_flags (GTK_WIDGET (self));
3364
3365 if (gtk_widget_get_realized (widget))
3366 {
3367 set_text_cursor (widget);
3368 priv->mouse_cursor_obscured = FALSE;
3369 }
3370
3371 if (!gtk_widget_is_sensitive (widget))
3372 {
3373 /* Clear any selection */
3374 gtk_text_set_selection_bounds (self, start: priv->current_pos, end: priv->current_pos);
3375 }
3376
3377 state &= ~GTK_STATE_FLAG_DROP_ACTIVE;
3378 if (priv->selection_node)
3379 gtk_css_node_set_state (cssnode: priv->selection_node, state_flags: state);
3380
3381 if (priv->block_cursor_node)
3382 gtk_css_node_set_state (cssnode: priv->block_cursor_node, state_flags: state);
3383
3384 gtk_css_node_set_state (cssnode: priv->undershoot_node[0], state_flags: state);
3385 gtk_css_node_set_state (cssnode: priv->undershoot_node[1], state_flags: state);
3386
3387 gtk_text_update_cached_style_values (self);
3388
3389 gtk_widget_queue_draw (widget);
3390}
3391
3392/* GtkEditable method implementations
3393 */
3394static void
3395gtk_text_insert_text (GtkText *self,
3396 const char *text,
3397 int length,
3398 int *position)
3399{
3400 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
3401 int n_inserted;
3402 int n_chars;
3403
3404 if (length == 0)
3405 return;
3406
3407 n_chars = g_utf8_strlen (p: text, max: length);
3408
3409 /*
3410 * The incoming text may a password or other secret. We make sure
3411 * not to copy it into temporary buffers.
3412 */
3413 begin_change (self);
3414
3415 n_inserted = gtk_entry_buffer_insert_text (buffer: get_buffer (self), position: *position, chars: text, n_chars);
3416
3417 end_change (self);
3418
3419 if (n_inserted != n_chars)
3420 gtk_widget_error_bell (GTK_WIDGET (self));
3421
3422 *position += n_inserted;
3423
3424 update_placeholder_visibility (self);
3425 if (priv->propagate_text_width)
3426 gtk_widget_queue_resize (GTK_WIDGET (self));
3427}
3428
3429static void
3430gtk_text_delete_text (GtkText *self,
3431 int start_pos,
3432 int end_pos)
3433{
3434 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
3435
3436 if (start_pos == end_pos)
3437 return;
3438
3439 begin_change (self);
3440
3441 gtk_entry_buffer_delete_text (buffer: get_buffer (self), position: start_pos, n_chars: end_pos - start_pos);
3442
3443 end_change (self);
3444 update_placeholder_visibility (self);
3445 if (priv->propagate_text_width)
3446 gtk_widget_queue_resize (GTK_WIDGET (self));
3447}
3448
3449static void
3450gtk_text_delete_selection (GtkText *self)
3451{
3452 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
3453
3454 int start_pos = MIN (priv->selection_bound, priv->current_pos);
3455 int end_pos = MAX (priv->selection_bound, priv->current_pos);
3456
3457 gtk_editable_delete_text (GTK_EDITABLE (self), start_pos, end_pos);
3458}
3459
3460static void
3461gtk_text_set_selection_bounds (GtkText *self,
3462 int start,
3463 int end)
3464{
3465 guint length;
3466
3467 length = gtk_entry_buffer_get_length (buffer: get_buffer (self));
3468 if (start < 0)
3469 start = length;
3470 if (end < 0)
3471 end = length;
3472
3473 gtk_text_reset_im_context (entry: self);
3474
3475 gtk_text_set_positions (entry: self, MIN (end, length), MIN (start, length));
3476
3477 gtk_text_update_primary_selection (self);
3478}
3479
3480static gboolean
3481gtk_text_get_selection_bounds (GtkText *self,
3482 int *start,
3483 int *end)
3484{
3485 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
3486
3487 *start = priv->selection_bound;
3488 *end = priv->current_pos;
3489
3490 return (priv->selection_bound != priv->current_pos);
3491}
3492
3493static gunichar
3494find_invisible_char (GtkWidget *widget)
3495{
3496 PangoLayout *layout;
3497 PangoAttrList *attr_list;
3498 int i;
3499 gunichar invisible_chars [] = {
3500 0x25cf, /* BLACK CIRCLE */
3501 0x2022, /* BULLET */
3502 0x2731, /* HEAVY ASTERISK */
3503 0x273a /* SIXTEEN POINTED ASTERISK */
3504 };
3505
3506 layout = gtk_widget_create_pango_layout (widget, NULL);
3507
3508 attr_list = pango_attr_list_new ();
3509 pango_attr_list_insert (list: attr_list, attr: pango_attr_fallback_new (FALSE));
3510
3511 pango_layout_set_attributes (layout, attrs: attr_list);
3512 pango_attr_list_unref (list: attr_list);
3513
3514 for (i = 0; i < G_N_ELEMENTS (invisible_chars); i++)
3515 {
3516 char text[7] = { 0, };
3517 int len, count;
3518
3519 len = g_unichar_to_utf8 (c: invisible_chars[i], outbuf: text);
3520 pango_layout_set_text (layout, text, length: len);
3521
3522 count = pango_layout_get_unknown_glyphs_count (layout);
3523
3524 if (count == 0)
3525 {
3526 g_object_unref (object: layout);
3527 return invisible_chars[i];
3528 }
3529 }
3530
3531 g_object_unref (object: layout);
3532
3533 return '*';
3534}
3535
3536static void
3537gtk_text_update_cached_style_values (GtkText *self)
3538{
3539 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
3540
3541 if (!priv->visible && !priv->invisible_char_set)
3542 {
3543 gunichar ch = find_invisible_char (GTK_WIDGET (self));
3544
3545 if (priv->invisible_char != ch)
3546 {
3547 priv->invisible_char = ch;
3548 g_object_notify_by_pspec (G_OBJECT (self), pspec: text_props[PROP_INVISIBLE_CHAR]);
3549 }
3550 }
3551}
3552
3553static void
3554gtk_text_css_changed (GtkWidget *widget,
3555 GtkCssStyleChange *change)
3556{
3557 GtkText *self = GTK_TEXT (widget);
3558
3559 GTK_WIDGET_CLASS (gtk_text_parent_class)->css_changed (widget, change);
3560
3561 gtk_text_update_cached_style_values (self);
3562
3563 if (gtk_css_style_change_affects (change, GTK_CSS_AFFECTS_TEXT |
3564 GTK_CSS_AFFECTS_BACKGROUND |
3565 GTK_CSS_AFFECTS_CONTENT))
3566 gtk_widget_queue_draw (widget);
3567
3568 if (gtk_css_style_change_affects (change, affects: GTK_CSS_AFFECTS_TEXT_ATTRS))
3569 gtk_text_recompute (self);
3570}
3571
3572static void
3573gtk_text_password_hint_free (GtkTextPasswordHint *password_hint)
3574{
3575 if (password_hint->source_id)
3576 g_source_remove (tag: password_hint->source_id);
3577
3578 g_slice_free (GtkTextPasswordHint, password_hint);
3579}
3580
3581
3582static gboolean
3583gtk_text_remove_password_hint (gpointer data)
3584{
3585 GtkTextPasswordHint *password_hint = g_object_get_qdata (object: data, quark: quark_password_hint);
3586 password_hint->position = -1;
3587 password_hint->source_id = 0;
3588
3589 /* Force the string to be redrawn, but now without a visible character */
3590 gtk_text_recompute (GTK_TEXT (data));
3591
3592 return G_SOURCE_REMOVE;
3593}
3594
3595static void
3596update_placeholder_visibility (GtkText *self)
3597{
3598 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
3599
3600 if (priv->placeholder)
3601 gtk_widget_set_child_visible (widget: priv->placeholder,
3602 child_visible: priv->preedit_length == 0 &&
3603 (priv->buffer == NULL ||
3604 gtk_entry_buffer_get_length (buffer: priv->buffer) == 0));
3605}
3606
3607/* GtkEntryBuffer signal handlers
3608 */
3609static void
3610buffer_inserted_text (GtkEntryBuffer *buffer,
3611 guint position,
3612 const char *chars,
3613 guint n_chars,
3614 GtkText *self)
3615{
3616 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
3617 guint password_hint_timeout;
3618 guint current_pos;
3619 int selection_bound;
3620
3621 current_pos = priv->current_pos;
3622 if (current_pos > position)
3623 current_pos += n_chars;
3624
3625 selection_bound = priv->selection_bound;
3626 if (selection_bound > position)
3627 selection_bound += n_chars;
3628
3629 gtk_text_set_positions (entry: self, current_pos, selection_bound);
3630 gtk_text_recompute (self);
3631
3632 gtk_text_history_text_inserted (self: priv->history, position, text: chars, len: -1);
3633
3634 /* Calculate the password hint if it needs to be displayed. */
3635 if (n_chars == 1 && !priv->visible)
3636 {
3637 g_object_get (object: gtk_widget_get_settings (GTK_WIDGET (self)),
3638 first_property_name: "gtk-entry-password-hint-timeout", &password_hint_timeout,
3639 NULL);
3640
3641 if (password_hint_timeout > 0)
3642 {
3643 GtkTextPasswordHint *password_hint = g_object_get_qdata (G_OBJECT (self),
3644 quark: quark_password_hint);
3645 if (!password_hint)
3646 {
3647 password_hint = g_slice_new0 (GtkTextPasswordHint);
3648 g_object_set_qdata_full (G_OBJECT (self), quark: quark_password_hint, data: password_hint,
3649 destroy: (GDestroyNotify)gtk_text_password_hint_free);
3650 }
3651
3652 password_hint->position = position;
3653 if (password_hint->source_id)
3654 g_source_remove (tag: password_hint->source_id);
3655 password_hint->source_id = g_timeout_add (interval: password_hint_timeout,
3656 function: (GSourceFunc)gtk_text_remove_password_hint,
3657 data: self);
3658 gdk_source_set_static_name_by_id (tag: password_hint->source_id, name: "[gtk] gtk_text_remove_password_hint");
3659 }
3660 }
3661}
3662
3663static void
3664buffer_deleted_text (GtkEntryBuffer *buffer,
3665 guint position,
3666 guint n_chars,
3667 GtkText *self)
3668{
3669 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
3670 guint end_pos = position + n_chars;
3671
3672 if (gtk_text_history_get_enabled (self: priv->history))
3673 {
3674 char *deleted_text;
3675
3676 deleted_text = gtk_editable_get_chars (GTK_EDITABLE (self),
3677 start_pos: position,
3678 end_pos);
3679 gtk_text_history_selection_changed (self: priv->history,
3680 selection_insert: priv->current_pos,
3681 selection_bound: priv->selection_bound);
3682 gtk_text_history_text_deleted (self: priv->history,
3683 begin: position,
3684 end: end_pos,
3685 text: deleted_text,
3686 len: -1);
3687
3688 g_free (mem: deleted_text);
3689 }
3690}
3691
3692static void
3693buffer_deleted_text_after (GtkEntryBuffer *buffer,
3694 guint position,
3695 guint n_chars,
3696 GtkText *self)
3697{
3698 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
3699 guint end_pos = position + n_chars;
3700 int selection_bound;
3701 guint current_pos;
3702
3703 current_pos = priv->current_pos;
3704 if (current_pos > position)
3705 current_pos -= MIN (current_pos, end_pos) - position;
3706
3707 selection_bound = priv->selection_bound;
3708 if (selection_bound > position)
3709 selection_bound -= MIN (selection_bound, end_pos) - position;
3710
3711 gtk_text_set_positions (entry: self, current_pos, selection_bound);
3712 gtk_text_recompute (self);
3713
3714 /* We might have deleted the selection */
3715 gtk_text_update_primary_selection (self);
3716
3717 /* Disable the password hint if one exists. */
3718 if (!priv->visible)
3719 {
3720 GtkTextPasswordHint *password_hint = g_object_get_qdata (G_OBJECT (self),
3721 quark: quark_password_hint);
3722 if (password_hint)
3723 {
3724 if (password_hint->source_id)
3725 g_source_remove (tag: password_hint->source_id);
3726 password_hint->source_id = 0;
3727 password_hint->position = -1;
3728 }
3729 }
3730}
3731
3732static void
3733buffer_notify_text (GtkEntryBuffer *buffer,
3734 GParamSpec *spec,
3735 GtkText *self)
3736{
3737 emit_changed (self);
3738 g_object_notify (G_OBJECT (self), property_name: "text");
3739}
3740
3741static void
3742buffer_notify_max_length (GtkEntryBuffer *buffer,
3743 GParamSpec *spec,
3744 GtkText *self)
3745{
3746 g_object_notify_by_pspec (G_OBJECT (self), pspec: text_props[PROP_MAX_LENGTH]);
3747}
3748
3749static void
3750buffer_connect_signals (GtkText *self)
3751{
3752 g_signal_connect (get_buffer (self), "inserted-text", G_CALLBACK (buffer_inserted_text), self);
3753 g_signal_connect (get_buffer (self), "deleted-text", G_CALLBACK (buffer_deleted_text), self);
3754 g_signal_connect_after (get_buffer (self), "deleted-text", G_CALLBACK (buffer_deleted_text_after), self);
3755 g_signal_connect (get_buffer (self), "notify::text", G_CALLBACK (buffer_notify_text), self);
3756 g_signal_connect (get_buffer (self), "notify::max-length", G_CALLBACK (buffer_notify_max_length), self);
3757}
3758
3759static void
3760buffer_disconnect_signals (GtkText *self)
3761{
3762 g_signal_handlers_disconnect_by_func (get_buffer (self), buffer_inserted_text, self);
3763 g_signal_handlers_disconnect_by_func (get_buffer (self), buffer_deleted_text, self);
3764 g_signal_handlers_disconnect_by_func (get_buffer (self), buffer_deleted_text_after, self);
3765 g_signal_handlers_disconnect_by_func (get_buffer (self), buffer_notify_text, self);
3766 g_signal_handlers_disconnect_by_func (get_buffer (self), buffer_notify_max_length, self);
3767}
3768
3769/* Compute the X position for an offset that corresponds to the "more important
3770 * cursor position for that offset. We use this when trying to guess to which
3771 * end of the selection we should go to when the user hits the left or
3772 * right arrow key.
3773 */
3774static int
3775get_better_cursor_x (GtkText *self,
3776 int offset)
3777{
3778 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
3779 GdkSeat *seat;
3780 GdkDevice *keyboard = NULL;
3781 PangoDirection direction = PANGO_DIRECTION_LTR;
3782 gboolean split_cursor;
3783 PangoLayout *layout = gtk_text_ensure_layout (self, TRUE);
3784 const char *text = pango_layout_get_text (layout);
3785 int index = g_utf8_offset_to_pointer (str: text, offset) - text;
3786 PangoRectangle strong_pos, weak_pos;
3787
3788 seat = gdk_display_get_default_seat (display: gtk_widget_get_display (GTK_WIDGET (self)));
3789 if (seat)
3790 keyboard = gdk_seat_get_keyboard (seat);
3791 if (keyboard)
3792 direction = gdk_device_get_direction (device: keyboard);
3793
3794 g_object_get (object: gtk_widget_get_settings (GTK_WIDGET (self)),
3795 first_property_name: "gtk-split-cursor", &split_cursor,
3796 NULL);
3797
3798 pango_layout_get_cursor_pos (layout, index_: index, strong_pos: &strong_pos, weak_pos: &weak_pos);
3799
3800 if (split_cursor)
3801 return strong_pos.x / PANGO_SCALE;
3802 else
3803 return (direction == priv->resolved_dir) ? strong_pos.x / PANGO_SCALE : weak_pos.x / PANGO_SCALE;
3804}
3805
3806static void
3807gtk_text_move_cursor (GtkText *self,
3808 GtkMovementStep step,
3809 int count,
3810 gboolean extend_selection)
3811{
3812 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
3813 int new_pos = priv->current_pos;
3814
3815 if (priv->current_pos != priv->selection_bound && !extend_selection)
3816 {
3817 /* If we have a current selection and aren't extending it, move to the
3818 * start/or end of the selection as appropriate
3819 */
3820 switch (step)
3821 {
3822 case GTK_MOVEMENT_VISUAL_POSITIONS:
3823 {
3824 int current_x = get_better_cursor_x (self, offset: priv->current_pos);
3825 int bound_x = get_better_cursor_x (self, offset: priv->selection_bound);
3826
3827 if (count <= 0)
3828 new_pos = current_x < bound_x ? priv->current_pos : priv->selection_bound;
3829 else
3830 new_pos = current_x > bound_x ? priv->current_pos : priv->selection_bound;
3831 }
3832 break;
3833
3834 case GTK_MOVEMENT_WORDS:
3835 if (priv->resolved_dir == PANGO_DIRECTION_RTL)
3836 count *= -1;
3837 G_GNUC_FALLTHROUGH;
3838
3839 case GTK_MOVEMENT_LOGICAL_POSITIONS:
3840 if (count < 0)
3841 new_pos = MIN (priv->current_pos, priv->selection_bound);
3842 else
3843 new_pos = MAX (priv->current_pos, priv->selection_bound);
3844
3845 break;
3846
3847 case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
3848 case GTK_MOVEMENT_PARAGRAPH_ENDS:
3849 case GTK_MOVEMENT_BUFFER_ENDS:
3850 new_pos = count < 0 ? 0 : gtk_entry_buffer_get_length (buffer: get_buffer (self));
3851 break;
3852
3853 case GTK_MOVEMENT_DISPLAY_LINES:
3854 case GTK_MOVEMENT_PARAGRAPHS:
3855 case GTK_MOVEMENT_PAGES:
3856 case GTK_MOVEMENT_HORIZONTAL_PAGES:
3857 default:
3858 break;
3859 }
3860 }
3861 else
3862 {
3863 switch (step)
3864 {
3865 case GTK_MOVEMENT_LOGICAL_POSITIONS:
3866 new_pos = gtk_text_move_logically (self, start: new_pos, count);
3867 break;
3868
3869 case GTK_MOVEMENT_VISUAL_POSITIONS:
3870 new_pos = gtk_text_move_visually (editable: self, start: new_pos, count);
3871
3872 if (priv->current_pos == new_pos)
3873 {
3874 if (!extend_selection)
3875 {
3876 if (!gtk_widget_keynav_failed (GTK_WIDGET (self),
3877 direction: count > 0 ?
3878 GTK_DIR_RIGHT : GTK_DIR_LEFT))
3879 {
3880 GtkWidget *toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (self)));
3881
3882 if (toplevel)
3883 gtk_widget_child_focus (widget: toplevel,
3884 direction: count > 0 ?
3885 GTK_DIR_RIGHT : GTK_DIR_LEFT);
3886 }
3887 }
3888 else
3889 {
3890 gtk_widget_error_bell (GTK_WIDGET (self));
3891 }
3892 }
3893 break;
3894
3895 case GTK_MOVEMENT_WORDS:
3896 if (priv->resolved_dir == PANGO_DIRECTION_RTL)
3897 count *= -1;
3898
3899 while (count > 0)
3900 {
3901 new_pos = gtk_text_move_forward_word (self, start: new_pos, FALSE);
3902 count--;
3903 }
3904
3905 while (count < 0)
3906 {
3907 new_pos = gtk_text_move_backward_word (self, start: new_pos, FALSE);
3908 count++;
3909 }
3910
3911 if (priv->current_pos == new_pos)
3912 gtk_widget_error_bell (GTK_WIDGET (self));
3913
3914 break;
3915
3916 case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
3917 case GTK_MOVEMENT_PARAGRAPH_ENDS:
3918 case GTK_MOVEMENT_BUFFER_ENDS:
3919 new_pos = count < 0 ? 0 : gtk_entry_buffer_get_length (buffer: get_buffer (self));
3920
3921 if (priv->current_pos == new_pos)
3922 gtk_widget_error_bell (GTK_WIDGET (self));
3923
3924 break;
3925
3926 case GTK_MOVEMENT_DISPLAY_LINES:
3927 case GTK_MOVEMENT_PARAGRAPHS:
3928 case GTK_MOVEMENT_PAGES:
3929 case GTK_MOVEMENT_HORIZONTAL_PAGES:
3930 default:
3931 break;
3932 }
3933 }
3934
3935 if (extend_selection)
3936 gtk_text_set_selection_bounds (self, start: priv->selection_bound, end: new_pos);
3937 else
3938 gtk_text_set_selection_bounds (self, start: new_pos, end: new_pos);
3939
3940 gtk_text_pend_cursor_blink (self);
3941
3942 priv->need_im_reset = TRUE;
3943 gtk_text_reset_im_context (entry: self);
3944}
3945
3946static void
3947gtk_text_insert_at_cursor (GtkText *self,
3948 const char *str)
3949{
3950 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
3951 int pos = priv->current_pos;
3952
3953 if (priv->editable)
3954 {
3955 gtk_text_reset_im_context (entry: self);
3956 gtk_editable_insert_text (GTK_EDITABLE (self), text: str, length: -1, position: &pos);
3957 gtk_text_set_selection_bounds (self, start: pos, end: pos);
3958 }
3959}
3960
3961static void
3962gtk_text_delete_from_cursor (GtkText *self,
3963 GtkDeleteType type,
3964 int count)
3965{
3966 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
3967 int start_pos = priv->current_pos;
3968 int end_pos = priv->current_pos;
3969 int old_n_bytes = gtk_entry_buffer_get_bytes (buffer: get_buffer (self));
3970
3971 if (!priv->editable)
3972 {
3973 gtk_widget_error_bell (GTK_WIDGET (self));
3974 return;
3975 }
3976
3977 if (priv->selection_bound != priv->current_pos)
3978 {
3979 gtk_text_delete_selection (self);
3980 gtk_text_schedule_im_reset (self);
3981 gtk_text_reset_im_context (entry: self);
3982 return;
3983 }
3984
3985 switch (type)
3986 {
3987 case GTK_DELETE_CHARS:
3988 end_pos = gtk_text_move_logically (self, start: priv->current_pos, count);
3989 gtk_editable_delete_text (GTK_EDITABLE (self), MIN (start_pos, end_pos), MAX (start_pos, end_pos));
3990 break;
3991
3992 case GTK_DELETE_WORDS:
3993 if (count < 0)
3994 {
3995 /* Move to end of current word, or if not on a word, end of previous word */
3996 end_pos = gtk_text_move_backward_word (self, start: end_pos, FALSE);
3997 end_pos = gtk_text_move_forward_word (self, start: end_pos, FALSE);
3998 }
3999 else if (count > 0)
4000 {
4001 /* Move to beginning of current word, or if not on a word, beginning of next word */
4002 start_pos = gtk_text_move_forward_word (self, start: start_pos, FALSE);
4003 start_pos = gtk_text_move_backward_word (self, start: start_pos, FALSE);
4004 }
4005 G_GNUC_FALLTHROUGH;
4006 case GTK_DELETE_WORD_ENDS:
4007 while (count < 0)
4008 {
4009 start_pos = gtk_text_move_backward_word (self, start: start_pos, FALSE);
4010 count++;
4011 }
4012
4013 while (count > 0)
4014 {
4015 end_pos = gtk_text_move_forward_word (self, start: end_pos, FALSE);
4016 count--;
4017 }
4018
4019 gtk_editable_delete_text (GTK_EDITABLE (self), start_pos, end_pos);
4020 break;
4021
4022 case GTK_DELETE_DISPLAY_LINE_ENDS:
4023 case GTK_DELETE_PARAGRAPH_ENDS:
4024 if (count < 0)
4025 gtk_editable_delete_text (GTK_EDITABLE (self), start_pos: 0, end_pos: priv->current_pos);
4026 else
4027 gtk_editable_delete_text (GTK_EDITABLE (self), start_pos: priv->current_pos, end_pos: -1);
4028
4029 break;
4030
4031 case GTK_DELETE_DISPLAY_LINES:
4032 case GTK_DELETE_PARAGRAPHS:
4033 gtk_editable_delete_text (GTK_EDITABLE (self), start_pos: 0, end_pos: -1);
4034 break;
4035
4036 case GTK_DELETE_WHITESPACE:
4037 gtk_text_delete_whitespace (self);
4038 break;
4039
4040 default:
4041 break;
4042 }
4043
4044 if (gtk_entry_buffer_get_bytes (buffer: get_buffer (self)) == old_n_bytes)
4045 gtk_widget_error_bell (GTK_WIDGET (self));
4046 else
4047 {
4048 gtk_text_schedule_im_reset (self);
4049 gtk_text_reset_im_context (entry: self);
4050 }
4051
4052 gtk_text_pend_cursor_blink (self);
4053}
4054
4055static void
4056gtk_text_backspace (GtkText *self)
4057{
4058 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
4059 int prev_pos;
4060
4061 if (!priv->editable)
4062 {
4063 gtk_widget_error_bell (GTK_WIDGET (self));
4064 return;
4065 }
4066
4067 if (priv->selection_bound != priv->current_pos)
4068 {
4069 gtk_text_delete_selection (self);
4070 gtk_text_schedule_im_reset (self);
4071 gtk_text_reset_im_context (entry: self);
4072 return;
4073 }
4074
4075 prev_pos = gtk_text_move_logically (self, start: priv->current_pos, count: -1);
4076
4077 if (prev_pos < priv->current_pos)
4078 {
4079 PangoLayout *layout = gtk_text_ensure_layout (self, FALSE);
4080 const PangoLogAttr *log_attrs;
4081 int n_attrs;
4082
4083 log_attrs = pango_layout_get_log_attrs_readonly (layout, n_attrs: &n_attrs);
4084
4085 /* Deleting parts of characters */
4086 if (log_attrs[priv->current_pos].backspace_deletes_character)
4087 {
4088 char *cluster_text;
4089 char *normalized_text;
4090 glong len;
4091
4092 cluster_text = gtk_text_get_display_text (self, start_pos: prev_pos, end_pos: priv->current_pos);
4093 normalized_text = g_utf8_normalize (str: cluster_text,
4094 len: strlen (s: cluster_text),
4095 mode: G_NORMALIZE_NFD);
4096 len = g_utf8_strlen (p: normalized_text, max: -1);
4097
4098 gtk_editable_delete_text (GTK_EDITABLE (self), start_pos: prev_pos, end_pos: priv->current_pos);
4099 if (len > 1)
4100 {
4101 int pos = priv->current_pos;
4102
4103 gtk_editable_insert_text (GTK_EDITABLE (self), text: normalized_text,
4104 length: g_utf8_offset_to_pointer (str: normalized_text, offset: len - 1) - normalized_text,
4105 position: &pos);
4106 gtk_text_set_selection_bounds (self, start: pos, end: pos);
4107 }
4108
4109 g_free (mem: normalized_text);
4110 g_free (mem: cluster_text);
4111 }
4112 else
4113 {
4114 gtk_editable_delete_text (GTK_EDITABLE (self), start_pos: prev_pos, end_pos: priv->current_pos);
4115 }
4116
4117 gtk_text_schedule_im_reset (self);
4118 gtk_text_reset_im_context (entry: self);
4119 }
4120 else
4121 {
4122 gtk_widget_error_bell (GTK_WIDGET (self));
4123 }
4124
4125 gtk_text_pend_cursor_blink (self);
4126}
4127
4128static void
4129gtk_text_copy_clipboard (GtkText *self)
4130{
4131 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
4132
4133 if (priv->selection_bound != priv->current_pos)
4134 {
4135 char *str;
4136
4137 if (!priv->visible)
4138 {
4139 gtk_widget_error_bell (GTK_WIDGET (self));
4140 return;
4141 }
4142
4143 if (priv->selection_bound < priv->current_pos)
4144 str = gtk_text_get_display_text (self, start_pos: priv->selection_bound, end_pos: priv->current_pos);
4145 else
4146 str = gtk_text_get_display_text (self, start_pos: priv->current_pos, end_pos: priv->selection_bound);
4147
4148 gdk_clipboard_set_text (clipboard: gtk_widget_get_clipboard (GTK_WIDGET (self)), text: str);
4149 g_free (mem: str);
4150 }
4151}
4152
4153static void
4154gtk_text_cut_clipboard (GtkText *self)
4155{
4156 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
4157
4158 if (!priv->visible)
4159 {
4160 gtk_widget_error_bell (GTK_WIDGET (self));
4161 return;
4162 }
4163
4164 gtk_text_copy_clipboard (self);
4165
4166 if (priv->editable)
4167 {
4168 if (priv->selection_bound != priv->current_pos)
4169 gtk_text_delete_selection (self);
4170 }
4171 else
4172 {
4173 gtk_widget_error_bell (GTK_WIDGET (self));
4174 }
4175
4176 gtk_text_selection_bubble_popup_unset (self);
4177
4178 gtk_text_update_handles (self);
4179}
4180
4181static void
4182gtk_text_paste_clipboard (GtkText *self)
4183{
4184 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
4185
4186 if (priv->editable)
4187 gtk_text_paste (self, clipboard: gtk_widget_get_clipboard (GTK_WIDGET (self)));
4188 else
4189 gtk_widget_error_bell (GTK_WIDGET (self));
4190
4191 gtk_text_update_handles (self);
4192}
4193
4194static void
4195gtk_text_delete_cb (GtkText *self)
4196{
4197 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
4198
4199 if (priv->editable)
4200 {
4201 if (priv->selection_bound != priv->current_pos)
4202 gtk_text_delete_selection (self);
4203 }
4204}
4205
4206static void
4207gtk_text_toggle_overwrite (GtkText *self)
4208{
4209 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
4210
4211 priv->overwrite_mode = !priv->overwrite_mode;
4212
4213 if (priv->overwrite_mode)
4214 {
4215 if (!priv->block_cursor_node)
4216 {
4217 GtkCssNode *widget_node = gtk_widget_get_css_node (GTK_WIDGET (self));
4218
4219 priv->block_cursor_node = gtk_css_node_new ();
4220 gtk_css_node_set_name (cssnode: priv->block_cursor_node, name: g_quark_from_static_string (string: "block-cursor"));
4221 gtk_css_node_set_parent (cssnode: priv->block_cursor_node, parent: widget_node);
4222 gtk_css_node_set_state (cssnode: priv->block_cursor_node, state_flags: gtk_css_node_get_state (cssnode: widget_node));
4223 g_object_unref (object: priv->block_cursor_node);
4224 }
4225 }
4226 else
4227 {
4228 if (priv->block_cursor_node)
4229 {
4230 gtk_css_node_set_parent (cssnode: priv->block_cursor_node, NULL);
4231 priv->block_cursor_node = NULL;
4232 }
4233 }
4234
4235 gtk_text_pend_cursor_blink (self);
4236 gtk_widget_queue_draw (GTK_WIDGET (self));
4237}
4238
4239static void
4240gtk_text_select_all (GtkText *self)
4241{
4242 gtk_text_select_line (self);
4243}
4244
4245static void
4246gtk_text_real_activate (GtkText *self)
4247{
4248 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
4249
4250 if (priv->activates_default)
4251 gtk_widget_activate_default (GTK_WIDGET (self));
4252}
4253
4254static void
4255direction_changed (GdkDevice *device,
4256 GParamSpec *pspec,
4257 GtkText *self)
4258{
4259 gtk_text_recompute (self);
4260}
4261
4262/* IM Context Callbacks
4263 */
4264
4265static void
4266gtk_text_preedit_start_cb (GtkIMContext *context,
4267 GtkText *self)
4268{
4269 gtk_text_delete_selection (self);
4270}
4271
4272static void
4273gtk_text_commit_cb (GtkIMContext *context,
4274 const char *str,
4275 GtkText *self)
4276{
4277 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
4278
4279 if (priv->editable)
4280 {
4281 gtk_text_enter_text (entry: self, text: str);
4282 gtk_text_obscure_mouse_cursor (self);
4283 }
4284}
4285
4286static void
4287gtk_text_preedit_changed_cb (GtkIMContext *context,
4288 GtkText *self)
4289{
4290 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
4291
4292 if (priv->editable)
4293 {
4294 char *preedit_string;
4295 int cursor_pos;
4296
4297 gtk_text_obscure_mouse_cursor (self);
4298
4299 gtk_im_context_get_preedit_string (context: priv->im_context,
4300 str: &preedit_string, NULL,
4301 cursor_pos: &cursor_pos);
4302 g_signal_emit (instance: self, signal_id: signals[PREEDIT_CHANGED], detail: 0, preedit_string);
4303 priv->preedit_length = strlen (s: preedit_string);
4304 cursor_pos = CLAMP (cursor_pos, 0, g_utf8_strlen (preedit_string, -1));
4305 priv->preedit_cursor = cursor_pos;
4306 g_free (mem: preedit_string);
4307
4308 gtk_text_recompute (self);
4309 update_placeholder_visibility (self);
4310 }
4311}
4312
4313static gboolean
4314gtk_text_retrieve_surrounding_cb (GtkIMContext *context,
4315 GtkText *self)
4316{
4317 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
4318 char *text;
4319
4320 /* XXXX ??? does this even make sense when text is not visible? Should we return FALSE? */
4321 text = gtk_text_get_display_text (self, start_pos: 0, end_pos: -1);
4322 gtk_im_context_set_surrounding_with_selection (context, text, len: strlen (s: text), /* Length in bytes */
4323 cursor_index: g_utf8_offset_to_pointer (str: text, offset: priv->current_pos) - text,
4324 anchor_index: g_utf8_offset_to_pointer (str: text, offset: priv->selection_bound) - text);
4325 g_free (mem: text);
4326
4327 return TRUE;
4328}
4329
4330static gboolean
4331gtk_text_delete_surrounding_cb (GtkIMContext *context,
4332 int offset,
4333 int n_chars,
4334 GtkText *self)
4335{
4336 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
4337
4338 if (priv->editable)
4339 {
4340 gtk_editable_delete_text (GTK_EDITABLE (self),
4341 start_pos: priv->current_pos + offset,
4342 end_pos: priv->current_pos + offset + n_chars);
4343 }
4344
4345 return TRUE;
4346}
4347
4348/* Internal functions
4349 */
4350
4351/* Used for im_commit_cb and inserting Unicode chars */
4352void
4353gtk_text_enter_text (GtkText *self,
4354 const char *str)
4355{
4356 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
4357 int tmp_pos;
4358 guint text_length;
4359
4360 priv->need_im_reset = FALSE;
4361
4362 if (priv->selection_bound != priv->current_pos)
4363 gtk_text_delete_selection (self);
4364 else
4365 {
4366 if (priv->overwrite_mode)
4367 {
4368 text_length = gtk_entry_buffer_get_length (buffer: get_buffer (self));
4369 if (priv->current_pos < text_length)
4370 gtk_text_delete_from_cursor (self, type: GTK_DELETE_CHARS, count: 1);
4371 }
4372 }
4373
4374 tmp_pos = priv->current_pos;
4375 gtk_editable_insert_text (GTK_EDITABLE (self), text: str, length: strlen (s: str), position: &tmp_pos);
4376 gtk_text_set_selection_bounds (self, start: tmp_pos, end: tmp_pos);
4377}
4378
4379/* All changes to priv->current_pos and priv->selection_bound
4380 * should go through this function.
4381 */
4382void
4383gtk_text_set_positions (GtkText *self,
4384 int current_pos,
4385 int selection_bound)
4386{
4387 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
4388 gboolean changed = FALSE;
4389
4390 g_object_freeze_notify (G_OBJECT (self));
4391
4392 if (current_pos != -1 &&
4393 priv->current_pos != current_pos)
4394 {
4395 priv->current_pos = current_pos;
4396 changed = TRUE;
4397
4398 g_object_notify (G_OBJECT (self), property_name: "cursor-position");
4399 }
4400
4401 if (selection_bound != -1 &&
4402 priv->selection_bound != selection_bound)
4403 {
4404 priv->selection_bound = selection_bound;
4405 changed = TRUE;
4406
4407 g_object_notify (G_OBJECT (self), property_name: "selection-bound");
4408 }
4409
4410 g_object_thaw_notify (G_OBJECT (self));
4411
4412 if (priv->current_pos != priv->selection_bound)
4413 {
4414 if (!priv->selection_node)
4415 {
4416 GtkCssNode *widget_node = gtk_widget_get_css_node (GTK_WIDGET (self));
4417
4418 priv->selection_node = gtk_css_node_new ();
4419 gtk_css_node_set_name (cssnode: priv->selection_node, name: g_quark_from_static_string (string: "selection"));
4420 gtk_css_node_set_parent (cssnode: priv->selection_node, parent: widget_node);
4421 gtk_css_node_set_state (cssnode: priv->selection_node, state_flags: gtk_css_node_get_state (cssnode: widget_node));
4422 g_object_unref (object: priv->selection_node);
4423 }
4424 }
4425 else
4426 {
4427 if (priv->selection_node)
4428 {
4429 gtk_css_node_set_parent (cssnode: priv->selection_node, NULL);
4430 priv->selection_node = NULL;
4431 }
4432 }
4433
4434 if (changed)
4435 {
4436 gtk_text_update_clipboard_actions (self);
4437 gtk_text_recompute (self);
4438 }
4439}
4440
4441static void
4442gtk_text_reset_layout (GtkText *self)
4443{
4444 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
4445
4446 if (priv->cached_layout)
4447 {
4448 g_object_unref (object: priv->cached_layout);
4449 priv->cached_layout = NULL;
4450 }
4451}
4452
4453static void
4454gtk_text_recompute (GtkText *self)
4455{
4456 gtk_text_reset_layout (self);
4457 gtk_widget_queue_draw (GTK_WIDGET (self));
4458
4459 if (!gtk_widget_get_mapped (GTK_WIDGET (self)))
4460 return;
4461
4462 gtk_text_check_cursor_blink (self);
4463 gtk_text_adjust_scroll (self);
4464 update_im_cursor_location (self);
4465 gtk_text_update_handles (self);
4466}
4467
4468static PangoLayout *
4469gtk_text_create_layout (GtkText *self,
4470 gboolean include_preedit)
4471{
4472 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
4473 GtkWidget *widget = GTK_WIDGET (self);
4474 PangoLayout *layout;
4475 PangoAttrList *tmp_attrs = NULL;
4476 char *preedit_string = NULL;
4477 int preedit_length = 0;
4478 PangoAttrList *preedit_attrs = NULL;
4479 char *display_text;
4480 guint n_bytes;
4481
4482 layout = gtk_widget_create_pango_layout (widget, NULL);
4483 pango_layout_set_single_paragraph_mode (layout, TRUE);
4484
4485 tmp_attrs = gtk_css_style_get_pango_attributes (style: gtk_css_node_get_style (cssnode: gtk_widget_get_css_node (widget)));
4486 if (!tmp_attrs)
4487 tmp_attrs = pango_attr_list_new ();
4488 tmp_attrs = _gtk_pango_attr_list_merge (into: tmp_attrs, from: priv->attrs);
4489
4490 display_text = gtk_text_get_display_text (self, start_pos: 0, end_pos: -1);
4491
4492 n_bytes = strlen (s: display_text);
4493
4494 if (include_preedit)
4495 {
4496 gtk_im_context_get_preedit_string (context: priv->im_context,
4497 str: &preedit_string, attrs: &preedit_attrs, NULL);
4498 preedit_length = priv->preedit_length;
4499 }
4500
4501 if (preedit_length)
4502 {
4503 GString *tmp_string = g_string_new (init: display_text);
4504 int pos;
4505
4506 pos = g_utf8_offset_to_pointer (str: display_text, offset: priv->current_pos) - display_text;
4507 g_string_insert (string: tmp_string, pos, val: preedit_string);
4508 pango_layout_set_text (layout, text: tmp_string->str, length: tmp_string->len);
4509 pango_attr_list_splice (list: tmp_attrs, other: preedit_attrs, pos, len: preedit_length);
4510 g_string_free (string: tmp_string, TRUE);
4511 }
4512 else
4513 {
4514 PangoDirection pango_dir;
4515
4516 if (gtk_text_get_display_mode (self) == DISPLAY_NORMAL)
4517 pango_dir = gdk_find_base_dir (text: display_text, len: n_bytes);
4518 else
4519 pango_dir = PANGO_DIRECTION_NEUTRAL;
4520
4521 if (pango_dir == PANGO_DIRECTION_NEUTRAL)
4522 {
4523 if (gtk_widget_has_focus (widget))
4524 {
4525 GdkDisplay *display;
4526 GdkSeat *seat;
4527 GdkDevice *keyboard = NULL;
4528 PangoDirection direction = PANGO_DIRECTION_LTR;
4529
4530 display = gtk_widget_get_display (widget);
4531 seat = gdk_display_get_default_seat (display);
4532 if (seat)
4533 keyboard = gdk_seat_get_keyboard (seat);
4534 if (keyboard)
4535 direction = gdk_device_get_direction (device: keyboard);
4536
4537 if (direction == PANGO_DIRECTION_RTL)
4538 pango_dir = PANGO_DIRECTION_RTL;
4539 else
4540 pango_dir = PANGO_DIRECTION_LTR;
4541 }
4542 else
4543 {
4544 if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
4545 pango_dir = PANGO_DIRECTION_RTL;
4546 else
4547 pango_dir = PANGO_DIRECTION_LTR;
4548 }
4549 }
4550
4551 pango_context_set_base_dir (context: gtk_widget_get_pango_context (widget), direction: pango_dir);
4552
4553 priv->resolved_dir = pango_dir;
4554
4555 pango_layout_set_text (layout, text: display_text, length: n_bytes);
4556 }
4557
4558 pango_layout_set_attributes (layout, attrs: tmp_attrs);
4559
4560 if (priv->tabs)
4561 pango_layout_set_tabs (layout, tabs: priv->tabs);
4562
4563 g_free (mem: preedit_string);
4564 g_free (mem: display_text);
4565
4566 pango_attr_list_unref (list: preedit_attrs);
4567 pango_attr_list_unref (list: tmp_attrs);
4568
4569 return layout;
4570}
4571
4572static PangoLayout *
4573gtk_text_ensure_layout (GtkText *self,
4574 gboolean include_preedit)
4575{
4576 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
4577
4578 if (priv->preedit_length > 0 &&
4579 !include_preedit != !priv->cache_includes_preedit)
4580 gtk_text_reset_layout (self);
4581
4582 if (!priv->cached_layout)
4583 {
4584 priv->cached_layout = gtk_text_create_layout (self, include_preedit);
4585 priv->cache_includes_preedit = include_preedit;
4586 }
4587
4588 return priv->cached_layout;
4589}
4590
4591static void
4592get_layout_position (GtkText *self,
4593 int *x,
4594 int *y)
4595{
4596 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
4597 const int text_height = gtk_widget_get_height (GTK_WIDGET (self));
4598 PangoLayout *layout;
4599 PangoRectangle logical_rect;
4600 int y_pos, area_height;
4601 PangoLayoutLine *line;
4602
4603 layout = gtk_text_ensure_layout (self, TRUE);
4604
4605 area_height = PANGO_SCALE * text_height;
4606
4607 line = pango_layout_get_lines_readonly (layout)->data;
4608 pango_layout_line_get_extents (line, NULL, logical_rect: &logical_rect);
4609
4610 /* Align primarily for locale's ascent/descent */
4611 if (priv->text_baseline < 0)
4612 y_pos = ((area_height - priv->ascent - priv->descent) / 2 +
4613 priv->ascent + logical_rect.y);
4614 else
4615 y_pos = PANGO_SCALE * priv->text_baseline - pango_layout_get_baseline (layout);
4616
4617 /* Now see if we need to adjust to fit in actual drawn string */
4618 if (logical_rect.height > area_height)
4619 y_pos = (area_height - logical_rect.height) / 2;
4620 else if (y_pos < 0)
4621 y_pos = 0;
4622 else if (y_pos + logical_rect.height > area_height)
4623 y_pos = area_height - logical_rect.height;
4624
4625 y_pos = y_pos / PANGO_SCALE;
4626
4627 if (x)
4628 *x = - priv->scroll_offset;
4629
4630 if (y)
4631 *y = y_pos;
4632}
4633
4634#define GRAPHENE_RECT_FROM_RECT(_r) (GRAPHENE_RECT_INIT ((_r)->x, (_r)->y, (_r)->width, (_r)->height))
4635
4636static void
4637gtk_text_draw_text (GtkText *self,
4638 GtkSnapshot *snapshot)
4639{
4640 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
4641 GtkWidget *widget = GTK_WIDGET (self);
4642 GtkStyleContext *context;
4643 PangoLayout *layout;
4644 int x, y;
4645
4646 /* Nothing to display at all */
4647 if (gtk_text_get_display_mode (self) == DISPLAY_BLANK)
4648 return;
4649
4650 context = gtk_widget_get_style_context (widget);
4651 layout = gtk_text_ensure_layout (self, TRUE);
4652
4653 gtk_text_get_layout_offsets (entry: self, x: &x, y: &y);
4654
4655 gtk_snapshot_render_layout (snapshot, context, x, y, layout);
4656
4657 if (priv->selection_bound != priv->current_pos)
4658 {
4659 const char *text = pango_layout_get_text (layout);
4660 int start_index = g_utf8_offset_to_pointer (str: text, offset: priv->selection_bound) - text;
4661 int end_index = g_utf8_offset_to_pointer (str: text, offset: priv->current_pos) - text;
4662 cairo_region_t *clip;
4663 cairo_rectangle_int_t clip_extents;
4664 int range[2];
4665 int width, height;
4666
4667 width = gtk_widget_get_width (widget);
4668 height = gtk_widget_get_height (widget);
4669
4670 range[0] = MIN (start_index, end_index);
4671 range[1] = MAX (start_index, end_index);
4672
4673 gtk_style_context_save_to_node (context, node: priv->selection_node);
4674
4675 clip = gdk_pango_layout_get_clip_region (layout, x_origin: x, y_origin: y, index_ranges: range, n_ranges: 1);
4676 cairo_region_get_extents (region: clip, extents: &clip_extents);
4677
4678 gtk_snapshot_push_clip (snapshot, bounds: &GRAPHENE_RECT_FROM_RECT (&clip_extents));
4679 gtk_snapshot_render_background (snapshot, context, x: 0, y: 0, width, height);
4680 gtk_snapshot_render_layout (snapshot, context, x, y, layout);
4681 gtk_snapshot_pop (snapshot);
4682
4683 cairo_region_destroy (region: clip);
4684
4685 gtk_style_context_restore (context);
4686 }
4687}
4688
4689static void
4690gtk_text_draw_cursor (GtkText *self,
4691 GtkSnapshot *snapshot,
4692 CursorType type)
4693{
4694 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
4695 GtkWidget *widget = GTK_WIDGET (self);
4696 GtkStyleContext *context;
4697 PangoRectangle cursor_rect;
4698 int cursor_index;
4699 gboolean block;
4700 gboolean block_at_line_end;
4701 PangoLayout *layout;
4702 const char *text;
4703 int x, y;
4704
4705 context = gtk_widget_get_style_context (widget);
4706
4707 layout = g_object_ref (gtk_text_ensure_layout (self, TRUE));
4708 text = pango_layout_get_text (layout);
4709 gtk_text_get_layout_offsets (entry: self, x: &x, y: &y);
4710
4711 if (type == CURSOR_DND)
4712 cursor_index = g_utf8_offset_to_pointer (str: text, offset: priv->dnd_position) - text;
4713 else
4714 cursor_index = g_utf8_offset_to_pointer (str: text, offset: priv->current_pos + priv->preedit_cursor) - text;
4715
4716 if (!priv->overwrite_mode)
4717 block = FALSE;
4718 else
4719 block = _gtk_text_util_get_block_cursor_location (layout,
4720 index_: cursor_index, rectangle: &cursor_rect, at_line_end: &block_at_line_end);
4721 if (!block)
4722 {
4723 gtk_snapshot_render_insertion_cursor (snapshot, context,
4724 x, y,
4725 layout, index: cursor_index, direction: priv->resolved_dir);
4726 }
4727 else /* overwrite_mode */
4728 {
4729 int width = gtk_widget_get_width (widget);
4730 int height = gtk_widget_get_height (widget);
4731 graphene_rect_t bounds;
4732
4733 bounds.origin.x = PANGO_PIXELS (cursor_rect.x) + x;
4734 bounds.origin.y = PANGO_PIXELS (cursor_rect.y) + y;
4735 bounds.size.width = PANGO_PIXELS (cursor_rect.width);
4736 bounds.size.height = PANGO_PIXELS (cursor_rect.height);
4737
4738 gtk_style_context_save_to_node (context, node: priv->block_cursor_node);
4739
4740 gtk_snapshot_push_clip (snapshot, bounds: &bounds);
4741 gtk_snapshot_render_background (snapshot, context, x: 0, y: 0, width, height);
4742 gtk_snapshot_render_layout (snapshot, context, x, y, layout);
4743 gtk_snapshot_pop (snapshot);
4744
4745 gtk_style_context_restore (context);
4746 }
4747
4748 g_object_unref (object: layout);
4749}
4750
4751static void
4752gtk_text_handle_dragged (GtkTextHandle *handle,
4753 int x,
4754 int y,
4755 GtkText *self)
4756{
4757 int cursor_pos, selection_bound_pos, tmp_pos, *old_pos;
4758 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
4759
4760 gtk_text_selection_bubble_popup_unset (self);
4761
4762 cursor_pos = priv->current_pos;
4763 selection_bound_pos = priv->selection_bound;
4764
4765 tmp_pos = gtk_text_find_position (self, x: x + priv->scroll_offset);
4766
4767 if (handle == priv->text_handles[TEXT_HANDLE_CURSOR])
4768 {
4769 /* Avoid running past the other handle in selection mode */
4770 if (tmp_pos >= selection_bound_pos &&
4771 gtk_widget_is_visible (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])))
4772 {
4773 tmp_pos = selection_bound_pos - 1;
4774 }
4775
4776 old_pos = &cursor_pos;
4777 }
4778 else if (handle == priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])
4779 {
4780 /* Avoid running past the other handle */
4781 if (tmp_pos <= cursor_pos)
4782 tmp_pos = cursor_pos + 1;
4783
4784 old_pos = &selection_bound_pos;
4785 }
4786 else
4787 g_assert_not_reached ();
4788
4789 if (tmp_pos != *old_pos)
4790 {
4791 *old_pos = tmp_pos;
4792
4793 if (handle == priv->text_handles[TEXT_HANDLE_CURSOR] &&
4794 !gtk_widget_is_visible (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])))
4795 gtk_text_set_positions (self, current_pos: cursor_pos, selection_bound: cursor_pos);
4796 else
4797 gtk_text_set_positions (self, current_pos: cursor_pos, selection_bound: selection_bound_pos);
4798
4799 if (handle == priv->text_handles[TEXT_HANDLE_CURSOR])
4800 priv->cursor_handle_dragged = TRUE;
4801 else if (handle == priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])
4802 priv->selection_handle_dragged = TRUE;
4803
4804 gtk_text_update_handles (self);
4805 }
4806
4807 gtk_text_show_magnifier (self, x, y);
4808}
4809
4810static void
4811gtk_text_handle_drag_started (GtkTextHandle *handle,
4812 GtkText *self)
4813{
4814 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
4815
4816 priv->cursor_handle_dragged = FALSE;
4817 priv->selection_handle_dragged = FALSE;
4818}
4819
4820static void
4821gtk_text_handle_drag_finished (GtkTextHandle *handle,
4822 GtkText *self)
4823{
4824 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
4825
4826 if (!priv->cursor_handle_dragged && !priv->selection_handle_dragged)
4827 {
4828 GtkSettings *settings;
4829 guint double_click_time;
4830
4831 settings = gtk_widget_get_settings (GTK_WIDGET (self));
4832 g_object_get (object: settings, first_property_name: "gtk-double-click-time", &double_click_time, NULL);
4833 if (g_get_monotonic_time() - priv->handle_place_time < double_click_time * 1000)
4834 {
4835 gtk_text_select_word (self);
4836 gtk_text_update_handles (self);
4837 }
4838 else
4839 gtk_text_selection_bubble_popup_set (self);
4840 }
4841
4842 if (priv->magnifier_popover)
4843 gtk_popover_popdown (GTK_POPOVER (priv->magnifier_popover));
4844}
4845
4846static void
4847gtk_text_schedule_im_reset (GtkText *self)
4848{
4849 GtkTextPrivate *priv;
4850
4851 priv = gtk_text_get_instance_private (self);
4852
4853 priv->need_im_reset = TRUE;
4854}
4855
4856void
4857gtk_text_reset_im_context (GtkText *self)
4858{
4859 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
4860
4861 g_return_if_fail (GTK_IS_TEXT (self));
4862
4863 if (priv->need_im_reset)
4864 {
4865 priv->need_im_reset = FALSE;
4866 gtk_im_context_reset (context: priv->im_context);
4867 }
4868}
4869
4870static int
4871gtk_text_find_position (GtkText *self,
4872 int x)
4873{
4874 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
4875 PangoLayout *layout;
4876 PangoLayoutLine *line;
4877 int index;
4878 int pos;
4879 int trailing;
4880 const char *text;
4881 int cursor_index;
4882
4883 layout = gtk_text_ensure_layout (self, TRUE);
4884 text = pango_layout_get_text (layout);
4885 cursor_index = g_utf8_offset_to_pointer (str: text, offset: priv->current_pos) - text;
4886
4887 line = pango_layout_get_lines_readonly (layout)->data;
4888 pango_layout_line_x_to_index (line, x_pos: x * PANGO_SCALE, index_: &index, trailing: &trailing);
4889
4890 if (index >= cursor_index && priv->preedit_length)
4891 {
4892 if (index >= cursor_index + priv->preedit_length)
4893 index -= priv->preedit_length;
4894 else
4895 {
4896 index = cursor_index;
4897 trailing = 0;
4898 }
4899 }
4900
4901 pos = g_utf8_pointer_to_offset (str: text, pos: text + index);
4902 pos += trailing;
4903
4904 return pos;
4905}
4906
4907static void
4908gtk_text_get_cursor_locations (GtkText *self,
4909 int *strong_x,
4910 int *weak_x)
4911{
4912 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
4913 DisplayMode mode = gtk_text_get_display_mode (self);
4914
4915 /* Nothing to display at all, so no cursor is relevant */
4916 if (mode == DISPLAY_BLANK)
4917 {
4918 if (strong_x)
4919 *strong_x = 0;
4920
4921 if (weak_x)
4922 *weak_x = 0;
4923 }
4924 else
4925 {
4926 PangoLayout *layout = gtk_text_ensure_layout (self, TRUE);
4927 const char *text = pango_layout_get_text (layout);
4928 PangoRectangle strong_pos, weak_pos;
4929 int index;
4930
4931 index = g_utf8_offset_to_pointer (str: text, offset: priv->current_pos + priv->preedit_cursor) - text;
4932
4933 pango_layout_get_cursor_pos (layout, index_: index, strong_pos: &strong_pos, weak_pos: &weak_pos);
4934
4935 if (strong_x)
4936 *strong_x = strong_pos.x / PANGO_SCALE;
4937
4938 if (weak_x)
4939 *weak_x = weak_pos.x / PANGO_SCALE;
4940 }
4941}
4942
4943static gboolean
4944gtk_text_get_is_selection_handle_dragged (GtkText *self)
4945{
4946 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
4947 GtkTextHandle *handle;
4948
4949 if (priv->current_pos >= priv->selection_bound)
4950 handle = priv->text_handles[TEXT_HANDLE_CURSOR];
4951 else
4952 handle = priv->text_handles[TEXT_HANDLE_SELECTION_BOUND];
4953
4954 return handle && gtk_text_handle_get_is_dragged (handle);
4955}
4956
4957static void
4958gtk_text_get_scroll_limits (GtkText *self,
4959 int *min_offset,
4960 int *max_offset)
4961{
4962 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
4963 float xalign;
4964 PangoLayout *layout;
4965 PangoLayoutLine *line;
4966 PangoRectangle logical_rect;
4967 int text_width, width;
4968
4969 layout = gtk_text_ensure_layout (self, TRUE);
4970 line = pango_layout_get_lines_readonly (layout)->data;
4971
4972 pango_layout_line_get_extents (line, NULL, logical_rect: &logical_rect);
4973
4974 /* Display as much text as we can */
4975
4976 if (priv->resolved_dir == PANGO_DIRECTION_LTR)
4977 xalign = priv->xalign;
4978 else
4979 xalign = 1.0 - priv->xalign;
4980
4981 text_width = PANGO_PIXELS(logical_rect.width);
4982 width = gtk_widget_get_width (GTK_WIDGET (self));
4983
4984 if (text_width > width)
4985 {
4986 *min_offset = 0;
4987 *max_offset = text_width - width;
4988 }
4989 else
4990 {
4991 *min_offset = (text_width - width) * xalign;
4992 *max_offset = *min_offset;
4993 }
4994}
4995
4996static void
4997gtk_text_adjust_scroll (GtkText *self)
4998{
4999 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
5000 const int text_width = gtk_widget_get_width (GTK_WIDGET (self));
5001 int min_offset, max_offset;
5002 int strong_x, weak_x;
5003 int strong_xoffset, weak_xoffset;
5004
5005 if (!gtk_widget_get_realized (GTK_WIDGET (self)))
5006 return;
5007
5008 gtk_text_get_scroll_limits (self, min_offset: &min_offset, max_offset: &max_offset);
5009
5010 priv->scroll_offset = CLAMP (priv->scroll_offset, min_offset, max_offset);
5011
5012 if (gtk_text_get_is_selection_handle_dragged (self))
5013 {
5014 /* The text handle corresponding to the selection bound is
5015 * being dragged, ensure it stays onscreen even if we scroll
5016 * cursors away, this is so both handles can cause content
5017 * to scroll.
5018 */
5019 strong_x = weak_x = gtk_text_get_selection_bound_location (self);
5020 }
5021 else
5022 {
5023 /* And make sure cursors are on screen. Note that the cursor is
5024 * actually drawn one pixel into the INNER_BORDER space on
5025 * the right, when the scroll is at the utmost right. This
5026 * looks better to me than confining the cursor inside the
5027 * border entirely, though it means that the cursor gets one
5028 * pixel closer to the edge of the widget on the right than
5029 * on the left. This might need changing if one changed
5030 * INNER_BORDER from 2 to 1, as one would do on a
5031 * small-screen-real-estate display.
5032 *
5033 * We always make sure that the strong cursor is on screen, and
5034 * put the weak cursor on screen if possible.
5035 */
5036 gtk_text_get_cursor_locations (self, strong_x: &strong_x, weak_x: &weak_x);
5037 }
5038
5039 strong_xoffset = strong_x - priv->scroll_offset;
5040
5041 if (strong_xoffset < 0)
5042 {
5043 priv->scroll_offset += strong_xoffset;
5044 strong_xoffset = 0;
5045 }
5046 else if (strong_xoffset > text_width)
5047 {
5048 priv->scroll_offset += strong_xoffset - text_width;
5049 strong_xoffset = text_width;
5050 }
5051
5052 weak_xoffset = weak_x - priv->scroll_offset;
5053
5054 if (weak_xoffset < 0 && strong_xoffset - weak_xoffset <= text_width)
5055 {
5056 priv->scroll_offset += weak_xoffset;
5057 }
5058 else if (weak_xoffset > text_width &&
5059 strong_xoffset - (weak_xoffset - text_width) >= 0)
5060 {
5061 priv->scroll_offset += weak_xoffset - text_width;
5062 }
5063
5064 g_object_notify_by_pspec (G_OBJECT (self), pspec: text_props[PROP_SCROLL_OFFSET]);
5065
5066 gtk_text_update_handles (self);
5067}
5068
5069static int
5070gtk_text_move_visually (GtkText *self,
5071 int start,
5072 int count)
5073{
5074 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
5075 int index;
5076 PangoLayout *layout = gtk_text_ensure_layout (self, FALSE);
5077 const char *text;
5078 gboolean split_cursor;
5079 gboolean strong;
5080
5081 text = pango_layout_get_text (layout);
5082
5083 index = g_utf8_offset_to_pointer (str: text, offset: start) - text;
5084
5085
5086 g_object_get (object: gtk_widget_get_settings (GTK_WIDGET (self)),
5087 first_property_name: "gtk-split-cursor", &split_cursor,
5088 NULL);
5089
5090 if (split_cursor)
5091 strong = TRUE;
5092 else
5093 {
5094 GdkDisplay *display;
5095 GdkSeat *seat;
5096 GdkDevice *keyboard = NULL;
5097 PangoDirection direction = PANGO_DIRECTION_LTR;
5098
5099 display = gtk_widget_get_display (GTK_WIDGET (self));
5100 seat = gdk_display_get_default_seat (display);
5101 if (seat)
5102 keyboard = gdk_seat_get_keyboard (seat);
5103 if (keyboard)
5104 direction = gdk_device_get_direction (device: keyboard);
5105
5106 strong = direction == priv->resolved_dir;
5107 }
5108
5109 while (count != 0)
5110 {
5111 int new_index, new_trailing;
5112
5113 if (count > 0)
5114 {
5115 pango_layout_move_cursor_visually (layout, strong, old_index: index, old_trailing: 0, direction: 1, new_index: &new_index, new_trailing: &new_trailing);
5116 count--;
5117 }
5118 else
5119 {
5120 pango_layout_move_cursor_visually (layout, strong, old_index: index, old_trailing: 0, direction: -1, new_index: &new_index, new_trailing: &new_trailing);
5121 count++;
5122 }
5123
5124 if (new_index < 0)
5125 index = 0;
5126 else if (new_index != G_MAXINT)
5127 index = new_index;
5128
5129 while (new_trailing--)
5130 index = g_utf8_next_char (text + index) - text;
5131 }
5132
5133 return g_utf8_pointer_to_offset (str: text, pos: text + index);
5134}
5135
5136static int
5137gtk_text_move_logically (GtkText *self,
5138 int start,
5139 int count)
5140{
5141 int new_pos = start;
5142 guint length;
5143
5144 length = gtk_entry_buffer_get_length (buffer: get_buffer (self));
5145
5146 /* Prevent any leak of information */
5147 if (gtk_text_get_display_mode (self) != DISPLAY_NORMAL)
5148 {
5149 new_pos = CLAMP (start + count, 0, length);
5150 }
5151 else
5152 {
5153 PangoLayout *layout = gtk_text_ensure_layout (self, FALSE);
5154 const PangoLogAttr *log_attrs;
5155 int n_attrs;
5156
5157 log_attrs = pango_layout_get_log_attrs_readonly (layout, n_attrs: &n_attrs);
5158
5159 while (count > 0 && new_pos < length)
5160 {
5161 do
5162 new_pos++;
5163 while (new_pos < length && !log_attrs[new_pos].is_cursor_position);
5164
5165 count--;
5166 }
5167 while (count < 0 && new_pos > 0)
5168 {
5169 do
5170 new_pos--;
5171 while (new_pos > 0 && !log_attrs[new_pos].is_cursor_position);
5172
5173 count++;
5174 }
5175 }
5176
5177 return new_pos;
5178}
5179
5180static int
5181gtk_text_move_forward_word (GtkText *self,
5182 int start,
5183 gboolean allow_whitespace)
5184{
5185 int new_pos = start;
5186 guint length;
5187
5188 length = gtk_entry_buffer_get_length (buffer: get_buffer (self));
5189
5190 /* Prevent any leak of information */
5191 if (gtk_text_get_display_mode (self) != DISPLAY_NORMAL)
5192 {
5193 new_pos = length;
5194 }
5195 else if (new_pos < length)
5196 {
5197 PangoLayout *layout = gtk_text_ensure_layout (self, FALSE);
5198 const PangoLogAttr *log_attrs;
5199 int n_attrs;
5200
5201 log_attrs = pango_layout_get_log_attrs_readonly (layout, n_attrs: &n_attrs);
5202
5203 /* Find the next word boundary */
5204 new_pos++;
5205 while (new_pos < n_attrs - 1 && !(log_attrs[new_pos].is_word_end ||
5206 (log_attrs[new_pos].is_word_start && allow_whitespace)))
5207 new_pos++;
5208 }
5209
5210 return new_pos;
5211}
5212
5213
5214static int
5215gtk_text_move_backward_word (GtkText *self,
5216 int start,
5217 gboolean allow_whitespace)
5218{
5219 int new_pos = start;
5220
5221 /* Prevent any leak of information */
5222 if (gtk_text_get_display_mode (self) != DISPLAY_NORMAL)
5223 {
5224 new_pos = 0;
5225 }
5226 else if (start > 0)
5227 {
5228 PangoLayout *layout = gtk_text_ensure_layout (self, FALSE);
5229 const PangoLogAttr *log_attrs;
5230 int n_attrs;
5231
5232 log_attrs = pango_layout_get_log_attrs_readonly (layout, n_attrs: &n_attrs);
5233
5234 new_pos = start - 1;
5235
5236 /* Find the previous word boundary */
5237 while (new_pos > 0 && !(log_attrs[new_pos].is_word_start ||
5238 (log_attrs[new_pos].is_word_end && allow_whitespace)))
5239 new_pos--;
5240 }
5241
5242 return new_pos;
5243}
5244
5245static void
5246gtk_text_delete_whitespace (GtkText *self)
5247{
5248 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
5249 PangoLayout *layout = gtk_text_ensure_layout (self, FALSE);
5250 const PangoLogAttr *log_attrs;
5251 int n_attrs;
5252 int start, end;
5253
5254 log_attrs = pango_layout_get_log_attrs_readonly (layout, n_attrs: &n_attrs);
5255
5256 start = end = priv->current_pos;
5257
5258 while (start > 0 && log_attrs[start-1].is_white)
5259 start--;
5260
5261 while (end < n_attrs && log_attrs[end].is_white)
5262 end++;
5263
5264 if (start != end)
5265 gtk_editable_delete_text (GTK_EDITABLE (self), start_pos: start, end_pos: end);
5266}
5267
5268
5269static void
5270gtk_text_select_word (GtkText *self)
5271{
5272 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
5273 int start_pos = gtk_text_move_backward_word (self, start: priv->current_pos, TRUE);
5274 int end_pos = gtk_text_move_forward_word (self, start: priv->current_pos, TRUE);
5275
5276 gtk_text_set_selection_bounds (self, start: start_pos, end: end_pos);
5277}
5278
5279static void
5280gtk_text_select_line (GtkText *self)
5281{
5282 gtk_text_set_selection_bounds (self, start: 0, end: -1);
5283}
5284
5285static int
5286truncate_multiline (const char *text)
5287{
5288 int length;
5289
5290 for (length = 0;
5291 text[length] && text[length] != '\n' && text[length] != '\r';
5292 length++);
5293
5294 return length;
5295}
5296
5297static void
5298paste_received (GObject *clipboard,
5299 GAsyncResult *result,
5300 gpointer data)
5301{
5302 GtkText *self = GTK_TEXT (data);
5303 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
5304 char *text;
5305 int pos, start, end;
5306 int length = -1;
5307
5308 text = gdk_clipboard_read_text_finish (GDK_CLIPBOARD (clipboard), result, NULL);
5309 if (text == NULL)
5310 {
5311 gtk_widget_error_bell (GTK_WIDGET (self));
5312 return;
5313 }
5314
5315 if (priv->insert_pos >= 0)
5316 {
5317 pos = priv->insert_pos;
5318 start = priv->selection_bound;
5319 end = priv->current_pos;
5320 if (!((start <= pos && pos <= end) || (end <= pos && pos <= start)))
5321 gtk_text_set_selection_bounds (self, start: pos, end: pos);
5322 priv->insert_pos = -1;
5323 }
5324
5325 if (priv->truncate_multiline)
5326 length = truncate_multiline (text);
5327
5328 begin_change (self);
5329 if (priv->selection_bound != priv->current_pos)
5330 gtk_text_delete_selection (self);
5331
5332 pos = priv->current_pos;
5333 gtk_editable_insert_text (GTK_EDITABLE (self), text, length, position: &pos);
5334 gtk_text_set_selection_bounds (self, start: pos, end: pos);
5335 end_change (self);
5336
5337 g_free (mem: text);
5338 g_object_unref (object: self);
5339}
5340
5341static void
5342gtk_text_paste (GtkText *self,
5343 GdkClipboard *clipboard)
5344{
5345 gdk_clipboard_read_text_async (clipboard, NULL, callback: paste_received, g_object_ref (self));
5346}
5347
5348static void
5349gtk_text_update_primary_selection (GtkText *self)
5350{
5351 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
5352 GdkClipboard *clipboard;
5353
5354 if (!gtk_widget_get_realized (GTK_WIDGET (self)))
5355 return;
5356
5357 clipboard = gtk_widget_get_primary_clipboard (GTK_WIDGET (self));
5358
5359 if (priv->selection_bound != priv->current_pos)
5360 {
5361 gdk_clipboard_set_content (clipboard, provider: priv->selection_content);
5362 }
5363 else
5364 {
5365 if (gdk_clipboard_get_content (clipboard) == priv->selection_content)
5366 gdk_clipboard_set_content (clipboard, NULL);
5367 }
5368}
5369
5370/* Public API
5371 */
5372
5373/**
5374 * gtk_text_new:
5375 *
5376 * Creates a new `GtkText`.
5377 *
5378 * Returns: a new `GtkText`.
5379 */
5380GtkWidget *
5381gtk_text_new (void)
5382{
5383 return g_object_new (GTK_TYPE_TEXT, NULL);
5384}
5385
5386/**
5387 * gtk_text_new_with_buffer:
5388 * @buffer: The buffer to use for the new `GtkText`.
5389 *
5390 * Creates a new `GtkText` with the specified text buffer.
5391 *
5392 * Returns: a new `GtkText`
5393 */
5394GtkWidget *
5395gtk_text_new_with_buffer (GtkEntryBuffer *buffer)
5396{
5397 g_return_val_if_fail (GTK_IS_ENTRY_BUFFER (buffer), NULL);
5398
5399 return g_object_new (GTK_TYPE_TEXT, first_property_name: "buffer", buffer, NULL);
5400}
5401
5402static GtkEntryBuffer *
5403get_buffer (GtkText *self)
5404{
5405 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
5406
5407 if (priv->buffer == NULL)
5408 {
5409 GtkEntryBuffer *buffer;
5410 buffer = gtk_entry_buffer_new (NULL, n_initial_chars: 0);
5411 gtk_text_set_buffer (self, buffer);
5412 g_object_unref (object: buffer);
5413 }
5414
5415 return priv->buffer;
5416}
5417
5418/**
5419 * gtk_text_get_buffer: (attributes org.gtk.Method.get_property=buffer)
5420 * @self: a `GtkText`
5421 *
5422 * Get the `GtkEntryBuffer` object which holds the text for
5423 * this widget.
5424 *
5425 * Returns: (transfer none): A `GtkEntryBuffer` object.
5426 */
5427GtkEntryBuffer *
5428gtk_text_get_buffer (GtkText *self)
5429{
5430 g_return_val_if_fail (GTK_IS_TEXT (self), NULL);
5431
5432 return get_buffer (self);
5433}
5434
5435/**
5436 * gtk_text_set_buffer: (attributes org.gtk.Method.set_property=buffer)
5437 * @self: a `GtkText`
5438 * @buffer: a `GtkEntryBuffer`
5439 *
5440 * Set the `GtkEntryBuffer` object which holds the text for
5441 * this widget.
5442 */
5443void
5444gtk_text_set_buffer (GtkText *self,
5445 GtkEntryBuffer *buffer)
5446{
5447 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
5448 GObject *obj;
5449 gboolean had_buffer = FALSE;
5450 guint old_length = 0;
5451 guint new_length = 0;
5452
5453 g_return_if_fail (GTK_IS_TEXT (self));
5454
5455 if (buffer)
5456 {
5457 g_return_if_fail (GTK_IS_ENTRY_BUFFER (buffer));
5458 g_object_ref (buffer);
5459 }
5460
5461 if (priv->buffer)
5462 {
5463 had_buffer = TRUE;
5464 old_length = gtk_entry_buffer_get_length (buffer: priv->buffer);
5465 buffer_disconnect_signals (self);
5466 g_object_unref (object: priv->buffer);
5467 }
5468
5469 priv->buffer = buffer;
5470
5471 if (priv->buffer)
5472 {
5473 new_length = gtk_entry_buffer_get_length (buffer: priv->buffer);
5474 buffer_connect_signals (self);
5475 }
5476
5477 update_placeholder_visibility (self);
5478
5479 obj = G_OBJECT (self);
5480 g_object_freeze_notify (object: obj);
5481 g_object_notify_by_pspec (object: obj, pspec: text_props[PROP_BUFFER]);
5482 g_object_notify_by_pspec (object: obj, pspec: text_props[PROP_MAX_LENGTH]);
5483 if (old_length != 0 || new_length != 0)
5484 g_object_notify (object: obj, property_name: "text");
5485
5486 if (had_buffer)
5487 {
5488 gtk_text_set_selection_bounds (self, start: 0, end: 0);
5489 gtk_text_recompute (self);
5490 }
5491
5492 g_object_thaw_notify (object: obj);
5493}
5494
5495static void
5496gtk_text_set_editable (GtkText *self,
5497 gboolean is_editable)
5498{
5499 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
5500
5501 if (is_editable != priv->editable)
5502 {
5503 GtkWidget *widget = GTK_WIDGET (self);
5504
5505 if (!is_editable)
5506 {
5507 gtk_text_reset_im_context (self);
5508 if (gtk_widget_has_focus (widget))
5509 gtk_im_context_focus_out (context: priv->im_context);
5510
5511 priv->preedit_length = 0;
5512 priv->preedit_cursor = 0;
5513
5514 gtk_widget_remove_css_class (GTK_WIDGET (self), css_class: "read-only");
5515 }
5516 else
5517 {
5518 gtk_widget_add_css_class (GTK_WIDGET (self), css_class: "read-only");
5519 }
5520
5521 priv->editable = is_editable;
5522
5523 if (is_editable && gtk_widget_has_focus (widget))
5524 gtk_im_context_focus_in (context: priv->im_context);
5525
5526 gtk_event_controller_key_set_im_context (GTK_EVENT_CONTROLLER_KEY (priv->key_controller),
5527 im_context: is_editable ? priv->im_context : NULL);
5528
5529 gtk_text_update_clipboard_actions (self);
5530 gtk_text_update_emoji_action (self);
5531
5532 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: self),
5533 first_property: GTK_ACCESSIBLE_PROPERTY_READ_ONLY, !priv->editable,
5534 -1);
5535
5536 g_object_notify (G_OBJECT (self), property_name: "editable");
5537 }
5538}
5539
5540static void
5541gtk_text_set_text (GtkText *self,
5542 const char *text)
5543{
5544 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
5545 int tmp_pos;
5546
5547 g_return_if_fail (GTK_IS_TEXT (self));
5548 g_return_if_fail (text != NULL);
5549
5550 /* Actually setting the text will affect the cursor and selection;
5551 * if the contents don't actually change, this will look odd to the user.
5552 */
5553 if (strcmp (s1: gtk_entry_buffer_get_text (buffer: get_buffer (self)), s2: text) == 0)
5554 return;
5555
5556 gtk_text_history_begin_irreversible_action (self: priv->history);
5557
5558 begin_change (self);
5559 g_object_freeze_notify (G_OBJECT (self));
5560 gtk_editable_delete_text (GTK_EDITABLE (self), start_pos: 0, end_pos: -1);
5561 tmp_pos = 0;
5562 gtk_editable_insert_text (GTK_EDITABLE (self), text, length: strlen (s: text), position: &tmp_pos);
5563 g_object_thaw_notify (G_OBJECT (self));
5564 end_change (self);
5565
5566 gtk_text_history_end_irreversible_action (self: priv->history);
5567}
5568
5569/**
5570 * gtk_text_set_visibility: (attributes org.gtk.Method.set_property=visibility)
5571 * @self: a `GtkText`
5572 * @visible: %TRUE if the contents of the `GtkText` are displayed
5573 * as plaintext
5574 *
5575 * Sets whether the contents of the `GtkText` are visible or not.
5576 *
5577 * When visibility is set to %FALSE, characters are displayed
5578 * as the invisible char, and will also appear that way when
5579 * the text in the widget is copied to the clipboard.
5580 *
5581 * By default, GTK picks the best invisible character available
5582 * in the current font, but it can be changed with
5583 * [method@Gtk.Text.set_invisible_char].
5584 *
5585 * Note that you probably want to set [property@Gtk.Text:input-purpose]
5586 * to %GTK_INPUT_PURPOSE_PASSWORD or %GTK_INPUT_PURPOSE_PIN to
5587 * inform input methods about the purpose of this self,
5588 * in addition to setting visibility to %FALSE.
5589 */
5590void
5591gtk_text_set_visibility (GtkText *self,
5592 gboolean visible)
5593{
5594 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
5595
5596 g_return_if_fail (GTK_IS_TEXT (self));
5597
5598 visible = visible != FALSE;
5599
5600 if (priv->visible != visible)
5601 {
5602 priv->visible = visible;
5603
5604 g_object_notify (G_OBJECT (self), property_name: "visibility");
5605 gtk_text_update_cached_style_values (self);
5606 gtk_text_recompute (self);
5607
5608 /* disable undo when invisible text is used */
5609 gtk_text_history_set_enabled (self: priv->history, enabled: visible);
5610
5611 gtk_text_update_clipboard_actions (self);
5612 }
5613}
5614
5615/**
5616 * gtk_text_get_visibility: (attributes org.gtk.Method.get_property=visibility)
5617 * @self: a `GtkText`
5618 *
5619 * Retrieves whether the text in @self is visible.
5620 *
5621 * Returns: %TRUE if the text is currently visible
5622 */
5623gboolean
5624gtk_text_get_visibility (GtkText *self)
5625{
5626 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
5627
5628 g_return_val_if_fail (GTK_IS_TEXT (self), FALSE);
5629
5630 return priv->visible;
5631}
5632
5633/**
5634 * gtk_text_set_invisible_char: (attributes org.gtk.Method.set_property=invisible-char)
5635 * @self: a `GtkText`
5636 * @ch: a Unicode character
5637 *
5638 * Sets the character to use when in “password mode”.
5639 *
5640 * By default, GTK picks the best invisible char available in the
5641 * current font. If you set the invisible char to 0, then the user
5642 * will get no feedback at all; there will be no text on the screen
5643 * as they type.
5644 */
5645void
5646gtk_text_set_invisible_char (GtkText *self,
5647 gunichar ch)
5648{
5649 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
5650
5651 g_return_if_fail (GTK_IS_TEXT (self));
5652
5653 if (!priv->invisible_char_set)
5654 {
5655 priv->invisible_char_set = TRUE;
5656 g_object_notify_by_pspec (G_OBJECT (self), pspec: text_props[PROP_INVISIBLE_CHAR_SET]);
5657 }
5658
5659 if (ch == priv->invisible_char)
5660 return;
5661
5662 priv->invisible_char = ch;
5663 g_object_notify_by_pspec (G_OBJECT (self), pspec: text_props[PROP_INVISIBLE_CHAR]);
5664 gtk_text_recompute (self);
5665}
5666
5667/**
5668 * gtk_text_get_invisible_char: (attributes org.gtk.Method.get_property=invisible-char)
5669 * @self: a `GtkText`
5670 *
5671 * Retrieves the character displayed when visibility is set to false.
5672 *
5673 * Note that GTK does not compute this value unless it needs it,
5674 * so the value returned by this function is not very useful unless
5675 * it has been explicitly set with [method@Gtk.Text.set_invisible_char].
5676 *
5677 * Returns: the current invisible char, or 0, if @text does not
5678 * show invisible text at all.
5679 */
5680gunichar
5681gtk_text_get_invisible_char (GtkText *self)
5682{
5683 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
5684
5685 g_return_val_if_fail (GTK_IS_TEXT (self), 0);
5686
5687 return priv->invisible_char;
5688}
5689
5690/**
5691 * gtk_text_unset_invisible_char:
5692 * @self: a `GtkText`
5693 *
5694 * Unsets the invisible char.
5695 *
5696 * After calling this, the default invisible
5697 * char is used again.
5698 */
5699void
5700gtk_text_unset_invisible_char (GtkText *self)
5701{
5702 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
5703 gunichar ch;
5704
5705 g_return_if_fail (GTK_IS_TEXT (self));
5706
5707 if (!priv->invisible_char_set)
5708 return;
5709
5710 priv->invisible_char_set = FALSE;
5711 ch = find_invisible_char (GTK_WIDGET (self));
5712
5713 if (priv->invisible_char != ch)
5714 {
5715 priv->invisible_char = ch;
5716 g_object_notify_by_pspec (G_OBJECT (self), pspec: text_props[PROP_INVISIBLE_CHAR]);
5717 }
5718
5719 g_object_notify_by_pspec (G_OBJECT (self), pspec: text_props[PROP_INVISIBLE_CHAR_SET]);
5720 gtk_text_recompute (self);
5721}
5722
5723/**
5724 * gtk_text_set_overwrite_mode: (attributes org.gtk.Method.set_property=overwrite-mode)
5725 * @self: a `GtkText`
5726 * @overwrite: new value
5727 *
5728 * Sets whether the text is overwritten when typing
5729 * in the `GtkText`.
5730 */
5731void
5732gtk_text_set_overwrite_mode (GtkText *self,
5733 gboolean overwrite)
5734{
5735 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
5736
5737 g_return_if_fail (GTK_IS_TEXT (self));
5738
5739 if (priv->overwrite_mode == overwrite)
5740 return;
5741
5742 gtk_text_toggle_overwrite (self);
5743
5744 g_object_notify_by_pspec (G_OBJECT (self), pspec: text_props[PROP_OVERWRITE_MODE]);
5745}
5746
5747/**
5748 * gtk_text_get_overwrite_mode: (attributes org.gtk.Method.get_property=overwrite-mode)
5749 * @self: a `GtkText`
5750 *
5751 * Gets whether text is overwritten when typing in the `GtkText`.
5752 *
5753 * See [method@Gtk.Text.set_overwrite_mode].
5754 *
5755 * Returns: whether the text is overwritten when typing
5756 */
5757gboolean
5758gtk_text_get_overwrite_mode (GtkText *self)
5759{
5760 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
5761
5762 g_return_val_if_fail (GTK_IS_TEXT (self), FALSE);
5763
5764 return priv->overwrite_mode;
5765
5766}
5767
5768/**
5769 * gtk_text_set_max_length: (attributes org.gtk.Method.set_property=max-length)
5770 * @self: a `GtkText`
5771 * @length: the maximum length of the `GtkText`, or 0 for no maximum.
5772 * (other than the maximum length of entries.) The value passed
5773 * in will be clamped to the range 0-65536.
5774 *
5775 * Sets the maximum allowed length of the contents of the widget.
5776 *
5777 * If the current contents are longer than the given length, then
5778 * they will be truncated to fit.
5779 *
5780 * This is equivalent to getting @self's `GtkEntryBuffer` and
5781 * calling [method@Gtk.EntryBuffer.set_max_length] on it.
5782 */
5783void
5784gtk_text_set_max_length (GtkText *self,
5785 int length)
5786{
5787 g_return_if_fail (GTK_IS_TEXT (self));
5788 gtk_entry_buffer_set_max_length (buffer: get_buffer (self), max_length: length);
5789}
5790
5791/**
5792 * gtk_text_get_max_length: (attributes org.gtk.Method.get_property=max-length)
5793 * @self: a `GtkText`
5794 *
5795 * Retrieves the maximum allowed length of the text in @self.
5796 *
5797 * See [method@Gtk.Text.set_max_length].
5798 *
5799 * This is equivalent to getting @self's `GtkEntryBuffer` and
5800 * calling [method@Gtk.EntryBuffer.get_max_length] on it.
5801 *
5802 * Returns: the maximum allowed number of characters
5803 * in `GtkText`, or 0 if there is no maximum.
5804 */
5805int
5806gtk_text_get_max_length (GtkText *self)
5807{
5808 g_return_val_if_fail (GTK_IS_TEXT (self), 0);
5809
5810 return gtk_entry_buffer_get_max_length (buffer: get_buffer (self));
5811}
5812
5813/**
5814 * gtk_text_get_text_length:
5815 * @self: a `GtkText`
5816 *
5817 * Retrieves the current length of the text in @self.
5818 *
5819 * This is equivalent to getting @self's `GtkEntryBuffer`
5820 * and calling [method@Gtk.EntryBuffer.get_length] on it.
5821 *
5822 * Returns: the current number of characters
5823 * in `GtkText`, or 0 if there are none.
5824 */
5825guint16
5826gtk_text_get_text_length (GtkText *self)
5827{
5828 g_return_val_if_fail (GTK_IS_TEXT (self), 0);
5829
5830 return gtk_entry_buffer_get_length (buffer: get_buffer (self));
5831}
5832
5833/**
5834 * gtk_text_set_activates_default: (attributes org.gtk.Method.set_property=activates-default)
5835 * @self: a `GtkText`
5836 * @activates: %TRUE to activate window’s default widget on Enter keypress
5837 *
5838 * If @activates is %TRUE, pressing Enter will activate
5839 * the default widget for the window containing @self.
5840 *
5841 * This usually means that the dialog containing the `GtkText`
5842 * will be closed, since the default widget is usually one of
5843 * the dialog buttons.
5844 */
5845void
5846gtk_text_set_activates_default (GtkText *self,
5847 gboolean activates)
5848{
5849 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
5850
5851 g_return_if_fail (GTK_IS_TEXT (self));
5852
5853 activates = activates != FALSE;
5854
5855 if (priv->activates_default != activates)
5856 {
5857 priv->activates_default = activates;
5858 g_object_notify_by_pspec (G_OBJECT (self), pspec: text_props[PROP_ACTIVATES_DEFAULT]);
5859 }
5860}
5861
5862/**
5863 * gtk_text_get_activates_default: (attributes org.gtk.Method.get_property=activates-default)
5864 * @self: a `GtkText`
5865 *
5866 * Returns whether pressing Enter will activate
5867 * the default widget for the window containing @self.
5868 *
5869 * See [method@Gtk.Text.set_activates_default].
5870 *
5871 * Returns: %TRUE if the `GtkText` will activate the default widget
5872 */
5873gboolean
5874gtk_text_get_activates_default (GtkText *self)
5875{
5876 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
5877
5878 g_return_val_if_fail (GTK_IS_TEXT (self), FALSE);
5879
5880 return priv->activates_default;
5881}
5882
5883static void
5884gtk_text_set_width_chars (GtkText *self,
5885 int n_chars)
5886{
5887 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
5888
5889 if (priv->width_chars != n_chars)
5890 {
5891 priv->width_chars = n_chars;
5892 g_object_notify (G_OBJECT (self), property_name: "width-chars");
5893 gtk_widget_queue_resize (GTK_WIDGET (self));
5894 }
5895}
5896
5897static void
5898gtk_text_set_max_width_chars (GtkText *self,
5899 int n_chars)
5900{
5901 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
5902
5903 if (priv->max_width_chars != n_chars)
5904 {
5905 priv->max_width_chars = n_chars;
5906 g_object_notify (G_OBJECT (self), property_name: "max-width-chars");
5907 gtk_widget_queue_resize (GTK_WIDGET (self));
5908 }
5909}
5910
5911PangoLayout *
5912gtk_text_get_layout (GtkText *self)
5913{
5914 PangoLayout *layout;
5915
5916 g_return_val_if_fail (GTK_IS_TEXT (self), NULL);
5917
5918 layout = gtk_text_ensure_layout (self, TRUE);
5919
5920 return layout;
5921}
5922
5923void
5924gtk_text_get_layout_offsets (GtkText *self,
5925 int *x,
5926 int *y)
5927{
5928 g_return_if_fail (GTK_IS_TEXT (self));
5929
5930 get_layout_position (self, x, y);
5931}
5932
5933static void
5934gtk_text_set_alignment (GtkText *self,
5935 float xalign)
5936{
5937 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
5938
5939 if (xalign < 0.0)
5940 xalign = 0.0;
5941 else if (xalign > 1.0)
5942 xalign = 1.0;
5943
5944 if (xalign != priv->xalign)
5945 {
5946 priv->xalign = xalign;
5947 gtk_text_recompute (self);
5948 if (priv->placeholder)
5949 gtk_label_set_xalign (GTK_LABEL (priv->placeholder), xalign);
5950
5951 g_object_notify (G_OBJECT (self), property_name: "xalign");
5952 }
5953}
5954
5955static void
5956hide_selection_bubble (GtkText *self)
5957{
5958 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
5959
5960 if (priv->selection_bubble && gtk_widget_get_visible (widget: priv->selection_bubble))
5961 gtk_widget_hide (widget: priv->selection_bubble);
5962}
5963
5964static void
5965gtk_text_activate_clipboard_cut (GtkWidget *widget,
5966 const char *action_name,
5967 GVariant *parameter)
5968{
5969 GtkText *self = GTK_TEXT (widget);
5970 g_signal_emit_by_name (instance: self, detailed_signal: "cut-clipboard");
5971 hide_selection_bubble (self);
5972}
5973
5974static void
5975gtk_text_activate_clipboard_copy (GtkWidget *widget,
5976 const char *action_name,
5977 GVariant *parameter)
5978{
5979 GtkText *self = GTK_TEXT (widget);
5980 g_signal_emit_by_name (instance: self, detailed_signal: "copy-clipboard");
5981 hide_selection_bubble (self);
5982}
5983
5984static void
5985gtk_text_activate_clipboard_paste (GtkWidget *widget,
5986 const char *action_name,
5987 GVariant *parameter)
5988{
5989 GtkText *self = GTK_TEXT (widget);
5990 g_signal_emit_by_name (instance: self, detailed_signal: "paste-clipboard");
5991 hide_selection_bubble (self);
5992}
5993
5994static void
5995gtk_text_activate_selection_delete (GtkWidget *widget,
5996 const char *action_name,
5997 GVariant *parameter)
5998{
5999 GtkText *self = GTK_TEXT (widget);
6000 gtk_text_delete_cb (self);
6001 hide_selection_bubble (self);
6002}
6003
6004static void
6005gtk_text_activate_selection_select_all (GtkWidget *widget,
6006 const char *action_name,
6007 GVariant *parameter)
6008{
6009 GtkText *self = GTK_TEXT (widget);
6010 gtk_text_select_all (self);
6011}
6012
6013static void
6014gtk_text_activate_misc_insert_emoji (GtkWidget *widget,
6015 const char *action_name,
6016 GVariant *parameter)
6017{
6018 GtkText *self = GTK_TEXT (widget);
6019 gtk_text_insert_emoji (self);
6020 hide_selection_bubble (self);
6021}
6022
6023static void
6024gtk_text_update_clipboard_actions (GtkText *self)
6025{
6026 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6027 DisplayMode mode;
6028 GdkClipboard *clipboard;
6029 gboolean has_clipboard;
6030 gboolean has_selection;
6031 gboolean has_content;
6032 gboolean visible;
6033
6034 clipboard = gtk_widget_get_clipboard (GTK_WIDGET (self));
6035 mode = gtk_text_get_display_mode (self);
6036 has_clipboard = gdk_content_formats_contain_gtype (formats: gdk_clipboard_get_formats (clipboard), G_TYPE_STRING);
6037 has_selection = priv->current_pos != priv->selection_bound;
6038 has_content = priv->buffer && (gtk_entry_buffer_get_length (buffer: priv->buffer) > 0);
6039 visible = mode == DISPLAY_NORMAL;
6040
6041 gtk_widget_action_set_enabled (GTK_WIDGET (self), action_name: "clipboard.cut",
6042 enabled: visible && priv->editable && has_selection);
6043 gtk_widget_action_set_enabled (GTK_WIDGET (self), action_name: "clipboard.copy",
6044 enabled: visible && has_selection);
6045 gtk_widget_action_set_enabled (GTK_WIDGET (self), action_name: "clipboard.paste",
6046 enabled: priv->editable && has_clipboard);
6047
6048 gtk_widget_action_set_enabled (GTK_WIDGET (self), action_name: "selection.delete",
6049 enabled: priv->editable && has_selection);
6050 gtk_widget_action_set_enabled (GTK_WIDGET (self), action_name: "selection.select-all",
6051 enabled: has_content);
6052}
6053
6054static void
6055gtk_text_update_emoji_action (GtkText *self)
6056{
6057 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6058
6059 gtk_widget_action_set_enabled (GTK_WIDGET (self), action_name: "misc.insert-emoji",
6060 enabled: priv->editable &&
6061 (gtk_text_get_input_hints (self) & GTK_INPUT_HINT_NO_EMOJI) == 0);
6062}
6063
6064static GMenuModel *
6065gtk_text_get_menu_model (GtkText *self)
6066{
6067 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6068 GtkJoinedMenu *joined;
6069 GMenu *menu, *section;
6070 GMenuItem *item;
6071
6072 joined = gtk_joined_menu_new ();
6073 menu = g_menu_new ();
6074
6075 section = g_menu_new ();
6076 item = g_menu_item_new (_("Cu_t"), detailed_action: "clipboard.cut");
6077 g_menu_item_set_attribute (menu_item: item, attribute: "touch-icon", format_string: "s", "edit-cut-symbolic");
6078 g_menu_append_item (menu: section, item);
6079 g_object_unref (object: item);
6080 item = g_menu_item_new (_("_Copy"), detailed_action: "clipboard.copy");
6081 g_menu_item_set_attribute (menu_item: item, attribute: "touch-icon", format_string: "s", "edit-copy-symbolic");
6082 g_menu_append_item (menu: section, item);
6083 g_object_unref (object: item);
6084 item = g_menu_item_new (_("_Paste"), detailed_action: "clipboard.paste");
6085 g_menu_item_set_attribute (menu_item: item, attribute: "touch-icon", format_string: "s", "edit-paste-symbolic");
6086 g_menu_append_item (menu: section, item);
6087 g_object_unref (object: item);
6088 item = g_menu_item_new (_("_Delete"), detailed_action: "selection.delete");
6089 g_menu_item_set_attribute (menu_item: item, attribute: "touch-icon", format_string: "s", "edit-delete-symbolic");
6090 g_menu_append_item (menu: section, item);
6091 g_object_unref (object: item);
6092 g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
6093 g_object_unref (object: section);
6094
6095 section = g_menu_new ();
6096
6097 item = g_menu_item_new (_("Select _All"), detailed_action: "selection.select-all");
6098 g_menu_item_set_attribute (menu_item: item, attribute: "touch-icon", format_string: "s", "edit-select-all-symbolic");
6099 g_menu_append_item (menu: section, item);
6100 g_object_unref (object: item);
6101
6102 item = g_menu_item_new ( _("Insert _Emoji"), detailed_action: "misc.insert-emoji");
6103 g_menu_item_set_attribute (menu_item: item, attribute: "hidden-when", format_string: "s", "action-disabled");
6104 g_menu_item_set_attribute (menu_item: item, attribute: "touch-icon", format_string: "s", "face-smile-symbolic");
6105 g_menu_append_item (menu: section, item);
6106 g_object_unref (object: item);
6107 g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
6108 g_object_unref (object: section);
6109
6110 gtk_joined_menu_append_menu (self: joined, G_MENU_MODEL (menu));
6111 g_object_unref (object: menu);
6112
6113 if (priv->extra_menu)
6114 gtk_joined_menu_append_menu (self: joined, model: priv->extra_menu);
6115
6116 return G_MENU_MODEL (joined);
6117}
6118
6119static gboolean
6120gtk_text_mnemonic_activate (GtkWidget *widget,
6121 gboolean group_cycling)
6122{
6123 gtk_widget_grab_focus (widget);
6124 return GDK_EVENT_STOP;
6125}
6126
6127static void
6128gtk_text_popup_menu (GtkWidget *widget,
6129 const char *action_name,
6130 GVariant *parameters)
6131{
6132 gtk_text_do_popup (GTK_TEXT (widget), x: -1, y: -1);
6133}
6134
6135static void
6136show_or_hide_handles (GtkWidget *popover,
6137 GParamSpec *pspec,
6138 GtkText *self)
6139{
6140 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6141 gboolean visible;
6142
6143 visible = gtk_widget_get_visible (widget: popover);
6144 priv->text_handles_enabled = !visible;
6145 gtk_text_update_handles (self);
6146}
6147
6148static void
6149append_bubble_item (GtkText *self,
6150 GtkWidget *toolbar,
6151 GMenuModel *model,
6152 int index)
6153{
6154 GtkActionMuxer *muxer;
6155 GtkWidget *item, *image;
6156 GVariant *att;
6157 const char *icon_name;
6158 const char *action_name;
6159 GMenuModel *link;
6160 gboolean enabled;
6161
6162 link = g_menu_model_get_item_link (model, item_index: index, link: "section");
6163 if (link)
6164 {
6165 int i;
6166 for (i = 0; i < g_menu_model_get_n_items (model: link); i++)
6167 append_bubble_item (self, toolbar, model: link, index: i);
6168 g_object_unref (object: link);
6169 return;
6170 }
6171
6172 att = g_menu_model_get_item_attribute_value (model, item_index: index, attribute: "touch-icon", G_VARIANT_TYPE_STRING);
6173 if (att == NULL)
6174 return;
6175
6176 icon_name = g_variant_get_string (value: att, NULL);
6177 g_variant_unref (value: att);
6178
6179 att = g_menu_model_get_item_attribute_value (model, item_index: index, attribute: "action", G_VARIANT_TYPE_STRING);
6180 if (att == NULL)
6181 return;
6182 action_name = g_variant_get_string (value: att, NULL);
6183 g_variant_unref (value: att);
6184
6185 muxer = _gtk_widget_get_action_muxer (GTK_WIDGET (self), FALSE);
6186 if (!gtk_action_muxer_query_action (muxer, action_name, enabled: &enabled,
6187 NULL, NULL, NULL, NULL) ||
6188 !enabled)
6189 return;
6190
6191 item = gtk_button_new ();
6192 gtk_widget_set_focus_on_click (widget: item, FALSE);
6193 image = gtk_image_new_from_icon_name (icon_name);
6194 gtk_widget_show (widget: image);
6195 gtk_button_set_child (GTK_BUTTON (item), child: image);
6196 gtk_widget_add_css_class (widget: item, css_class: "image-button");
6197 gtk_actionable_set_action_name (GTK_ACTIONABLE (item), action_name);
6198 gtk_widget_show (GTK_WIDGET (item));
6199 gtk_box_append (GTK_BOX (toolbar), child: item);
6200}
6201
6202static gboolean
6203gtk_text_selection_bubble_popup_show (gpointer user_data)
6204{
6205 GtkText *self = user_data;
6206 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6207 const int text_width = gtk_widget_get_width (GTK_WIDGET (self));
6208 const int text_height = gtk_widget_get_height (GTK_WIDGET (self));
6209 cairo_rectangle_int_t rect;
6210 GtkAllocation allocation;
6211 gboolean has_selection;
6212 int start_x, end_x;
6213 GtkWidget *box;
6214 GtkWidget *toolbar;
6215 GMenuModel *model;
6216 int i;
6217
6218 gtk_text_update_clipboard_actions (self);
6219
6220 has_selection = priv->selection_bound != priv->current_pos;
6221
6222 if (!has_selection && !priv->editable)
6223 {
6224 priv->selection_bubble_timeout_id = 0;
6225 return G_SOURCE_REMOVE;
6226 }
6227
6228 g_clear_pointer (&priv->selection_bubble, gtk_widget_unparent);
6229
6230 priv->selection_bubble = gtk_popover_new ();
6231 gtk_widget_set_parent (widget: priv->selection_bubble, GTK_WIDGET (self));
6232 gtk_widget_add_css_class (widget: priv->selection_bubble, css_class: "touch-selection");
6233 gtk_popover_set_position (GTK_POPOVER (priv->selection_bubble), position: GTK_POS_BOTTOM);
6234 gtk_popover_set_autohide (GTK_POPOVER (priv->selection_bubble), FALSE);
6235 g_signal_connect (priv->selection_bubble, "notify::visible",
6236 G_CALLBACK (show_or_hide_handles), self);
6237
6238 box = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 5);
6239 gtk_widget_set_margin_start (widget: box, margin: 10);
6240 gtk_widget_set_margin_end (widget: box, margin: 10);
6241 gtk_widget_set_margin_top (widget: box, margin: 10);
6242 gtk_widget_set_margin_bottom (widget: box, margin: 10);
6243 gtk_widget_show (widget: box);
6244 toolbar = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 0);
6245 gtk_widget_add_css_class (widget: toolbar, css_class: "linked");
6246 gtk_popover_set_child (GTK_POPOVER (priv->selection_bubble), child: box);
6247 gtk_box_append (GTK_BOX (box), child: toolbar);
6248
6249 model = gtk_text_get_menu_model (self);
6250
6251 for (i = 0; i < g_menu_model_get_n_items (model); i++)
6252 append_bubble_item (self, toolbar, model, index: i);
6253
6254 g_object_unref (object: model);
6255
6256 gtk_widget_get_allocation (GTK_WIDGET (self), allocation: &allocation);
6257
6258 gtk_text_get_cursor_locations (self, strong_x: &start_x, NULL);
6259
6260 start_x -= priv->scroll_offset;
6261 start_x = CLAMP (start_x, 0, text_width);
6262 rect.y = - allocation.y;
6263 rect.height = text_height;
6264
6265 if (has_selection)
6266 {
6267 end_x = gtk_text_get_selection_bound_location (self) - priv->scroll_offset;
6268 end_x = CLAMP (end_x, 0, text_width);
6269
6270 rect.x = - allocation.x + MIN (start_x, end_x);
6271 rect.width = ABS (end_x - start_x);
6272 }
6273 else
6274 {
6275 rect.x = - allocation.x + start_x;
6276 rect.width = 0;
6277 }
6278
6279 rect.x -= 5;
6280 rect.y -= 5;
6281 rect.width += 10;
6282 rect.height += 10;
6283
6284 gtk_popover_set_pointing_to (GTK_POPOVER (priv->selection_bubble), rect: &rect);
6285 gtk_popover_popup (GTK_POPOVER (priv->selection_bubble));
6286
6287 priv->selection_bubble_timeout_id = 0;
6288
6289 return G_SOURCE_REMOVE;
6290}
6291
6292static void
6293gtk_text_selection_bubble_popup_unset (GtkText *self)
6294{
6295 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6296
6297 if (priv->selection_bubble)
6298 gtk_widget_hide (widget: priv->selection_bubble);
6299
6300 if (priv->selection_bubble_timeout_id)
6301 {
6302 g_source_remove (tag: priv->selection_bubble_timeout_id);
6303 priv->selection_bubble_timeout_id = 0;
6304 }
6305}
6306
6307static void
6308gtk_text_selection_bubble_popup_set (GtkText *self)
6309{
6310 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6311
6312 if (priv->selection_bubble_timeout_id)
6313 g_source_remove (tag: priv->selection_bubble_timeout_id);
6314
6315 priv->selection_bubble_timeout_id =
6316 g_timeout_add (interval: 50, function: gtk_text_selection_bubble_popup_show, data: self);
6317 gdk_source_set_static_name_by_id (tag: priv->selection_bubble_timeout_id, name: "[gtk] gtk_text_selection_bubble_popup_cb");
6318}
6319
6320static void
6321gtk_text_drag_leave (GtkDropTarget *dest,
6322 GtkText *self)
6323{
6324 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6325 GtkWidget *widget = GTK_WIDGET (self);
6326
6327 priv->dnd_position = -1;
6328 gtk_widget_queue_draw (widget);
6329}
6330
6331static gboolean
6332gtk_text_drag_drop (GtkDropTarget *dest,
6333 const GValue *value,
6334 double x,
6335 double y,
6336 GtkText *self)
6337{
6338 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6339 int drop_position;
6340 int length;
6341 const char *str;
6342
6343 if (!priv->editable)
6344 return FALSE;
6345
6346 drop_position = gtk_text_find_position (self, x: x + priv->scroll_offset);
6347
6348 str = g_value_get_string (value);
6349 if (priv->truncate_multiline)
6350 length = truncate_multiline (text: str);
6351 else
6352 length = -1;
6353
6354 if (priv->selection_bound == priv->current_pos ||
6355 drop_position < priv->selection_bound ||
6356 drop_position > priv->current_pos)
6357 {
6358 gtk_editable_insert_text (GTK_EDITABLE (self), text: str, length, position: &drop_position);
6359 }
6360 else
6361 {
6362 int pos;
6363 /* Replacing selection */
6364 begin_change (self);
6365 gtk_text_delete_selection (self);
6366 pos = MIN (priv->selection_bound, priv->current_pos);
6367 gtk_editable_insert_text (GTK_EDITABLE (self), text: str, length, position: &pos);
6368 end_change (self);
6369 }
6370
6371 return TRUE;
6372}
6373
6374static gboolean
6375gtk_text_drag_accept (GtkDropTarget *dest,
6376 GdkDrop *drop,
6377 GtkText *self)
6378{
6379 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6380
6381 if (!priv->editable)
6382 return FALSE;
6383
6384 if ((gdk_drop_get_actions (self: drop) & gtk_drop_target_get_actions (self: dest)) == 0)
6385 return FALSE;
6386
6387 return gdk_content_formats_match (first: gtk_drop_target_get_formats (self: dest), second: gdk_drop_get_formats (self: drop));
6388}
6389
6390static GdkDragAction
6391gtk_text_drag_motion (GtkDropTarget *target,
6392 double x,
6393 double y,
6394 GtkText *self)
6395{
6396 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6397 int new_position, old_position;
6398
6399 if (!priv->editable)
6400 {
6401 gtk_drop_target_reject (self: target);
6402 return 0;
6403 }
6404
6405 old_position = priv->dnd_position;
6406 new_position = gtk_text_find_position (self, x: x + priv->scroll_offset);
6407
6408 if (priv->selection_bound == priv->current_pos ||
6409 new_position < priv->selection_bound ||
6410 new_position > priv->current_pos)
6411 {
6412 priv->dnd_position = new_position;
6413 }
6414 else
6415 {
6416 priv->dnd_position = -1;
6417 }
6418
6419 if (priv->dnd_position != old_position)
6420 gtk_widget_queue_draw (GTK_WIDGET (self));
6421
6422 if (priv->drag)
6423 return GDK_ACTION_MOVE;
6424 else
6425 return GDK_ACTION_COPY;
6426}
6427
6428/* We display the cursor when
6429 *
6430 * - the selection is empty, AND
6431 * - the widget has focus
6432 */
6433
6434#define CURSOR_ON_MULTIPLIER 2
6435#define CURSOR_OFF_MULTIPLIER 1
6436#define CURSOR_PEND_MULTIPLIER 3
6437#define CURSOR_DIVIDER 3
6438
6439static gboolean
6440cursor_blinks (GtkText *self)
6441{
6442 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6443
6444 if (gtk_event_controller_focus_is_focus (GTK_EVENT_CONTROLLER_FOCUS (priv->focus_controller)) &&
6445 priv->editable &&
6446 priv->selection_bound == priv->current_pos)
6447 {
6448 GtkSettings *settings;
6449 gboolean blink;
6450
6451 settings = gtk_widget_get_settings (GTK_WIDGET (self));
6452 g_object_get (object: settings, first_property_name: "gtk-cursor-blink", &blink, NULL);
6453
6454 return blink;
6455 }
6456 else
6457 return FALSE;
6458}
6459
6460static gboolean
6461get_middle_click_paste (GtkText *self)
6462{
6463 GtkSettings *settings;
6464 gboolean paste;
6465
6466 settings = gtk_widget_get_settings (GTK_WIDGET (self));
6467 g_object_get (object: settings, first_property_name: "gtk-enable-primary-paste", &paste, NULL);
6468
6469 return paste;
6470}
6471
6472static int
6473get_cursor_time (GtkText *self)
6474{
6475 GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (self));
6476 int time;
6477
6478 g_object_get (object: settings, first_property_name: "gtk-cursor-blink-time", &time, NULL);
6479
6480 return time;
6481}
6482
6483static int
6484get_cursor_blink_timeout (GtkText *self)
6485{
6486 GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (self));
6487 int timeout;
6488
6489 g_object_get (object: settings, first_property_name: "gtk-cursor-blink-timeout", &timeout, NULL);
6490
6491 return timeout;
6492}
6493
6494typedef struct {
6495 guint64 start;
6496 guint64 end;
6497} BlinkData;
6498
6499static gboolean blink_cb (GtkWidget *widget,
6500 GdkFrameClock *clock,
6501 gpointer user_data);
6502
6503static void
6504add_blink_timeout (GtkText *self,
6505 gboolean delay)
6506{
6507 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6508 BlinkData *data;
6509 int blink_time;
6510
6511 priv->blink_start_time = g_get_monotonic_time ();
6512 priv->cursor_alpha = 1.0;
6513
6514 blink_time = get_cursor_time (self);
6515
6516 data = g_new (BlinkData, 1);
6517 data->start = priv->blink_start_time;
6518 if (delay)
6519 data->start += blink_time * 1000 / 2;
6520 data->end = data->start + blink_time * 1000;
6521
6522 priv->blink_tick = gtk_widget_add_tick_callback (GTK_WIDGET (self),
6523 callback: blink_cb,
6524 user_data: data,
6525 notify: g_free);
6526}
6527
6528static void
6529remove_blink_timeout (GtkText *self)
6530{
6531 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6532
6533 if (priv->blink_tick)
6534 {
6535 gtk_widget_remove_tick_callback (GTK_WIDGET (self), id: priv->blink_tick);
6536 priv->blink_tick = 0;
6537 }
6538}
6539
6540/*
6541 * Blink!
6542 */
6543
6544static float
6545blink_alpha (float phase)
6546{
6547 /* keep it simple, and split the blink cycle evenly
6548 * into visible, fading out, invisible, fading in
6549 */
6550 if (phase < 0.25)
6551 return 1;
6552 else if (phase < 0.5)
6553 return 1 - 4 * (phase - 0.25);
6554 else if (phase < 0.75)
6555 return 0;
6556 else
6557 return 4 * (phase - 0.75);
6558}
6559
6560static gboolean
6561blink_cb (GtkWidget *widget,
6562 GdkFrameClock *clock,
6563 gpointer user_data)
6564{
6565 GtkText *self = GTK_TEXT (widget);
6566 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6567 BlinkData *data = user_data;
6568 int blink_timeout;
6569 int blink_time;
6570 guint64 now;
6571 float phase;
6572 float alpha;
6573
6574 if (!gtk_widget_has_focus (GTK_WIDGET (self)))
6575 {
6576 g_warning ("GtkText - did not receive a focus-out event.\n"
6577 "If you handle this event, you must return\n"
6578 "GDK_EVENT_PROPAGATE so the default handler\n"
6579 "gets the event as well");
6580
6581 gtk_text_check_cursor_blink (self);
6582 return G_SOURCE_REMOVE;
6583 }
6584
6585 if (priv->selection_bound != priv->current_pos)
6586 {
6587 g_warning ("GtkText - unexpected blinking selection. Removing");
6588
6589 gtk_text_check_cursor_blink (self);
6590 return G_SOURCE_REMOVE;
6591 }
6592
6593 blink_timeout = get_cursor_blink_timeout (self);
6594 blink_time = get_cursor_time (self);
6595
6596 now = g_get_monotonic_time ();
6597
6598 if (now > priv->blink_start_time + blink_timeout * 1000000)
6599 {
6600 /* we've blinked enough without the user doing anything, stop blinking */
6601 priv->cursor_alpha = 1.0;
6602 remove_blink_timeout (self);
6603 gtk_widget_queue_draw (widget);
6604
6605 return G_SOURCE_REMOVE;
6606 }
6607
6608 phase = (now - data->start) / (float) (data->end - data->start);
6609
6610 if (now >= data->end)
6611 {
6612 data->start = data->end;
6613 data->end = data->start + blink_time * 1000;
6614 }
6615
6616 alpha = blink_alpha (phase);
6617
6618 if (priv->cursor_alpha != alpha)
6619 {
6620 priv->cursor_alpha = alpha;
6621 gtk_widget_queue_draw (widget);
6622 }
6623
6624 return G_SOURCE_CONTINUE;
6625}
6626
6627static void
6628gtk_text_check_cursor_blink (GtkText *self)
6629{
6630 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6631
6632 if (cursor_blinks (self))
6633 {
6634 if (!priv->blink_tick)
6635 add_blink_timeout (self, FALSE);
6636 }
6637 else
6638 {
6639 if (priv->blink_tick)
6640 remove_blink_timeout (self);
6641 }
6642}
6643
6644static void
6645gtk_text_pend_cursor_blink (GtkText *self)
6646{
6647 if (cursor_blinks (self))
6648 {
6649 remove_blink_timeout (self);
6650 add_blink_timeout (self, TRUE);
6651 }
6652}
6653
6654static void
6655gtk_text_reset_blink_time (GtkText *self)
6656{
6657 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6658
6659 priv->blink_start_time = g_get_monotonic_time ();
6660}
6661
6662/**
6663 * gtk_text_set_placeholder_text: (attributes org.gtk.Method.set_property=placeholder-text)
6664 * @self: a `GtkText`
6665 * @text: (nullable): a string to be displayed when @self
6666 * is empty and unfocused
6667 *
6668 * Sets text to be displayed in @self when it is empty.
6669 *
6670 * This can be used to give a visual hint of the expected
6671 * contents of the `GtkText`.
6672 */
6673void
6674gtk_text_set_placeholder_text (GtkText *self,
6675 const char *text)
6676{
6677 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6678
6679 g_return_if_fail (GTK_IS_TEXT (self));
6680
6681 if (priv->placeholder == NULL)
6682 {
6683 priv->placeholder = g_object_new (GTK_TYPE_LABEL,
6684 first_property_name: "label", text,
6685 "css-name", "placeholder",
6686 "xalign", priv->xalign,
6687 "ellipsize", PANGO_ELLIPSIZE_END,
6688 NULL);
6689 gtk_label_set_attributes (GTK_LABEL (priv->placeholder), attrs: priv->attrs);
6690 gtk_widget_insert_after (widget: priv->placeholder, GTK_WIDGET (self), NULL);
6691 }
6692 else
6693 {
6694 gtk_label_set_text (GTK_LABEL (priv->placeholder), str: text);
6695 }
6696
6697 update_placeholder_visibility (self);
6698
6699 g_object_notify_by_pspec (G_OBJECT (self), pspec: text_props[PROP_PLACEHOLDER_TEXT]);
6700}
6701
6702/**
6703 * gtk_text_get_placeholder_text: (attributes org.gtk.Method.get_property=placeholder-text)
6704 * @self: a `GtkText`
6705 *
6706 * Retrieves the text that will be displayed when
6707 * @self is empty and unfocused
6708 *
6709 * If no placeholder text has been set, %NULL will be returned.
6710 *
6711 * Returns: (nullable) (transfer none): the placeholder text
6712 */
6713const char *
6714gtk_text_get_placeholder_text (GtkText *self)
6715{
6716 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6717
6718 g_return_val_if_fail (GTK_IS_TEXT (self), NULL);
6719
6720 if (!priv->placeholder)
6721 return NULL;
6722
6723 return gtk_label_get_text (GTK_LABEL (priv->placeholder));
6724}
6725
6726/**
6727 * gtk_text_set_input_purpose: (attributes org.gtk.Method.set_property=input-purpose)
6728 * @self: a `GtkText`
6729 * @purpose: the purpose
6730 *
6731 * Sets the input purpose of the `GtkText`.
6732 *
6733 * This can be used by on-screen keyboards and other
6734 * input methods to adjust their behaviour.
6735 */
6736void
6737gtk_text_set_input_purpose (GtkText *self,
6738 GtkInputPurpose purpose)
6739{
6740 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6741
6742 g_return_if_fail (GTK_IS_TEXT (self));
6743
6744 if (gtk_text_get_input_purpose (self) != purpose)
6745 {
6746 g_object_set (G_OBJECT (priv->im_context),
6747 first_property_name: "input-purpose", purpose,
6748 NULL);
6749
6750 g_object_notify_by_pspec (G_OBJECT (self), pspec: text_props[PROP_INPUT_PURPOSE]);
6751 }
6752}
6753
6754/**
6755 * gtk_text_get_input_purpose: (attributes org.gtk.Method.get_property=input-purpose)
6756 * @self: a `GtkText`
6757 *
6758 * Gets the input purpose of the `GtkText`.
6759 */
6760GtkInputPurpose
6761gtk_text_get_input_purpose (GtkText *self)
6762{
6763 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6764 GtkInputPurpose purpose;
6765
6766 g_return_val_if_fail (GTK_IS_TEXT (self), GTK_INPUT_PURPOSE_FREE_FORM);
6767
6768 g_object_get (G_OBJECT (priv->im_context),
6769 first_property_name: "input-purpose", &purpose,
6770 NULL);
6771
6772 return purpose;
6773}
6774
6775/**
6776 * gtk_text_set_input_hints: (attributes org.gtk.Method.set_property=input-hints)
6777 * @self: a `GtkText`
6778 * @hints: the hints
6779 *
6780 * Sets input hints that allow input methods
6781 * to fine-tune their behaviour.
6782 */
6783void
6784gtk_text_set_input_hints (GtkText *self,
6785 GtkInputHints hints)
6786
6787{
6788 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6789
6790 g_return_if_fail (GTK_IS_TEXT (self));
6791
6792 if (gtk_text_get_input_hints (self) != hints)
6793 {
6794 g_object_set (G_OBJECT (priv->im_context),
6795 first_property_name: "input-hints", hints,
6796 NULL);
6797
6798 g_object_notify_by_pspec (G_OBJECT (self), pspec: text_props[PROP_INPUT_HINTS]);
6799 gtk_text_update_emoji_action (self);
6800 }
6801}
6802
6803/**
6804 * gtk_text_get_input_hints: (attributes org.gtk.Method.get_property=input-hints)
6805 * @self: a `GtkText`
6806 *
6807 * Gets the input hints of the `GtkText`.
6808 */
6809GtkInputHints
6810gtk_text_get_input_hints (GtkText *self)
6811{
6812 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6813 GtkInputHints hints;
6814
6815 g_return_val_if_fail (GTK_IS_TEXT (self), GTK_INPUT_HINT_NONE);
6816
6817 g_object_get (G_OBJECT (priv->im_context),
6818 first_property_name: "input-hints", &hints,
6819 NULL);
6820
6821 return hints;
6822}
6823
6824/**
6825 * gtk_text_set_attributes: (attributes org.gtk.Method.set_property=attributes)
6826 * @self: a `GtkText`
6827 * @attrs: (nullable): a `PangoAttrList`
6828 *
6829 * Sets attributes that are applied to the text.
6830 */
6831void
6832gtk_text_set_attributes (GtkText *self,
6833 PangoAttrList *attrs)
6834{
6835 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6836
6837 g_return_if_fail (GTK_IS_TEXT (self));
6838
6839 if (attrs)
6840 pango_attr_list_ref (list: attrs);
6841
6842 if (priv->attrs)
6843 pango_attr_list_unref (list: priv->attrs);
6844 priv->attrs = attrs;
6845
6846 if (priv->placeholder)
6847 gtk_label_set_attributes (GTK_LABEL (priv->placeholder), attrs);
6848
6849 g_object_notify_by_pspec (G_OBJECT (self), pspec: text_props[PROP_ATTRIBUTES]);
6850
6851 gtk_text_recompute (self);
6852 gtk_widget_queue_resize (GTK_WIDGET (self));
6853}
6854
6855/**
6856 * gtk_text_get_attributes: (attributes org.gtk.Method.get_property=attributes)
6857 * @self: a `GtkText`
6858 *
6859 * Gets the attribute list that was set on the `GtkText`.
6860 *
6861 * See [method@Gtk.Text.set_attributes].
6862 *
6863 * Returns: (transfer none) (nullable): the attribute list
6864 */
6865PangoAttrList *
6866gtk_text_get_attributes (GtkText *self)
6867{
6868 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6869
6870 g_return_val_if_fail (GTK_IS_TEXT (self), NULL);
6871
6872 return priv->attrs;
6873}
6874
6875/**
6876 * gtk_text_set_tabs: (attributes org.gtk.Method.set_property=tabs)
6877 * @self: a `GtkText`
6878 * @tabs: (nullable): a `PangoTabArray`
6879 *
6880 * Sets tabstops that are applied to the text.
6881 */
6882void
6883gtk_text_set_tabs (GtkText *self,
6884 PangoTabArray *tabs)
6885{
6886 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6887
6888 g_return_if_fail (GTK_IS_TEXT (self));
6889
6890 if (priv->tabs)
6891 pango_tab_array_free(tab_array: priv->tabs);
6892
6893 if (tabs)
6894 priv->tabs = pango_tab_array_copy (src: tabs);
6895 else
6896 priv->tabs = NULL;
6897
6898 g_object_notify_by_pspec (G_OBJECT (self), pspec: text_props[PROP_TABS]);
6899
6900 gtk_text_recompute (self);
6901 gtk_widget_queue_resize (GTK_WIDGET (self));
6902}
6903
6904/**
6905 * gtk_text_get_tabs: (attributes org.gtk.Method.get_property=tabs)
6906 * @self: a `GtkText`
6907 *
6908 * Gets the tabstops that were set on the `GtkText`.
6909 *
6910 * See [method@Gtk.Text.set_tabs].
6911 *
6912 * Returns: (nullable) (transfer none): the tabstops
6913 */
6914PangoTabArray *
6915gtk_text_get_tabs (GtkText *self)
6916{
6917 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6918
6919 g_return_val_if_fail (GTK_IS_TEXT (self), NULL);
6920
6921 return priv->tabs;
6922}
6923
6924static void
6925emoji_picked (GtkEmojiChooser *chooser,
6926 const char *text,
6927 GtkText *self)
6928{
6929 int pos;
6930
6931 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6932
6933 begin_change (self);
6934 if (priv->selection_bound != priv->current_pos)
6935 gtk_text_delete_selection (self);
6936
6937 pos = priv->current_pos;
6938 gtk_editable_insert_text (GTK_EDITABLE (self), text, length: -1, position: &pos);
6939 gtk_text_set_selection_bounds (self, start: pos, end: pos);
6940 end_change (self);
6941}
6942
6943static void
6944gtk_text_insert_emoji (GtkText *self)
6945{
6946 GtkWidget *chooser;
6947
6948 if (gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_TYPE_EMOJI_CHOOSER) != NULL)
6949 return;
6950
6951 chooser = GTK_WIDGET (g_object_get_data (G_OBJECT (self), "gtk-emoji-chooser"));
6952 if (!chooser)
6953 {
6954 chooser = gtk_emoji_chooser_new ();
6955 g_object_set_data (G_OBJECT (self), key: "gtk-emoji-chooser", data: chooser);
6956
6957 gtk_widget_set_parent (widget: chooser, GTK_WIDGET (self));
6958 g_signal_connect (chooser, "emoji-picked", G_CALLBACK (emoji_picked), self);
6959 g_signal_connect_swapped (chooser, "hide", G_CALLBACK (gtk_text_grab_focus_without_selecting), self);
6960 }
6961
6962 gtk_popover_popup (GTK_POPOVER (chooser));
6963}
6964
6965static void
6966set_text_cursor (GtkWidget *widget)
6967{
6968 gtk_widget_set_cursor_from_name (widget, name: "text");
6969}
6970
6971GtkEventController *
6972gtk_text_get_key_controller (GtkText *self)
6973{
6974 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6975
6976 return priv->key_controller;
6977}
6978
6979/**
6980 * gtk_text_set_extra_menu: (attributes org.gtk.Method.set_property=extra-menu)
6981 * @self: a `GtkText`
6982 * @model: (nullable): a `GMenuModel`
6983 *
6984 * Sets a menu model to add when constructing
6985 * the context menu for @self.
6986 */
6987void
6988gtk_text_set_extra_menu (GtkText *self,
6989 GMenuModel *model)
6990{
6991 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
6992
6993 g_return_if_fail (GTK_IS_TEXT (self));
6994
6995 if (g_set_object (&priv->extra_menu, model))
6996 {
6997 g_clear_pointer (&priv->popup_menu, gtk_widget_unparent);
6998
6999 g_object_notify_by_pspec (G_OBJECT (self), pspec: text_props[PROP_EXTRA_MENU]);
7000 }
7001}
7002
7003/**
7004 * gtk_text_get_extra_menu: (attributes org.gtk.Method.get_property=extra-menu)
7005 * @self: a `GtkText`
7006 *
7007 * Gets the menu model for extra items in the context menu.
7008 *
7009 * See [method@Gtk.Text.set_extra_menu].
7010 *
7011 * Returns: (transfer none) (nullable): the menu model
7012 */
7013GMenuModel *
7014gtk_text_get_extra_menu (GtkText *self)
7015{
7016 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
7017
7018 g_return_val_if_fail (GTK_IS_TEXT (self), NULL);
7019
7020 return priv->extra_menu;
7021}
7022
7023/**
7024 * gtk_text_set_enable_emoji_completion: (attributes org.gtk.Method.set_property=enable-emoji-completion)
7025 * @self: a `GtkText`
7026 * @enable_emoji_completion: %TRUE to enable Emoji completion
7027 *
7028 * Sets whether Emoji completion is enabled.
7029 *
7030 * If it is, typing ':', followed by a recognized keyword,
7031 * will pop up a window with suggested Emojis matching the
7032 * keyword.
7033 */
7034void
7035gtk_text_set_enable_emoji_completion (GtkText *self,
7036 gboolean enable_emoji_completion)
7037{
7038 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
7039
7040 g_return_if_fail (GTK_IS_TEXT (self));
7041
7042 if (priv->enable_emoji_completion == enable_emoji_completion)
7043 return;
7044
7045 priv->enable_emoji_completion = enable_emoji_completion;
7046
7047 if (priv->enable_emoji_completion)
7048 priv->emoji_completion = gtk_emoji_completion_new (text: self);
7049 else
7050 g_clear_pointer (&priv->emoji_completion, gtk_widget_unparent);
7051
7052 g_object_notify_by_pspec (G_OBJECT (self), pspec: text_props[PROP_ENABLE_EMOJI_COMPLETION]);
7053}
7054
7055/**
7056 * gtk_text_get_enable_emoji_completion: (attributes org.gtk.Method.get_property=enable-emoji-completion)
7057 * @self: a `GtkText`
7058 *
7059 * Returns whether Emoji completion is enabled for this
7060 * `GtkText` widget.
7061 *
7062 * Returns: %TRUE if Emoji completion is enabled
7063 */
7064gboolean
7065gtk_text_get_enable_emoji_completion (GtkText *self)
7066{
7067 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
7068
7069 g_return_val_if_fail (GTK_IS_TEXT (self), FALSE);
7070
7071 return priv->enable_emoji_completion;
7072}
7073
7074/**
7075 * gtk_text_set_propagate_text_width: (attributes org.gtk.Method.set_property=propagate-text-width)
7076 * @self: a `GtkText`
7077 * @propagate_text_width: %TRUE to propagate the text width
7078 *
7079 * Sets whether the `GtkText` should grow and shrink with the content.
7080 */
7081void
7082gtk_text_set_propagate_text_width (GtkText *self,
7083 gboolean propagate_text_width)
7084{
7085 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
7086
7087 g_return_if_fail (GTK_IS_TEXT (self));
7088
7089 if (priv->propagate_text_width == propagate_text_width)
7090 return;
7091
7092 priv->propagate_text_width = propagate_text_width;
7093
7094 gtk_widget_queue_resize (GTK_WIDGET (self));
7095
7096 g_object_notify_by_pspec (G_OBJECT (self), pspec: text_props[PROP_PROPAGATE_TEXT_WIDTH]);
7097}
7098
7099/**
7100 * gtk_text_get_propagate_text_width: (attributes org.gtk.Method.get_property=propagate-text-width)
7101 * @self: a `GtkText`
7102 *
7103 * Returns whether the `GtkText` will grow and shrink
7104 * with the content.
7105 *
7106 * Returns: %TRUE if @self will propagate the text width
7107 */
7108gboolean
7109gtk_text_get_propagate_text_width (GtkText *self)
7110{
7111 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
7112
7113 g_return_val_if_fail (GTK_IS_TEXT (self), FALSE);
7114
7115 return priv->propagate_text_width;
7116}
7117
7118/**
7119 * gtk_text_set_truncate_multiline: (attributes org.gtk.Method.set_property=truncate-multiline)
7120 * @self: a `GtkText`
7121 * @truncate_multiline: %TRUE to truncate multi-line text
7122 *
7123 * Sets whether the `GtkText` should truncate multi-line text
7124 * that is pasted into the widget.
7125 */
7126void
7127gtk_text_set_truncate_multiline (GtkText *self,
7128 gboolean truncate_multiline)
7129{
7130 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
7131
7132 g_return_if_fail (GTK_IS_TEXT (self));
7133
7134 if (priv->truncate_multiline == truncate_multiline)
7135 return;
7136
7137 priv->truncate_multiline = truncate_multiline;
7138
7139 g_object_notify_by_pspec (G_OBJECT (self), pspec: text_props[PROP_TRUNCATE_MULTILINE]);
7140}
7141
7142/**
7143 * gtk_text_get_truncate_multiline: (attributes org.gtk.Method.get_property=truncate-multiline)
7144 * @self: a `GtkText`
7145 *
7146 * Returns whether the `GtkText` will truncate multi-line text
7147 * that is pasted into the widget
7148 *
7149 * Returns: %TRUE if @self will truncate multi-line text
7150 */
7151gboolean
7152gtk_text_get_truncate_multiline (GtkText *self)
7153{
7154 GtkTextPrivate *priv = gtk_text_get_instance_private (self);
7155
7156 g_return_val_if_fail (GTK_IS_TEXT (self), FALSE);
7157
7158 return priv->truncate_multiline;
7159}
7160
7161/**
7162 * gtk_text_compute_cursor_extents:
7163 * @self: a `GtkText`
7164 * @position: the character position
7165 * @strong: (out) (optional): location to store the strong cursor position
7166 * @weak: (out) (optional): location to store the weak cursor position
7167 *
7168 * Determine the positions of the strong and weak cursors if the
7169 * insertion point in the layout is at @position.
7170 *
7171 * The position of each cursor is stored as a zero-width rectangle.
7172 * The strong cursor location is the location where characters of
7173 * the directionality equal to the base direction are inserted.
7174 * The weak cursor location is the location where characters of
7175 * the directionality opposite to the base direction are inserted.
7176 *
7177 * The rectangle positions are in widget coordinates.
7178 *
7179 * Since: 4.4
7180 */
7181void
7182gtk_text_compute_cursor_extents (GtkText *self,
7183 gsize position,
7184 graphene_rect_t *strong,
7185 graphene_rect_t *weak)
7186{
7187 PangoLayout *layout;
7188 PangoRectangle pango_strong_pos;
7189 PangoRectangle pango_weak_pos;
7190 int offset_x, offset_y, index;
7191 const char *text;
7192
7193 g_return_if_fail (GTK_IS_TEXT (self));
7194
7195 layout = gtk_text_ensure_layout (self, TRUE);
7196 text = pango_layout_get_text (layout);
7197 position = CLAMP (position, 0, g_utf8_strlen (text, -1));
7198 index = g_utf8_offset_to_pointer (str: text, offset: position) - text;
7199
7200 pango_layout_get_cursor_pos (layout, index_: index,
7201 strong_pos: strong ? &pango_strong_pos : NULL,
7202 weak_pos: weak ? &pango_weak_pos : NULL);
7203 gtk_text_get_layout_offsets (self, x: &offset_x, y: &offset_y);
7204
7205 if (strong)
7206 {
7207 graphene_rect_init (r: strong,
7208 x: offset_x + pango_strong_pos.x / PANGO_SCALE,
7209 y: offset_y + pango_strong_pos.y / PANGO_SCALE,
7210 width: 0,
7211 height: pango_strong_pos.height / PANGO_SCALE);
7212 }
7213
7214 if (weak)
7215 {
7216 graphene_rect_init (r: weak,
7217 x: offset_x + pango_weak_pos.x / PANGO_SCALE,
7218 y: offset_y + pango_weak_pos.y / PANGO_SCALE,
7219 width: 0,
7220 height: pango_weak_pos.height / PANGO_SCALE);
7221 }
7222}
7223
7224static void
7225gtk_text_real_undo (GtkWidget *widget,
7226 const char *action_name,
7227 GVariant *parameters)
7228{
7229 GtkText *text = GTK_TEXT (widget);
7230 GtkTextPrivate *priv = gtk_text_get_instance_private (self: text);
7231
7232 gtk_text_history_undo (self: priv->history);
7233}
7234
7235static void
7236gtk_text_real_redo (GtkWidget *widget,
7237 const char *action_name,
7238 GVariant *parameters)
7239{
7240 GtkText *text = GTK_TEXT (widget);
7241 GtkTextPrivate *priv = gtk_text_get_instance_private (self: text);
7242
7243 gtk_text_history_redo (self: priv->history);
7244}
7245
7246static void
7247gtk_text_history_change_state_cb (gpointer funcs_data,
7248 gboolean is_modified,
7249 gboolean can_undo,
7250 gboolean can_redo)
7251{
7252 /* Do nothing */
7253}
7254
7255static void
7256gtk_text_history_insert_cb (gpointer funcs_data,
7257 guint begin,
7258 guint end,
7259 const char *str,
7260 guint len)
7261{
7262 GtkText *text = funcs_data;
7263 int location = begin;
7264
7265 gtk_editable_insert_text (GTK_EDITABLE (text), text: str, length: len, position: &location);
7266}
7267
7268static void
7269gtk_text_history_delete_cb (gpointer funcs_data,
7270 guint begin,
7271 guint end,
7272 const char *expected_text,
7273 guint len)
7274{
7275 GtkText *text = funcs_data;
7276
7277 gtk_editable_delete_text (GTK_EDITABLE (text), start_pos: begin, end_pos: end);
7278}
7279
7280static void
7281gtk_text_history_select_cb (gpointer funcs_data,
7282 int selection_insert,
7283 int selection_bound)
7284{
7285 GtkText *text = funcs_data;
7286
7287 gtk_editable_select_region (GTK_EDITABLE (text),
7288 start_pos: selection_insert,
7289 end_pos: selection_bound);
7290}
7291

source code of gtk/gtk/gtktext.c