1/* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */
2/* GTK - The GIMP Toolkit
3 * gtktextview.c Copyright (C) 2000 Red Hat, Inc.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19/*
20 * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
21 * file for a list of people on the GTK+ Team. See the ChangeLog
22 * files for a list of changes. These files are distributed with
23 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
24 */
25
26#include "config.h"
27
28#include "gtktextviewprivate.h"
29
30#include <string.h>
31
32#include "gtkadjustmentprivate.h"
33#include "gtkcsscolorvalueprivate.h"
34#include "gtkdebug.h"
35#include "gtkdragsourceprivate.h"
36#include "gtkdropcontrollermotion.h"
37#include "gtkintl.h"
38#include "gtkmain.h"
39#include "gtkmarshalers.h"
40#include "gtkrenderbackgroundprivate.h"
41#include "gtksettings.h"
42#include "gtktextiterprivate.h"
43#include "gtkimmulticontext.h"
44#include "gtkprivate.h"
45#include "gtktextutil.h"
46#include "gtkwidgetprivate.h"
47#include "gtkwindow.h"
48#include "gtkscrollable.h"
49#include "gtktypebuiltins.h"
50#include "gtktextviewchildprivate.h"
51#include "gtktexthandleprivate.h"
52#include "gtkstylecontextprivate.h"
53#include "gtkpopover.h"
54#include "gtkmagnifierprivate.h"
55#include "gtkemojichooser.h"
56#include "gtkpango.h"
57#include "gtknative.h"
58#include "gtkwidgetprivate.h"
59#include "gtkjoinedmenuprivate.h"
60#include "gtkcsslineheightvalueprivate.h"
61#include "gtkcssenumvalueprivate.h"
62
63
64/**
65 * GtkTextView:
66 *
67 * A widget that displays the contents of a [class@Gtk.TextBuffer].
68 *
69 * ![An example GtkTextview](multiline-text.png)
70 *
71 * You may wish to begin by reading the [conceptual overview](section-text-widget.html),
72 * which gives an overview of all the objects and data types related to the
73 * text widget and how they work together.
74 *
75 * ## CSS nodes
76 *
77 * ```
78 * textview.view
79 * ├── border.top
80 * ├── border.left
81 * ├── text
82 * │ ╰── [selection]
83 * ├── border.right
84 * ├── border.bottom
85 * ╰── [window.popup]
86 * ```
87 *
88 * `GtkTextView` has a main css node with name textview and style class .view,
89 * and subnodes for each of the border windows, and the main text area,
90 * with names border and text, respectively. The border nodes each get
91 * one of the style classes .left, .right, .top or .bottom.
92 *
93 * A node representing the selection will appear below the text node.
94 *
95 * If a context menu is opened, the window node will appear as a subnode
96 * of the main node.
97 *
98 * ## Accessibility
99 *
100 * `GtkTextView` uses the %GTK_ACCESSIBLE_ROLE_TEXT_BOX role.
101 */
102
103/* How scrolling, validation, exposes, etc. work.
104 *
105 * The expose_event handler has the invariant that the onscreen lines
106 * have been validated.
107 *
108 * There are two ways that onscreen lines can become invalid. The first
109 * is to change which lines are onscreen. This happens when the value
110 * of a scroll adjustment changes. So the code path begins in
111 * gtk_text_view_value_changed() and goes like this:
112 * - gdk_surface_scroll() to reflect the new adjustment value
113 * - validate the lines that were moved onscreen
114 * - gdk_surface_process_updates() to handle the exposes immediately
115 *
116 * The second way is that you get the “invalidated” signal from the layout,
117 * indicating that lines have become invalid. This code path begins in
118 * invalidated_handler() and goes like this:
119 * - install high-priority idle which does the rest of the steps
120 * - if a scroll is pending from scroll_to_mark(), do the scroll,
121 * jumping to the gtk_text_view_value_changed() code path
122 * - otherwise, validate the onscreen lines
123 * - DO NOT process updates
124 *
125 * In both cases, validating the onscreen lines can trigger a scroll
126 * due to maintaining the first_para on the top of the screen.
127 * If validation triggers a scroll, we jump to the top of the code path
128 * for value_changed, and bail out of the current code path.
129 *
130 * Also, in size_allocate, if we invalidate some lines from changing
131 * the layout width, we need to go ahead and run the high-priority idle,
132 * because GTK sends exposes right after doing the size allocates without
133 * returning to the main loop. This is also why the high-priority idle
134 * is at a higher priority than resizing.
135 *
136 */
137
138#if 0
139#define DEBUG_VALIDATION_AND_SCROLLING
140#endif
141
142#ifdef DEBUG_VALIDATION_AND_SCROLLING
143#define DV(x) (x)
144#else
145#define DV(x)
146#endif
147
148#define SCREEN_WIDTH(widget) text_window_get_width (GTK_TEXT_VIEW (widget)->priv->text_window)
149#define SCREEN_HEIGHT(widget) text_window_get_height (GTK_TEXT_VIEW (widget)->priv->text_window)
150
151#define SPACE_FOR_CURSOR 1
152
153typedef struct _GtkTextWindow GtkTextWindow;
154typedef struct _GtkTextPendingScroll GtkTextPendingScroll;
155
156enum
157{
158 TEXT_HANDLE_CURSOR,
159 TEXT_HANDLE_SELECTION_BOUND,
160 TEXT_HANDLE_N_HANDLES
161};
162
163struct _GtkTextViewPrivate
164{
165 GtkTextLayout *layout;
166 GtkTextBuffer *buffer;
167
168 guint blink_time; /* time in msec the cursor has blinked since last user event */
169 guint im_spot_idle;
170 char *im_module;
171
172 int dnd_x;
173 int dnd_y;
174
175 GtkTextHandle *text_handles[TEXT_HANDLE_N_HANDLES];
176 GtkWidget *selection_bubble;
177 guint selection_bubble_timeout_id;
178
179 GtkWidget *magnifier_popover;
180 GtkWidget *magnifier;
181
182 GtkBorder border_window_size;
183 GtkTextWindow *text_window;
184
185 GQueue anchored_children;
186
187 GtkTextViewChild *left_child;
188 GtkTextViewChild *right_child;
189 GtkTextViewChild *top_child;
190 GtkTextViewChild *bottom_child;
191 GtkTextViewChild *center_child;
192
193 GtkAdjustment *hadjustment;
194 GtkAdjustment *vadjustment;
195
196 /* X offset between widget coordinates and buffer coordinates
197 * taking left_padding in account
198 */
199 int xoffset;
200
201 /* Y offset between widget coordinates and buffer coordinates
202 * taking top_padding and top_margin in account
203 */
204 int yoffset;
205
206 /* Width and height of the buffer */
207 int width;
208 int height;
209
210 /* The virtual cursor position is normally the same as the
211 * actual (strong) cursor position, except in two circumstances:
212 *
213 * a) When the cursor is moved vertically with the keyboard
214 * b) When the text view is scrolled with the keyboard
215 *
216 * In case a), virtual_cursor_x is preserved, but not virtual_cursor_y
217 * In case b), both virtual_cursor_x and virtual_cursor_y are preserved.
218 */
219 int virtual_cursor_x; /* -1 means use actual cursor position */
220 int virtual_cursor_y; /* -1 means use actual cursor position */
221
222 GtkTextMark *first_para_mark; /* Mark at the beginning of the first onscreen paragraph */
223 int first_para_pixels; /* Offset of top of screen in the first onscreen paragraph */
224
225 guint64 blink_start_time;
226 guint blink_tick;
227 float cursor_alpha;
228
229 guint scroll_timeout;
230
231 guint first_validate_idle; /* Idle to revalidate onscreen portion, runs before resize */
232 guint incremental_validate_idle; /* Idle to revalidate offscreen portions, runs after redraw */
233
234 /* Mark for drop target */
235 GtkTextMark *dnd_mark;
236
237 /* Mark for selection of drag source */
238 GtkTextMark *dnd_drag_begin_mark;
239 GtkTextMark *dnd_drag_end_mark;
240
241 GtkIMContext *im_context;
242 GtkWidget *popup_menu;
243 GMenuModel *extra_menu;
244
245 GtkTextPendingScroll *pending_scroll;
246
247 GtkGesture *drag_gesture;
248 GtkEventController *key_controller;
249
250 GtkCssNode *selection_node;
251
252 GdkDrag *drag;
253
254 /* Default style settings */
255 int pixels_above_lines;
256 int pixels_below_lines;
257 int pixels_inside_wrap;
258 GtkWrapMode wrap_mode;
259 GtkJustification justify;
260
261 int left_margin;
262 int right_margin;
263 int top_margin;
264 int bottom_margin;
265 int left_padding;
266 int right_padding;
267 int top_padding;
268 int bottom_padding;
269
270 int indent;
271
272 guint32 obscured_cursor_timestamp;
273
274 gint64 handle_place_time;
275 PangoTabArray *tabs;
276
277 guint editable : 1;
278
279 guint overwrite_mode : 1;
280 guint cursor_visible : 1;
281
282 /* if we have reset the IM since the last character entered */
283 guint need_im_reset : 1;
284
285 guint accepts_tab : 1;
286
287 /* debug flag - means that we've validated onscreen since the
288 * last "invalidate" signal from the layout
289 */
290 guint onscreen_validated : 1;
291
292 guint mouse_cursor_obscured : 1;
293
294 guint scroll_after_paste : 1;
295
296 guint text_handles_enabled : 1;
297
298 /* GtkScrollablePolicy needs to be checked when
299 * driving the scrollable adjustment values */
300 guint hscroll_policy : 1;
301 guint vscroll_policy : 1;
302 guint cursor_handle_dragged : 1;
303 guint selection_handle_dragged : 1;
304};
305
306struct _GtkTextPendingScroll
307{
308 GtkTextMark *mark;
309 double within_margin;
310 gboolean use_align;
311 double xalign;
312 double yalign;
313};
314
315typedef enum
316{
317 SELECT_CHARACTERS,
318 SELECT_WORDS,
319 SELECT_LINES
320} SelectionGranularity;
321
322enum
323{
324 MOVE_CURSOR,
325 PAGE_HORIZONTALLY,
326 SET_ANCHOR,
327 INSERT_AT_CURSOR,
328 DELETE_FROM_CURSOR,
329 BACKSPACE,
330 CUT_CLIPBOARD,
331 COPY_CLIPBOARD,
332 PASTE_CLIPBOARD,
333 TOGGLE_OVERWRITE,
334 MOVE_VIEWPORT,
335 SELECT_ALL,
336 TOGGLE_CURSOR_VISIBLE,
337 PREEDIT_CHANGED,
338 EXTEND_SELECTION,
339 INSERT_EMOJI,
340 LAST_SIGNAL
341};
342
343enum
344{
345 PROP_0,
346 PROP_PIXELS_ABOVE_LINES,
347 PROP_PIXELS_BELOW_LINES,
348 PROP_PIXELS_INSIDE_WRAP,
349 PROP_EDITABLE,
350 PROP_WRAP_MODE,
351 PROP_JUSTIFICATION,
352 PROP_LEFT_MARGIN,
353 PROP_RIGHT_MARGIN,
354 PROP_TOP_MARGIN,
355 PROP_BOTTOM_MARGIN,
356 PROP_INDENT,
357 PROP_TABS,
358 PROP_CURSOR_VISIBLE,
359 PROP_BUFFER,
360 PROP_OVERWRITE,
361 PROP_ACCEPTS_TAB,
362 PROP_IM_MODULE,
363 PROP_HADJUSTMENT,
364 PROP_VADJUSTMENT,
365 PROP_HSCROLL_POLICY,
366 PROP_VSCROLL_POLICY,
367 PROP_INPUT_PURPOSE,
368 PROP_INPUT_HINTS,
369 PROP_MONOSPACE,
370 PROP_EXTRA_MENU
371};
372
373static GQuark quark_text_selection_data = 0;
374static GQuark quark_gtk_signal = 0;
375static GQuark quark_text_view_child = 0;
376
377static void gtk_text_view_finalize (GObject *object);
378static void gtk_text_view_set_property (GObject *object,
379 guint prop_id,
380 const GValue *value,
381 GParamSpec *pspec);
382static void gtk_text_view_get_property (GObject *object,
383 guint prop_id,
384 GValue *value,
385 GParamSpec *pspec);
386static void gtk_text_view_dispose (GObject *object);
387static void gtk_text_view_measure (GtkWidget *widget,
388 GtkOrientation orientation,
389 int for_size,
390 int *minimum,
391 int *natural,
392 int *minimum_baseline,
393 int *natural_baseline);
394static void gtk_text_view_size_allocate (GtkWidget *widget,
395 int width,
396 int height,
397 int baseline);
398static void gtk_text_view_realize (GtkWidget *widget);
399static void gtk_text_view_unrealize (GtkWidget *widget);
400static void gtk_text_view_map (GtkWidget *widget);
401static void gtk_text_view_css_changed (GtkWidget *widget,
402 GtkCssStyleChange *change);
403static void gtk_text_view_direction_changed (GtkWidget *widget,
404 GtkTextDirection previous_direction);
405static void gtk_text_view_system_setting_changed (GtkWidget *widget,
406 GtkSystemSetting setting);
407static void gtk_text_view_state_flags_changed (GtkWidget *widget,
408 GtkStateFlags previous_state);
409
410static void gtk_text_view_click_gesture_pressed (GtkGestureClick *gesture,
411 int n_press,
412 double x,
413 double y,
414 GtkTextView *text_view);
415static void gtk_text_view_drag_gesture_update (GtkGestureDrag *gesture,
416 double offset_x,
417 double offset_y,
418 GtkTextView *text_view);
419static void gtk_text_view_drag_gesture_end (GtkGestureDrag *gesture,
420 double offset_x,
421 double offset_y,
422 GtkTextView *text_view);
423
424static gboolean gtk_text_view_key_controller_key_pressed (GtkEventControllerKey *controller,
425 guint keyval,
426 guint keycode,
427 GdkModifierType state,
428 GtkTextView *text_view);
429static void gtk_text_view_key_controller_im_update (GtkEventControllerKey *controller,
430 GtkTextView *text_view);
431
432static void gtk_text_view_focus_in (GtkWidget *widget);
433static void gtk_text_view_focus_out (GtkWidget *widget);
434static void gtk_text_view_motion (GtkEventController *controller,
435 double x,
436 double y,
437 gpointer user_data);
438static void gtk_text_view_snapshot (GtkWidget *widget,
439 GtkSnapshot *snapshot);
440static void gtk_text_view_select_all (GtkWidget *widget,
441 gboolean select);
442static gboolean get_middle_click_paste (GtkTextView *text_view);
443
444static GtkTextBuffer* gtk_text_view_create_buffer (GtkTextView *text_view);
445
446/* Target side drag signals */
447static void gtk_text_view_drag_leave (GtkDropTarget *dest,
448 GtkTextView *text_view);
449static GdkDragAction
450 gtk_text_view_drag_motion (GtkDropTarget *dest,
451 double x,
452 double y,
453 GtkTextView *text_view);
454static gboolean gtk_text_view_drag_drop (GtkDropTarget *dest,
455 const GValue *value,
456 double x,
457 double y,
458 GtkTextView *text_view);
459
460static void gtk_text_view_popup_menu (GtkWidget *widget,
461 const char *action_name,
462 GVariant *parameters);
463static void gtk_text_view_move_cursor (GtkTextView *text_view,
464 GtkMovementStep step,
465 int count,
466 gboolean extend_selection);
467static void gtk_text_view_move_viewport (GtkTextView *text_view,
468 GtkScrollStep step,
469 int count);
470static void gtk_text_view_set_anchor (GtkTextView *text_view);
471static gboolean gtk_text_view_scroll_pages (GtkTextView *text_view,
472 int count,
473 gboolean extend_selection);
474static gboolean gtk_text_view_scroll_hpages(GtkTextView *text_view,
475 int count,
476 gboolean extend_selection);
477static void gtk_text_view_insert_at_cursor (GtkTextView *text_view,
478 const char *str);
479static void gtk_text_view_delete_from_cursor (GtkTextView *text_view,
480 GtkDeleteType type,
481 int count);
482static void gtk_text_view_backspace (GtkTextView *text_view);
483static void gtk_text_view_cut_clipboard (GtkTextView *text_view);
484static void gtk_text_view_copy_clipboard (GtkTextView *text_view);
485static void gtk_text_view_paste_clipboard (GtkTextView *text_view);
486static void gtk_text_view_toggle_overwrite (GtkTextView *text_view);
487static void gtk_text_view_toggle_cursor_visible (GtkTextView *text_view);
488
489static void gtk_text_view_unselect (GtkTextView *text_view);
490
491static void gtk_text_view_validate_onscreen (GtkTextView *text_view);
492static void gtk_text_view_get_first_para_iter (GtkTextView *text_view,
493 GtkTextIter *iter);
494static void gtk_text_view_update_layout_width (GtkTextView *text_view);
495static void gtk_text_view_set_attributes_from_style (GtkTextView *text_view,
496 GtkTextAttributes *values);
497static void gtk_text_view_ensure_layout (GtkTextView *text_view);
498static void gtk_text_view_destroy_layout (GtkTextView *text_view);
499static void gtk_text_view_check_keymap_direction (GtkTextView *text_view);
500static void gtk_text_view_start_selection_drag (GtkTextView *text_view,
501 const GtkTextIter *iter,
502 SelectionGranularity granularity,
503 gboolean extends);
504static gboolean gtk_text_view_end_selection_drag (GtkTextView *text_view);
505static void gtk_text_view_start_selection_dnd (GtkTextView *text_view,
506 const GtkTextIter *iter,
507 GdkEvent *event,
508 int x,
509 int y);
510static void gtk_text_view_check_cursor_blink (GtkTextView *text_view);
511static void gtk_text_view_pend_cursor_blink (GtkTextView *text_view);
512static void gtk_text_view_stop_cursor_blink (GtkTextView *text_view);
513static void gtk_text_view_reset_blink_time (GtkTextView *text_view);
514
515static void gtk_text_view_value_changed (GtkAdjustment *adjustment,
516 GtkTextView *view);
517static void gtk_text_view_commit_handler (GtkIMContext *context,
518 const char *str,
519 GtkTextView *text_view);
520static void gtk_text_view_commit_text (GtkTextView *text_view,
521 const char *text);
522static void gtk_text_view_preedit_start_handler (GtkIMContext *context,
523 GtkTextView *text_view);
524static void gtk_text_view_preedit_changed_handler (GtkIMContext *context,
525 GtkTextView *text_view);
526static gboolean gtk_text_view_retrieve_surrounding_handler (GtkIMContext *context,
527 GtkTextView *text_view);
528static gboolean gtk_text_view_delete_surrounding_handler (GtkIMContext *context,
529 int offset,
530 int n_chars,
531 GtkTextView *text_view);
532
533static void gtk_text_view_mark_set_handler (GtkTextBuffer *buffer,
534 const GtkTextIter *location,
535 GtkTextMark *mark,
536 gpointer data);
537static void gtk_text_view_paste_done_handler (GtkTextBuffer *buffer,
538 GdkClipboard *clipboard,
539 gpointer data);
540static void gtk_text_view_buffer_changed_handler (GtkTextBuffer *buffer,
541 gpointer data);
542static void gtk_text_view_buffer_notify_redo (GtkTextBuffer *buffer,
543 GParamSpec *pspec,
544 GtkTextView *view);
545static void gtk_text_view_buffer_notify_undo (GtkTextBuffer *buffer,
546 GParamSpec *pspec,
547 GtkTextView *view);
548static void gtk_text_view_get_virtual_cursor_pos (GtkTextView *text_view,
549 GtkTextIter *cursor,
550 int *x,
551 int *y);
552static void gtk_text_view_set_virtual_cursor_pos (GtkTextView *text_view,
553 int x,
554 int y);
555
556static void gtk_text_view_do_popup (GtkTextView *text_view,
557 GdkEvent *event);
558
559static void cancel_pending_scroll (GtkTextView *text_view);
560static void gtk_text_view_queue_scroll (GtkTextView *text_view,
561 GtkTextMark *mark,
562 double within_margin,
563 gboolean use_align,
564 double xalign,
565 double yalign);
566
567static gboolean gtk_text_view_flush_scroll (GtkTextView *text_view);
568static void gtk_text_view_update_adjustments (GtkTextView *text_view);
569static void gtk_text_view_invalidate (GtkTextView *text_view);
570static void gtk_text_view_flush_first_validate (GtkTextView *text_view);
571
572static void gtk_text_view_set_hadjustment (GtkTextView *text_view,
573 GtkAdjustment *adjustment);
574static void gtk_text_view_set_vadjustment (GtkTextView *text_view,
575 GtkAdjustment *adjustment);
576static void gtk_text_view_set_hadjustment_values (GtkTextView *text_view);
577static void gtk_text_view_set_vadjustment_values (GtkTextView *text_view);
578
579static void gtk_text_view_update_im_spot_location (GtkTextView *text_view);
580static void gtk_text_view_insert_emoji (GtkTextView *text_view);
581
582static void update_node_ordering (GtkWidget *widget);
583static void gtk_text_view_update_pango_contexts (GtkTextView *text_view);
584
585/* GtkTextHandle handlers */
586static void gtk_text_view_handle_drag_started (GtkTextHandle *handle,
587 GtkTextView *text_view);
588static void gtk_text_view_handle_dragged (GtkTextHandle *handle,
589 int x,
590 int y,
591 GtkTextView *text_view);
592static void gtk_text_view_handle_drag_finished (GtkTextHandle *handle,
593 GtkTextView *text_view);
594static void gtk_text_view_update_handles (GtkTextView *text_view);
595
596static void gtk_text_view_selection_bubble_popup_unset (GtkTextView *text_view);
597static void gtk_text_view_selection_bubble_popup_set (GtkTextView *text_view);
598
599static gboolean gtk_text_view_extend_selection (GtkTextView *text_view,
600 GtkTextExtendSelection granularity,
601 const GtkTextIter *location,
602 GtkTextIter *start,
603 GtkTextIter *end);
604static void extend_selection (GtkTextView *text_view,
605 SelectionGranularity granularity,
606 const GtkTextIter *location,
607 GtkTextIter *start,
608 GtkTextIter *end);
609
610
611static void gtk_text_view_update_clipboard_actions (GtkTextView *text_view);
612static void gtk_text_view_update_emoji_action (GtkTextView *text_view);
613
614static void gtk_text_view_activate_clipboard_cut (GtkWidget *widget,
615 const char *action_name,
616 GVariant *parameter);
617static void gtk_text_view_activate_clipboard_copy (GtkWidget *widget,
618 const char *action_name,
619 GVariant *parameter);
620static void gtk_text_view_activate_clipboard_paste (GtkWidget *widget,
621 const char *action_name,
622 GVariant *parameter);
623static void gtk_text_view_activate_selection_delete (GtkWidget *widget,
624 const char *action_name,
625 GVariant *parameter);
626static void gtk_text_view_activate_selection_select_all (GtkWidget *widget,
627 const char *action_name,
628 GVariant *parameter);
629static void gtk_text_view_activate_misc_insert_emoji (GtkWidget *widget,
630 const char *action_name,
631 GVariant *parameter);
632
633static void gtk_text_view_real_undo (GtkWidget *widget,
634 const char *action_name,
635 GVariant *parameter);
636static void gtk_text_view_real_redo (GtkWidget *widget,
637 const char *action_name,
638 GVariant *parameter);
639
640
641/* FIXME probably need the focus methods. */
642
643typedef struct
644{
645 GList link;
646 GtkWidget *widget;
647 GtkTextChildAnchor *anchor;
648 int from_top_of_line;
649 int from_left_of_buffer;
650} AnchoredChild;
651
652static AnchoredChild *anchored_child_new (GtkWidget *child,
653 GtkTextChildAnchor *anchor,
654 GtkTextLayout *layout);
655static void anchored_child_free (AnchoredChild *child);
656
657struct _GtkTextWindow
658{
659 GtkTextWindowType type;
660 GtkWidget *widget;
661 GtkCssNode *css_node;
662 GdkRectangle allocation;
663};
664
665static GtkTextWindow *text_window_new (GtkWidget *widget);
666static void text_window_free (GtkTextWindow *win);
667static void text_window_size_allocate (GtkTextWindow *win,
668 GdkRectangle *rect);
669static int text_window_get_width (GtkTextWindow *win);
670static int text_window_get_height (GtkTextWindow *win);
671
672
673static guint signals[LAST_SIGNAL] = { 0 };
674
675G_DEFINE_TYPE_WITH_CODE (GtkTextView, gtk_text_view, GTK_TYPE_WIDGET,
676 G_ADD_PRIVATE (GtkTextView)
677 G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL))
678
679static GtkTextBuffer*
680get_buffer (GtkTextView *text_view)
681{
682 if (text_view->priv->buffer == NULL)
683 {
684 GtkTextBuffer *b;
685 b = GTK_TEXT_VIEW_GET_CLASS (text_view)->create_buffer (text_view);
686 gtk_text_view_set_buffer (text_view, buffer: b);
687 g_object_unref (object: b);
688 }
689
690 return text_view->priv->buffer;
691}
692
693#define UPPER_OFFSET_ANCHOR 0.8
694#define LOWER_OFFSET_ANCHOR 0.2
695
696static gboolean
697check_scroll (double offset, GtkAdjustment *adjustment)
698{
699 if ((offset > UPPER_OFFSET_ANCHOR &&
700 gtk_adjustment_get_value (adjustment) + gtk_adjustment_get_page_size (adjustment) < gtk_adjustment_get_upper (adjustment)) ||
701 (offset < LOWER_OFFSET_ANCHOR &&
702 gtk_adjustment_get_value (adjustment) > gtk_adjustment_get_lower (adjustment)))
703 return TRUE;
704
705 return FALSE;
706}
707
708static int
709gtk_text_view_drop_motion_scroll_timeout (gpointer data)
710{
711 GtkTextView *text_view;
712 GtkTextViewPrivate *priv;
713 GtkTextIter newplace;
714 double pointer_xoffset, pointer_yoffset;
715
716 text_view = GTK_TEXT_VIEW (data);
717 priv = text_view->priv;
718
719 gtk_text_layout_get_iter_at_pixel (layout: priv->layout,
720 iter: &newplace,
721 x: priv->dnd_x + priv->xoffset,
722 y: priv->dnd_y + priv->yoffset);
723
724 gtk_text_buffer_move_mark (buffer: get_buffer (text_view), mark: priv->dnd_mark, where: &newplace);
725
726 pointer_xoffset = (double) priv->dnd_x / text_window_get_width (win: priv->text_window);
727 pointer_yoffset = (double) priv->dnd_y / text_window_get_height (win: priv->text_window);
728
729 if (check_scroll (offset: pointer_xoffset, adjustment: priv->hadjustment) ||
730 check_scroll (offset: pointer_yoffset, adjustment: priv->vadjustment))
731 {
732 /* do not make offsets surpass lower nor upper anchors, this makes
733 * scrolling speed relative to the distance of the pointer to the
734 * anchors when it moves beyond them.
735 */
736 pointer_xoffset = CLAMP (pointer_xoffset, LOWER_OFFSET_ANCHOR, UPPER_OFFSET_ANCHOR);
737 pointer_yoffset = CLAMP (pointer_yoffset, LOWER_OFFSET_ANCHOR, UPPER_OFFSET_ANCHOR);
738
739 gtk_text_view_scroll_to_mark (text_view,
740 mark: priv->dnd_mark,
741 within_margin: 0., TRUE, xalign: pointer_xoffset, yalign: pointer_yoffset);
742 }
743
744 return G_SOURCE_CONTINUE;
745}
746
747static void
748gtk_text_view_drop_scroll_motion (GtkDropControllerMotion *motion,
749 double x,
750 double y,
751 GtkTextView *self)
752{
753 GtkTextViewPrivate *priv = self->priv;
754 GdkRectangle target_rect;
755
756 target_rect = priv->text_window->allocation;
757
758 if (x < target_rect.x ||
759 y < target_rect.y ||
760 x > (target_rect.x + target_rect.width) ||
761 y > (target_rect.y + target_rect.height))
762 {
763 priv->dnd_x = priv->dnd_y = -1;
764 g_clear_handle_id (&priv->scroll_timeout, g_source_remove);
765 return;
766 }
767
768 /* DnD uses text window coords, so subtract extra widget
769 * coords that happen e.g. when displaying line numbers.
770 */
771 priv->dnd_x = x - target_rect.x;
772 priv->dnd_y = y - target_rect.y;
773
774 if (!priv->scroll_timeout)
775 {
776 priv->scroll_timeout = g_timeout_add (interval: 100, function: gtk_text_view_drop_motion_scroll_timeout, data: self);
777 gdk_source_set_static_name_by_id (tag: priv->scroll_timeout, name: "[gtk] gtk_text_view_drop_motion_scroll_timeout");
778 }
779}
780
781static void
782gtk_text_view_drop_scroll_leave (GtkDropControllerMotion *motion,
783 GtkTextView *self)
784{
785 GtkTextViewPrivate *priv = self->priv;
786
787 priv->dnd_x = priv->dnd_y = -1;
788 g_clear_handle_id (&priv->scroll_timeout, g_source_remove);
789}
790
791static void
792add_move_binding (GtkWidgetClass *widget_class,
793 guint keyval,
794 guint modmask,
795 GtkMovementStep step,
796 int count)
797{
798 g_assert ((modmask & GDK_SHIFT_MASK) == 0);
799
800 gtk_widget_class_add_binding_signal (widget_class,
801 keyval, mods: modmask,
802 signal: "move-cursor",
803 format_string: "(iib)", step, count, FALSE);
804
805 /* Selection-extending version */
806 gtk_widget_class_add_binding_signal (widget_class,
807 keyval, mods: modmask | GDK_SHIFT_MASK,
808 signal: "move-cursor",
809 format_string: "(iib)", step, count, TRUE);
810}
811
812static void
813gtk_text_view_class_init (GtkTextViewClass *klass)
814{
815 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
816 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
817
818 /* Default handlers and virtual methods
819 */
820 gobject_class->set_property = gtk_text_view_set_property;
821 gobject_class->get_property = gtk_text_view_get_property;
822 gobject_class->finalize = gtk_text_view_finalize;
823 gobject_class->dispose = gtk_text_view_dispose;
824
825 widget_class->realize = gtk_text_view_realize;
826 widget_class->unrealize = gtk_text_view_unrealize;
827 widget_class->map = gtk_text_view_map;
828 widget_class->css_changed = gtk_text_view_css_changed;
829 widget_class->direction_changed = gtk_text_view_direction_changed;
830 widget_class->system_setting_changed = gtk_text_view_system_setting_changed;
831 widget_class->state_flags_changed = gtk_text_view_state_flags_changed;
832 widget_class->measure = gtk_text_view_measure;
833 widget_class->size_allocate = gtk_text_view_size_allocate;
834 widget_class->snapshot = gtk_text_view_snapshot;
835
836 klass->move_cursor = gtk_text_view_move_cursor;
837 klass->set_anchor = gtk_text_view_set_anchor;
838 klass->insert_at_cursor = gtk_text_view_insert_at_cursor;
839 klass->delete_from_cursor = gtk_text_view_delete_from_cursor;
840 klass->backspace = gtk_text_view_backspace;
841 klass->cut_clipboard = gtk_text_view_cut_clipboard;
842 klass->copy_clipboard = gtk_text_view_copy_clipboard;
843 klass->paste_clipboard = gtk_text_view_paste_clipboard;
844 klass->toggle_overwrite = gtk_text_view_toggle_overwrite;
845 klass->create_buffer = gtk_text_view_create_buffer;
846 klass->extend_selection = gtk_text_view_extend_selection;
847 klass->insert_emoji = gtk_text_view_insert_emoji;
848
849 /*
850 * Properties
851 */
852
853 /**
854 * GtkTextview:pixels-above-lines: (attributes org.gtk.Property.get=gtk_text_view_get_pixels_above_lines org.gtk.Property.set=gtk_text_view_set_pixels_above_lines)
855 *
856 * Pixels of blank space above paragraphs.
857 */
858 g_object_class_install_property (oclass: gobject_class,
859 property_id: PROP_PIXELS_ABOVE_LINES,
860 pspec: g_param_spec_int (name: "pixels-above-lines",
861 P_("Pixels Above Lines"),
862 P_("Pixels of blank space above paragraphs"),
863 minimum: 0, G_MAXINT, default_value: 0,
864 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
865
866 /**
867 * GtkTextview:pixels-below-lines: (attributes org.gtk.Property.get=gtk_text_view_get_pixels_below_lines org.gtk.Property.set=gtk_text_view_set_pixels_below_lines)
868 *
869 * Pixels of blank space below paragraphs.
870 */
871 g_object_class_install_property (oclass: gobject_class,
872 property_id: PROP_PIXELS_BELOW_LINES,
873 pspec: g_param_spec_int (name: "pixels-below-lines",
874 P_("Pixels Below Lines"),
875 P_("Pixels of blank space below paragraphs"),
876 minimum: 0, G_MAXINT, default_value: 0,
877 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
878
879 /**
880 * GtkTextview:pixels-inside-wrap: (attributes org.gtk.Property.get=gtk_text_view_get_pixels_inside_wrap org.gtk.Property.set=gtk_text_view_set_pixels_inside_wrap)
881 *
882 * Pixels of blank space between wrapped lines in a paragraph.
883 */
884 g_object_class_install_property (oclass: gobject_class,
885 property_id: PROP_PIXELS_INSIDE_WRAP,
886 pspec: g_param_spec_int (name: "pixels-inside-wrap",
887 P_("Pixels Inside Wrap"),
888 P_("Pixels of blank space between wrapped lines in a paragraph"),
889 minimum: 0, G_MAXINT, default_value: 0,
890 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
891
892 /**
893 * GtkTextview:editable: (attributes org.gtk.Property.get=gtk_text_view_get_editable org.gtk.Property.set=gtk_text_view_set_editable)
894 *
895 * Whether the text can be modified by the user.
896 */
897 g_object_class_install_property (oclass: gobject_class,
898 property_id: PROP_EDITABLE,
899 pspec: g_param_spec_boolean (name: "editable",
900 P_("Editable"),
901 P_("Whether the text can be modified by the user"),
902 TRUE,
903 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
904
905 /**
906 * GtkTextview:wrap-mode: (attributes org.gtk.Property.get=gtk_text_view_get_wrap_mode org.gtk.Property.set=gtk_text_view_set_wrap_mode)
907 *
908 * Whether to wrap lines never, at word boundaries, or at character boundaries.
909 */
910 g_object_class_install_property (oclass: gobject_class,
911 property_id: PROP_WRAP_MODE,
912 pspec: g_param_spec_enum (name: "wrap-mode",
913 P_("Wrap Mode"),
914 P_("Whether to wrap lines never, at word boundaries, or at character boundaries"),
915 enum_type: GTK_TYPE_WRAP_MODE,
916 default_value: GTK_WRAP_NONE,
917 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
918
919 /**
920 * GtkTextview:justification: (attributes org.gtk.Property.get=gtk_text_view_get_justification org.gtk.Property.set=gtk_text_view_set_justification)
921 *
922 * Left, right, or center justification.
923 */
924 g_object_class_install_property (oclass: gobject_class,
925 property_id: PROP_JUSTIFICATION,
926 pspec: g_param_spec_enum (name: "justification",
927 P_("Justification"),
928 P_("Left, right, or center justification"),
929 enum_type: GTK_TYPE_JUSTIFICATION,
930 default_value: GTK_JUSTIFY_LEFT,
931 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
932
933 /**
934 * GtkTextView:left-margin: (attributes org.gtk.Property.get=gtk_text_view_get_left_margin org.gtk.Property.set=gtk_text_view_set_left_margin)
935 *
936 * The default left margin for text in the text view.
937 *
938 * Tags in the buffer may override the default.
939 *
940 * Note that this property is confusingly named. In CSS terms,
941 * the value set here is padding, and it is applied in addition
942 * to the padding from the theme.
943 */
944 g_object_class_install_property (oclass: gobject_class,
945 property_id: PROP_LEFT_MARGIN,
946 pspec: g_param_spec_int (name: "left-margin",
947 P_("Left Margin"),
948 P_("Width of the left margin in pixels"),
949 minimum: 0, G_MAXINT, default_value: 0,
950 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
951
952 /**
953 * GtkTextView:right-margin: (attributes org.gtk.Property.get=gtk_text_view_get_right_margin org.gtk.Property.set=gtk_text_view_set_right_margin)
954 *
955 * The default right margin for text in the text view.
956 *
957 * Tags in the buffer may override the default.
958 *
959 * Note that this property is confusingly named. In CSS terms,
960 * the value set here is padding, and it is applied in addition
961 * to the padding from the theme.
962 */
963 g_object_class_install_property (oclass: gobject_class,
964 property_id: PROP_RIGHT_MARGIN,
965 pspec: g_param_spec_int (name: "right-margin",
966 P_("Right Margin"),
967 P_("Width of the right margin in pixels"),
968 minimum: 0, G_MAXINT, default_value: 0,
969 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
970
971 /**
972 * GtkTextView:top-margin: (attributes org.gtk.Property.get=gtk_text_view_get_top_margin org.gtk.Property.set=gtk_text_view_set_top_margin)
973 *
974 * The top margin for text in the text view.
975 *
976 * Note that this property is confusingly named. In CSS terms,
977 * the value set here is padding, and it is applied in addition
978 * to the padding from the theme.
979 *
980 * Don't confuse this property with [property@Gtk.Widget:margin-top].
981 */
982 g_object_class_install_property (oclass: gobject_class,
983 property_id: PROP_TOP_MARGIN,
984 pspec: g_param_spec_int (name: "top-margin",
985 P_("Top Margin"),
986 P_("Height of the top margin in pixels"),
987 minimum: 0, G_MAXINT, default_value: 0,
988 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
989
990 /**
991 * GtkTextView:bottom-margin: (attributes org.gtk.Property.get=gtk_text_view_get_bottom_margin org.gtk.Property.set=gtk_text_view_set_bottom_margin)
992 *
993 * The bottom margin for text in the text view.
994 *
995 * Note that this property is confusingly named. In CSS terms,
996 * the value set here is padding, and it is applied in addition
997 * to the padding from the theme.
998 *
999 * Don't confuse this property with [property@Gtk.Widget:margin-bottom].
1000 */
1001 g_object_class_install_property (oclass: gobject_class,
1002 property_id: PROP_BOTTOM_MARGIN,
1003 pspec: g_param_spec_int (name: "bottom-margin",
1004 P_("Bottom Margin"),
1005 P_("Height of the bottom margin in pixels"),
1006 minimum: 0, G_MAXINT, default_value: 0,
1007 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
1008
1009 /**
1010 * GtkTextView:indent: (attributes org.gtk.Property.get=gtk_text_view_get_indent org.gtk.Property.set=gtk_text_view_set_indent)
1011 *
1012 * Amount to indent the paragraph, in pixels.
1013 */
1014 g_object_class_install_property (oclass: gobject_class,
1015 property_id: PROP_INDENT,
1016 pspec: g_param_spec_int (name: "indent",
1017 P_("Indent"),
1018 P_("Amount to indent the paragraph, in pixels"),
1019 G_MININT, G_MAXINT, default_value: 0,
1020 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
1021
1022 /**
1023 * GtkTextview:tabs: (attributes org.gtk.Property.get=gtk_text_view_get_tabs org.gtk.Property.set=gtk_text_view_set_tabs)
1024 *
1025 * Custom tabs for this text.
1026 */
1027 g_object_class_install_property (oclass: gobject_class,
1028 property_id: PROP_TABS,
1029 pspec: g_param_spec_boxed (name: "tabs",
1030 P_("Tabs"),
1031 P_("Custom tabs for this text"),
1032 PANGO_TYPE_TAB_ARRAY,
1033 GTK_PARAM_READWRITE));
1034
1035 /**
1036 * GtkTextView:cursor-visible: (attributes org.gtk.Property.get=gtk_text_view_get_cursor_visible org.gtk.Property.set=gtk_text_view_set_cursor_visible)
1037 *
1038 * If the insertion cursor is shown.
1039 */
1040 g_object_class_install_property (oclass: gobject_class,
1041 property_id: PROP_CURSOR_VISIBLE,
1042 pspec: g_param_spec_boolean (name: "cursor-visible",
1043 P_("Cursor Visible"),
1044 P_("If the insertion cursor is shown"),
1045 TRUE,
1046 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
1047
1048 /**
1049 * GtkTextView:buffer: (attributes org.gtk.Property.get=gtk_text_view_get_buffer org.gtk.Property.set=gtk_text_view_set_buffer)
1050 *
1051 * The buffer which is displayed.
1052 */
1053 g_object_class_install_property (oclass: gobject_class,
1054 property_id: PROP_BUFFER,
1055 pspec: g_param_spec_object (name: "buffer",
1056 P_("Buffer"),
1057 P_("The buffer which is displayed"),
1058 GTK_TYPE_TEXT_BUFFER,
1059 GTK_PARAM_READWRITE));
1060
1061 /**
1062 * GtkTextView:overwrite: (attributes org.gtk.Property.get=gtk_text_view_get_overwrite org.gtk.Property.set=gtk_text_view_set_overwrite)
1063 *
1064 * Whether entered text overwrites existing contents.
1065 */
1066 g_object_class_install_property (oclass: gobject_class,
1067 property_id: PROP_OVERWRITE,
1068 pspec: g_param_spec_boolean (name: "overwrite",
1069 P_("Overwrite mode"),
1070 P_("Whether entered text overwrites existing contents"),
1071 FALSE,
1072 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
1073
1074 /**
1075 * GtkTextView:accepts-tab: (attributes org.gtk.Property.get=gtk_text_view_get_accepts_tab org.gtk.Property.set=gtk_text_view_set_accepts_tab)
1076 *
1077 * Whether Tab will result in a tab character being entered.
1078 */
1079 g_object_class_install_property (oclass: gobject_class,
1080 property_id: PROP_ACCEPTS_TAB,
1081 pspec: g_param_spec_boolean (name: "accepts-tab",
1082 P_("Accepts tab"),
1083 P_("Whether Tab will result in a tab character being entered"),
1084 TRUE,
1085 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
1086
1087 /**
1088 * GtkTextView:im-module:
1089 *
1090 * Which IM (input method) module should be used for this text_view.
1091 *
1092 * See [class@Gtk.IMMulticontext].
1093 *
1094 * Setting this to a non-%NULL value overrides the system-wide IM module
1095 * setting. See the GtkSettings [property@Gtk.Settings:gtk-im-module] property.
1096 */
1097 g_object_class_install_property (oclass: gobject_class,
1098 property_id: PROP_IM_MODULE,
1099 pspec: g_param_spec_string (name: "im-module",
1100 P_("IM module"),
1101 P_("Which IM module should be used"),
1102 NULL,
1103 GTK_PARAM_READWRITE));
1104
1105 /**
1106 * GtkTextView:input-purpose: (attributes org.gtk.Property.get=gtk_text_view_get_input_purpose org.gtk.Property.set=gtk_text_view_set_input_purpose)
1107 *
1108 * The purpose of this text field.
1109 *
1110 * This property can be used by on-screen keyboards and other input
1111 * methods to adjust their behaviour.
1112 */
1113 g_object_class_install_property (oclass: gobject_class,
1114 property_id: PROP_INPUT_PURPOSE,
1115 pspec: g_param_spec_enum (name: "input-purpose",
1116 P_("Purpose"),
1117 P_("Purpose of the text field"),
1118 enum_type: GTK_TYPE_INPUT_PURPOSE,
1119 default_value: GTK_INPUT_PURPOSE_FREE_FORM,
1120 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
1121
1122
1123 /**
1124 * GtkTextView:input-hints: (attributes org.gtk.Property.get=gtk_text_view_get_input_hints org.gtk.Property.set=gtk_text_view_set_input_hints)
1125 *
1126 * Additional hints (beyond [property@Gtk.TextView:input-purpose])
1127 * that allow input methods to fine-tune their behaviour.
1128 */
1129 g_object_class_install_property (oclass: gobject_class,
1130 property_id: PROP_INPUT_HINTS,
1131 pspec: g_param_spec_flags (name: "input-hints",
1132 P_("hints"),
1133 P_("Hints for the text field behaviour"),
1134 flags_type: GTK_TYPE_INPUT_HINTS,
1135 default_value: GTK_INPUT_HINT_NONE,
1136 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
1137
1138
1139 /**
1140 * GtkTextView:monospace: (attributes org.gtk.Property.get=gtk_text_view_get_monospace org.gtk.Property.set=gtk_text_view_set_monospace)
1141 *
1142 * Whether text should be displayed in a monospace font.
1143 *
1144 * If %TRUE, set the .monospace style class on the
1145 * text view to indicate that a monospace font is desired.
1146 */
1147 g_object_class_install_property (oclass: gobject_class,
1148 property_id: PROP_MONOSPACE,
1149 pspec: g_param_spec_boolean (name: "monospace",
1150 P_("Monospace"),
1151 P_("Whether to use a monospace font"),
1152 FALSE,
1153 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
1154
1155 /**
1156 * GtkTextView:extra-menu: (attributes org.gtk.Property.get=gtk_text_view_get_extra_menu org.gtk.Property.set=gtk_text_view_set_extra_menu)
1157 *
1158 * A menu model whose contents will be appended to the context menu.
1159 */
1160 g_object_class_install_property (oclass: gobject_class,
1161 property_id: PROP_EXTRA_MENU,
1162 pspec: g_param_spec_object (name: "extra-menu",
1163 P_("Extra menu"),
1164 P_("Menu model to append to the context menu"),
1165 G_TYPE_MENU_MODEL,
1166 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
1167
1168 /* GtkScrollable interface */
1169 g_object_class_override_property (oclass: gobject_class, property_id: PROP_HADJUSTMENT, name: "hadjustment");
1170 g_object_class_override_property (oclass: gobject_class, property_id: PROP_VADJUSTMENT, name: "vadjustment");
1171 g_object_class_override_property (oclass: gobject_class, property_id: PROP_HSCROLL_POLICY, name: "hscroll-policy");
1172 g_object_class_override_property (oclass: gobject_class, property_id: PROP_VSCROLL_POLICY, name: "vscroll-policy");
1173
1174 /*
1175 * Signals
1176 */
1177
1178 /**
1179 * GtkTextView::move-cursor:
1180 * @text_view: the object which received the signal
1181 * @step: the granularity of the move, as a `GtkMovementStep`
1182 * @count: the number of @step units to move
1183 * @extend_selection: %TRUE if the move should extend the selection
1184 *
1185 * Gets emitted when the user initiates a cursor movement.
1186 *
1187 * The ::move-cursor signal is a [keybinding signal](class.SignalAction.html).
1188 * If the cursor is not visible in @text_view, this signal causes
1189 * the viewport to be moved instead.
1190 *
1191 * Applications should not connect to it, but may emit it with
1192 * g_signal_emit_by_name() if they need to control the cursor
1193 * programmatically.
1194 *
1195 *
1196 * The default bindings for this signal come in two variants,
1197 * the variant with the <kbd>Shift</kbd> modifier extends the
1198 * selection, the variant without it does not.
1199 * There are too many key combinations to list them all here.
1200 *
1201 * - <kbd>←</kbd>, <kbd>→</kbd>, <kbd>↑</kbd>, <kbd>↓</kbd>
1202 * move by individual characters/lines
1203 * - <kbd>Ctrl</kbd>-<kbd>→</kbd>, etc. move by words/paragraphs
1204 * - <kbd>Home</kbd>, <kbd>End</kbd> move to the ends of the buffer
1205 * - <kbd>PgUp</kbd>, <kbd>PgDn</kbd> move vertically by pages
1206 * - <kbd>Ctrl</kbd>-<kbd>PgUp</kbd>, <kbd>Ctrl</kbd>-<kbd>PgDn</kbd>
1207 * move horizontally by pages
1208 */
1209 signals[MOVE_CURSOR] =
1210 g_signal_new (I_("move-cursor"),
1211 G_OBJECT_CLASS_TYPE (gobject_class),
1212 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1213 G_STRUCT_OFFSET (GtkTextViewClass, move_cursor),
1214 NULL, NULL,
1215 c_marshaller: _gtk_marshal_VOID__ENUM_INT_BOOLEAN,
1216 G_TYPE_NONE, n_params: 3,
1217 GTK_TYPE_MOVEMENT_STEP,
1218 G_TYPE_INT,
1219 G_TYPE_BOOLEAN);
1220 g_signal_set_va_marshaller (signal_id: signals[MOVE_CURSOR],
1221 G_OBJECT_CLASS_TYPE (gobject_class),
1222 va_marshaller: _gtk_marshal_VOID__ENUM_INT_BOOLEANv);
1223
1224 /**
1225 * GtkTextView::move-viewport:
1226 * @text_view: the object which received the signal
1227 * @step: the granularity of the movement, as a `GtkScrollStep`
1228 * @count: the number of @step units to move
1229 *
1230 * Gets emitted to move the viewport.
1231 *
1232 * The ::move-viewport signal is a [keybinding signal](class.SignalAction.html),
1233 * which can be bound to key combinations to allow the user to move the viewport,
1234 * i.e. change what part of the text view is visible in a containing scrolled
1235 * window.
1236 *
1237 * There are no default bindings for this signal.
1238 */
1239 signals[MOVE_VIEWPORT] =
1240 g_signal_new_class_handler (I_("move-viewport"),
1241 G_OBJECT_CLASS_TYPE (gobject_class),
1242 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1243 G_CALLBACK (gtk_text_view_move_viewport),
1244 NULL, NULL,
1245 c_marshaller: _gtk_marshal_VOID__ENUM_INT,
1246 G_TYPE_NONE, n_params: 2,
1247 GTK_TYPE_SCROLL_STEP,
1248 G_TYPE_INT);
1249 g_signal_set_va_marshaller (signal_id: signals[MOVE_VIEWPORT],
1250 G_OBJECT_CLASS_TYPE (gobject_class),
1251 va_marshaller: _gtk_marshal_VOID__ENUM_INTv);
1252
1253 /**
1254 * GtkTextView::set-anchor:
1255 * @text_view: the object which received the signal
1256 *
1257 * Gets emitted when the user initiates settings the "anchor" mark.
1258 *
1259 * The ::set-anchor signal is a [keybinding signal](class.SignalAction.html)
1260 * which gets emitted when the user initiates setting the "anchor"
1261 * mark. The "anchor" mark gets placed at the same position as the
1262 * "insert" mark.
1263 *
1264 * This signal has no default bindings.
1265 */
1266 signals[SET_ANCHOR] =
1267 g_signal_new (I_("set-anchor"),
1268 G_OBJECT_CLASS_TYPE (gobject_class),
1269 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1270 G_STRUCT_OFFSET (GtkTextViewClass, set_anchor),
1271 NULL, NULL,
1272 NULL,
1273 G_TYPE_NONE, n_params: 0);
1274
1275 /**
1276 * GtkTextView::insert-at-cursor:
1277 * @text_view: the object which received the signal
1278 * @string: the string to insert
1279 *
1280 * Gets emitted when the user initiates the insertion of a
1281 * fixed string at the cursor.
1282 *
1283 * The ::insert-at-cursor signal is a [keybinding signal](class.SignalAction.html).
1284 *
1285 * This signal has no default bindings.
1286 */
1287 signals[INSERT_AT_CURSOR] =
1288 g_signal_new (I_("insert-at-cursor"),
1289 G_OBJECT_CLASS_TYPE (gobject_class),
1290 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1291 G_STRUCT_OFFSET (GtkTextViewClass, insert_at_cursor),
1292 NULL, NULL,
1293 NULL,
1294 G_TYPE_NONE, n_params: 1,
1295 G_TYPE_STRING);
1296
1297 /**
1298 * GtkTextView::delete-from-cursor:
1299 * @text_view: the object which received the signal
1300 * @type: the granularity of the deletion, as a `GtkDeleteType`
1301 * @count: the number of @type units to delete
1302 *
1303 * Gets emitted when the user initiates a text deletion.
1304 *
1305 * The ::delete-from-cursor signal is a [keybinding signal](class.SignalAction.html).
1306 *
1307 * If the @type is %GTK_DELETE_CHARS, GTK deletes the selection
1308 * if there is one, otherwise it deletes the requested number
1309 * of characters.
1310 *
1311 * The default bindings for this signal are <kbd>Delete</kbd> for
1312 * deleting a character, <kbd>Ctrl</kbd>-<kbd>Delete</kbd> for
1313 * deleting a word and <kbd>Ctrl</kbd>-<kbd>Backspace</kbd> for
1314 * deleting a word backwards.
1315 */
1316 signals[DELETE_FROM_CURSOR] =
1317 g_signal_new (I_("delete-from-cursor"),
1318 G_OBJECT_CLASS_TYPE (gobject_class),
1319 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1320 G_STRUCT_OFFSET (GtkTextViewClass, delete_from_cursor),
1321 NULL, NULL,
1322 c_marshaller: _gtk_marshal_VOID__ENUM_INT,
1323 G_TYPE_NONE, n_params: 2,
1324 GTK_TYPE_DELETE_TYPE,
1325 G_TYPE_INT);
1326 g_signal_set_va_marshaller (signal_id: signals[DELETE_FROM_CURSOR],
1327 G_OBJECT_CLASS_TYPE (gobject_class),
1328 va_marshaller: _gtk_marshal_VOID__ENUM_INTv);
1329
1330 /**
1331 * GtkTextView::backspace:
1332 * @text_view: the object which received the signal
1333 *
1334 * Gets emitted when the user asks for it.
1335 *
1336 * The ::backspace signal is a [keybinding signal](class.SignalAction.html).
1337 *
1338 * The default bindings for this signal are
1339 * <kbd>Backspace</kbd> and <kbd>Shift</kbd>-<kbd>Backspace</kbd>.
1340 */
1341 signals[BACKSPACE] =
1342 g_signal_new (I_("backspace"),
1343 G_OBJECT_CLASS_TYPE (gobject_class),
1344 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1345 G_STRUCT_OFFSET (GtkTextViewClass, backspace),
1346 NULL, NULL,
1347 NULL,
1348 G_TYPE_NONE, n_params: 0);
1349
1350 /**
1351 * GtkTextView::cut-clipboard:
1352 * @text_view: the object which received the signal
1353 *
1354 * Gets emitted to cut the selection to the clipboard.
1355 *
1356 * The ::cut-clipboard signal is a [keybinding signal](class.SignalAction.html).
1357 *
1358 * The default bindings for this signal are
1359 * <kbd>Ctrl</kbd>-<kbd>x</kbd> and
1360 * <kbd>Shift</kbd>-<kbd>Delete</kbd>.
1361 */
1362 signals[CUT_CLIPBOARD] =
1363 g_signal_new (I_("cut-clipboard"),
1364 G_OBJECT_CLASS_TYPE (gobject_class),
1365 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1366 G_STRUCT_OFFSET (GtkTextViewClass, cut_clipboard),
1367 NULL, NULL,
1368 NULL,
1369 G_TYPE_NONE, n_params: 0);
1370
1371 /**
1372 * GtkTextView::copy-clipboard:
1373 * @text_view: the object which received the signal
1374 *
1375 * Gets emitted to copy the selection to the clipboard.
1376 *
1377 * The ::copy-clipboard signal is a [keybinding signal](class.SignalAction.html).
1378 *
1379 * The default bindings for this signal are
1380 * <kbd>Ctrl</kbd>-<kbd>c</kbd> and
1381 * <kbd>Ctrl</kbd>-<kbd>Insert</kbd>.
1382 */
1383 signals[COPY_CLIPBOARD] =
1384 g_signal_new (I_("copy-clipboard"),
1385 G_OBJECT_CLASS_TYPE (gobject_class),
1386 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1387 G_STRUCT_OFFSET (GtkTextViewClass, copy_clipboard),
1388 NULL, NULL,
1389 NULL,
1390 G_TYPE_NONE, n_params: 0);
1391
1392 /**
1393 * GtkTextView::paste-clipboard:
1394 * @text_view: the object which received the signal
1395 *
1396 * Gets emitted to paste the contents of the clipboard
1397 * into the text view.
1398 *
1399 * The ::paste-clipboard signal is a [keybinding signal](class.SignalAction.html).
1400 *
1401 * The default bindings for this signal are
1402 * <kbd>Ctrl</kbd>-<kbd>v</kbd> and
1403 * <kbd>Shift</kbd>-<kbd>Insert</kbd>.
1404 */
1405 signals[PASTE_CLIPBOARD] =
1406 g_signal_new (I_("paste-clipboard"),
1407 G_OBJECT_CLASS_TYPE (gobject_class),
1408 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1409 G_STRUCT_OFFSET (GtkTextViewClass, paste_clipboard),
1410 NULL, NULL,
1411 NULL,
1412 G_TYPE_NONE, n_params: 0);
1413
1414 /**
1415 * GtkTextView::toggle-overwrite:
1416 * @text_view: the object which received the signal
1417 *
1418 * Gets emitted to toggle the overwrite mode of the text view.
1419 *
1420 * The ::toggle-overwrite signal is a [keybinding signal](class.SignalAction.html).
1421 *
1422 * The default binding for this signal is <kbd>Insert</kbd>.
1423 */
1424 signals[TOGGLE_OVERWRITE] =
1425 g_signal_new (I_("toggle-overwrite"),
1426 G_OBJECT_CLASS_TYPE (gobject_class),
1427 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1428 G_STRUCT_OFFSET (GtkTextViewClass, toggle_overwrite),
1429 NULL, NULL,
1430 NULL,
1431 G_TYPE_NONE, n_params: 0);
1432
1433 /**
1434 * GtkTextView::select-all:
1435 * @text_view: the object which received the signal
1436 * @select: %TRUE to select, %FALSE to unselect
1437 *
1438 * Gets emitted to select or unselect the complete contents of the text view.
1439 *
1440 * The ::select-all signal is a [keybinding signal](class.SignalAction.html).
1441 *
1442 * The default bindings for this signal are
1443 * <kbd>Ctrl</kbd>-<kbd>a</kbd> and
1444 * <kbd>Ctrl</kbd>-<kbd>/</kbd> for selecting and
1445 * <kbd>Shift</kbd>-<kbd>Ctrl</kbd>-<kbd>a</kbd> and
1446 * <kbd>Ctrl</kbd>-<kbd>\</kbd> for unselecting.
1447 */
1448 signals[SELECT_ALL] =
1449 g_signal_new_class_handler (I_("select-all"),
1450 G_OBJECT_CLASS_TYPE (gobject_class),
1451 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1452 G_CALLBACK (gtk_text_view_select_all),
1453 NULL, NULL,
1454 NULL,
1455 G_TYPE_NONE, n_params: 1, G_TYPE_BOOLEAN);
1456
1457 /**
1458 * GtkTextView::toggle-cursor-visible:
1459 * @text_view: the object which received the signal
1460 *
1461 * Gets emitted to toggle the `cursor-visible` property.
1462 *
1463 * The ::toggle-cursor-visible signal is a
1464 * [keybinding signal](class.SignalAction.html).
1465 *
1466 * The default binding for this signal is <kbd>F7</kbd>.
1467 */
1468 signals[TOGGLE_CURSOR_VISIBLE] =
1469 g_signal_new_class_handler (I_("toggle-cursor-visible"),
1470 G_OBJECT_CLASS_TYPE (gobject_class),
1471 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1472 G_CALLBACK (gtk_text_view_toggle_cursor_visible),
1473 NULL, NULL,
1474 NULL,
1475 G_TYPE_NONE, n_params: 0);
1476
1477 /**
1478 * GtkTextView::preedit-changed:
1479 * @text_view: the object which received the signal
1480 * @preedit: the current preedit string
1481 *
1482 * Emitted when preedit text of the active IM changes.
1483 *
1484 * If an input method is used, the typed text will not immediately
1485 * be committed to the buffer. So if you are interested in the text,
1486 * connect to this signal.
1487 *
1488 * This signal is only emitted if the text at the given position
1489 * is actually editable.
1490 */
1491 signals[PREEDIT_CHANGED] =
1492 g_signal_new_class_handler (I_("preedit-changed"),
1493 G_OBJECT_CLASS_TYPE (gobject_class),
1494 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1495 NULL,
1496 NULL, NULL,
1497 NULL,
1498 G_TYPE_NONE, n_params: 1,
1499 G_TYPE_STRING);
1500
1501 /**
1502 * GtkTextView::extend-selection:
1503 * @text_view: the object which received the signal
1504 * @granularity: the granularity type
1505 * @location: the location where to extend the selection
1506 * @start: where the selection should start
1507 * @end: where the selection should end
1508 *
1509 * Emitted when the selection needs to be extended at @location.
1510 *
1511 * Returns: %GDK_EVENT_STOP to stop other handlers from being invoked for the
1512 * event. %GDK_EVENT_PROPAGATE to propagate the event further.
1513 */
1514 signals[EXTEND_SELECTION] =
1515 g_signal_new (I_("extend-selection"),
1516 G_OBJECT_CLASS_TYPE (gobject_class),
1517 signal_flags: G_SIGNAL_RUN_LAST,
1518 G_STRUCT_OFFSET (GtkTextViewClass, extend_selection),
1519 accumulator: _gtk_boolean_handled_accumulator, NULL,
1520 c_marshaller: _gtk_marshal_BOOLEAN__ENUM_BOXED_BOXED_BOXED,
1521 G_TYPE_BOOLEAN, n_params: 4,
1522 GTK_TYPE_TEXT_EXTEND_SELECTION,
1523 GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE,
1524 GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE,
1525 GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE);
1526 g_signal_set_va_marshaller (signal_id: signals[EXTEND_SELECTION],
1527 G_TYPE_FROM_CLASS (klass),
1528 va_marshaller: _gtk_marshal_BOOLEAN__ENUM_BOXED_BOXED_BOXEDv);
1529
1530 /**
1531 * GtkTextView::insert-emoji:
1532 * @text_view: the object which received the signal
1533 *
1534 * Gets emitted to present the Emoji chooser for the @text_view.
1535 *
1536 * The ::insert-emoji signal is a [keybinding signal](class.SignalAction.html).
1537 *
1538 * The default bindings for this signal are
1539 * <kbd>Ctrl</kbd>-<kbd>.</kbd> and
1540 * <kbd>Ctrl</kbd>-<kbd>;</kbd>
1541 */
1542 signals[INSERT_EMOJI] =
1543 g_signal_new (I_("insert-emoji"),
1544 G_OBJECT_CLASS_TYPE (gobject_class),
1545 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1546 G_STRUCT_OFFSET (GtkTextViewClass, insert_emoji),
1547 NULL, NULL,
1548 NULL,
1549 G_TYPE_NONE, n_params: 0);
1550
1551 /*
1552 * Actions
1553 */
1554
1555 /**
1556 * GtkTextView|clipboard.cut:
1557 *
1558 * Copies the contents to the clipboard and deletes it from the widget.
1559 */
1560 gtk_widget_class_install_action (widget_class, action_name: "clipboard.cut", NULL,
1561 activate: gtk_text_view_activate_clipboard_cut);
1562
1563 /**
1564 * GtkTextView|clipboard.copy:
1565 *
1566 * Copies the contents to the clipboard.
1567 */
1568 gtk_widget_class_install_action (widget_class, action_name: "clipboard.copy", NULL,
1569 activate: gtk_text_view_activate_clipboard_copy);
1570
1571 /**
1572 * GtkTextView|clipboard.paste:
1573 *
1574 * Inserts the contents of the clipboard into the widget.
1575 */
1576 gtk_widget_class_install_action (widget_class, action_name: "clipboard.paste", NULL,
1577 activate: gtk_text_view_activate_clipboard_paste);
1578
1579 /**
1580 * GtkTextView|selection.delete:
1581 *
1582 * Deletes the current selection.
1583 */
1584 gtk_widget_class_install_action (widget_class, action_name: "selection.delete", NULL,
1585 activate: gtk_text_view_activate_selection_delete);
1586
1587 /**
1588 * GtkTextView|selection.select-all:
1589 *
1590 * Selects all of the widgets content.
1591 */
1592 gtk_widget_class_install_action (widget_class, action_name: "selection.select-all", NULL,
1593 activate: gtk_text_view_activate_selection_select_all);
1594
1595 /**
1596 * GtkTextView|misc.insert-emoji:
1597 *
1598 * Opens the Emoji chooser.
1599 */
1600 gtk_widget_class_install_action (widget_class, action_name: "misc.insert-emoji", NULL,
1601 activate: gtk_text_view_activate_misc_insert_emoji);
1602
1603 /**
1604 * GtkTextView|text.undo:
1605 *
1606 * Undoes the last change to the contents.
1607 */
1608 gtk_widget_class_install_action (widget_class, action_name: "text.undo", NULL, activate: gtk_text_view_real_undo);
1609
1610 /**
1611 * GtkTextView|text.redo:
1612 *
1613 * Redoes the last change to the contents.
1614 */
1615 gtk_widget_class_install_action (widget_class, action_name: "text.redo", NULL, activate: gtk_text_view_real_redo);
1616
1617 /**
1618 * GtkTextView|menu.popup:
1619 *
1620 * Opens the context menu.
1621 */
1622 gtk_widget_class_install_action (widget_class, action_name: "menu.popup", NULL, activate: gtk_text_view_popup_menu);
1623
1624 /*
1625 * Key bindings
1626 */
1627
1628 gtk_widget_class_add_binding_action (widget_class,
1629 GDK_KEY_F10, mods: GDK_SHIFT_MASK,
1630 action_name: "menu.popup",
1631 NULL);
1632 gtk_widget_class_add_binding_action (widget_class,
1633 GDK_KEY_Menu, mods: 0,
1634 action_name: "menu.popup",
1635 NULL);
1636
1637 /* Moving the insertion point */
1638 add_move_binding (widget_class, GDK_KEY_Right, modmask: 0,
1639 step: GTK_MOVEMENT_VISUAL_POSITIONS, count: 1);
1640
1641 add_move_binding (widget_class, GDK_KEY_KP_Right, modmask: 0,
1642 step: GTK_MOVEMENT_VISUAL_POSITIONS, count: 1);
1643
1644 add_move_binding (widget_class, GDK_KEY_Left, modmask: 0,
1645 step: GTK_MOVEMENT_VISUAL_POSITIONS, count: -1);
1646
1647 add_move_binding (widget_class, GDK_KEY_KP_Left, modmask: 0,
1648 step: GTK_MOVEMENT_VISUAL_POSITIONS, count: -1);
1649
1650 add_move_binding (widget_class, GDK_KEY_Right, modmask: GDK_CONTROL_MASK,
1651 step: GTK_MOVEMENT_WORDS, count: 1);
1652
1653 add_move_binding (widget_class, GDK_KEY_KP_Right, modmask: GDK_CONTROL_MASK,
1654 step: GTK_MOVEMENT_WORDS, count: 1);
1655
1656 add_move_binding (widget_class, GDK_KEY_Left, modmask: GDK_CONTROL_MASK,
1657 step: GTK_MOVEMENT_WORDS, count: -1);
1658
1659 add_move_binding (widget_class, GDK_KEY_KP_Left, modmask: GDK_CONTROL_MASK,
1660 step: GTK_MOVEMENT_WORDS, count: -1);
1661
1662 add_move_binding (widget_class, GDK_KEY_Up, modmask: 0,
1663 step: GTK_MOVEMENT_DISPLAY_LINES, count: -1);
1664
1665 add_move_binding (widget_class, GDK_KEY_KP_Up, modmask: 0,
1666 step: GTK_MOVEMENT_DISPLAY_LINES, count: -1);
1667
1668 add_move_binding (widget_class, GDK_KEY_Down, modmask: 0,
1669 step: GTK_MOVEMENT_DISPLAY_LINES, count: 1);
1670
1671 add_move_binding (widget_class, GDK_KEY_KP_Down, modmask: 0,
1672 step: GTK_MOVEMENT_DISPLAY_LINES, count: 1);
1673
1674 add_move_binding (widget_class, GDK_KEY_Up, modmask: GDK_CONTROL_MASK,
1675 step: GTK_MOVEMENT_PARAGRAPHS, count: -1);
1676
1677 add_move_binding (widget_class, GDK_KEY_KP_Up, modmask: GDK_CONTROL_MASK,
1678 step: GTK_MOVEMENT_PARAGRAPHS, count: -1);
1679
1680 add_move_binding (widget_class, GDK_KEY_Down, modmask: GDK_CONTROL_MASK,
1681 step: GTK_MOVEMENT_PARAGRAPHS, count: 1);
1682
1683 add_move_binding (widget_class, GDK_KEY_KP_Down, modmask: GDK_CONTROL_MASK,
1684 step: GTK_MOVEMENT_PARAGRAPHS, count: 1);
1685
1686 add_move_binding (widget_class, GDK_KEY_Home, modmask: 0,
1687 step: GTK_MOVEMENT_DISPLAY_LINE_ENDS, count: -1);
1688
1689 add_move_binding (widget_class, GDK_KEY_KP_Home, modmask: 0,
1690 step: GTK_MOVEMENT_DISPLAY_LINE_ENDS, count: -1);
1691
1692 add_move_binding (widget_class, GDK_KEY_End, modmask: 0,
1693 step: GTK_MOVEMENT_DISPLAY_LINE_ENDS, count: 1);
1694
1695 add_move_binding (widget_class, GDK_KEY_KP_End, modmask: 0,
1696 step: GTK_MOVEMENT_DISPLAY_LINE_ENDS, count: 1);
1697
1698 add_move_binding (widget_class, GDK_KEY_Home, modmask: GDK_CONTROL_MASK,
1699 step: GTK_MOVEMENT_BUFFER_ENDS, count: -1);
1700
1701 add_move_binding (widget_class, GDK_KEY_KP_Home, modmask: GDK_CONTROL_MASK,
1702 step: GTK_MOVEMENT_BUFFER_ENDS, count: -1);
1703
1704 add_move_binding (widget_class, GDK_KEY_End, modmask: GDK_CONTROL_MASK,
1705 step: GTK_MOVEMENT_BUFFER_ENDS, count: 1);
1706
1707 add_move_binding (widget_class, GDK_KEY_KP_End, modmask: GDK_CONTROL_MASK,
1708 step: GTK_MOVEMENT_BUFFER_ENDS, count: 1);
1709
1710 add_move_binding (widget_class, GDK_KEY_Page_Up, modmask: 0,
1711 step: GTK_MOVEMENT_PAGES, count: -1);
1712
1713 add_move_binding (widget_class, GDK_KEY_KP_Page_Up, modmask: 0,
1714 step: GTK_MOVEMENT_PAGES, count: -1);
1715
1716 add_move_binding (widget_class, GDK_KEY_Page_Down, modmask: 0,
1717 step: GTK_MOVEMENT_PAGES, count: 1);
1718
1719 add_move_binding (widget_class, GDK_KEY_KP_Page_Down, modmask: 0,
1720 step: GTK_MOVEMENT_PAGES, count: 1);
1721
1722 add_move_binding (widget_class, GDK_KEY_Page_Up, modmask: GDK_CONTROL_MASK,
1723 step: GTK_MOVEMENT_HORIZONTAL_PAGES, count: -1);
1724
1725 add_move_binding (widget_class, GDK_KEY_KP_Page_Up, modmask: GDK_CONTROL_MASK,
1726 step: GTK_MOVEMENT_HORIZONTAL_PAGES, count: -1);
1727
1728 add_move_binding (widget_class, GDK_KEY_Page_Down, modmask: GDK_CONTROL_MASK,
1729 step: GTK_MOVEMENT_HORIZONTAL_PAGES, count: 1);
1730
1731 add_move_binding (widget_class, GDK_KEY_KP_Page_Down, modmask: GDK_CONTROL_MASK,
1732 step: GTK_MOVEMENT_HORIZONTAL_PAGES, count: 1);
1733
1734 /* Select all */
1735 gtk_widget_class_add_binding_signal (widget_class,
1736 GDK_KEY_a, mods: GDK_CONTROL_MASK,
1737 signal: "select-all",
1738 format_string: "(b)", TRUE);
1739
1740 gtk_widget_class_add_binding_signal (widget_class,
1741 GDK_KEY_slash, mods: GDK_CONTROL_MASK,
1742 signal: "select-all",
1743 format_string: "(b)", TRUE);
1744
1745 /* Unselect all */
1746 gtk_widget_class_add_binding_signal (widget_class,
1747 GDK_KEY_backslash, mods: GDK_CONTROL_MASK,
1748 signal: "select-all",
1749 format_string: "(b)", FALSE);
1750
1751 gtk_widget_class_add_binding_signal (widget_class,
1752 GDK_KEY_a, mods: GDK_SHIFT_MASK | GDK_CONTROL_MASK,
1753 signal: "select-all",
1754 format_string: "(b)", FALSE);
1755
1756 /* Deleting text */
1757 gtk_widget_class_add_binding_signal (widget_class,
1758 GDK_KEY_Delete, mods: 0,
1759 signal: "delete-from-cursor",
1760 format_string: "(ii)", GTK_DELETE_CHARS, 1);
1761
1762 gtk_widget_class_add_binding_signal (widget_class,
1763 GDK_KEY_KP_Delete, mods: 0,
1764 signal: "delete-from-cursor",
1765 format_string: "(ii)", GTK_DELETE_CHARS, 1);
1766
1767 gtk_widget_class_add_binding_signal (widget_class,
1768 GDK_KEY_BackSpace, mods: 0,
1769 signal: "backspace",
1770 NULL);
1771
1772 /* Make this do the same as Backspace, to help with mis-typing */
1773 gtk_widget_class_add_binding_signal (widget_class,
1774 GDK_KEY_BackSpace, mods: GDK_SHIFT_MASK,
1775 signal: "backspace",
1776 NULL);
1777
1778 gtk_widget_class_add_binding_signal (widget_class,
1779 GDK_KEY_Delete, mods: GDK_CONTROL_MASK,
1780 signal: "delete-from-cursor",
1781 format_string: "(ii)", GTK_DELETE_WORD_ENDS, 1);
1782
1783 gtk_widget_class_add_binding_signal (widget_class,
1784 GDK_KEY_KP_Delete, mods: GDK_CONTROL_MASK,
1785 signal: "delete-from-cursor",
1786 format_string: "(ii)", GTK_DELETE_WORD_ENDS, 1);
1787
1788 gtk_widget_class_add_binding_signal (widget_class,
1789 GDK_KEY_BackSpace, mods: GDK_CONTROL_MASK,
1790 signal: "delete-from-cursor",
1791 format_string: "(ii)", GTK_DELETE_WORD_ENDS, -1);
1792
1793 gtk_widget_class_add_binding_signal (widget_class,
1794 GDK_KEY_Delete, mods: GDK_SHIFT_MASK | GDK_CONTROL_MASK,
1795 signal: "delete-from-cursor",
1796 format_string: "(ii)", GTK_DELETE_PARAGRAPH_ENDS, 1);
1797
1798 gtk_widget_class_add_binding_signal (widget_class,
1799 GDK_KEY_KP_Delete, mods: GDK_SHIFT_MASK | GDK_CONTROL_MASK,
1800 signal: "delete-from-cursor",
1801 format_string: "(ii)", GTK_DELETE_PARAGRAPH_ENDS, 1);
1802
1803 gtk_widget_class_add_binding_signal (widget_class,
1804 GDK_KEY_BackSpace, mods: GDK_SHIFT_MASK | GDK_CONTROL_MASK,
1805 signal: "delete-from-cursor",
1806 format_string: "(ii)", GTK_DELETE_PARAGRAPH_ENDS, -1);
1807
1808 /* Cut/copy/paste */
1809
1810 gtk_widget_class_add_binding_signal (widget_class,
1811 GDK_KEY_x, mods: GDK_CONTROL_MASK,
1812 signal: "cut-clipboard",
1813 NULL);
1814 gtk_widget_class_add_binding_signal (widget_class,
1815 GDK_KEY_c, mods: GDK_CONTROL_MASK,
1816 signal: "copy-clipboard",
1817 NULL);
1818 gtk_widget_class_add_binding_signal (widget_class,
1819 GDK_KEY_v, mods: GDK_CONTROL_MASK,
1820 signal: "paste-clipboard",
1821 NULL);
1822
1823 gtk_widget_class_add_binding_signal (widget_class,
1824 GDK_KEY_KP_Delete, mods: GDK_SHIFT_MASK,
1825 signal: "cut-clipboard",
1826 NULL);
1827 gtk_widget_class_add_binding_signal (widget_class,
1828 GDK_KEY_KP_Insert, mods: GDK_CONTROL_MASK,
1829 signal: "copy-clipboard",
1830 NULL);
1831 gtk_widget_class_add_binding_signal (widget_class,
1832 GDK_KEY_KP_Insert, mods: GDK_SHIFT_MASK,
1833 signal: "paste-clipboard",
1834 NULL);
1835
1836 gtk_widget_class_add_binding_signal (widget_class,
1837 GDK_KEY_Delete, mods: GDK_SHIFT_MASK,
1838 signal: "cut-clipboard",
1839 NULL);
1840 gtk_widget_class_add_binding_signal (widget_class,
1841 GDK_KEY_Insert, mods: GDK_CONTROL_MASK,
1842 signal: "copy-clipboard",
1843 NULL);
1844 gtk_widget_class_add_binding_signal (widget_class,
1845 GDK_KEY_Insert, mods: GDK_SHIFT_MASK,
1846 signal: "paste-clipboard",
1847 NULL);
1848
1849 /* Undo/Redo */
1850 gtk_widget_class_add_binding_action (widget_class,
1851 GDK_KEY_z, mods: GDK_CONTROL_MASK,
1852 action_name: "text.undo", NULL);
1853 gtk_widget_class_add_binding_action (widget_class,
1854 GDK_KEY_y, mods: GDK_CONTROL_MASK,
1855 action_name: "text.redo", NULL);
1856 gtk_widget_class_add_binding_action (widget_class,
1857 GDK_KEY_z, mods: GDK_CONTROL_MASK | GDK_SHIFT_MASK,
1858 action_name: "text.redo", NULL);
1859
1860 /* Overwrite */
1861 gtk_widget_class_add_binding_signal (widget_class,
1862 GDK_KEY_Insert, mods: 0,
1863 signal: "toggle-overwrite",
1864 NULL);
1865 gtk_widget_class_add_binding_signal (widget_class,
1866 GDK_KEY_KP_Insert, mods: 0,
1867 signal: "toggle-overwrite",
1868 NULL);
1869
1870 /* Emoji */
1871 gtk_widget_class_add_binding_action (widget_class,
1872 GDK_KEY_period, mods: GDK_CONTROL_MASK,
1873 action_name: "misc.insert-emoji",
1874 NULL);
1875 gtk_widget_class_add_binding_action (widget_class,
1876 GDK_KEY_semicolon, mods: GDK_CONTROL_MASK,
1877 action_name: "misc.insert-emoji",
1878 NULL);
1879
1880 /* Caret mode */
1881 gtk_widget_class_add_binding_signal (widget_class,
1882 GDK_KEY_F7, mods: 0,
1883 signal: "toggle-cursor-visible",
1884 NULL);
1885
1886 /* Control-tab focus motion */
1887 gtk_widget_class_add_binding_signal (widget_class,
1888 GDK_KEY_Tab, mods: GDK_CONTROL_MASK,
1889 signal: "move-focus",
1890 format_string: "(i)", GTK_DIR_TAB_FORWARD);
1891 gtk_widget_class_add_binding_signal (widget_class,
1892 GDK_KEY_KP_Tab, mods: GDK_CONTROL_MASK,
1893 signal: "move-focus",
1894 format_string: "(i)", GTK_DIR_TAB_FORWARD);
1895
1896 gtk_widget_class_add_binding_signal (widget_class,
1897 GDK_KEY_Tab, mods: GDK_SHIFT_MASK | GDK_CONTROL_MASK,
1898 signal: "move-focus",
1899 format_string: "(i)", GTK_DIR_TAB_BACKWARD);
1900 gtk_widget_class_add_binding_signal (widget_class,
1901 GDK_KEY_KP_Tab, mods: GDK_SHIFT_MASK | GDK_CONTROL_MASK,
1902 signal: "move-focus",
1903 format_string: "(i)", GTK_DIR_TAB_BACKWARD);
1904
1905 gtk_widget_class_set_css_name (widget_class, I_("textview"));
1906 gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_TEXT_BOX);
1907
1908 quark_text_selection_data = g_quark_from_static_string (string: "gtk-text-view-text-selection-data");
1909 quark_gtk_signal = g_quark_from_static_string (string: "gtk-signal");
1910 quark_text_view_child = g_quark_from_static_string (string: "gtk-text-view-child");
1911}
1912
1913static void
1914_gtk_text_view_ensure_text_handles (GtkTextView *text_view)
1915{
1916 GtkTextViewPrivate *priv = text_view->priv;
1917 int i;
1918
1919 for (i = 0; i < TEXT_HANDLE_N_HANDLES; i++)
1920 {
1921 if (priv->text_handles[i])
1922 continue;
1923 priv->text_handles[i] = gtk_text_handle_new (GTK_WIDGET (text_view));
1924 g_signal_connect (priv->text_handles[i], "drag-started",
1925 G_CALLBACK (gtk_text_view_handle_drag_started), text_view);
1926 g_signal_connect (priv->text_handles[i], "handle-dragged",
1927 G_CALLBACK (gtk_text_view_handle_dragged), text_view);
1928 g_signal_connect (priv->text_handles[i], "drag-finished",
1929 G_CALLBACK (gtk_text_view_handle_drag_finished), text_view);
1930 }
1931}
1932
1933static void
1934gtk_text_view_init (GtkTextView *text_view)
1935{
1936 GtkWidget *widget = GTK_WIDGET (text_view);
1937 GtkDropTarget *dest;
1938 GtkTextViewPrivate *priv;
1939 GtkEventController *controller;
1940 GtkGesture *gesture;
1941
1942 text_view->priv = gtk_text_view_get_instance_private (self: text_view);
1943 priv = text_view->priv;
1944
1945 gtk_widget_set_focusable (widget, TRUE);
1946 gtk_widget_set_overflow (widget, overflow: GTK_OVERFLOW_HIDDEN);
1947
1948 gtk_widget_add_css_class (widget, css_class: "view");
1949
1950 gtk_widget_set_cursor_from_name (widget, name: "text");
1951
1952 /* Set up default style */
1953 priv->wrap_mode = GTK_WRAP_NONE;
1954 priv->pixels_above_lines = 0;
1955 priv->pixels_below_lines = 0;
1956 priv->pixels_inside_wrap = 0;
1957 priv->justify = GTK_JUSTIFY_LEFT;
1958 priv->indent = 0;
1959 priv->tabs = NULL;
1960 priv->editable = TRUE;
1961 priv->cursor_alpha = 1.0;
1962
1963 priv->scroll_after_paste = FALSE;
1964
1965 dest = gtk_drop_target_new (G_TYPE_STRING, actions: GDK_ACTION_COPY | GDK_ACTION_MOVE);
1966 g_signal_connect (dest, "enter", G_CALLBACK (gtk_text_view_drag_motion), text_view);
1967 g_signal_connect (dest, "motion", G_CALLBACK (gtk_text_view_drag_motion), text_view);
1968 g_signal_connect (dest, "leave", G_CALLBACK (gtk_text_view_drag_leave), text_view);
1969 g_signal_connect (dest, "drop", G_CALLBACK (gtk_text_view_drag_drop), text_view);
1970 gtk_widget_add_controller (GTK_WIDGET (text_view), GTK_EVENT_CONTROLLER (dest));
1971
1972 controller = gtk_drop_controller_motion_new ();
1973 g_signal_connect (controller, "enter", G_CALLBACK (gtk_text_view_drop_scroll_motion), text_view);
1974 g_signal_connect (controller, "motion", G_CALLBACK (gtk_text_view_drop_scroll_motion), text_view);
1975 g_signal_connect (controller, "leave", G_CALLBACK (gtk_text_view_drop_scroll_leave), text_view);
1976 gtk_widget_add_controller (GTK_WIDGET (text_view), controller);
1977
1978 priv->virtual_cursor_x = -1;
1979 priv->virtual_cursor_y = -1;
1980
1981 /* This object is completely private. No external entity can gain a reference
1982 * to it; so we create it here and destroy it in finalize ().
1983 */
1984 priv->im_context = gtk_im_multicontext_new ();
1985
1986 g_signal_connect (priv->im_context, "commit",
1987 G_CALLBACK (gtk_text_view_commit_handler), text_view);
1988 g_signal_connect (priv->im_context, "preedit-start",
1989 G_CALLBACK (gtk_text_view_preedit_start_handler), text_view);
1990 g_signal_connect (priv->im_context, "preedit-changed",
1991 G_CALLBACK (gtk_text_view_preedit_changed_handler), text_view);
1992 g_signal_connect (priv->im_context, "retrieve-surrounding",
1993 G_CALLBACK (gtk_text_view_retrieve_surrounding_handler), text_view);
1994 g_signal_connect (priv->im_context, "delete-surrounding",
1995 G_CALLBACK (gtk_text_view_delete_surrounding_handler), text_view);
1996
1997 priv->cursor_visible = TRUE;
1998
1999 priv->accepts_tab = TRUE;
2000
2001 priv->text_window = text_window_new (widget);
2002
2003 gesture = gtk_gesture_click_new ();
2004 gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), button: 0);
2005 g_signal_connect (gesture, "pressed",
2006 G_CALLBACK (gtk_text_view_click_gesture_pressed),
2007 widget);
2008 gtk_widget_add_controller (widget, GTK_EVENT_CONTROLLER (gesture));
2009
2010 priv->drag_gesture = gtk_gesture_drag_new ();
2011 g_signal_connect (priv->drag_gesture, "drag-update",
2012 G_CALLBACK (gtk_text_view_drag_gesture_update),
2013 widget);
2014 g_signal_connect (priv->drag_gesture, "drag-end",
2015 G_CALLBACK (gtk_text_view_drag_gesture_end),
2016 widget);
2017 gtk_widget_add_controller (widget, GTK_EVENT_CONTROLLER (priv->drag_gesture));
2018
2019 controller = gtk_event_controller_motion_new ();
2020 g_signal_connect (controller, "motion", G_CALLBACK (gtk_text_view_motion), widget);
2021 gtk_widget_add_controller (widget, controller);
2022
2023 priv->key_controller = gtk_event_controller_key_new ();
2024 g_signal_connect (priv->key_controller, "key-pressed",
2025 G_CALLBACK (gtk_text_view_key_controller_key_pressed),
2026 widget);
2027 g_signal_connect (priv->key_controller, "im-update",
2028 G_CALLBACK (gtk_text_view_key_controller_im_update),
2029 widget);
2030 gtk_event_controller_key_set_im_context (GTK_EVENT_CONTROLLER_KEY (priv->key_controller),
2031 im_context: priv->im_context);
2032 gtk_widget_add_controller (widget, controller: priv->key_controller);
2033 controller = gtk_event_controller_focus_new ();
2034 g_signal_connect_swapped (controller, "enter",
2035 G_CALLBACK (gtk_text_view_focus_in), widget);
2036 g_signal_connect_swapped (controller, "leave",
2037 G_CALLBACK (gtk_text_view_focus_out), widget);
2038 gtk_widget_add_controller (widget, controller);
2039
2040 priv->selection_node = gtk_css_node_new ();
2041 gtk_css_node_set_name (cssnode: priv->selection_node, name: g_quark_from_static_string (string: "selection"));
2042 gtk_css_node_set_parent (cssnode: priv->selection_node, parent: priv->text_window->css_node);
2043 gtk_css_node_set_state (cssnode: priv->selection_node,
2044 state_flags: gtk_css_node_get_state (cssnode: priv->text_window->css_node) & ~GTK_STATE_FLAG_DROP_ACTIVE);
2045 gtk_css_node_set_visible (cssnode: priv->selection_node, FALSE);
2046 g_object_unref (object: priv->selection_node);
2047
2048 gtk_widget_action_set_enabled (GTK_WIDGET (text_view), action_name: "text.can-redo", FALSE);
2049 gtk_widget_action_set_enabled (GTK_WIDGET (text_view), action_name: "text.can-undo", FALSE);
2050
2051 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: widget),
2052 first_property: GTK_ACCESSIBLE_PROPERTY_MULTI_LINE, TRUE,
2053 -1);
2054}
2055
2056GtkCssNode *
2057gtk_text_view_get_text_node (GtkTextView *text_view)
2058{
2059 return text_view->priv->text_window->css_node;
2060}
2061
2062GtkCssNode *
2063gtk_text_view_get_selection_node (GtkTextView *text_view)
2064{
2065 return text_view->priv->selection_node;
2066}
2067
2068static void
2069_gtk_text_view_ensure_magnifier (GtkTextView *text_view)
2070{
2071 GtkTextViewPrivate *priv = text_view->priv;
2072
2073 if (priv->magnifier_popover)
2074 return;
2075
2076 priv->magnifier = _gtk_magnifier_new (GTK_WIDGET (text_view));
2077 _gtk_magnifier_set_magnification (GTK_MAGNIFIER (priv->magnifier), magnification: 2.0);
2078 priv->magnifier_popover = gtk_popover_new ();
2079 gtk_popover_set_position (GTK_POPOVER (priv->magnifier_popover), position: GTK_POS_TOP);
2080 gtk_widget_set_parent (widget: priv->magnifier_popover, GTK_WIDGET (text_view));
2081 gtk_widget_add_css_class (widget: priv->magnifier_popover, css_class: "magnifier");
2082 gtk_popover_set_autohide (GTK_POPOVER (priv->magnifier_popover), FALSE);
2083 gtk_popover_set_child (GTK_POPOVER (priv->magnifier_popover), child: priv->magnifier);
2084 gtk_widget_show (widget: priv->magnifier);
2085}
2086
2087/**
2088 * gtk_text_view_new:
2089 *
2090 * Creates a new `GtkTextView`.
2091 *
2092 * If you don’t call [method@Gtk.TextView.set_buffer] before using the
2093 * text view, an empty default buffer will be created for you. Get the
2094 * buffer with [method@Gtk.TextView.get_buffer]. If you want to specify
2095 * your own buffer, consider [ctor@Gtk.TextView.new_with_buffer].
2096 *
2097 * Returns: a new `GtkTextView`
2098 */
2099GtkWidget*
2100gtk_text_view_new (void)
2101{
2102 return g_object_new (GTK_TYPE_TEXT_VIEW, NULL);
2103}
2104
2105/**
2106 * gtk_text_view_new_with_buffer:
2107 * @buffer: a `GtkTextBuffer`
2108 *
2109 * Creates a new `GtkTextView` widget displaying the buffer @buffer.
2110 *
2111 * One buffer can be shared among many widgets. @buffer may be %NULL
2112 * to create a default buffer, in which case this function is equivalent
2113 * to [ctor@Gtk.TextView.new]. The text view adds its own reference count
2114 * to the buffer; it does not take over an existing reference.
2115 *
2116 * Returns: a new `GtkTextView`.
2117 */
2118GtkWidget*
2119gtk_text_view_new_with_buffer (GtkTextBuffer *buffer)
2120{
2121 GtkTextView *text_view;
2122
2123 text_view = (GtkTextView*)gtk_text_view_new ();
2124
2125 gtk_text_view_set_buffer (text_view, buffer);
2126
2127 return GTK_WIDGET (text_view);
2128}
2129
2130/**
2131 * gtk_text_view_set_buffer: (attributes org.gtk.Method.set_property=buffer)
2132 * @text_view: a `GtkTextView`
2133 * @buffer: (nullable): a `GtkTextBuffer`
2134 *
2135 * Sets @buffer as the buffer being displayed by @text_view.
2136 *
2137 * The previous buffer displayed by the text view is unreferenced, and
2138 * a reference is added to @buffer. If you owned a reference to @buffer
2139 * before passing it to this function, you must remove that reference
2140 * yourself; `GtkTextView` will not “adopt” it.
2141 */
2142void
2143gtk_text_view_set_buffer (GtkTextView *text_view,
2144 GtkTextBuffer *buffer)
2145{
2146 GtkTextViewPrivate *priv;
2147 GtkTextBuffer *old_buffer;
2148
2149 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
2150 g_return_if_fail (buffer == NULL || GTK_IS_TEXT_BUFFER (buffer));
2151
2152 priv = text_view->priv;
2153
2154 if (priv->buffer == buffer)
2155 return;
2156
2157 old_buffer = priv->buffer;
2158
2159 if (old_buffer != NULL)
2160 {
2161 while (priv->anchored_children.length)
2162 {
2163 AnchoredChild *ac = g_queue_peek_head (queue: &priv->anchored_children);
2164 gtk_text_view_remove (text_view, child: ac->widget);
2165 /* ac is now invalid! */
2166 }
2167
2168 g_signal_handlers_disconnect_by_func (priv->buffer,
2169 gtk_text_view_mark_set_handler,
2170 text_view);
2171 g_signal_handlers_disconnect_by_func (priv->buffer,
2172 gtk_text_view_paste_done_handler,
2173 text_view);
2174 g_signal_handlers_disconnect_by_func (priv->buffer,
2175 gtk_text_view_buffer_changed_handler,
2176 text_view);
2177 g_signal_handlers_disconnect_by_func (priv->buffer,
2178 gtk_text_view_buffer_notify_redo,
2179 text_view);
2180 g_signal_handlers_disconnect_by_func (priv->buffer,
2181 gtk_text_view_buffer_notify_undo,
2182 text_view);
2183
2184 if (gtk_widget_get_realized (GTK_WIDGET (text_view)))
2185 {
2186 GdkClipboard *clipboard = gtk_widget_get_primary_clipboard (GTK_WIDGET (text_view));
2187 gtk_text_buffer_remove_selection_clipboard (buffer: priv->buffer, clipboard);
2188 }
2189
2190 if (priv->layout)
2191 gtk_text_layout_set_buffer (layout: priv->layout, NULL);
2192
2193 priv->dnd_mark = NULL;
2194 priv->first_para_mark = NULL;
2195 cancel_pending_scroll (text_view);
2196 }
2197
2198 priv->buffer = buffer;
2199
2200 if (priv->layout)
2201 gtk_text_layout_set_buffer (layout: priv->layout, buffer);
2202
2203 if (buffer != NULL)
2204 {
2205 GtkTextIter start;
2206 gboolean can_undo = FALSE;
2207 gboolean can_redo = FALSE;
2208
2209 g_object_ref (buffer);
2210
2211 gtk_text_buffer_get_iter_at_offset (buffer: priv->buffer, iter: &start, char_offset: 0);
2212
2213 priv->dnd_mark = gtk_text_buffer_create_mark (buffer: priv->buffer,
2214 mark_name: "gtk_drag_target",
2215 where: &start, FALSE);
2216
2217 priv->first_para_mark = gtk_text_buffer_create_mark (buffer: priv->buffer,
2218 NULL,
2219 where: &start, TRUE);
2220
2221 priv->first_para_pixels = 0;
2222
2223
2224 g_signal_connect (priv->buffer, "mark-set",
2225 G_CALLBACK (gtk_text_view_mark_set_handler),
2226 text_view);
2227 g_signal_connect (priv->buffer, "paste-done",
2228 G_CALLBACK (gtk_text_view_paste_done_handler),
2229 text_view);
2230 g_signal_connect (priv->buffer, "changed",
2231 G_CALLBACK (gtk_text_view_buffer_changed_handler),
2232 text_view);
2233 g_signal_connect (priv->buffer, "notify",
2234 G_CALLBACK (gtk_text_view_buffer_notify_undo),
2235 text_view);
2236 g_signal_connect (priv->buffer, "notify",
2237 G_CALLBACK (gtk_text_view_buffer_notify_redo),
2238 text_view);
2239
2240 can_undo = gtk_text_buffer_get_can_undo (buffer);
2241 can_redo = gtk_text_buffer_get_can_redo (buffer);
2242
2243 if (gtk_widget_get_realized (GTK_WIDGET (text_view)))
2244 {
2245 GdkClipboard *clipboard = gtk_widget_get_primary_clipboard (GTK_WIDGET (text_view));
2246 gtk_text_buffer_add_selection_clipboard (buffer: priv->buffer, clipboard);
2247 }
2248
2249 gtk_text_view_update_handles (text_view);
2250
2251 gtk_widget_action_set_enabled (GTK_WIDGET (text_view), action_name: "text.undo", enabled: can_undo);
2252 gtk_widget_action_set_enabled (GTK_WIDGET (text_view), action_name: "text.redo", enabled: can_redo);
2253 }
2254
2255 if (old_buffer)
2256 g_object_unref (object: old_buffer);
2257
2258 g_object_notify (G_OBJECT (text_view), property_name: "buffer");
2259
2260 if (gtk_widget_get_visible (GTK_WIDGET (text_view)))
2261 gtk_widget_queue_draw (GTK_WIDGET (text_view));
2262
2263 DV(g_print ("Invalidating due to set_buffer\n"));
2264 gtk_text_view_invalidate (text_view);
2265}
2266
2267static GtkTextBuffer*
2268gtk_text_view_create_buffer (GtkTextView *text_view)
2269{
2270 return gtk_text_buffer_new (NULL);
2271}
2272
2273/**
2274 * gtk_text_view_get_buffer: (attributes org.gtk.Method.get_property=buffer)
2275 * @text_view: a `GtkTextView`
2276 *
2277 * Returns the `GtkTextBuffer` being displayed by this text view.
2278 *
2279 * The reference count on the buffer is not incremented; the caller
2280 * of this function won’t own a new reference.
2281 *
2282 * Returns: (transfer none): a `GtkTextBuffer`
2283 */
2284GtkTextBuffer*
2285gtk_text_view_get_buffer (GtkTextView *text_view)
2286{
2287 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), NULL);
2288
2289 return get_buffer (text_view);
2290}
2291
2292/**
2293 * gtk_text_view_get_cursor_locations:
2294 * @text_view: a `GtkTextView`
2295 * @iter: (nullable): a `GtkTextIter`
2296 * @strong: (out) (optional): location to store the strong cursor position
2297 * @weak: (out) (optional): location to store the weak cursor position
2298 *
2299 * Determine the positions of the strong and weak cursors if the
2300 * insertion point is at @iter.
2301 *
2302 * The position of each cursor is stored as a zero-width rectangle.
2303 * The strong cursor location is the location where characters of
2304 * the directionality equal to the base direction of the paragraph
2305 * are inserted. The weak cursor location is the location where
2306 * characters of the directionality opposite to the base direction
2307 * of the paragraph are inserted.
2308 *
2309 * If @iter is %NULL, the actual cursor position is used.
2310 *
2311 * Note that if @iter happens to be the actual cursor position, and
2312 * there is currently an IM preedit sequence being entered, the
2313 * returned locations will be adjusted to account for the preedit
2314 * cursor’s offset within the preedit sequence.
2315 *
2316 * The rectangle position is in buffer coordinates; use
2317 * [method@Gtk.TextView.buffer_to_window_coords] to convert these
2318 * coordinates to coordinates for one of the windows in the text view.
2319 */
2320void
2321gtk_text_view_get_cursor_locations (GtkTextView *text_view,
2322 const GtkTextIter *iter,
2323 GdkRectangle *strong,
2324 GdkRectangle *weak)
2325{
2326 GtkTextIter insert;
2327
2328 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
2329 g_return_if_fail (iter == NULL ||
2330 gtk_text_iter_get_buffer (iter) == get_buffer (text_view));
2331
2332 gtk_text_view_ensure_layout (text_view);
2333
2334 if (iter)
2335 insert = *iter;
2336 else
2337 gtk_text_buffer_get_iter_at_mark (buffer: get_buffer (text_view), iter: &insert,
2338 mark: gtk_text_buffer_get_insert (buffer: get_buffer (text_view)));
2339
2340 gtk_text_layout_get_cursor_locations (layout: text_view->priv->layout, iter: &insert,
2341 strong_pos: strong, weak_pos: weak);
2342}
2343
2344/**
2345 * gtk_text_view_get_iter_at_location:
2346 * @text_view: a `GtkTextView`
2347 * @iter: (out): a `GtkTextIter`
2348 * @x: x position, in buffer coordinates
2349 * @y: y position, in buffer coordinates
2350 *
2351 * Retrieves the iterator at buffer coordinates @x and @y.
2352 *
2353 * Buffer coordinates are coordinates for the entire buffer, not just
2354 * the currently-displayed portion. If you have coordinates from an
2355 * event, you have to convert those to buffer coordinates with
2356 * [method@Gtk.TextView.window_to_buffer_coords].
2357 *
2358 * Returns: %TRUE if the position is over text
2359 */
2360gboolean
2361gtk_text_view_get_iter_at_location (GtkTextView *text_view,
2362 GtkTextIter *iter,
2363 int x,
2364 int y)
2365{
2366 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), FALSE);
2367 g_return_val_if_fail (iter != NULL, FALSE);
2368
2369 gtk_text_view_ensure_layout (text_view);
2370
2371 return gtk_text_layout_get_iter_at_pixel (layout: text_view->priv->layout, iter, x, y);
2372}
2373
2374/**
2375 * gtk_text_view_get_iter_at_position:
2376 * @text_view: a `GtkTextView`
2377 * @iter: (out): a `GtkTextIter`
2378 * @trailing: (out) (optional): if non-%NULL, location to store
2379 * an integer indicating where in the grapheme the user clicked.
2380 * It will either be zero, or the number of characters in the grapheme.
2381 * 0 represents the trailing edge of the grapheme.
2382 * @x: x position, in buffer coordinates
2383 * @y: y position, in buffer coordinates
2384 *
2385 * Retrieves the iterator pointing to the character at buffer
2386 * coordinates @x and @y.
2387 *
2388 * Buffer coordinates are coordinates for the entire buffer, not just
2389 * the currently-displayed portion. If you have coordinates from an event,
2390 * you have to convert those to buffer coordinates with
2391 * [method@Gtk.TextView.window_to_buffer_coords].
2392 *
2393 * Note that this is different from [method@Gtk.TextView.get_iter_at_location],
2394 * which returns cursor locations, i.e. positions between characters.
2395 *
2396 * Returns: %TRUE if the position is over text
2397 */
2398gboolean
2399gtk_text_view_get_iter_at_position (GtkTextView *text_view,
2400 GtkTextIter *iter,
2401 int *trailing,
2402 int x,
2403 int y)
2404{
2405 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), FALSE);
2406 g_return_val_if_fail (iter != NULL, FALSE);
2407
2408 gtk_text_view_ensure_layout (text_view);
2409
2410 return gtk_text_layout_get_iter_at_position (layout: text_view->priv->layout, iter, trailing, x, y);
2411}
2412
2413/**
2414 * gtk_text_view_get_iter_location:
2415 * @text_view: a `GtkTextView`
2416 * @iter: a `GtkTextIter`
2417 * @location: (out): bounds of the character at @iter
2418 *
2419 * Gets a rectangle which roughly contains the character at @iter.
2420 *
2421 * The rectangle position is in buffer coordinates; use
2422 * [method@Gtk.TextView.buffer_to_window_coords] to convert these
2423 * coordinates to coordinates for one of the windows in the text view.
2424 */
2425void
2426gtk_text_view_get_iter_location (GtkTextView *text_view,
2427 const GtkTextIter *iter,
2428 GdkRectangle *location)
2429{
2430 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
2431 g_return_if_fail (gtk_text_iter_get_buffer (iter) == get_buffer (text_view));
2432
2433 gtk_text_view_ensure_layout (text_view);
2434
2435 gtk_text_layout_get_iter_location (layout: text_view->priv->layout, iter, rect: location);
2436}
2437
2438/**
2439 * gtk_text_view_get_line_yrange:
2440 * @text_view: a `GtkTextView`
2441 * @iter: a `GtkTextIter`
2442 * @y: (out): return location for a y coordinate
2443 * @height: (out): return location for a height
2444 *
2445 * Gets the y coordinate of the top of the line containing @iter,
2446 * and the height of the line.
2447 *
2448 * The coordinate is a buffer coordinate; convert to window
2449 * coordinates with [method@Gtk.TextView.buffer_to_window_coords].
2450 */
2451void
2452gtk_text_view_get_line_yrange (GtkTextView *text_view,
2453 const GtkTextIter *iter,
2454 int *y,
2455 int *height)
2456{
2457 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
2458 g_return_if_fail (gtk_text_iter_get_buffer (iter) == get_buffer (text_view));
2459
2460 gtk_text_view_ensure_layout (text_view);
2461
2462 gtk_text_layout_get_line_yrange (layout: text_view->priv->layout,
2463 iter,
2464 y,
2465 height);
2466}
2467
2468/**
2469 * gtk_text_view_get_line_at_y:
2470 * @text_view: a `GtkTextView`
2471 * @target_iter: (out): a `GtkTextIter`
2472 * @y: a y coordinate
2473 * @line_top: (out): return location for top coordinate of the line
2474 *
2475 * Gets the `GtkTextIter` at the start of the line containing
2476 * the coordinate @y.
2477 *
2478 * @y is in buffer coordinates, convert from window coordinates with
2479 * [method@Gtk.TextView.window_to_buffer_coords]. If non-%NULL,
2480 * @line_top will be filled with the coordinate of the top edge
2481 * of the line.
2482 */
2483void
2484gtk_text_view_get_line_at_y (GtkTextView *text_view,
2485 GtkTextIter *target_iter,
2486 int y,
2487 int *line_top)
2488{
2489 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
2490
2491 gtk_text_view_ensure_layout (text_view);
2492
2493 gtk_text_layout_get_line_at_y (layout: text_view->priv->layout,
2494 target_iter,
2495 y,
2496 line_top);
2497}
2498
2499/* Same as gtk_text_view_scroll_to_iter but deal with
2500 * (top_margin / top_padding) and (bottom_margin / bottom_padding).
2501 * When with_border == TRUE and you scroll on the edges,
2502 * all borders are shown for the corresponding edge.
2503 * When with_border == FALSE, only left margin and right_margin
2504 * can be seen because they can be can be overwritten by tags.
2505 */
2506static gboolean
2507_gtk_text_view_scroll_to_iter (GtkTextView *text_view,
2508 GtkTextIter *iter,
2509 double within_margin,
2510 gboolean use_align,
2511 double xalign,
2512 double yalign,
2513 gboolean with_border)
2514{
2515 GtkTextViewPrivate *priv = text_view->priv;
2516 GtkWidget *widget;
2517
2518 GdkRectangle cursor;
2519 int cursor_bottom;
2520 int cursor_right;
2521
2522 GdkRectangle screen;
2523 GdkRectangle screen_dest;
2524
2525 int screen_inner_left;
2526 int screen_inner_right;
2527 int screen_inner_top;
2528 int screen_inner_bottom;
2529
2530 int border_xoffset = 0;
2531 int border_yoffset = 0;
2532 int within_margin_xoffset;
2533 int within_margin_yoffset;
2534
2535 int buffer_bottom;
2536 int buffer_right;
2537
2538 gboolean retval = FALSE;
2539
2540 /* FIXME why don't we do the validate-at-scroll-destination thing
2541 * from flush_scroll in this function? I think it wasn't done before
2542 * because changed_handler was screwed up, but I could be wrong.
2543 */
2544
2545 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), FALSE);
2546 g_return_val_if_fail (iter != NULL, FALSE);
2547 g_return_val_if_fail (within_margin >= 0.0 && within_margin < 0.5, FALSE);
2548 g_return_val_if_fail (xalign >= 0.0 && xalign <= 1.0, FALSE);
2549 g_return_val_if_fail (yalign >= 0.0 && yalign <= 1.0, FALSE);
2550
2551 widget = GTK_WIDGET (text_view);
2552
2553 DV(g_print(G_STRLOC"\n"));
2554
2555 gtk_text_layout_get_iter_location (layout: priv->layout,
2556 iter,
2557 rect: &cursor);
2558
2559 DV (g_print (" target cursor %d,%d %d x %d\n", cursor.x, cursor.y, cursor.width, cursor.height));
2560
2561 /* In each direction, *_border are the addition of *_padding and *_margin
2562 *
2563 * Vadjustment value:
2564 * (-priv->top_margin) [top padding][top margin] (0) [text][bottom margin][bottom padding]
2565 *
2566 * Hadjustment value:
2567 * (-priv->left_padding) [left padding] (0) [left margin][text][right margin][right padding]
2568 *
2569 * Buffer coordinates:
2570 * on x: (0) [left margin][text][right margin]
2571 * on y: (0) [text]
2572 *
2573 * left margin and right margin are part of the x buffer coordinate
2574 * because they are part of the pango layout so that they can be
2575 * overwritten by tags.
2576 *
2577 * Canvas coordinates:
2578 * (the canvas is the virtual window where the content of the buffer is drawn )
2579 *
2580 * on x: (-priv->left_padding) [left padding] (0) [left margin][text][right margin][right padding]
2581 * on y: (-priv->top_margin) [top margin][top padding] (0) [text][bottom margin][bottom padding]
2582 *
2583 * (priv->xoffset, priv->yoffset) is the origin of the view (visible part of the canvas)
2584 * in canvas coordinates.
2585 * As you can see, canvas coordinates and buffer coordinates are compatible but the canvas
2586 * can be larger than the buffer depending of the border size.
2587 */
2588
2589 cursor_bottom = cursor.y + cursor.height;
2590 cursor_right = cursor.x + cursor.width;
2591
2592 /* Current position of the view in canvas coordinates */
2593 screen.x = priv->xoffset;
2594 screen.y = priv->yoffset;
2595 screen.width = SCREEN_WIDTH (widget);
2596 screen.height = SCREEN_HEIGHT (widget);
2597
2598 within_margin_xoffset = screen.width * within_margin;
2599 within_margin_yoffset = screen.height * within_margin;
2600
2601 screen_inner_left = screen.x + within_margin_xoffset;
2602 screen_inner_top = screen.y + within_margin_yoffset;
2603 screen_inner_right = screen.x + screen.width - within_margin_xoffset;
2604 screen_inner_bottom = screen.y + screen.height - within_margin_yoffset;
2605
2606 buffer_bottom = priv->height - priv->bottom_margin;
2607 buffer_right = priv->width - priv->right_margin - priv->left_padding - 1;
2608
2609 screen_dest.x = screen.x;
2610 screen_dest.y = screen.y;
2611 screen_dest.width = screen.width - within_margin_xoffset * 2;
2612 screen_dest.height = screen.height - within_margin_yoffset * 2;
2613
2614 /* Minimum authorised size check */
2615 if (screen_dest.width < 1)
2616 screen_dest.width = 1;
2617 if (screen_dest.height < 1)
2618 screen_dest.height = 1;
2619
2620 /* The alignment affects the point in the target character that we
2621 * choose to align. If we're doing right/bottom alignment, we align
2622 * the right/bottom edge of the character the mark is at; if we're
2623 * doing left/top we align the left/top edge of the character; if
2624 * we're doing center alignment we align the center of the
2625 * character.
2626 *
2627 * The different cases handle on each direction:
2628 * 1. cursor outside of the inner area define by within_margin
2629 * 2. if use_align == TRUE, alignment with xalign and yalign
2630 * 3. scrolling on the edges dependent of with_border
2631 */
2632
2633 /* Vertical scroll */
2634 if (use_align)
2635 {
2636 int cursor_y_alignment_offset;
2637
2638 cursor_y_alignment_offset = (cursor.height * yalign) - (screen_dest.height * yalign);
2639 screen_dest.y = cursor.y + cursor_y_alignment_offset - within_margin_yoffset;
2640 }
2641 else
2642 {
2643 /* move minimum to get onscreen, showing the
2644 * top_margin or bottom_margin when necessary
2645 */
2646 if (cursor.y < screen_inner_top)
2647 {
2648 if (cursor.y == 0)
2649 border_yoffset = with_border ? priv->top_padding : 0;
2650
2651 screen_dest.y = cursor.y - MAX (within_margin_yoffset, border_yoffset);
2652 }
2653 else if (cursor_bottom > screen_inner_bottom)
2654 {
2655 if (cursor_bottom == buffer_bottom - priv->top_margin)
2656 border_yoffset = with_border ? priv->bottom_padding : 0;
2657
2658 screen_dest.y = cursor_bottom - screen_dest.height -
2659 MAX (within_margin_yoffset, border_yoffset);
2660 }
2661 }
2662
2663 if (screen_dest.y != screen.y)
2664 {
2665 gtk_adjustment_animate_to_value (adjustment: priv->vadjustment, value: screen_dest.y + priv->top_margin);
2666
2667 DV (g_print (" vert increment %d\n", screen_dest.y - screen.y));
2668 }
2669
2670 /* Horizontal scroll */
2671
2672 if (use_align)
2673 {
2674 int cursor_x_alignment_offset;
2675
2676 cursor_x_alignment_offset = (cursor.width * xalign) - (screen_dest.width * xalign);
2677 screen_dest.x = cursor.x + cursor_x_alignment_offset - within_margin_xoffset;
2678 }
2679 else
2680 {
2681 /* move minimum to get onscreen, showing the
2682 * left_margin or right_margin when necessary
2683 */
2684 if (cursor.x < screen_inner_left)
2685 {
2686 if (cursor.x == priv->left_margin)
2687 border_xoffset = with_border ? priv->left_padding : 0;
2688
2689 screen_dest.x = cursor.x - MAX (within_margin_xoffset, border_xoffset);
2690 }
2691 else if (cursor_right >= screen_inner_right - 1)
2692 {
2693 if (cursor.x >= buffer_right - priv->right_padding)
2694 border_xoffset = with_border ? priv->right_padding : 0;
2695
2696 screen_dest.x = cursor_right - screen_dest.width -
2697 MAX (within_margin_xoffset, border_xoffset) + 1;
2698 }
2699 }
2700
2701 if (screen_dest.x != screen.x)
2702 {
2703 gtk_adjustment_animate_to_value (adjustment: priv->hadjustment, value: screen_dest.x + priv->left_padding);
2704
2705 DV (g_print (" horiz increment %d\n", screen_dest.x - screen.x));
2706 }
2707
2708 retval = (screen.y != screen_dest.y) || (screen.x != screen_dest.x);
2709
2710 DV(g_print (">%s ("G_STRLOC")\n", retval ? "Actually scrolled" : "Didn't end up scrolling"));
2711
2712 return retval;
2713}
2714
2715/**
2716 * gtk_text_view_scroll_to_iter:
2717 * @text_view: a `GtkTextView`
2718 * @iter: a `GtkTextIter`
2719 * @within_margin: margin as a [0.0,0.5) fraction of screen size
2720 * @use_align: whether to use alignment arguments (if %FALSE,
2721 * just get the mark onscreen)
2722 * @xalign: horizontal alignment of mark within visible area
2723 * @yalign: vertical alignment of mark within visible area
2724 *
2725 * Scrolls @text_view so that @iter is on the screen in the position
2726 * indicated by @xalign and @yalign.
2727 *
2728 * An alignment of 0.0 indicates left or top, 1.0 indicates right or
2729 * bottom, 0.5 means center. If @use_align is %FALSE, the text scrolls
2730 * the minimal distance to get the mark onscreen, possibly not scrolling
2731 * at all. The effective screen for purposes of this function is reduced
2732 * by a margin of size @within_margin.
2733 *
2734 * Note that this function uses the currently-computed height of the
2735 * lines in the text buffer. Line heights are computed in an idle
2736 * handler; so this function may not have the desired effect if it’s
2737 * called before the height computations. To avoid oddness, consider
2738 * using [method@Gtk.TextView.scroll_to_mark] which saves a point to be
2739 * scrolled to after line validation.
2740 *
2741 * Returns: %TRUE if scrolling occurred
2742 */
2743gboolean
2744gtk_text_view_scroll_to_iter (GtkTextView *text_view,
2745 GtkTextIter *iter,
2746 double within_margin,
2747 gboolean use_align,
2748 double xalign,
2749 double yalign)
2750{
2751 return _gtk_text_view_scroll_to_iter (text_view,
2752 iter,
2753 within_margin,
2754 use_align,
2755 xalign,
2756 yalign,
2757 FALSE);
2758}
2759
2760static void
2761free_pending_scroll (GtkTextPendingScroll *scroll)
2762{
2763 if (!gtk_text_mark_get_deleted (mark: scroll->mark))
2764 gtk_text_buffer_delete_mark (buffer: gtk_text_mark_get_buffer (mark: scroll->mark),
2765 mark: scroll->mark);
2766 g_object_unref (object: scroll->mark);
2767 g_slice_free (GtkTextPendingScroll, scroll);
2768}
2769
2770static void
2771cancel_pending_scroll (GtkTextView *text_view)
2772{
2773 if (text_view->priv->pending_scroll)
2774 {
2775 free_pending_scroll (scroll: text_view->priv->pending_scroll);
2776 text_view->priv->pending_scroll = NULL;
2777 }
2778}
2779
2780static void
2781gtk_text_view_queue_scroll (GtkTextView *text_view,
2782 GtkTextMark *mark,
2783 double within_margin,
2784 gboolean use_align,
2785 double xalign,
2786 double yalign)
2787{
2788 GtkTextIter iter;
2789 GtkTextPendingScroll *scroll;
2790
2791 DV(g_print(G_STRLOC"\n"));
2792
2793 scroll = g_slice_new (GtkTextPendingScroll);
2794
2795 scroll->within_margin = within_margin;
2796 scroll->use_align = use_align;
2797 scroll->xalign = xalign;
2798 scroll->yalign = yalign;
2799
2800 gtk_text_buffer_get_iter_at_mark (buffer: get_buffer (text_view), iter: &iter, mark);
2801
2802 scroll->mark = gtk_text_buffer_create_mark (buffer: get_buffer (text_view),
2803 NULL,
2804 where: &iter,
2805 left_gravity: gtk_text_mark_get_left_gravity (mark));
2806
2807 g_object_ref (scroll->mark);
2808
2809 cancel_pending_scroll (text_view);
2810
2811 text_view->priv->pending_scroll = scroll;
2812}
2813
2814static gboolean
2815gtk_text_view_flush_scroll (GtkTextView *text_view)
2816{
2817 int height;
2818 GtkTextIter iter;
2819 GtkTextPendingScroll *scroll;
2820 gboolean retval;
2821 GtkWidget *widget;
2822
2823 widget = GTK_WIDGET (text_view);
2824
2825 DV(g_print(G_STRLOC"\n"));
2826
2827 if (text_view->priv->pending_scroll == NULL)
2828 {
2829 DV (g_print ("in flush scroll, no pending scroll\n"));
2830 return FALSE;
2831 }
2832
2833 scroll = text_view->priv->pending_scroll;
2834
2835 /* avoid recursion */
2836 text_view->priv->pending_scroll = NULL;
2837
2838 gtk_text_buffer_get_iter_at_mark (buffer: get_buffer (text_view), iter: &iter, mark: scroll->mark);
2839
2840 /* Validate area around the scroll destination, so the adjustment
2841 * can meaningfully point into that area. We must validate
2842 * enough area to be sure that after we scroll, everything onscreen
2843 * is valid; otherwise, validation will maintain the first para
2844 * in one place, but may push the target iter off the bottom of
2845 * the screen.
2846 */
2847 DV(g_print (">Validating scroll destination ("G_STRLOC")\n"));
2848 height = gtk_widget_get_height (widget);
2849 gtk_text_layout_validate_yrange (layout: text_view->priv->layout, anchor_line: &iter,
2850 y0_: - (height * 2),
2851 y1_: height * 2);
2852
2853 DV(g_print (">Done validating scroll destination ("G_STRLOC")\n"));
2854
2855 /* Ensure we have updated width/height */
2856 gtk_text_view_update_adjustments (text_view);
2857
2858 retval = _gtk_text_view_scroll_to_iter (text_view,
2859 iter: &iter,
2860 within_margin: scroll->within_margin,
2861 use_align: scroll->use_align,
2862 xalign: scroll->xalign,
2863 yalign: scroll->yalign,
2864 TRUE);
2865
2866 gtk_text_view_update_handles (text_view);
2867
2868 free_pending_scroll (scroll);
2869
2870 return retval;
2871}
2872
2873static void
2874gtk_text_view_update_adjustments (GtkTextView *text_view)
2875{
2876 GtkTextViewPrivate *priv;
2877 int width = 0, height = 0;
2878
2879 DV(g_print(">Updating adjustments ("G_STRLOC")\n"));
2880
2881 priv = text_view->priv;
2882
2883 if (priv->layout)
2884 gtk_text_layout_get_size (layout: priv->layout, width: &width, height: &height);
2885
2886 /* Make room for the cursor after the last character in the widest line */
2887 width += SPACE_FOR_CURSOR;
2888 height += priv->top_margin + priv->bottom_margin;
2889
2890 if (priv->width != width || priv->height != height)
2891 {
2892 priv->width = width;
2893 priv->height = height;
2894
2895 gtk_text_view_set_hadjustment_values (text_view);
2896 gtk_text_view_set_vadjustment_values (text_view);
2897 }
2898}
2899
2900static void
2901gtk_text_view_update_layout_width (GtkTextView *text_view)
2902{
2903 DV(g_print(">Updating layout width ("G_STRLOC")\n"));
2904
2905 gtk_text_view_ensure_layout (text_view);
2906
2907 gtk_text_layout_set_screen_width (layout: text_view->priv->layout,
2908 MAX (1, SCREEN_WIDTH (text_view) - SPACE_FOR_CURSOR));
2909}
2910
2911static void
2912gtk_text_view_update_im_spot_location (GtkTextView *text_view)
2913{
2914 GdkRectangle area;
2915
2916 if (text_view->priv->layout == NULL)
2917 return;
2918
2919 gtk_text_view_get_cursor_locations (text_view, NULL, strong: &area, NULL);
2920
2921 area.x -= text_view->priv->xoffset;
2922 area.y -= text_view->priv->yoffset;
2923
2924 /* Width returned by Pango indicates direction of cursor,
2925 * by its sign more than the size of cursor.
2926 */
2927 area.width = 0;
2928
2929 gtk_im_context_set_cursor_location (context: text_view->priv->im_context, area: &area);
2930}
2931
2932static gboolean
2933do_update_im_spot_location (gpointer text_view)
2934{
2935 GtkTextViewPrivate *priv;
2936
2937 priv = GTK_TEXT_VIEW (text_view)->priv;
2938 priv->im_spot_idle = 0;
2939
2940 gtk_text_view_update_im_spot_location (text_view);
2941 return FALSE;
2942}
2943
2944static void
2945queue_update_im_spot_location (GtkTextView *text_view)
2946{
2947 GtkTextViewPrivate *priv;
2948
2949 priv = text_view->priv;
2950
2951 /* Use priority a little higher than GTK_TEXT_VIEW_PRIORITY_VALIDATE,
2952 * so we don't wait until the entire buffer has been validated. */
2953 if (!priv->im_spot_idle)
2954 {
2955 priv->im_spot_idle = g_idle_add_full (GTK_TEXT_VIEW_PRIORITY_VALIDATE - 1,
2956 function: do_update_im_spot_location,
2957 data: text_view,
2958 NULL);
2959 gdk_source_set_static_name_by_id (tag: priv->im_spot_idle, name: "[gtk] do_update_im_spot_location");
2960 }
2961}
2962
2963static void
2964flush_update_im_spot_location (GtkTextView *text_view)
2965{
2966 GtkTextViewPrivate *priv;
2967
2968 priv = text_view->priv;
2969
2970 if (priv->im_spot_idle)
2971 {
2972 g_source_remove (tag: priv->im_spot_idle);
2973 priv->im_spot_idle = 0;
2974 gtk_text_view_update_im_spot_location (text_view);
2975 }
2976}
2977
2978/**
2979 * gtk_text_view_scroll_to_mark:
2980 * @text_view: a `GtkTextView`
2981 * @mark: a `GtkTextMark`
2982 * @within_margin: margin as a [0.0,0.5) fraction of screen size
2983 * @use_align: whether to use alignment arguments (if %FALSE, just
2984 * get the mark onscreen)
2985 * @xalign: horizontal alignment of mark within visible area
2986 * @yalign: vertical alignment of mark within visible area
2987 *
2988 * Scrolls @text_view so that @mark is on the screen in the position
2989 * indicated by @xalign and @yalign.
2990 *
2991 * An alignment of 0.0 indicates left or top, 1.0 indicates right or
2992 * bottom, 0.5 means center. If @use_align is %FALSE, the text scrolls
2993 * the minimal distance to get the mark onscreen, possibly not scrolling
2994 * at all. The effective screen for purposes of this function is reduced
2995 * by a margin of size @within_margin.
2996 */
2997void
2998gtk_text_view_scroll_to_mark (GtkTextView *text_view,
2999 GtkTextMark *mark,
3000 double within_margin,
3001 gboolean use_align,
3002 double xalign,
3003 double yalign)
3004{
3005 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
3006 g_return_if_fail (GTK_IS_TEXT_MARK (mark));
3007 g_return_if_fail (within_margin >= 0.0 && within_margin < 0.5);
3008 g_return_if_fail (xalign >= 0.0 && xalign <= 1.0);
3009 g_return_if_fail (yalign >= 0.0 && yalign <= 1.0);
3010
3011 /* We need to verify that the buffer contains the mark, otherwise this
3012 * can lead to data structure corruption later on.
3013 */
3014 g_return_if_fail (get_buffer (text_view) == gtk_text_mark_get_buffer (mark));
3015
3016 gtk_text_view_queue_scroll (text_view, mark,
3017 within_margin,
3018 use_align,
3019 xalign,
3020 yalign);
3021
3022 /* If no validation is pending, we need to go ahead and force an
3023 * immediate scroll.
3024 */
3025 if (text_view->priv->layout &&
3026 gtk_text_layout_is_valid (layout: text_view->priv->layout))
3027 gtk_text_view_flush_scroll (text_view);
3028}
3029
3030/**
3031 * gtk_text_view_scroll_mark_onscreen:
3032 * @text_view: a `GtkTextView`
3033 * @mark: a mark in the buffer for @text_view
3034 *
3035 * Scrolls @text_view the minimum distance such that @mark is contained
3036 * within the visible area of the widget.
3037 */
3038void
3039gtk_text_view_scroll_mark_onscreen (GtkTextView *text_view,
3040 GtkTextMark *mark)
3041{
3042 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
3043 g_return_if_fail (GTK_IS_TEXT_MARK (mark));
3044
3045 /* We need to verify that the buffer contains the mark, otherwise this
3046 * can lead to data structure corruption later on.
3047 */
3048 g_return_if_fail (get_buffer (text_view) == gtk_text_mark_get_buffer (mark));
3049
3050 gtk_text_view_scroll_to_mark (text_view, mark, within_margin: 0.0, FALSE, xalign: 0.0, yalign: 0.0);
3051}
3052
3053static gboolean
3054clamp_iter_onscreen (GtkTextView *text_view, GtkTextIter *iter)
3055{
3056 GdkRectangle visible_rect;
3057 gtk_text_view_get_visible_rect (text_view, visible_rect: &visible_rect);
3058
3059 return gtk_text_layout_clamp_iter_to_vrange (layout: text_view->priv->layout, iter,
3060 top: visible_rect.y,
3061 bottom: visible_rect.y + visible_rect.height);
3062}
3063
3064/**
3065 * gtk_text_view_move_mark_onscreen:
3066 * @text_view: a `GtkTextView`
3067 * @mark: a `GtkTextMark`
3068 *
3069 * Moves a mark within the buffer so that it's
3070 * located within the currently-visible text area.
3071 *
3072 * Returns: %TRUE if the mark moved (wasn’t already onscreen)
3073 */
3074gboolean
3075gtk_text_view_move_mark_onscreen (GtkTextView *text_view,
3076 GtkTextMark *mark)
3077{
3078 GtkTextIter iter;
3079
3080 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), FALSE);
3081 g_return_val_if_fail (mark != NULL, FALSE);
3082
3083 gtk_text_buffer_get_iter_at_mark (buffer: get_buffer (text_view), iter: &iter, mark);
3084
3085 if (clamp_iter_onscreen (text_view, iter: &iter))
3086 {
3087 gtk_text_buffer_move_mark (buffer: get_buffer (text_view), mark, where: &iter);
3088 return TRUE;
3089 }
3090 else
3091 return FALSE;
3092}
3093
3094/**
3095 * gtk_text_view_get_visible_rect:
3096 * @text_view: a `GtkTextView`
3097 * @visible_rect: (out): rectangle to fill
3098 *
3099 * Fills @visible_rect with the currently-visible
3100 * region of the buffer, in buffer coordinates.
3101 *
3102 * Convert to window coordinates with
3103 * [method@Gtk.TextView.buffer_to_window_coords].
3104 */
3105void
3106gtk_text_view_get_visible_rect (GtkTextView *text_view,
3107 GdkRectangle *visible_rect)
3108{
3109 GtkWidget *widget;
3110
3111 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
3112
3113 widget = GTK_WIDGET (text_view);
3114
3115 if (visible_rect)
3116 {
3117 visible_rect->x = text_view->priv->xoffset;
3118 visible_rect->y = text_view->priv->yoffset;
3119 visible_rect->width = SCREEN_WIDTH (widget);
3120 visible_rect->height = SCREEN_HEIGHT (widget);
3121
3122 DV(g_print(" visible rect: %d,%d %d x %d\n",
3123 visible_rect->x,
3124 visible_rect->y,
3125 visible_rect->width,
3126 visible_rect->height));
3127 }
3128}
3129
3130/**
3131 * gtk_text_view_set_wrap_mode: (attributes org.gtk.Method.set_property=wrap-mode)
3132 * @text_view: a `GtkTextView`
3133 * @wrap_mode: a `GtkWrapMode`
3134 *
3135 * Sets the line wrapping for the view.
3136 */
3137void
3138gtk_text_view_set_wrap_mode (GtkTextView *text_view,
3139 GtkWrapMode wrap_mode)
3140{
3141 GtkTextViewPrivate *priv;
3142
3143 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
3144
3145 priv = text_view->priv;
3146
3147 if (priv->wrap_mode != wrap_mode)
3148 {
3149 priv->wrap_mode = wrap_mode;
3150
3151 if (priv->layout && priv->layout->default_style)
3152 {
3153 priv->layout->default_style->wrap_mode = wrap_mode;
3154 gtk_text_layout_default_style_changed (layout: priv->layout);
3155 }
3156 g_object_notify (G_OBJECT (text_view), property_name: "wrap-mode");
3157 }
3158}
3159
3160/**
3161 * gtk_text_view_get_wrap_mode: (attributes org.gtk.Method.get_property=wrap-mode)
3162 * @text_view: a `GtkTextView`
3163 *
3164 * Gets the line wrapping for the view.
3165 *
3166 * Returns: the line wrap setting
3167 */
3168GtkWrapMode
3169gtk_text_view_get_wrap_mode (GtkTextView *text_view)
3170{
3171 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), GTK_WRAP_NONE);
3172
3173 return text_view->priv->wrap_mode;
3174}
3175
3176/**
3177 * gtk_text_view_set_editable: (attributes org.gtk.Method.set_property=editable)
3178 * @text_view: a `GtkTextView`
3179 * @setting: whether it’s editable
3180 *
3181 * Sets the default editability of the `GtkTextView`.
3182 *
3183 * You can override this default setting with tags in the buffer,
3184 * using the “editable” attribute of tags.
3185 */
3186void
3187gtk_text_view_set_editable (GtkTextView *text_view,
3188 gboolean setting)
3189{
3190 GtkTextViewPrivate *priv;
3191
3192 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
3193
3194 priv = text_view->priv;
3195 setting = setting != FALSE;
3196
3197 if (priv->editable != setting)
3198 {
3199 if (!setting)
3200 {
3201 gtk_text_view_reset_im_context (text_view);
3202 if (gtk_widget_has_focus (GTK_WIDGET (text_view)))
3203 gtk_im_context_focus_out (context: priv->im_context);
3204 }
3205
3206 priv->editable = setting;
3207
3208 if (setting && gtk_widget_has_focus (GTK_WIDGET (text_view)))
3209 gtk_im_context_focus_in (context: priv->im_context);
3210
3211 gtk_event_controller_key_set_im_context (GTK_EVENT_CONTROLLER_KEY (priv->key_controller),
3212 im_context: setting ? priv->im_context : NULL);
3213
3214 if (priv->layout && priv->layout->default_style)
3215 {
3216 gtk_text_layout_set_overwrite_mode (layout: priv->layout,
3217 overwrite: priv->overwrite_mode && priv->editable);
3218 priv->layout->default_style->editable = priv->editable;
3219 gtk_text_layout_default_style_changed (layout: priv->layout);
3220 }
3221
3222 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: text_view),
3223 first_property: GTK_ACCESSIBLE_PROPERTY_READ_ONLY, !setting,
3224 -1);
3225 gtk_text_view_update_emoji_action (text_view);
3226
3227 g_object_notify (G_OBJECT (text_view), property_name: "editable");
3228 }
3229}
3230
3231/**
3232 * gtk_text_view_get_editable: (attributes org.gtk.Method.get_property=editable)
3233 * @text_view: a `GtkTextView`
3234 *
3235 * Returns the default editability of the `GtkTextView`.
3236 *
3237 * Tags in the buffer may override this setting for some ranges of text.
3238 *
3239 * Returns: whether text is editable by default
3240 */
3241gboolean
3242gtk_text_view_get_editable (GtkTextView *text_view)
3243{
3244 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), FALSE);
3245
3246 return text_view->priv->editable;
3247}
3248
3249/**
3250 * gtk_text_view_set_pixels_above_lines: (attributes org.gtk.Method.set_property=pixels-above-lines)
3251 * @text_view: a `GtkTextView`
3252 * @pixels_above_lines: pixels above paragraphs
3253 *
3254 * Sets the default number of blank pixels above paragraphs in @text_view.
3255 *
3256 * Tags in the buffer for @text_view may override the defaults.
3257 */
3258void
3259gtk_text_view_set_pixels_above_lines (GtkTextView *text_view,
3260 int pixels_above_lines)
3261{
3262 GtkTextViewPrivate *priv;
3263
3264 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
3265
3266 priv = text_view->priv;
3267
3268 if (priv->pixels_above_lines != pixels_above_lines)
3269 {
3270 priv->pixels_above_lines = pixels_above_lines;
3271
3272 if (priv->layout && priv->layout->default_style)
3273 {
3274 priv->layout->default_style->pixels_above_lines = pixels_above_lines;
3275 gtk_text_layout_default_style_changed (layout: priv->layout);
3276 }
3277
3278 g_object_notify (G_OBJECT (text_view), property_name: "pixels-above-lines");
3279 }
3280}
3281
3282/**
3283 * gtk_text_view_get_pixels_above_lines: (attributes org.gtk.Method.get_property=pixels-above-lines)
3284 * @text_view: a `GtkTextView`
3285 *
3286 * Gets the default number of pixels to put above paragraphs.
3287 *
3288 * Adding this function with [method@Gtk.TextView.get_pixels_below_lines]
3289 * is equal to the line space between each paragraph.
3290 *
3291 * Returns: default number of pixels above paragraphs
3292 */
3293int
3294gtk_text_view_get_pixels_above_lines (GtkTextView *text_view)
3295{
3296 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), 0);
3297
3298 return text_view->priv->pixels_above_lines;
3299}
3300
3301/**
3302 * gtk_text_view_set_pixels_below_lines: (attributes org.gtk.Method.set_property=pixels-below-lines)
3303 * @text_view: a `GtkTextView`
3304 * @pixels_below_lines: pixels below paragraphs
3305 *
3306 * Sets the default number of pixels of blank space
3307 * to put below paragraphs in @text_view.
3308 *
3309 * May be overridden by tags applied to @text_view’s buffer.
3310 */
3311void
3312gtk_text_view_set_pixels_below_lines (GtkTextView *text_view,
3313 int pixels_below_lines)
3314{
3315 GtkTextViewPrivate *priv;
3316
3317 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
3318
3319 priv = text_view->priv;
3320
3321 if (priv->pixels_below_lines != pixels_below_lines)
3322 {
3323 priv->pixels_below_lines = pixels_below_lines;
3324
3325 if (priv->layout && priv->layout->default_style)
3326 {
3327 priv->layout->default_style->pixels_below_lines = pixels_below_lines;
3328 gtk_text_layout_default_style_changed (layout: priv->layout);
3329 }
3330
3331 g_object_notify (G_OBJECT (text_view), property_name: "pixels-below-lines");
3332 }
3333}
3334
3335/**
3336 * gtk_text_view_get_pixels_below_lines: (attributes org.gtk.Method.get_property=pixels-below-lines)
3337 * @text_view: a `GtkTextView`
3338 *
3339 * Gets the default number of pixels to put below paragraphs.
3340 *
3341 * The line space is the sum of the value returned by this function and
3342 * the value returned by [method@Gtk.TextView.get_pixels_above_lines].
3343 *
3344 * Returns: default number of blank pixels below paragraphs
3345 */
3346int
3347gtk_text_view_get_pixels_below_lines (GtkTextView *text_view)
3348{
3349 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), 0);
3350
3351 return text_view->priv->pixels_below_lines;
3352}
3353
3354/**
3355 * gtk_text_view_set_pixels_inside_wrap: (attributes org.gtk.Method.set_property=pixels-inside-wrap)
3356 * @text_view: a `GtkTextView`
3357 * @pixels_inside_wrap: default number of pixels between wrapped lines
3358 *
3359 * Sets the default number of pixels of blank space to leave between
3360 * display/wrapped lines within a paragraph.
3361 *
3362 * May be overridden by tags in @text_view’s buffer.
3363 */
3364void
3365gtk_text_view_set_pixels_inside_wrap (GtkTextView *text_view,
3366 int pixels_inside_wrap)
3367{
3368 GtkTextViewPrivate *priv;
3369
3370 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
3371
3372 priv = text_view->priv;
3373
3374 if (priv->pixels_inside_wrap != pixels_inside_wrap)
3375 {
3376 priv->pixels_inside_wrap = pixels_inside_wrap;
3377
3378 if (priv->layout && priv->layout->default_style)
3379 {
3380 priv->layout->default_style->pixels_inside_wrap = pixels_inside_wrap;
3381 gtk_text_layout_default_style_changed (layout: priv->layout);
3382 }
3383
3384 g_object_notify (G_OBJECT (text_view), property_name: "pixels-inside-wrap");
3385 }
3386}
3387
3388/**
3389 * gtk_text_view_get_pixels_inside_wrap: (attributes org.gtk.Method.get_property=pixels-inside-wrap)
3390 * @text_view: a `GtkTextView`
3391 *
3392 * Gets the default number of pixels to put between wrapped lines
3393 * inside a paragraph.
3394 *
3395 * Returns: default number of pixels of blank space between wrapped lines
3396 */
3397int
3398gtk_text_view_get_pixels_inside_wrap (GtkTextView *text_view)
3399{
3400 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), 0);
3401
3402 return text_view->priv->pixels_inside_wrap;
3403}
3404
3405/**
3406 * gtk_text_view_set_justification: (attributes org.gtk.Method.set_property=justification)
3407 * @text_view: a `GtkTextView`
3408 * @justification: justification
3409 *
3410 * Sets the default justification of text in @text_view.
3411 *
3412 * Tags in the view’s buffer may override the default.
3413 */
3414void
3415gtk_text_view_set_justification (GtkTextView *text_view,
3416 GtkJustification justification)
3417{
3418 GtkTextViewPrivate *priv;
3419
3420 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
3421
3422 priv = text_view->priv;
3423
3424 if (priv->justify != justification)
3425 {
3426 priv->justify = justification;
3427
3428 if (priv->layout && priv->layout->default_style)
3429 {
3430 priv->layout->default_style->justification = justification;
3431 gtk_text_layout_default_style_changed (layout: priv->layout);
3432 }
3433
3434 g_object_notify (G_OBJECT (text_view), property_name: "justification");
3435 }
3436}
3437
3438/**
3439 * gtk_text_view_get_justification: (attributes org.gtk.Method.get_property=justification)
3440 * @text_view: a `GtkTextView`
3441 *
3442 * Gets the default justification of paragraphs in @text_view.
3443 *
3444 * Tags in the buffer may override the default.
3445 *
3446 * Returns: default justification
3447 */
3448GtkJustification
3449gtk_text_view_get_justification (GtkTextView *text_view)
3450{
3451 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), GTK_JUSTIFY_LEFT);
3452
3453 return text_view->priv->justify;
3454}
3455
3456/**
3457 * gtk_text_view_set_left_margin: (attributes org.gtk.Method.set_property=left-margin)
3458 * @text_view: a `GtkTextView`
3459 * @left_margin: left margin in pixels
3460 *
3461 * Sets the default left margin for text in @text_view.
3462 *
3463 * Tags in the buffer may override the default.
3464 *
3465 * Note that this function is confusingly named.
3466 * In CSS terms, the value set here is padding.
3467 */
3468void
3469gtk_text_view_set_left_margin (GtkTextView *text_view,
3470 int left_margin)
3471{
3472 GtkTextViewPrivate *priv = text_view->priv;
3473
3474 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
3475
3476 if (priv->left_margin != left_margin)
3477 {
3478 priv->left_margin = left_margin;
3479 priv->left_margin = left_margin + priv->left_padding;
3480
3481 if (priv->layout && priv->layout->default_style)
3482 {
3483 priv->layout->default_style->left_margin = left_margin;
3484 gtk_text_layout_default_style_changed (layout: priv->layout);
3485 }
3486
3487 g_object_notify (G_OBJECT (text_view), property_name: "left-margin");
3488 }
3489}
3490
3491/**
3492 * gtk_text_view_get_left_margin: (attributes org.gtk.Method.get_property=left-margin)
3493 * @text_view: a `GtkTextView`
3494 *
3495 * Gets the default left margin size of paragraphs in the @text_view.
3496 *
3497 * Tags in the buffer may override the default.
3498 *
3499 * Returns: left margin in pixels
3500 */
3501int
3502gtk_text_view_get_left_margin (GtkTextView *text_view)
3503{
3504 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), 0);
3505
3506 return text_view->priv->left_margin;
3507}
3508
3509/**
3510 * gtk_text_view_set_right_margin: (attributes org.gtk.Method.set_property=right-margin)
3511 * @text_view: a `GtkTextView`
3512 * @right_margin: right margin in pixels
3513 *
3514 * Sets the default right margin for text in the text view.
3515 *
3516 * Tags in the buffer may override the default.
3517 *
3518 * Note that this function is confusingly named.
3519 * In CSS terms, the value set here is padding.
3520 */
3521void
3522gtk_text_view_set_right_margin (GtkTextView *text_view,
3523 int right_margin)
3524{
3525 GtkTextViewPrivate *priv = text_view->priv;
3526
3527 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
3528
3529 if (priv->right_margin != right_margin)
3530 {
3531 priv->right_margin = right_margin;
3532 priv->right_margin = right_margin + priv->right_padding;
3533
3534 if (priv->layout && priv->layout->default_style)
3535 {
3536 priv->layout->default_style->right_margin = right_margin;
3537 gtk_text_layout_default_style_changed (layout: priv->layout);
3538 }
3539
3540 g_object_notify (G_OBJECT (text_view), property_name: "right-margin");
3541 }
3542}
3543
3544/**
3545 * gtk_text_view_get_right_margin: (attributes org.gtk.Method.get_property=right-margin)
3546 * @text_view: a `GtkTextView`
3547 *
3548 * Gets the default right margin for text in @text_view.
3549 *
3550 * Tags in the buffer may override the default.
3551 *
3552 * Returns: right margin in pixels
3553 */
3554int
3555gtk_text_view_get_right_margin (GtkTextView *text_view)
3556{
3557 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), 0);
3558
3559 return text_view->priv->right_margin;
3560}
3561
3562/**
3563 * gtk_text_view_set_top_margin: (attributes org.gtk.Method.set_property=top-margin)
3564 * @text_view: a `GtkTextView`
3565 * @top_margin: top margin in pixels
3566 *
3567 * Sets the top margin for text in @text_view.
3568 *
3569 * Note that this function is confusingly named.
3570 * In CSS terms, the value set here is padding.
3571 */
3572void
3573gtk_text_view_set_top_margin (GtkTextView *text_view,
3574 int top_margin)
3575{
3576 GtkTextViewPrivate *priv = text_view->priv;
3577
3578 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
3579
3580 if (priv->top_margin != top_margin)
3581 {
3582 priv->yoffset += priv->top_margin - top_margin;
3583
3584 priv->top_margin = top_margin;
3585 priv->top_margin = top_margin + priv->top_padding;
3586
3587 if (priv->layout && priv->layout->default_style)
3588 gtk_text_layout_default_style_changed (layout: priv->layout);
3589
3590 gtk_text_view_invalidate (text_view);
3591
3592 g_object_notify (G_OBJECT (text_view), property_name: "top-margin");
3593 }
3594}
3595
3596/**
3597 * gtk_text_view_get_top_margin: (attributes org.gtk.Method.get_property=top-margin)
3598 * @text_view: a `GtkTextView`
3599 *
3600 * Gets the top margin for text in the @text_view.
3601 *
3602 * Returns: top margin in pixels
3603 */
3604int
3605gtk_text_view_get_top_margin (GtkTextView *text_view)
3606{
3607 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), 0);
3608
3609 return text_view->priv->top_margin;
3610}
3611
3612/**
3613 * gtk_text_view_set_bottom_margin: (attributes org.gtk.Method.set_property=bottom-margin)
3614 * @text_view: a `GtkTextView`
3615 * @bottom_margin: bottom margin in pixels
3616 *
3617 * Sets the bottom margin for text in @text_view.
3618 *
3619 * Note that this function is confusingly named.
3620 * In CSS terms, the value set here is padding.
3621 */
3622void
3623gtk_text_view_set_bottom_margin (GtkTextView *text_view,
3624 int bottom_margin)
3625{
3626 GtkTextViewPrivate *priv = text_view->priv;
3627
3628 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
3629
3630 if (priv->bottom_margin != bottom_margin)
3631 {
3632 priv->bottom_margin = bottom_margin;
3633 priv->bottom_margin = bottom_margin + priv->bottom_padding;
3634
3635 if (priv->layout && priv->layout->default_style)
3636 gtk_text_layout_default_style_changed (layout: priv->layout);
3637
3638 g_object_notify (G_OBJECT (text_view), property_name: "bottom-margin");
3639 }
3640}
3641
3642/**
3643 * gtk_text_view_get_bottom_margin: (attributes org.gtk.Method.get_property=bottom-margin)
3644 * @text_view: a `GtkTextView`
3645 *
3646 * Gets the bottom margin for text in the @text_view.
3647 *
3648 * Returns: bottom margin in pixels
3649 */
3650int
3651gtk_text_view_get_bottom_margin (GtkTextView *text_view)
3652{
3653 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), 0);
3654
3655 return text_view->priv->bottom_margin;
3656}
3657
3658/**
3659 * gtk_text_view_set_indent: (attributes org.gtk.Method.set_property=indent)
3660 * @text_view: a `GtkTextView`
3661 * @indent: indentation in pixels
3662 *
3663 * Sets the default indentation for paragraphs in @text_view.
3664 *
3665 * Tags in the buffer may override the default.
3666 */
3667void
3668gtk_text_view_set_indent (GtkTextView *text_view,
3669 int indent)
3670{
3671 GtkTextViewPrivate *priv;
3672
3673 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
3674
3675 priv = text_view->priv;
3676
3677 if (priv->indent != indent)
3678 {
3679 priv->indent = indent;
3680
3681 if (priv->layout && priv->layout->default_style)
3682 {
3683 priv->layout->default_style->indent = indent;
3684 gtk_text_layout_default_style_changed (layout: priv->layout);
3685 }
3686
3687 g_object_notify (G_OBJECT (text_view), property_name: "indent");
3688 }
3689}
3690
3691/**
3692 * gtk_text_view_get_indent: (attributes org.gtk.Method.get_property=indent)
3693 * @text_view: a `GtkTextView`
3694 *
3695 * Gets the default indentation of paragraphs in @text_view.
3696 *
3697 * Tags in the view’s buffer may override the default.
3698 * The indentation may be negative.
3699 *
3700 * Returns: number of pixels of indentation
3701 */
3702int
3703gtk_text_view_get_indent (GtkTextView *text_view)
3704{
3705 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), 0);
3706
3707 return text_view->priv->indent;
3708}
3709
3710/**
3711 * gtk_text_view_set_tabs: (attributes org.gtk.Method.set_property=tabs)
3712 * @text_view: a `GtkTextView`
3713 * @tabs: tabs as a `PangoTabArray`
3714 *
3715 * Sets the default tab stops for paragraphs in @text_view.
3716 *
3717 * Tags in the buffer may override the default.
3718 */
3719void
3720gtk_text_view_set_tabs (GtkTextView *text_view,
3721 PangoTabArray *tabs)
3722{
3723 GtkTextViewPrivate *priv;
3724
3725 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
3726
3727 priv = text_view->priv;
3728
3729 if (priv->tabs)
3730 pango_tab_array_free (tab_array: priv->tabs);
3731
3732 priv->tabs = tabs ? pango_tab_array_copy (src: tabs) : NULL;
3733
3734 if (priv->layout && priv->layout->default_style)
3735 {
3736 /* some unkosher futzing in internal struct details... */
3737 if (priv->layout->default_style->tabs)
3738 pango_tab_array_free (tab_array: priv->layout->default_style->tabs);
3739
3740 priv->layout->default_style->tabs =
3741 priv->tabs ? pango_tab_array_copy (src: priv->tabs) : NULL;
3742
3743 gtk_text_layout_default_style_changed (layout: priv->layout);
3744 }
3745
3746 g_object_notify (G_OBJECT (text_view), property_name: "tabs");
3747}
3748
3749/**
3750 * gtk_text_view_get_tabs: (attributes org.gtk.Method.get_property=tabs)
3751 * @text_view: a `GtkTextView`
3752 *
3753 * Gets the default tabs for @text_view.
3754 *
3755 * Tags in the buffer may override the defaults. The returned array
3756 * will be %NULL if “standard” (8-space) tabs are used. Free the
3757 * return value with [method@Pango.TabArray.free].
3758 *
3759 * Returns: (nullable) (transfer full): copy of default tab array,
3760 * or %NULL if standard tabs are used; must be freed with
3761 * [method@Pango.TabArray.free].
3762 */
3763PangoTabArray*
3764gtk_text_view_get_tabs (GtkTextView *text_view)
3765{
3766 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), NULL);
3767
3768 return text_view->priv->tabs ? pango_tab_array_copy (src: text_view->priv->tabs) : NULL;
3769}
3770
3771static void
3772gtk_text_view_toggle_cursor_visible (GtkTextView *text_view)
3773{
3774 gtk_text_view_set_cursor_visible (text_view, setting: !text_view->priv->cursor_visible);
3775}
3776
3777/**
3778 * gtk_text_view_set_cursor_visible: (attributes org.gtk.Method.set_property=cursor-visible)
3779 * @text_view: a `GtkTextView`
3780 * @setting: whether to show the insertion cursor
3781 *
3782 * Toggles whether the insertion point should be displayed.
3783 *
3784 * A buffer with no editable text probably shouldn’t have a visible
3785 * cursor, so you may want to turn the cursor off.
3786 *
3787 * Note that this property may be overridden by the
3788 * [property@GtkSettings:gtk-keynav-use-caret] setting.
3789 */
3790void
3791gtk_text_view_set_cursor_visible (GtkTextView *text_view,
3792 gboolean setting)
3793{
3794 GtkTextViewPrivate *priv;
3795
3796 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
3797
3798 priv = text_view->priv;
3799 setting = (setting != FALSE);
3800
3801 if (priv->cursor_visible != setting)
3802 {
3803 priv->cursor_visible = setting;
3804
3805 if (gtk_widget_has_focus (GTK_WIDGET (text_view)))
3806 {
3807 if (priv->layout)
3808 {
3809 gtk_text_layout_set_cursor_visible (layout: priv->layout, cursor_visible: setting);
3810 gtk_text_view_check_cursor_blink (text_view);
3811 }
3812 }
3813
3814 g_object_notify (G_OBJECT (text_view), property_name: "cursor-visible");
3815 }
3816}
3817
3818/**
3819 * gtk_text_view_get_cursor_visible: (attributes org.gtk.Method.get_property=cursor-visible)
3820 * @text_view: a `GtkTextView`
3821 *
3822 * Find out whether the cursor should be displayed.
3823 *
3824 * Returns: whether the insertion mark is visible
3825 */
3826gboolean
3827gtk_text_view_get_cursor_visible (GtkTextView *text_view)
3828{
3829 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), FALSE);
3830
3831 return text_view->priv->cursor_visible;
3832}
3833
3834/**
3835 * gtk_text_view_reset_cursor_blink:
3836 * @text_view: a `GtkTextView`
3837 *
3838 * Ensures that the cursor is shown.
3839 *
3840 * This also resets the time that it will stay blinking (or
3841 * visible, in case blinking is disabled).
3842 *
3843 * This function should be called in response to user input
3844 * (e.g. from derived classes that override the textview's
3845 * event handlers).
3846 */
3847void
3848gtk_text_view_reset_cursor_blink (GtkTextView *text_view)
3849{
3850 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
3851
3852 gtk_text_view_reset_blink_time (text_view);
3853 gtk_text_view_pend_cursor_blink (text_view);
3854}
3855
3856/**
3857 * gtk_text_view_place_cursor_onscreen:
3858 * @text_view: a `GtkTextView`
3859 *
3860 * Moves the cursor to the currently visible region of the
3861 * buffer.
3862 *
3863 * Returns: %TRUE if the cursor had to be moved.
3864 */
3865gboolean
3866gtk_text_view_place_cursor_onscreen (GtkTextView *text_view)
3867{
3868 GtkTextIter insert;
3869
3870 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), FALSE);
3871
3872 gtk_text_buffer_get_iter_at_mark (buffer: get_buffer (text_view), iter: &insert,
3873 mark: gtk_text_buffer_get_insert (buffer: get_buffer (text_view)));
3874
3875 if (clamp_iter_onscreen (text_view, iter: &insert))
3876 {
3877 gtk_text_buffer_place_cursor (buffer: get_buffer (text_view), where: &insert);
3878 return TRUE;
3879 }
3880 else
3881 return FALSE;
3882}
3883
3884static void
3885gtk_text_view_remove_validate_idles (GtkTextView *text_view)
3886{
3887 GtkTextViewPrivate *priv = text_view->priv;
3888
3889 if (priv->first_validate_idle != 0)
3890 {
3891 DV (g_print ("Removing first validate idle: %s\n", G_STRLOC));
3892 g_source_remove (tag: priv->first_validate_idle);
3893 priv->first_validate_idle = 0;
3894 }
3895
3896 if (priv->incremental_validate_idle != 0)
3897 {
3898 g_source_remove (tag: priv->incremental_validate_idle);
3899 priv->incremental_validate_idle = 0;
3900 }
3901}
3902
3903static void
3904gtk_text_view_dispose (GObject *object)
3905{
3906 GtkTextView *text_view = GTK_TEXT_VIEW (object);
3907 GtkTextViewPrivate *priv = text_view->priv;
3908 GtkWidget *child;
3909
3910 child = g_object_get_data (object, key: "gtk-emoji-chooser");
3911 if (child)
3912 {
3913 gtk_widget_unparent (widget: child);
3914 g_object_set_data (object, key: "gtk-emoji-chooser", NULL);
3915 }
3916
3917 gtk_text_view_remove_validate_idles (text_view);
3918 gtk_text_view_set_buffer (text_view, NULL);
3919 gtk_text_view_destroy_layout (text_view);
3920
3921 if (text_view->priv->scroll_timeout)
3922 {
3923 g_source_remove (tag: text_view->priv->scroll_timeout);
3924 text_view->priv->scroll_timeout = 0;
3925 }
3926
3927 if (priv->im_spot_idle)
3928 {
3929 g_source_remove (tag: priv->im_spot_idle);
3930 priv->im_spot_idle = 0;
3931 }
3932
3933 if (priv->magnifier)
3934 _gtk_magnifier_set_inspected (GTK_MAGNIFIER (priv->magnifier), NULL);
3935
3936 g_clear_pointer ((GtkWidget **) &priv->text_handles[TEXT_HANDLE_CURSOR], gtk_widget_unparent);
3937 g_clear_pointer ((GtkWidget **) &priv->text_handles[TEXT_HANDLE_SELECTION_BOUND], gtk_widget_unparent);
3938
3939 g_clear_pointer (&priv->selection_bubble, gtk_widget_unparent);
3940 g_clear_pointer (&priv->magnifier_popover, gtk_widget_unparent);
3941
3942 while ((child = gtk_widget_get_first_child (GTK_WIDGET (text_view))))
3943 gtk_text_view_remove (text_view, child);
3944
3945 G_OBJECT_CLASS (gtk_text_view_parent_class)->dispose (object);
3946}
3947
3948static void
3949gtk_text_view_finalize (GObject *object)
3950{
3951 GtkTextView *text_view;
3952 GtkTextViewPrivate *priv;
3953
3954 text_view = GTK_TEXT_VIEW (object);
3955 priv = text_view->priv;
3956
3957 gtk_text_view_destroy_layout (text_view);
3958 gtk_text_view_set_buffer (text_view, NULL);
3959
3960 /* at this point, no "notify::buffer" handler should recreate the buffer. */
3961 g_assert (priv->buffer == NULL);
3962
3963 /* Ensure all children were removed */
3964 g_assert (priv->anchored_children.length == 0);
3965 g_assert (priv->left_child == NULL);
3966 g_assert (priv->right_child == NULL);
3967 g_assert (priv->top_child == NULL);
3968 g_assert (priv->bottom_child == NULL);
3969 g_assert (priv->center_child == NULL);
3970
3971 cancel_pending_scroll (text_view);
3972
3973 if (priv->tabs)
3974 pango_tab_array_free (tab_array: priv->tabs);
3975
3976 if (priv->hadjustment)
3977 g_object_unref (object: priv->hadjustment);
3978 if (priv->vadjustment)
3979 g_object_unref (object: priv->vadjustment);
3980
3981 text_window_free (win: priv->text_window);
3982
3983 g_object_unref (object: priv->im_context);
3984
3985 g_free (mem: priv->im_module);
3986
3987 g_clear_pointer (&priv->popup_menu, gtk_widget_unparent);
3988 g_clear_object (&priv->extra_menu);
3989
3990 G_OBJECT_CLASS (gtk_text_view_parent_class)->finalize (object);
3991}
3992
3993static void
3994gtk_text_view_set_property (GObject *object,
3995 guint prop_id,
3996 const GValue *value,
3997 GParamSpec *pspec)
3998{
3999 GtkTextView *text_view;
4000 GtkTextViewPrivate *priv;
4001
4002 text_view = GTK_TEXT_VIEW (object);
4003 priv = text_view->priv;
4004
4005 switch (prop_id)
4006 {
4007 case PROP_PIXELS_ABOVE_LINES:
4008 gtk_text_view_set_pixels_above_lines (text_view, pixels_above_lines: g_value_get_int (value));
4009 break;
4010
4011 case PROP_PIXELS_BELOW_LINES:
4012 gtk_text_view_set_pixels_below_lines (text_view, pixels_below_lines: g_value_get_int (value));
4013 break;
4014
4015 case PROP_PIXELS_INSIDE_WRAP:
4016 gtk_text_view_set_pixels_inside_wrap (text_view, pixels_inside_wrap: g_value_get_int (value));
4017 break;
4018
4019 case PROP_EDITABLE:
4020 gtk_text_view_set_editable (text_view, setting: g_value_get_boolean (value));
4021 break;
4022
4023 case PROP_WRAP_MODE:
4024 gtk_text_view_set_wrap_mode (text_view, wrap_mode: g_value_get_enum (value));
4025 break;
4026
4027 case PROP_JUSTIFICATION:
4028 gtk_text_view_set_justification (text_view, justification: g_value_get_enum (value));
4029 break;
4030
4031 case PROP_LEFT_MARGIN:
4032 gtk_text_view_set_left_margin (text_view, left_margin: g_value_get_int (value));
4033 break;
4034
4035 case PROP_RIGHT_MARGIN:
4036 gtk_text_view_set_right_margin (text_view, right_margin: g_value_get_int (value));
4037 break;
4038
4039 case PROP_TOP_MARGIN:
4040 gtk_text_view_set_top_margin (text_view, top_margin: g_value_get_int (value));
4041 break;
4042
4043 case PROP_BOTTOM_MARGIN:
4044 gtk_text_view_set_bottom_margin (text_view, bottom_margin: g_value_get_int (value));
4045 break;
4046
4047 case PROP_INDENT:
4048 gtk_text_view_set_indent (text_view, indent: g_value_get_int (value));
4049 break;
4050
4051 case PROP_TABS:
4052 gtk_text_view_set_tabs (text_view, tabs: g_value_get_boxed (value));
4053 break;
4054
4055 case PROP_CURSOR_VISIBLE:
4056 gtk_text_view_set_cursor_visible (text_view, setting: g_value_get_boolean (value));
4057 break;
4058
4059 case PROP_OVERWRITE:
4060 gtk_text_view_set_overwrite (text_view, overwrite: g_value_get_boolean (value));
4061 break;
4062
4063 case PROP_BUFFER:
4064 gtk_text_view_set_buffer (text_view, GTK_TEXT_BUFFER (g_value_get_object (value)));
4065 break;
4066
4067 case PROP_ACCEPTS_TAB:
4068 gtk_text_view_set_accepts_tab (text_view, accepts_tab: g_value_get_boolean (value));
4069 break;
4070
4071 case PROP_IM_MODULE:
4072 g_free (mem: priv->im_module);
4073 priv->im_module = g_value_dup_string (value);
4074 if (GTK_IS_IM_MULTICONTEXT (priv->im_context))
4075 gtk_im_multicontext_set_context_id (GTK_IM_MULTICONTEXT (priv->im_context), context_id: priv->im_module);
4076 break;
4077
4078 case PROP_HADJUSTMENT:
4079 gtk_text_view_set_hadjustment (text_view, adjustment: g_value_get_object (value));
4080 break;
4081
4082 case PROP_VADJUSTMENT:
4083 gtk_text_view_set_vadjustment (text_view, adjustment: g_value_get_object (value));
4084 break;
4085
4086 case PROP_HSCROLL_POLICY:
4087 if (priv->hscroll_policy != g_value_get_enum (value))
4088 {
4089 priv->hscroll_policy = g_value_get_enum (value);
4090 gtk_widget_queue_resize (GTK_WIDGET (text_view));
4091 g_object_notify_by_pspec (object, pspec);
4092 }
4093 break;
4094
4095 case PROP_VSCROLL_POLICY:
4096 if (priv->vscroll_policy != g_value_get_enum (value))
4097 {
4098 priv->vscroll_policy = g_value_get_enum (value);
4099 gtk_widget_queue_resize (GTK_WIDGET (text_view));
4100 g_object_notify_by_pspec (object, pspec);
4101 }
4102 break;
4103
4104 case PROP_INPUT_PURPOSE:
4105 gtk_text_view_set_input_purpose (text_view, purpose: g_value_get_enum (value));
4106 break;
4107
4108 case PROP_INPUT_HINTS:
4109 gtk_text_view_set_input_hints (text_view, hints: g_value_get_flags (value));
4110 break;
4111
4112 case PROP_MONOSPACE:
4113 gtk_text_view_set_monospace (text_view, monospace: g_value_get_boolean (value));
4114 break;
4115
4116 case PROP_EXTRA_MENU:
4117 gtk_text_view_set_extra_menu (text_view, model: g_value_get_object (value));
4118 break;
4119
4120 default:
4121 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
4122 break;
4123 }
4124}
4125
4126static void
4127gtk_text_view_get_property (GObject *object,
4128 guint prop_id,
4129 GValue *value,
4130 GParamSpec *pspec)
4131{
4132 GtkTextView *text_view;
4133 GtkTextViewPrivate *priv;
4134
4135 text_view = GTK_TEXT_VIEW (object);
4136 priv = text_view->priv;
4137
4138 switch (prop_id)
4139 {
4140 case PROP_PIXELS_ABOVE_LINES:
4141 g_value_set_int (value, v_int: priv->pixels_above_lines);
4142 break;
4143
4144 case PROP_PIXELS_BELOW_LINES:
4145 g_value_set_int (value, v_int: priv->pixels_below_lines);
4146 break;
4147
4148 case PROP_PIXELS_INSIDE_WRAP:
4149 g_value_set_int (value, v_int: priv->pixels_inside_wrap);
4150 break;
4151
4152 case PROP_EDITABLE:
4153 g_value_set_boolean (value, v_boolean: priv->editable);
4154 break;
4155
4156 case PROP_WRAP_MODE:
4157 g_value_set_enum (value, v_enum: priv->wrap_mode);
4158 break;
4159
4160 case PROP_JUSTIFICATION:
4161 g_value_set_enum (value, v_enum: priv->justify);
4162 break;
4163
4164 case PROP_LEFT_MARGIN:
4165 g_value_set_int (value, v_int: priv->left_margin);
4166 break;
4167
4168 case PROP_RIGHT_MARGIN:
4169 g_value_set_int (value, v_int: priv->right_margin);
4170 break;
4171
4172 case PROP_TOP_MARGIN:
4173 g_value_set_int (value, v_int: priv->top_margin);
4174 break;
4175
4176 case PROP_BOTTOM_MARGIN:
4177 g_value_set_int (value, v_int: priv->bottom_margin);
4178 break;
4179
4180 case PROP_INDENT:
4181 g_value_set_int (value, v_int: priv->indent);
4182 break;
4183
4184 case PROP_TABS:
4185 g_value_set_boxed (value, v_boxed: priv->tabs);
4186 break;
4187
4188 case PROP_CURSOR_VISIBLE:
4189 g_value_set_boolean (value, v_boolean: priv->cursor_visible);
4190 break;
4191
4192 case PROP_BUFFER:
4193 g_value_set_object (value, v_object: get_buffer (text_view));
4194 break;
4195
4196 case PROP_OVERWRITE:
4197 g_value_set_boolean (value, v_boolean: priv->overwrite_mode);
4198 break;
4199
4200 case PROP_ACCEPTS_TAB:
4201 g_value_set_boolean (value, v_boolean: priv->accepts_tab);
4202 break;
4203
4204 case PROP_IM_MODULE:
4205 g_value_set_string (value, v_string: priv->im_module);
4206 break;
4207
4208 case PROP_HADJUSTMENT:
4209 g_value_set_object (value, v_object: priv->hadjustment);
4210 break;
4211
4212 case PROP_VADJUSTMENT:
4213 g_value_set_object (value, v_object: priv->vadjustment);
4214 break;
4215
4216 case PROP_HSCROLL_POLICY:
4217 g_value_set_enum (value, v_enum: priv->hscroll_policy);
4218 break;
4219
4220 case PROP_VSCROLL_POLICY:
4221 g_value_set_enum (value, v_enum: priv->vscroll_policy);
4222 break;
4223
4224 case PROP_INPUT_PURPOSE:
4225 g_value_set_enum (value, v_enum: gtk_text_view_get_input_purpose (text_view));
4226 break;
4227
4228 case PROP_INPUT_HINTS:
4229 g_value_set_flags (value, v_flags: gtk_text_view_get_input_hints (text_view));
4230 break;
4231
4232 case PROP_MONOSPACE:
4233 g_value_set_boolean (value, v_boolean: gtk_text_view_get_monospace (text_view));
4234 break;
4235
4236 case PROP_EXTRA_MENU:
4237 g_value_set_object (value, v_object: gtk_text_view_get_extra_menu (text_view));
4238 break;
4239
4240 default:
4241 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
4242 break;
4243 }
4244}
4245
4246static void
4247gtk_text_view_measure_borders (GtkTextView *text_view,
4248 GtkBorder *border)
4249{
4250 GtkTextViewPrivate *priv = text_view->priv;
4251 int left = 0;
4252 int right = 0;
4253 int top = 0;
4254 int bottom = 0;
4255
4256 if (priv->left_child)
4257 gtk_widget_measure (GTK_WIDGET (priv->left_child),
4258 orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1,
4259 minimum: &left, NULL, NULL, NULL);
4260
4261 if (priv->right_child)
4262 gtk_widget_measure (GTK_WIDGET (priv->right_child),
4263 orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1,
4264 minimum: &right, NULL, NULL, NULL);
4265
4266 if (priv->top_child)
4267 gtk_widget_measure (GTK_WIDGET (priv->top_child),
4268 orientation: GTK_ORIENTATION_VERTICAL, for_size: -1,
4269 minimum: &top, NULL, NULL, NULL);
4270
4271 if (priv->bottom_child)
4272 gtk_widget_measure (GTK_WIDGET (priv->bottom_child),
4273 orientation: GTK_ORIENTATION_VERTICAL, for_size: -1,
4274 minimum: &bottom, NULL, NULL, NULL);
4275
4276 border->left = left;
4277 border->right = right;
4278 border->top = top;
4279 border->bottom = bottom;
4280}
4281
4282static void
4283gtk_text_view_measure (GtkWidget *widget,
4284 GtkOrientation orientation,
4285 int for_size,
4286 int *minimum,
4287 int *natural,
4288 int *minimum_baseline,
4289 int *natural_baseline)
4290{
4291 GtkTextView *text_view = GTK_TEXT_VIEW (widget);
4292 GtkTextViewPrivate *priv = text_view->priv;
4293 const GList *list;
4294 GtkBorder borders;
4295 int min = 0;
4296 int nat = 0;
4297 int extra;
4298
4299 gtk_text_view_measure_borders (text_view, border: &borders);
4300
4301 if (priv->center_child)
4302 gtk_widget_measure (GTK_WIDGET (priv->center_child),
4303 orientation, for_size,
4304 minimum: &min, natural: &nat, NULL, NULL);
4305
4306 for (list = priv->anchored_children.head; list; list = list->next)
4307 {
4308 const AnchoredChild *child = list->data;
4309 int child_min = 0;
4310 int child_nat = 0;
4311
4312 gtk_widget_measure (widget: child->widget, orientation, for_size,
4313 minimum: &child_min, natural: &child_nat,
4314 NULL, NULL);
4315
4316 /* Invalidate layout lines if required */
4317 if (child->anchor && priv->layout)
4318 gtk_text_child_anchor_queue_resize (anchor: child->anchor, layout: priv->layout);
4319
4320 min = MAX (min, child_min);
4321 nat = MAX (nat, child_nat);
4322 }
4323
4324 if (orientation == GTK_ORIENTATION_HORIZONTAL)
4325 extra = borders.left + priv->left_margin + priv->right_margin + borders.right;
4326 else
4327 extra = borders.top + priv->height + borders.bottom;
4328
4329 *minimum = min + extra;
4330 *natural = nat + extra;
4331}
4332
4333static void
4334gtk_text_view_compute_child_allocation (GtkTextView *text_view,
4335 const AnchoredChild *vc,
4336 GtkAllocation *allocation)
4337{
4338 int buffer_y;
4339 GtkTextIter iter;
4340 GtkRequisition req;
4341
4342 gtk_text_buffer_get_iter_at_child_anchor (buffer: get_buffer (text_view),
4343 iter: &iter,
4344 anchor: vc->anchor);
4345
4346 gtk_text_layout_get_line_yrange (layout: text_view->priv->layout, iter: &iter,
4347 y: &buffer_y, NULL);
4348
4349 buffer_y += vc->from_top_of_line;
4350
4351 allocation->x = vc->from_left_of_buffer - text_view->priv->xoffset;
4352 allocation->y = buffer_y - text_view->priv->yoffset;
4353
4354 gtk_widget_get_preferred_size (widget: vc->widget, minimum_size: &req, NULL);
4355 allocation->width = req.width;
4356 allocation->height = req.height;
4357}
4358
4359static void
4360gtk_text_view_update_child_allocation (GtkTextView *text_view,
4361 const AnchoredChild *vc)
4362{
4363 GtkAllocation allocation;
4364
4365 gtk_text_view_compute_child_allocation (text_view, vc, allocation: &allocation);
4366
4367 gtk_widget_size_allocate (widget: vc->widget, allocation: &allocation, baseline: -1);
4368
4369#if 0
4370 g_print ("allocation for %p allocated to %d,%d yoffset = %d\n",
4371 vc->widget,
4372 vc->widget->allocation.x,
4373 vc->widget->allocation.y,
4374 text_view->priv->yoffset);
4375#endif
4376}
4377
4378static void
4379gtk_anchored_child_allocated (GtkTextLayout *layout,
4380 GtkWidget *child,
4381 int x,
4382 int y,
4383 gpointer data)
4384{
4385 AnchoredChild *vc = NULL;
4386 GtkTextView *text_view = data;
4387
4388 /* x,y is the position of the child from the top of the line, and
4389 * from the left of the buffer. We have to translate that into text
4390 * window coordinates, then size_allocate the child.
4391 */
4392
4393 vc = g_object_get_qdata (G_OBJECT (child), quark: quark_text_view_child);
4394
4395 g_assert (vc != NULL);
4396
4397 DV (g_print ("child allocated at %d,%d\n", x, y));
4398
4399 vc->from_left_of_buffer = x;
4400 vc->from_top_of_line = y;
4401
4402 gtk_text_view_update_child_allocation (text_view, vc);
4403}
4404
4405static void
4406gtk_text_view_allocate_children (GtkTextView *text_view)
4407{
4408 GtkTextViewPrivate *priv = text_view->priv;
4409 const GList *iter;
4410
4411 DV(g_print(G_STRLOC"\n"));
4412
4413 for (iter = priv->anchored_children.head; iter; iter = iter->next)
4414 {
4415 const AnchoredChild *child = iter->data;
4416 GtkTextIter child_loc;
4417 GtkRequisition child_req;
4418 GtkAllocation allocation;
4419
4420 /* We need to force-validate the regions containing children. */
4421 gtk_text_buffer_get_iter_at_child_anchor (buffer: get_buffer (text_view),
4422 iter: &child_loc,
4423 anchor: child->anchor);
4424
4425 /* Since anchored children are only ever allocated from
4426 * gtk_text_layout_get_line_display() we have to make sure
4427 * that the display line caching in the layout doesn't
4428 * get in the way. Invalidating the layout around the anchor
4429 * achieves this.
4430 */
4431 if (_gtk_widget_get_alloc_needed (widget: child->widget))
4432 {
4433 GtkTextIter end = child_loc;
4434 gtk_text_iter_forward_char (iter: &end);
4435 gtk_text_layout_invalidate (layout: priv->layout, start: &child_loc, end: &end);
4436 }
4437
4438 gtk_text_layout_validate_yrange (layout: priv->layout, anchor_line: &child_loc, y0_: 0, y1_: 1);
4439
4440 gtk_widget_get_preferred_size (widget: child->widget, minimum_size: &child_req, NULL);
4441
4442 allocation.x = - child_req.width;
4443 allocation.y = - child_req.height;
4444 allocation.width = child_req.width;
4445 allocation.height = child_req.height;
4446
4447 gtk_widget_size_allocate (widget: child->widget, allocation: &allocation, baseline: -1);
4448 }
4449}
4450
4451static GtkTextViewChild **
4452find_child_for_window_type (GtkTextView *text_view,
4453 GtkTextWindowType window_type)
4454{
4455 switch (window_type)
4456 {
4457 case GTK_TEXT_WINDOW_LEFT:
4458 return &text_view->priv->left_child;
4459 case GTK_TEXT_WINDOW_RIGHT:
4460 return &text_view->priv->right_child;
4461 case GTK_TEXT_WINDOW_TOP:
4462 return &text_view->priv->top_child;
4463 case GTK_TEXT_WINDOW_BOTTOM:
4464 return &text_view->priv->bottom_child;
4465 case GTK_TEXT_WINDOW_TEXT:
4466 return &text_view->priv->center_child;
4467 case GTK_TEXT_WINDOW_WIDGET:
4468 default:
4469 return NULL;
4470 }
4471}
4472
4473/**
4474 * gtk_text_view_get_gutter:
4475 * @text_view: a `GtkTextView`
4476 * @win: a `GtkTextWindowType`
4477 *
4478 * Gets a `GtkWidget` that has previously been set as gutter.
4479 *
4480 * See [method@Gtk.TextView.set_gutter].
4481 *
4482 * @win must be one of %GTK_TEXT_WINDOW_LEFT, %GTK_TEXT_WINDOW_RIGHT,
4483 * %GTK_TEXT_WINDOW_TOP, or %GTK_TEXT_WINDOW_BOTTOM.
4484 *
4485 * Returns: (transfer none) (nullable): a `GtkWidget`
4486 */
4487GtkWidget *
4488gtk_text_view_get_gutter (GtkTextView *text_view,
4489 GtkTextWindowType win)
4490{
4491 GtkTextViewChild **childp;
4492
4493 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), NULL);
4494 g_return_val_if_fail (win == GTK_TEXT_WINDOW_LEFT ||
4495 win == GTK_TEXT_WINDOW_RIGHT ||
4496 win == GTK_TEXT_WINDOW_TOP ||
4497 win == GTK_TEXT_WINDOW_BOTTOM, NULL);
4498
4499 childp = find_child_for_window_type (text_view, window_type: win);
4500
4501 if (childp != NULL && *childp != NULL)
4502 return GTK_WIDGET (*childp);
4503
4504 return NULL;
4505}
4506
4507/**
4508 * gtk_text_view_set_gutter:
4509 * @text_view: a `GtkTextView`
4510 * @win: a `GtkTextWindowType`
4511 * @widget: (nullable): a `GtkWidget`
4512 *
4513 * Places @widget into the gutter specified by @win.
4514 *
4515 * @win must be one of %GTK_TEXT_WINDOW_LEFT, %GTK_TEXT_WINDOW_RIGHT,
4516 * %GTK_TEXT_WINDOW_TOP, or %GTK_TEXT_WINDOW_BOTTOM.
4517 */
4518void
4519gtk_text_view_set_gutter (GtkTextView *text_view,
4520 GtkTextWindowType win,
4521 GtkWidget *widget)
4522{
4523 GtkTextViewChild **childp;
4524 GtkTextViewChild *old_child;
4525 GtkTextViewChild *new_child;
4526
4527 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
4528 g_return_if_fail (widget == NULL || GTK_IS_WIDGET (widget));
4529 g_return_if_fail (win == GTK_TEXT_WINDOW_LEFT ||
4530 win == GTK_TEXT_WINDOW_RIGHT ||
4531 win == GTK_TEXT_WINDOW_TOP ||
4532 win == GTK_TEXT_WINDOW_BOTTOM);
4533
4534 childp = find_child_for_window_type (text_view, window_type: win);
4535 if (childp == NULL)
4536 return;
4537
4538 old_child = *childp;
4539
4540 if ((GtkWidget *)old_child == widget)
4541 return;
4542
4543 if (old_child != NULL)
4544 {
4545 *childp = NULL;
4546 gtk_widget_unparent (GTK_WIDGET (old_child));
4547 g_object_unref (object: old_child);
4548 }
4549
4550 if (widget == NULL)
4551 return;
4552
4553 new_child = GTK_TEXT_VIEW_CHILD (ptr: gtk_text_view_child_new (window_type: win));
4554 gtk_text_view_child_add (self: new_child, widget);
4555
4556 *childp = g_object_ref (new_child);
4557 gtk_widget_set_parent (GTK_WIDGET (new_child), GTK_WIDGET (text_view));
4558 update_node_ordering (GTK_WIDGET (text_view));
4559}
4560
4561static void
4562gtk_text_view_size_allocate (GtkWidget *widget,
4563 int widget_width,
4564 int widget_height,
4565 int baseline)
4566{
4567 GtkTextView *text_view;
4568 GtkTextViewPrivate *priv;
4569 int width, height;
4570 GdkRectangle text_rect;
4571 GdkRectangle left_rect;
4572 GdkRectangle right_rect;
4573 GdkRectangle top_rect;
4574 GdkRectangle bottom_rect;
4575 GtkWidget *chooser;
4576 PangoLayout *layout;
4577 guint mru_size;
4578
4579 text_view = GTK_TEXT_VIEW (widget);
4580 priv = text_view->priv;
4581
4582 DV(g_print(G_STRLOC"\n"));
4583
4584 gtk_text_view_measure_borders (text_view, border: &priv->border_window_size);
4585
4586 /* distribute width/height among child windows. Ensure all
4587 * windows get at least a 1x1 allocation.
4588 */
4589 left_rect.width = priv->border_window_size.left;
4590 right_rect.width = priv->border_window_size.right;
4591 width = widget_width - left_rect.width - right_rect.width;
4592 text_rect.width = MAX (1, width);
4593 top_rect.width = text_rect.width;
4594 bottom_rect.width = text_rect.width;
4595
4596 top_rect.height = priv->border_window_size.top;
4597 bottom_rect.height = priv->border_window_size.bottom;
4598 height = widget_height - top_rect.height - bottom_rect.height;
4599 text_rect.height = MAX (1, height);
4600 left_rect.height = text_rect.height;
4601 right_rect.height = text_rect.height;
4602
4603 /* Origins */
4604 left_rect.x = 0;
4605 top_rect.y = 0;
4606
4607 text_rect.x = left_rect.x + left_rect.width;
4608 text_rect.y = top_rect.y + top_rect.height;
4609
4610 left_rect.y = text_rect.y;
4611 right_rect.y = text_rect.y;
4612
4613 top_rect.x = text_rect.x;
4614 bottom_rect.x = text_rect.x;
4615
4616 right_rect.x = text_rect.x + text_rect.width;
4617 bottom_rect.y = text_rect.y + text_rect.height;
4618
4619 text_window_size_allocate (win: priv->text_window, rect: &text_rect);
4620
4621 if (priv->center_child)
4622 {
4623 gtk_text_view_child_set_offset (child: priv->center_child, xoffset: priv->xoffset, yoffset: priv->yoffset);
4624 gtk_widget_size_allocate (GTK_WIDGET (priv->center_child), allocation: &text_rect, baseline: -1);
4625 }
4626
4627 if (priv->left_child)
4628 {
4629 gtk_text_view_child_set_offset (child: priv->left_child, xoffset: priv->xoffset, yoffset: priv->yoffset);
4630 gtk_widget_size_allocate (GTK_WIDGET (priv->left_child), allocation: &left_rect, baseline: -1);
4631 }
4632
4633 if (priv->right_child)
4634 {
4635 gtk_text_view_child_set_offset (child: priv->right_child, xoffset: priv->xoffset, yoffset: priv->yoffset);
4636 gtk_widget_size_allocate (GTK_WIDGET (priv->right_child), allocation: &right_rect, baseline: -1);
4637 }
4638
4639 if (priv->top_child)
4640 {
4641 gtk_text_view_child_set_offset (child: priv->top_child, xoffset: priv->xoffset, yoffset: priv->yoffset);
4642 gtk_widget_size_allocate (GTK_WIDGET (priv->top_child), allocation: &top_rect, baseline: -1);
4643 }
4644
4645 if (priv->bottom_child)
4646 {
4647 gtk_text_view_child_set_offset (child: priv->bottom_child, xoffset: priv->xoffset, yoffset: priv->yoffset);
4648 gtk_widget_size_allocate (GTK_WIDGET (priv->bottom_child), allocation: &bottom_rect, baseline: -1);
4649 }
4650
4651 gtk_text_view_update_layout_width (text_view);
4652
4653 /* Note that this will do some layout validation */
4654 gtk_text_view_allocate_children (text_view);
4655
4656 /* Update adjustments */
4657 if (!gtk_adjustment_is_animating (adjustment: priv->hadjustment))
4658 gtk_text_view_set_hadjustment_values (text_view);
4659 if (!gtk_adjustment_is_animating (adjustment: priv->vadjustment))
4660 gtk_text_view_set_vadjustment_values (text_view);
4661
4662 /* Optimize display cache size */
4663 layout = gtk_widget_create_pango_layout (widget, text: "X");
4664 pango_layout_get_pixel_size (layout, width: &width, height: &height);
4665 if (height > 0)
4666 {
4667 mru_size = SCREEN_HEIGHT (widget) / height * 3;
4668 gtk_text_layout_set_mru_size (layout: priv->layout, mru_size);
4669 }
4670 g_object_unref (object: layout);
4671
4672 /* The GTK resize loop processes all the pending exposes right
4673 * after doing the resize stuff, so the idle sizer won't have a
4674 * chance to run. So we do the work here.
4675 */
4676 gtk_text_view_flush_first_validate (text_view);
4677
4678 chooser = g_object_get_data (G_OBJECT (text_view), key: "gtk-emoji-chooser");
4679 if (chooser)
4680 gtk_popover_present (GTK_POPOVER (chooser));
4681
4682 if (priv->magnifier_popover)
4683 gtk_popover_present (GTK_POPOVER (priv->magnifier_popover));
4684
4685 if (priv->popup_menu)
4686 gtk_popover_present (GTK_POPOVER (priv->popup_menu));
4687
4688 if (priv->text_handles[TEXT_HANDLE_CURSOR])
4689 gtk_text_handle_present (handle: priv->text_handles[TEXT_HANDLE_CURSOR]);
4690
4691 if (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])
4692 gtk_text_handle_present (handle: priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]);
4693
4694 if (priv->selection_bubble)
4695 gtk_popover_present (GTK_POPOVER (priv->selection_bubble));
4696}
4697
4698static void
4699gtk_text_view_get_first_para_iter (GtkTextView *text_view,
4700 GtkTextIter *iter)
4701{
4702 gtk_text_buffer_get_iter_at_mark (buffer: get_buffer (text_view), iter,
4703 mark: text_view->priv->first_para_mark);
4704}
4705
4706static void
4707gtk_text_view_validate_onscreen (GtkTextView *text_view)
4708{
4709 GtkWidget *widget;
4710 GtkTextViewPrivate *priv;
4711
4712 widget = GTK_WIDGET (text_view);
4713 priv = text_view->priv;
4714
4715 DV(g_print(">Validating onscreen ("G_STRLOC")\n"));
4716
4717 if (SCREEN_HEIGHT (widget) > 0)
4718 {
4719 GtkTextIter first_para;
4720
4721 /* Be sure we've validated the stuff onscreen; if we
4722 * scrolled, these calls won't have any effect, because
4723 * they were called in the recursive validate_onscreen
4724 */
4725 gtk_text_view_get_first_para_iter (text_view, iter: &first_para);
4726
4727 gtk_text_layout_validate_yrange (layout: priv->layout,
4728 anchor_line: &first_para,
4729 y0_: 0,
4730 y1_: priv->first_para_pixels +
4731 SCREEN_HEIGHT (widget));
4732 }
4733
4734 priv->onscreen_validated = TRUE;
4735
4736 DV(g_print(">Done validating onscreen, onscreen_validated = TRUE ("G_STRLOC")\n"));
4737
4738 /* This can have the odd side effect of triggering a scroll, which should
4739 * flip "onscreen_validated" back to FALSE, but should also get us
4740 * back into this function to turn it on again.
4741 */
4742 gtk_text_view_update_adjustments (text_view);
4743
4744 g_assert (priv->onscreen_validated);
4745}
4746
4747static void
4748gtk_text_view_flush_first_validate (GtkTextView *text_view)
4749{
4750 GtkTextViewPrivate *priv = text_view->priv;
4751
4752 if (priv->first_validate_idle == 0)
4753 return;
4754
4755 /* Do this first, which means that if an "invalidate"
4756 * occurs during any of this process, a new first_validate_callback
4757 * will be installed, and we'll start again.
4758 */
4759 DV (g_print ("removing first validate in %s\n", G_STRLOC));
4760 g_source_remove (tag: priv->first_validate_idle);
4761 priv->first_validate_idle = 0;
4762
4763 /* be sure we have up-to-date screen size set on the
4764 * layout.
4765 */
4766 gtk_text_view_update_layout_width (text_view);
4767
4768 /* Bail out if we invalidated stuff; scrolling right away will just
4769 * confuse the issue.
4770 */
4771 if (priv->first_validate_idle != 0)
4772 {
4773 DV(g_print(">Width change forced requeue ("G_STRLOC")\n"));
4774 }
4775 else
4776 {
4777 /* scroll to any marks, if that's pending. This can jump us to
4778 * the validation codepath used for scrolling onscreen, if so we
4779 * bail out. It won't jump if already in that codepath since
4780 * value_changed is not recursive, so also validate if
4781 * necessary.
4782 */
4783 if (!gtk_text_view_flush_scroll (text_view) ||
4784 !priv->onscreen_validated)
4785 gtk_text_view_validate_onscreen (text_view);
4786
4787 DV(g_print(">Leaving first validate idle ("G_STRLOC")\n"));
4788
4789 g_assert (priv->onscreen_validated);
4790 }
4791}
4792
4793static gboolean
4794first_validate_callback (gpointer data)
4795{
4796 GtkTextView *text_view = data;
4797
4798 /* Note that some of this code is duplicated at the end of size_allocate,
4799 * keep in sync with that.
4800 */
4801
4802 DV(g_print(G_STRLOC"\n"));
4803
4804 gtk_text_view_flush_first_validate (text_view);
4805
4806 return FALSE;
4807}
4808
4809static gboolean
4810incremental_validate_callback (gpointer data)
4811{
4812 GtkTextView *text_view = data;
4813 gboolean result = TRUE;
4814
4815 DV(g_print(G_STRLOC"\n"));
4816
4817 gtk_text_layout_validate (layout: text_view->priv->layout, max_pixels: 2000);
4818
4819 gtk_text_view_update_adjustments (text_view);
4820
4821 if (gtk_text_layout_is_valid (layout: text_view->priv->layout))
4822 {
4823 text_view->priv->incremental_validate_idle = 0;
4824 result = FALSE;
4825 }
4826
4827 return result;
4828}
4829
4830static void
4831gtk_text_view_invalidate (GtkTextView *text_view)
4832{
4833 GtkTextViewPrivate *priv = text_view->priv;
4834
4835 DV (g_print (">Invalidate, onscreen_validated = %d now FALSE ("G_STRLOC")\n",
4836 priv->onscreen_validated));
4837
4838 priv->onscreen_validated = FALSE;
4839
4840 /* We'll invalidate when the layout is created */
4841 if (priv->layout == NULL)
4842 return;
4843
4844 if (!priv->first_validate_idle)
4845 {
4846 priv->first_validate_idle = g_idle_add_full (GTK_PRIORITY_RESIZE - 2, function: first_validate_callback, data: text_view, NULL);
4847 gdk_source_set_static_name_by_id (tag: priv->first_validate_idle, name: "[gtk] first_validate_callback");
4848 DV (g_print (G_STRLOC": adding first validate idle %d\n",
4849 priv->first_validate_idle));
4850 }
4851
4852 if (!priv->incremental_validate_idle)
4853 {
4854 priv->incremental_validate_idle = g_idle_add_full (GTK_TEXT_VIEW_PRIORITY_VALIDATE, function: incremental_validate_callback, data: text_view, NULL);
4855 gdk_source_set_static_name_by_id (tag: priv->incremental_validate_idle, name: "[gtk] incremental_validate_callback");
4856 DV (g_print (G_STRLOC": adding incremental validate idle %d\n",
4857 priv->incremental_validate_idle));
4858 }
4859}
4860
4861static void
4862invalidated_handler (GtkTextLayout *layout,
4863 gpointer data)
4864{
4865 GtkTextView *text_view;
4866
4867 text_view = GTK_TEXT_VIEW (data);
4868
4869 DV (g_print ("Invalidating due to layout invalidate signal\n"));
4870 gtk_text_view_invalidate (text_view);
4871}
4872
4873static void
4874changed_handler (GtkTextLayout *layout,
4875 int start_y,
4876 int old_height,
4877 int new_height,
4878 gpointer data)
4879{
4880 GtkTextView *text_view;
4881 GtkTextViewPrivate *priv;
4882 GtkWidget *widget;
4883
4884 text_view = GTK_TEXT_VIEW (data);
4885 priv = text_view->priv;
4886 widget = GTK_WIDGET (data);
4887
4888 DV(g_print(">Lines Validated ("G_STRLOC")\n"));
4889
4890 if (gtk_widget_get_realized (widget))
4891 {
4892 gtk_widget_queue_draw (widget);
4893
4894 queue_update_im_spot_location (text_view);
4895 }
4896
4897 if (old_height != new_height)
4898 {
4899 const GList *iter;
4900 GtkTextIter first;
4901 int new_first_para_top;
4902 int old_first_para_top;
4903
4904 /* If the bottom of the old area was above the top of the
4905 * screen, we need to scroll to keep the current top of the
4906 * screen in place. Remember that first_para_pixels is the
4907 * position of the top of the screen in coordinates relative to
4908 * the first paragraph onscreen.
4909 *
4910 * In short we are adding the height delta of the portion of the
4911 * changed region above first_para_mark to priv->yoffset.
4912 */
4913 gtk_text_buffer_get_iter_at_mark (buffer: get_buffer (text_view), iter: &first,
4914 mark: priv->first_para_mark);
4915
4916 gtk_text_layout_get_line_yrange (layout, iter: &first, y: &new_first_para_top, NULL);
4917
4918 old_first_para_top = priv->yoffset - priv->first_para_pixels + priv->top_margin;
4919
4920 if (new_first_para_top != old_first_para_top)
4921 {
4922 priv->yoffset += new_first_para_top - old_first_para_top;
4923
4924 gtk_adjustment_set_value (adjustment: text_view->priv->vadjustment, value: priv->yoffset);
4925 }
4926
4927 /* FIXME be smarter about which anchored widgets we update */
4928
4929 for (iter = priv->anchored_children.head; iter; iter = iter->next)
4930 {
4931 const AnchoredChild *ac = iter->data;
4932 gtk_text_view_update_child_allocation (text_view, vc: ac);
4933 }
4934
4935 gtk_widget_queue_resize (widget);
4936 }
4937}
4938
4939static void
4940gtk_text_view_realize (GtkWidget *widget)
4941{
4942 GtkTextView *text_view;
4943 GtkTextViewPrivate *priv;
4944
4945 text_view = GTK_TEXT_VIEW (widget);
4946 priv = text_view->priv;
4947
4948 GTK_WIDGET_CLASS (gtk_text_view_parent_class)->realize (widget);
4949
4950 if (gtk_widget_is_sensitive (widget))
4951 {
4952 gtk_im_context_set_client_widget (GTK_TEXT_VIEW (widget)->priv->im_context,
4953 widget);
4954 }
4955
4956 gtk_text_view_ensure_layout (text_view);
4957 gtk_text_view_invalidate (text_view);
4958
4959 if (priv->buffer)
4960 {
4961 GdkClipboard *clipboard = gtk_widget_get_primary_clipboard (GTK_WIDGET (text_view));
4962 gtk_text_buffer_add_selection_clipboard (buffer: priv->buffer, clipboard);
4963 }
4964
4965 /* Ensure updating the spot location. */
4966 gtk_text_view_update_im_spot_location (text_view);
4967}
4968
4969static void
4970gtk_text_view_unrealize (GtkWidget *widget)
4971{
4972 GtkTextView *text_view;
4973 GtkTextViewPrivate *priv;
4974
4975 text_view = GTK_TEXT_VIEW (widget);
4976 priv = text_view->priv;
4977
4978 if (priv->buffer)
4979 {
4980 GdkClipboard *clipboard = gtk_widget_get_primary_clipboard (GTK_WIDGET (text_view));
4981 gtk_text_buffer_remove_selection_clipboard (buffer: priv->buffer, clipboard);
4982 }
4983
4984 gtk_text_view_remove_validate_idles (text_view);
4985
4986 g_clear_pointer (&priv->popup_menu, gtk_widget_unparent);
4987
4988 gtk_im_context_set_client_widget (context: priv->im_context, NULL);
4989
4990 GTK_WIDGET_CLASS (gtk_text_view_parent_class)->unrealize (widget);
4991}
4992
4993static void
4994gtk_text_view_map (GtkWidget *widget)
4995{
4996 gtk_widget_set_cursor_from_name (widget, name: "text");
4997
4998 GTK_WIDGET_CLASS (gtk_text_view_parent_class)->map (widget);
4999}
5000
5001static void
5002gtk_text_view_css_changed (GtkWidget *widget,
5003 GtkCssStyleChange *change)
5004{
5005 GtkTextView *text_view;
5006 GtkTextViewPrivate *priv;
5007
5008 text_view = GTK_TEXT_VIEW (widget);
5009 priv = text_view->priv;
5010
5011 GTK_WIDGET_CLASS (gtk_text_view_parent_class)->css_changed (widget, change);
5012
5013 if ((change == NULL ||
5014 gtk_css_style_change_affects (change, GTK_CSS_AFFECTS_TEXT |
5015 GTK_CSS_AFFECTS_TEXT_ATTRS |
5016 GTK_CSS_AFFECTS_BACKGROUND |
5017 GTK_CSS_AFFECTS_CONTENT)) &&
5018 priv->layout && priv->layout->default_style)
5019 {
5020 gtk_text_view_set_attributes_from_style (text_view,
5021 values: priv->layout->default_style);
5022 gtk_text_layout_default_style_changed (layout: priv->layout);
5023 }
5024
5025 if ((change == NULL ||
5026 gtk_css_style_change_affects (change, GTK_CSS_AFFECTS_TEXT)) &&
5027 priv->layout)
5028 {
5029 gtk_text_view_update_pango_contexts (text_view);
5030 }
5031}
5032
5033static void
5034gtk_text_view_direction_changed (GtkWidget *widget,
5035 GtkTextDirection previous_direction)
5036{
5037 GtkTextViewPrivate *priv = GTK_TEXT_VIEW (widget)->priv;
5038
5039 if (priv->layout && priv->layout->default_style)
5040 {
5041 priv->layout->default_style->direction = gtk_widget_get_direction (widget);
5042
5043 gtk_text_layout_default_style_changed (layout: priv->layout);
5044 }
5045}
5046
5047static void
5048gtk_text_view_update_pango_contexts (GtkTextView *text_view)
5049{
5050 GtkWidget *widget = GTK_WIDGET (text_view);
5051 GtkTextViewPrivate *priv = text_view->priv;
5052 gboolean update_ltr, update_rtl;
5053
5054 if (!priv->layout)
5055 return;
5056
5057 update_ltr = gtk_widget_update_pango_context (widget, context: priv->layout->ltr_context, direction: GTK_TEXT_DIR_LTR);
5058
5059 update_rtl = gtk_widget_update_pango_context (widget, context: priv->layout->rtl_context, direction: GTK_TEXT_DIR_RTL);
5060
5061 if (update_ltr || update_rtl)
5062 {
5063 GtkTextIter start, end;
5064
5065 gtk_text_buffer_get_bounds (buffer: get_buffer (text_view), start: &start, end: &end);
5066 gtk_text_layout_invalidate (layout: priv->layout, start: &start, end: &end);
5067 gtk_widget_queue_draw (widget);
5068 }
5069}
5070
5071static void
5072gtk_text_view_system_setting_changed (GtkWidget *widget,
5073 GtkSystemSetting setting)
5074{
5075 if (setting == GTK_SYSTEM_SETTING_DPI ||
5076 setting == GTK_SYSTEM_SETTING_FONT_NAME ||
5077 setting == GTK_SYSTEM_SETTING_FONT_CONFIG)
5078 {
5079 gtk_text_view_update_pango_contexts (GTK_TEXT_VIEW (widget));
5080 }
5081}
5082
5083static void
5084gtk_text_view_state_flags_changed (GtkWidget *widget,
5085 GtkStateFlags previous_state)
5086{
5087 GtkTextView *text_view = GTK_TEXT_VIEW (widget);
5088 GtkTextViewPrivate *priv = text_view->priv;
5089 GtkStateFlags state;
5090
5091 if (!gtk_widget_is_sensitive (widget))
5092 {
5093 /* Clear any selection */
5094 gtk_text_view_unselect (text_view);
5095 }
5096
5097 state = gtk_widget_get_state_flags (widget);
5098 gtk_css_node_set_state (cssnode: priv->text_window->css_node, state_flags: state);
5099
5100 state &= ~GTK_STATE_FLAG_DROP_ACTIVE;
5101
5102 gtk_css_node_set_state (cssnode: priv->selection_node, state_flags: state);
5103
5104 if (priv->layout)
5105 gtk_text_layout_invalidate_selection (layout: priv->layout);
5106
5107 gtk_widget_queue_draw (widget);
5108}
5109
5110static void
5111gtk_text_view_obscure_mouse_cursor (GtkTextView *text_view)
5112{
5113 GdkDisplay *display;
5114 GdkSeat *seat;
5115 GdkDevice *device;
5116
5117 if (text_view->priv->mouse_cursor_obscured)
5118 return;
5119
5120 gtk_widget_set_cursor_from_name (GTK_WIDGET (text_view), name: "none");
5121
5122 display = gtk_widget_get_display (GTK_WIDGET (text_view));
5123 seat = gdk_display_get_default_seat (display);
5124 device = gdk_seat_get_pointer (seat);
5125
5126 text_view->priv->obscured_cursor_timestamp = gdk_device_get_timestamp (device);
5127 text_view->priv->mouse_cursor_obscured = TRUE;
5128}
5129
5130static void
5131gtk_text_view_unobscure_mouse_cursor (GtkTextView *text_view)
5132{
5133 GdkDisplay *display;
5134 GdkSeat *seat;
5135 GdkDevice *device;
5136
5137 display = gtk_widget_get_display (GTK_WIDGET (text_view));
5138 seat = gdk_display_get_default_seat (display);
5139 device = gdk_seat_get_pointer (seat);
5140
5141 if (text_view->priv->mouse_cursor_obscured &&
5142 gdk_device_get_timestamp (device) != text_view->priv->obscured_cursor_timestamp)
5143 {
5144 gtk_widget_set_cursor_from_name (GTK_WIDGET (text_view), name: "text");
5145 text_view->priv->mouse_cursor_obscured = FALSE;
5146 }
5147}
5148
5149/*
5150 * Events
5151 */
5152
5153static void
5154_text_window_to_widget_coords (GtkTextView *text_view,
5155 int *x,
5156 int *y)
5157{
5158 GtkTextViewPrivate *priv = text_view->priv;
5159
5160 (*x) += priv->border_window_size.left;
5161 (*y) += priv->border_window_size.top;
5162}
5163
5164static void
5165_widget_to_text_surface_coords (GtkTextView *text_view,
5166 int *x,
5167 int *y)
5168{
5169 GtkTextViewPrivate *priv = text_view->priv;
5170
5171 (*x) -= priv->border_window_size.left;
5172 (*y) -= priv->border_window_size.top;
5173}
5174
5175static void
5176gtk_text_view_set_handle_position (GtkTextView *text_view,
5177 GtkTextHandle *handle,
5178 GtkTextIter *iter)
5179{
5180 GtkTextViewPrivate *priv;
5181 GdkRectangle rect;
5182 int x, y;
5183
5184 priv = text_view->priv;
5185 gtk_text_view_get_cursor_locations (text_view, iter, strong: &rect, NULL);
5186
5187 x = rect.x - priv->xoffset;
5188 y = rect.y - priv->yoffset;
5189
5190 if (!gtk_text_handle_get_is_dragged (handle) &&
5191 (x < 0 || x > SCREEN_WIDTH (text_view) ||
5192 y < 0 || y > SCREEN_HEIGHT (text_view)))
5193 {
5194 /* Hide the handle if it's not being manipulated
5195 * and fell outside of the visible text area.
5196 */
5197 gtk_widget_hide (GTK_WIDGET (handle));
5198 }
5199 else
5200 {
5201 GtkTextDirection dir = GTK_TEXT_DIR_LTR;
5202 GtkTextAttributes attributes = { 0 };
5203
5204 gtk_widget_show (GTK_WIDGET (handle));
5205
5206 rect.x = CLAMP (x, 0, SCREEN_WIDTH (text_view));
5207 rect.y = CLAMP (y, 0, SCREEN_HEIGHT (text_view));
5208 _text_window_to_widget_coords (text_view, x: &rect.x, y: &rect.y);
5209
5210 gtk_text_handle_set_position (handle, rect: &rect);
5211
5212 if (gtk_text_iter_get_attributes (iter, values: &attributes))
5213 dir = attributes.direction;
5214
5215 gtk_widget_set_direction (GTK_WIDGET (handle), dir);
5216 }
5217}
5218
5219static void
5220gtk_text_view_show_magnifier (GtkTextView *text_view,
5221 GtkTextIter *iter,
5222 int x,
5223 int y)
5224{
5225 cairo_rectangle_int_t rect;
5226 GtkTextViewPrivate *priv;
5227 GtkAllocation allocation;
5228 GtkRequisition req;
5229
5230#define N_LINES 1
5231
5232 gtk_widget_get_allocation (GTK_WIDGET (text_view), allocation: &allocation);
5233
5234 priv = text_view->priv;
5235 _gtk_text_view_ensure_magnifier (text_view);
5236
5237 /* Set size/content depending on iter rect */
5238 gtk_text_view_get_iter_location (text_view, iter,
5239 location: (GdkRectangle *) &rect);
5240 rect.x = x + priv->xoffset;
5241 gtk_text_view_buffer_to_window_coords (text_view, win: GTK_TEXT_WINDOW_TEXT,
5242 buffer_x: rect.x, buffer_y: rect.y, window_x: &rect.x, window_y: &rect.y);
5243 _text_window_to_widget_coords (text_view, x: &rect.x, y: &rect.y);
5244 req.height = rect.height * N_LINES *
5245 _gtk_magnifier_get_magnification (GTK_MAGNIFIER (priv->magnifier));
5246 req.width = MAX ((req.height * 4) / 3, 80);
5247 gtk_widget_set_size_request (widget: priv->magnifier, width: req.width, height: req.height);
5248
5249 _gtk_magnifier_set_coords (GTK_MAGNIFIER (priv->magnifier),
5250 x: rect.x, y: rect.y + rect.height / 2);
5251
5252 rect.x = CLAMP (rect.x, 0, allocation.width);
5253 rect.y += rect.height / 4;
5254 rect.height -= rect.height / 4;
5255 gtk_popover_set_pointing_to (GTK_POPOVER (priv->magnifier_popover),
5256 rect: &rect);
5257
5258 gtk_popover_popup (GTK_POPOVER (priv->magnifier_popover));
5259
5260#undef N_LINES
5261}
5262
5263static void
5264gtk_text_view_handle_dragged (GtkTextHandle *handle,
5265 int x,
5266 int y,
5267 GtkTextView *text_view)
5268{
5269 GtkTextViewPrivate *priv;
5270 GtkTextIter cursor, bound, iter, *old_iter;
5271 GtkTextBuffer *buffer;
5272
5273 priv = text_view->priv;
5274 buffer = get_buffer (text_view);
5275
5276 _widget_to_text_surface_coords (text_view, x: &x, y: &y);
5277
5278 gtk_text_view_selection_bubble_popup_unset (text_view);
5279 gtk_text_layout_get_iter_at_pixel (layout: priv->layout, iter: &iter,
5280 x: x + priv->xoffset,
5281 y: y + priv->yoffset);
5282
5283 gtk_text_buffer_get_iter_at_mark (buffer, iter: &cursor,
5284 mark: gtk_text_buffer_get_insert (buffer));
5285 gtk_text_buffer_get_iter_at_mark (buffer, iter: &bound,
5286 mark: gtk_text_buffer_get_selection_bound (buffer));
5287
5288
5289 if (handle == priv->text_handles[TEXT_HANDLE_CURSOR])
5290 {
5291 /* Avoid running past the other handle in selection mode */
5292 if (gtk_text_iter_compare (lhs: &iter, rhs: &bound) >= 0 &&
5293 gtk_widget_is_visible (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])))
5294 {
5295 iter = bound;
5296 gtk_text_iter_backward_char (iter: &iter);
5297 }
5298
5299 old_iter = &cursor;
5300 gtk_text_view_set_handle_position (text_view, handle, iter: &iter);
5301 }
5302 else if (handle == priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])
5303 {
5304 /* Avoid running past the other handle */
5305 if (gtk_text_iter_compare (lhs: &iter, rhs: &cursor) <= 0)
5306 {
5307 iter = cursor;
5308 gtk_text_iter_forward_char (iter: &iter);
5309 }
5310
5311 old_iter = &bound;
5312 gtk_text_view_set_handle_position (text_view, handle, iter: &iter);
5313 }
5314 else
5315 g_assert_not_reached ();
5316
5317 if (gtk_text_iter_compare (lhs: &iter, rhs: old_iter) != 0)
5318 {
5319 *old_iter = iter;
5320
5321 if (handle == priv->text_handles[TEXT_HANDLE_CURSOR] &&
5322 gtk_text_handle_get_role (handle: priv->text_handles[TEXT_HANDLE_CURSOR]) == GTK_TEXT_HANDLE_ROLE_CURSOR)
5323 gtk_text_buffer_place_cursor (buffer, where: &cursor);
5324 else
5325 gtk_text_buffer_select_range (buffer, ins: &cursor, bound: &bound);
5326
5327 if (handle == priv->text_handles[TEXT_HANDLE_CURSOR])
5328 {
5329 text_view->priv->cursor_handle_dragged = TRUE;
5330 gtk_text_view_scroll_mark_onscreen (text_view,
5331 mark: gtk_text_buffer_get_insert (buffer));
5332 }
5333 else if (handle == priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])
5334 {
5335 text_view->priv->selection_handle_dragged = TRUE;
5336 gtk_text_view_scroll_mark_onscreen (text_view,
5337 mark: gtk_text_buffer_get_selection_bound (buffer));
5338 }
5339
5340 gtk_text_view_update_handles (text_view);
5341 }
5342
5343 gtk_text_view_show_magnifier (text_view, iter: &iter, x, y);
5344}
5345
5346static void
5347gtk_text_view_handle_drag_started (GtkTextHandle *handle,
5348 GtkTextView *text_view)
5349{
5350 text_view->priv->cursor_handle_dragged = FALSE;
5351 text_view->priv->selection_handle_dragged = FALSE;
5352}
5353
5354static void
5355gtk_text_view_handle_drag_finished (GtkTextHandle *handle,
5356 GtkTextView *text_view)
5357{
5358 GtkTextViewPrivate *priv = text_view->priv;
5359
5360 if (!priv->cursor_handle_dragged && !priv->selection_handle_dragged)
5361 {
5362 GtkTextBuffer *buffer;
5363 GtkTextIter cursor, start, end;
5364 GtkSettings *settings;
5365 guint double_click_time;
5366
5367 settings = gtk_widget_get_settings (GTK_WIDGET (text_view));
5368 g_object_get (object: settings, first_property_name: "gtk-double-click-time", &double_click_time, NULL);
5369 if (g_get_monotonic_time() - priv->handle_place_time < double_click_time * 1000)
5370 {
5371 buffer = get_buffer (text_view);
5372 gtk_text_buffer_get_iter_at_mark (buffer, iter: &cursor,
5373 mark: gtk_text_buffer_get_insert (buffer));
5374 extend_selection (text_view, granularity: SELECT_WORDS, location: &cursor, start: &start, end: &end);
5375 gtk_text_buffer_select_range (buffer, ins: &start, bound: &end);
5376
5377 gtk_text_view_update_handles (text_view);
5378 }
5379 else
5380 gtk_text_view_selection_bubble_popup_set (text_view);
5381 }
5382
5383 if (priv->magnifier_popover)
5384 gtk_popover_popdown (GTK_POPOVER (priv->magnifier_popover));
5385}
5386
5387static gboolean cursor_visible (GtkTextView *text_view);
5388
5389static void
5390gtk_text_view_update_handles (GtkTextView *text_view)
5391{
5392 GtkTextViewPrivate *priv = text_view->priv;
5393 GtkTextIter cursor, bound;
5394 GtkTextBuffer *buffer;
5395
5396 if (!priv->text_handles_enabled)
5397 {
5398 if (priv->text_handles[TEXT_HANDLE_CURSOR])
5399 gtk_widget_hide (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_CURSOR]));
5400 if (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])
5401 gtk_widget_hide (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]));
5402 }
5403 else
5404 {
5405 _gtk_text_view_ensure_text_handles (text_view);
5406 buffer = get_buffer (text_view);
5407
5408 gtk_text_buffer_get_iter_at_mark (buffer, iter: &cursor,
5409 mark: gtk_text_buffer_get_insert (buffer));
5410 gtk_text_buffer_get_iter_at_mark (buffer, iter: &bound,
5411 mark: gtk_text_buffer_get_selection_bound (buffer));
5412
5413 if (gtk_text_iter_compare (lhs: &cursor, rhs: &bound) == 0 && priv->editable)
5414 {
5415 /* Cursor mode */
5416 gtk_widget_hide (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]));
5417
5418 gtk_text_view_set_handle_position (text_view,
5419 handle: priv->text_handles[TEXT_HANDLE_CURSOR],
5420 iter: &cursor);
5421 gtk_text_handle_set_role (handle: priv->text_handles[TEXT_HANDLE_CURSOR],
5422 role: GTK_TEXT_HANDLE_ROLE_CURSOR);
5423 }
5424 else if (gtk_text_iter_compare (lhs: &cursor, rhs: &bound) != 0)
5425 {
5426 /* Selection mode */
5427 gtk_text_view_set_handle_position (text_view,
5428 handle: priv->text_handles[TEXT_HANDLE_CURSOR],
5429 iter: &cursor);
5430 gtk_text_handle_set_role (handle: priv->text_handles[TEXT_HANDLE_CURSOR],
5431 role: GTK_TEXT_HANDLE_ROLE_SELECTION_START);
5432
5433 gtk_text_view_set_handle_position (text_view,
5434 handle: priv->text_handles[TEXT_HANDLE_SELECTION_BOUND],
5435 iter: &bound);
5436 gtk_text_handle_set_role (handle: priv->text_handles[TEXT_HANDLE_SELECTION_BOUND],
5437 role: GTK_TEXT_HANDLE_ROLE_SELECTION_END);
5438 }
5439 else
5440 {
5441 gtk_widget_hide (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_CURSOR]));
5442 gtk_widget_hide (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]));
5443 }
5444 }
5445}
5446
5447static gboolean
5448gtk_text_view_key_controller_key_pressed (GtkEventControllerKey *controller,
5449 guint keyval,
5450 guint keycode,
5451 GdkModifierType state,
5452 GtkTextView *text_view)
5453{
5454 GtkTextViewPrivate *priv;
5455 gboolean retval = FALSE;
5456
5457 priv = text_view->priv;
5458
5459 if (priv->layout == NULL || get_buffer (text_view) == NULL)
5460 return FALSE;
5461
5462 /* Make sure input method knows where it is */
5463 flush_update_im_spot_location (text_view);
5464
5465 /* use overall editability not can_insert, more predictable for users */
5466
5467 if (priv->editable &&
5468 (keyval == GDK_KEY_Return ||
5469 keyval == GDK_KEY_ISO_Enter ||
5470 keyval == GDK_KEY_KP_Enter))
5471 {
5472 /* this won't actually insert the newline if the cursor isn't
5473 * editable
5474 */
5475 gtk_text_view_reset_im_context (text_view);
5476 gtk_text_view_commit_text (text_view, text: "\n");
5477 retval = TRUE;
5478 }
5479 /* Pass through Tab as literal tab, unless Control is held down */
5480 else if ((keyval == GDK_KEY_Tab ||
5481 keyval == GDK_KEY_KP_Tab ||
5482 keyval == GDK_KEY_ISO_Left_Tab) &&
5483 !(state & GDK_CONTROL_MASK))
5484 {
5485 /* If the text widget isn't editable overall, or if the application
5486 * has turned off "accepts_tab", move the focus instead
5487 */
5488 if (priv->accepts_tab && priv->editable)
5489 {
5490 gtk_text_view_reset_im_context (text_view);
5491 gtk_text_view_commit_text (text_view, text: "\t");
5492 }
5493 else
5494 g_signal_emit_by_name (instance: text_view, detailed_signal: "move-focus",
5495 (state & GDK_SHIFT_MASK) ?
5496 GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD);
5497
5498 retval = TRUE;
5499 }
5500 else
5501 retval = FALSE;
5502
5503 gtk_text_view_reset_blink_time (text_view);
5504 gtk_text_view_pend_cursor_blink (text_view);
5505
5506 text_view->priv->text_handles_enabled = FALSE;
5507 gtk_text_view_update_handles (text_view);
5508
5509 gtk_text_view_selection_bubble_popup_unset (text_view);
5510
5511 return retval;
5512}
5513
5514static void
5515gtk_text_view_key_controller_im_update (GtkEventControllerKey *controller,
5516 GtkTextView *text_view)
5517{
5518 GtkTextViewPrivate *priv = text_view->priv;
5519
5520 priv->need_im_reset = TRUE;
5521}
5522
5523static gboolean
5524get_iter_from_gesture (GtkTextView *text_view,
5525 GtkGesture *gesture,
5526 GtkTextIter *iter,
5527 int *x,
5528 int *y)
5529{
5530 GdkEventSequence *sequence;
5531 GtkTextViewPrivate *priv;
5532 int xcoord, ycoord;
5533 double px, py;
5534
5535 priv = text_view->priv;
5536 sequence =
5537 gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
5538
5539 if (!gtk_gesture_get_point (gesture, sequence, x: &px, y: &py))
5540 return FALSE;
5541
5542 xcoord = px + priv->xoffset;
5543 ycoord = py + priv->yoffset;
5544 _widget_to_text_surface_coords (text_view, x: &xcoord, y: &ycoord);
5545 gtk_text_layout_get_iter_at_pixel (layout: priv->layout, iter, x: xcoord, y: ycoord);
5546
5547 if (x)
5548 *x = xcoord;
5549 if (y)
5550 *y = ycoord;
5551
5552 return TRUE;
5553}
5554
5555static void
5556gtk_text_view_click_gesture_pressed (GtkGestureClick *gesture,
5557 int n_press,
5558 double x,
5559 double y,
5560 GtkTextView *text_view)
5561{
5562 GdkEventSequence *sequence;
5563 GtkTextViewPrivate *priv;
5564 GdkEvent *event;
5565 gboolean is_touchscreen;
5566 GdkDevice *device;
5567 GtkTextIter iter;
5568 guint button;
5569
5570 priv = text_view->priv;
5571 sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
5572 button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
5573 event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
5574
5575 gtk_widget_grab_focus (GTK_WIDGET (text_view));
5576
5577 gtk_text_view_reset_blink_time (text_view);
5578
5579 device = gdk_event_get_device (event: (GdkEvent *) event);
5580 is_touchscreen = gtk_simulate_touchscreen () ||
5581 gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN;
5582
5583 if (n_press == 1)
5584 {
5585 /* Always emit reset when preedit is shown */
5586 priv->need_im_reset = TRUE;
5587 gtk_text_view_reset_im_context (text_view);
5588 }
5589
5590 if (n_press == 1 &&
5591 gdk_event_triggers_context_menu (event))
5592 {
5593 gtk_gesture_set_sequence_state (GTK_GESTURE (gesture), sequence,
5594 state: GTK_EVENT_SEQUENCE_CLAIMED);
5595 gtk_text_view_do_popup (text_view, event);
5596 }
5597 else if (button == GDK_BUTTON_MIDDLE &&
5598 get_middle_click_paste (text_view))
5599 {
5600 gtk_gesture_set_sequence_state (GTK_GESTURE (gesture), sequence,
5601 state: GTK_EVENT_SEQUENCE_CLAIMED);
5602 get_iter_from_gesture (text_view, GTK_GESTURE (gesture),
5603 iter: &iter, NULL, NULL);
5604 gtk_text_buffer_paste_clipboard (buffer: get_buffer (text_view),
5605 clipboard: gtk_widget_get_primary_clipboard (GTK_WIDGET (text_view)),
5606 override_location: &iter,
5607 default_editable: priv->editable);
5608 }
5609 else if (button == GDK_BUTTON_PRIMARY)
5610 {
5611 gboolean extends = FALSE;
5612 GdkModifierType state;
5613
5614 state = gdk_event_get_modifier_state (event);
5615
5616 if (state & GDK_SHIFT_MASK)
5617 extends = TRUE;
5618
5619 switch (n_press)
5620 {
5621 case 1:
5622 {
5623 /* If we're in the selection, start a drag copy/move of the
5624 * selection; otherwise, start creating a new selection.
5625 */
5626 GtkTextIter start, end;
5627
5628 priv->text_handles_enabled = is_touchscreen;
5629
5630 get_iter_from_gesture (text_view, GTK_GESTURE (gesture),
5631 iter: &iter, NULL, NULL);
5632
5633 if (gtk_text_buffer_get_selection_bounds (buffer: get_buffer (text_view),
5634 start: &start, end: &end) &&
5635 gtk_text_iter_in_range (iter: &iter, start: &start, end: &end) && !extends)
5636 {
5637 if (is_touchscreen)
5638 {
5639 gtk_gesture_set_sequence_state (GTK_GESTURE (gesture), sequence,
5640 state: GTK_EVENT_SEQUENCE_CLAIMED);
5641 if (!priv->selection_bubble ||
5642 !gtk_widget_get_visible (widget: priv->selection_bubble))
5643 {
5644 gtk_text_view_selection_bubble_popup_set (text_view);
5645 priv->text_handles_enabled = FALSE;
5646 }
5647 else
5648 {
5649 gtk_text_view_selection_bubble_popup_unset (text_view);
5650 }
5651 }
5652 else
5653 {
5654 /* Claim the sequence on the drag gesture, but attach no
5655 * selection data, this is a special case to start DnD.
5656 */
5657 gtk_gesture_set_state (gesture: priv->drag_gesture,
5658 state: GTK_EVENT_SEQUENCE_CLAIMED);
5659 }
5660 break;
5661 }
5662 else
5663 {
5664 gtk_text_view_selection_bubble_popup_unset (text_view);
5665
5666 if (is_touchscreen)
5667 priv->handle_place_time = g_get_monotonic_time ();
5668 else
5669 gtk_text_view_start_selection_drag (text_view, iter: &iter,
5670 granularity: SELECT_CHARACTERS, extends);
5671 }
5672 break;
5673 }
5674 case 2:
5675 case 3:
5676 gtk_text_view_end_selection_drag (text_view);
5677
5678 get_iter_from_gesture (text_view, GTK_GESTURE (gesture),
5679 iter: &iter, NULL, NULL);
5680 gtk_text_view_start_selection_drag (text_view, iter: &iter,
5681 granularity: n_press == 2 ? SELECT_WORDS : SELECT_LINES,
5682 extends);
5683 break;
5684 default:
5685 break;
5686 }
5687
5688 gtk_text_view_update_handles (text_view);
5689 }
5690
5691 if (n_press >= 3)
5692 gtk_event_controller_reset (GTK_EVENT_CONTROLLER (gesture));
5693}
5694
5695static void
5696direction_changed (GdkDevice *device,
5697 GParamSpec *pspec,
5698 GtkTextView *text_view)
5699{
5700 gtk_text_view_check_keymap_direction (text_view);
5701}
5702
5703static void
5704gtk_text_view_focus_in (GtkWidget *widget)
5705{
5706 GtkTextView *text_view = GTK_TEXT_VIEW (widget);
5707 GtkTextViewPrivate *priv = text_view->priv;
5708 GdkSeat *seat;
5709 GdkDevice *keyboard;
5710
5711 gtk_widget_queue_draw (widget);
5712
5713 DV(g_print (G_STRLOC": focus_in\n"));
5714
5715 gtk_text_view_reset_blink_time (text_view);
5716
5717 if (cursor_visible (text_view) && priv->layout)
5718 {
5719 gtk_text_layout_set_cursor_visible (layout: priv->layout, TRUE);
5720 gtk_text_view_check_cursor_blink (text_view);
5721 }
5722
5723 seat = gdk_display_get_default_seat (display: gtk_widget_get_display (widget));
5724 if (seat)
5725 keyboard = gdk_seat_get_keyboard (seat);
5726 else
5727 keyboard = NULL;
5728
5729 if (keyboard)
5730 g_signal_connect (keyboard, "notify::direction",
5731 G_CALLBACK (direction_changed), text_view);
5732 gtk_text_view_check_keymap_direction (text_view);
5733
5734 if (priv->editable)
5735 {
5736 priv->need_im_reset = TRUE;
5737 gtk_im_context_focus_in (context: priv->im_context);
5738 }
5739}
5740
5741static void
5742gtk_text_view_focus_out (GtkWidget *widget)
5743{
5744 GtkTextView *text_view = GTK_TEXT_VIEW (widget);
5745 GtkTextViewPrivate *priv = text_view->priv;
5746 GdkSeat *seat;
5747 GdkDevice *keyboard;
5748
5749 gtk_text_view_end_selection_drag (text_view);
5750
5751 gtk_widget_queue_draw (widget);
5752
5753 DV(g_print (G_STRLOC": focus_out\n"));
5754
5755 if (cursor_visible (text_view) && priv->layout)
5756 {
5757 gtk_text_view_check_cursor_blink (text_view);
5758 gtk_text_layout_set_cursor_visible (layout: priv->layout, FALSE);
5759 }
5760
5761 seat = gdk_display_get_default_seat (display: gtk_widget_get_display (widget));
5762 if (seat)
5763 keyboard = gdk_seat_get_keyboard (seat);
5764 else
5765 keyboard = NULL;
5766 if (keyboard)
5767 g_signal_handlers_disconnect_by_func (keyboard, direction_changed, text_view);
5768 gtk_text_view_selection_bubble_popup_unset (text_view);
5769
5770 text_view->priv->text_handles_enabled = FALSE;
5771 gtk_text_view_update_handles (text_view);
5772
5773 if (priv->editable)
5774 {
5775 priv->need_im_reset = TRUE;
5776 gtk_im_context_focus_out (context: priv->im_context);
5777 }
5778}
5779
5780static void
5781gtk_text_view_motion (GtkEventController *controller,
5782 double x,
5783 double y,
5784 gpointer user_data)
5785{
5786 gtk_text_view_unobscure_mouse_cursor (GTK_TEXT_VIEW (user_data));
5787}
5788
5789static void
5790gtk_text_view_paint (GtkWidget *widget,
5791 GtkSnapshot *snapshot)
5792{
5793 GtkTextView *text_view;
5794 GtkTextViewPrivate *priv;
5795
5796 text_view = GTK_TEXT_VIEW (widget);
5797 priv = text_view->priv;
5798
5799 g_return_if_fail (priv->layout != NULL);
5800 g_return_if_fail (priv->xoffset >= - priv->left_padding);
5801 g_return_if_fail (priv->yoffset >= - priv->top_margin);
5802
5803 while (priv->first_validate_idle != 0)
5804 {
5805 DV (g_print (G_STRLOC": first_validate_idle: %d\n",
5806 priv->first_validate_idle));
5807 gtk_text_view_flush_first_validate (text_view);
5808 }
5809
5810 if (!priv->onscreen_validated)
5811 {
5812 g_warning (G_STRLOC ": somehow some text lines were modified or scrolling occurred since the last validation of lines on the screen - may be a text widget bug.");
5813 g_assert_not_reached ();
5814 }
5815
5816 gtk_snapshot_save (snapshot);
5817 gtk_snapshot_translate (snapshot, point: &GRAPHENE_POINT_INIT (-priv->xoffset, -priv->yoffset));
5818
5819 gtk_text_layout_snapshot (layout: priv->layout,
5820 widget,
5821 snapshot,
5822 clip: &(GdkRectangle) {
5823 priv->xoffset,
5824 priv->yoffset,
5825 gtk_widget_get_width (widget),
5826 gtk_widget_get_height (widget)
5827 },
5828 cursor_alpha: priv->cursor_alpha);
5829
5830 gtk_snapshot_restore (snapshot);
5831}
5832
5833static void
5834draw_text (GtkWidget *widget,
5835 GtkSnapshot *snapshot)
5836{
5837 GtkTextView *text_view = GTK_TEXT_VIEW (widget);
5838 GtkTextViewPrivate *priv = text_view->priv;
5839 GtkStyleContext *context;
5840 gboolean did_save = FALSE;
5841
5842 if (priv->border_window_size.left || priv->border_window_size.top)
5843 {
5844 did_save = TRUE;
5845 gtk_snapshot_save (snapshot);
5846 gtk_snapshot_translate (snapshot,
5847 point: &GRAPHENE_POINT_INIT (priv->border_window_size.left,
5848 priv->border_window_size.top));
5849 }
5850
5851 gtk_snapshot_push_clip (snapshot,
5852 bounds: &GRAPHENE_RECT_INIT (0,
5853 0,
5854 SCREEN_WIDTH (widget),
5855 SCREEN_HEIGHT (widget)));
5856
5857 context = gtk_widget_get_style_context (widget);
5858 gtk_style_context_save_to_node (context, node: text_view->priv->text_window->css_node);
5859 gtk_snapshot_render_background (snapshot, context,
5860 x: -priv->xoffset, y: -priv->yoffset - priv->top_margin,
5861 MAX (SCREEN_WIDTH (text_view), priv->width),
5862 MAX (SCREEN_HEIGHT (text_view), priv->height));
5863 gtk_snapshot_render_frame (snapshot, context,
5864 x: -priv->xoffset, y: -priv->yoffset - priv->top_margin,
5865 MAX (SCREEN_WIDTH (text_view), priv->width),
5866 MAX (SCREEN_HEIGHT (text_view), priv->height));
5867 gtk_style_context_restore (context);
5868
5869 if (GTK_TEXT_VIEW_GET_CLASS (text_view)->snapshot_layer != NULL)
5870 {
5871 gtk_snapshot_save (snapshot);
5872 gtk_snapshot_translate (snapshot, point: &GRAPHENE_POINT_INIT (-priv->xoffset, -priv->yoffset));
5873 GTK_TEXT_VIEW_GET_CLASS (text_view)->snapshot_layer (text_view, GTK_TEXT_VIEW_LAYER_BELOW_TEXT, snapshot);
5874 gtk_snapshot_restore (snapshot);
5875 }
5876
5877 gtk_text_view_paint (widget, snapshot);
5878
5879 if (GTK_TEXT_VIEW_GET_CLASS (text_view)->snapshot_layer != NULL)
5880 {
5881 gtk_snapshot_save (snapshot);
5882 gtk_snapshot_translate (snapshot, point: &GRAPHENE_POINT_INIT (-priv->xoffset, -priv->yoffset));
5883 GTK_TEXT_VIEW_GET_CLASS (text_view)->snapshot_layer (text_view, GTK_TEXT_VIEW_LAYER_ABOVE_TEXT, snapshot);
5884 gtk_snapshot_restore (snapshot);
5885 }
5886
5887 gtk_snapshot_pop (snapshot);
5888
5889 if (did_save)
5890 gtk_snapshot_restore (snapshot);
5891}
5892
5893static inline void
5894snapshot_text_view_child (GtkWidget *widget,
5895 GtkTextViewChild *child,
5896 GtkSnapshot *snapshot)
5897{
5898 if (child != NULL)
5899 gtk_widget_snapshot_child (widget, GTK_WIDGET (child), snapshot);
5900}
5901
5902static void
5903gtk_text_view_snapshot (GtkWidget *widget,
5904 GtkSnapshot *snapshot)
5905{
5906 GtkTextView *text_view = GTK_TEXT_VIEW (widget);
5907 GtkTextViewPrivate *priv = text_view->priv;
5908 const GList *iter;
5909
5910 DV(g_print (">Exposed ("G_STRLOC")\n"));
5911
5912 draw_text (widget, snapshot);
5913
5914 snapshot_text_view_child (widget, child: priv->left_child, snapshot);
5915 snapshot_text_view_child (widget, child: priv->right_child, snapshot);
5916 snapshot_text_view_child (widget, child: priv->top_child, snapshot);
5917 snapshot_text_view_child (widget, child: priv->bottom_child, snapshot);
5918 snapshot_text_view_child (widget, child: priv->center_child, snapshot);
5919
5920 for (iter = priv->anchored_children.head; iter; iter = iter->next)
5921 {
5922 const AnchoredChild *vc = iter->data;
5923 gtk_widget_snapshot_child (widget, child: vc->widget, snapshot);
5924 }
5925}
5926
5927/**
5928 * gtk_text_view_remove:
5929 * @text_view: a `GtkTextView`
5930 * @child: the child to remove
5931 *
5932 * Removes a child widget from @text_view.
5933 */
5934void
5935gtk_text_view_remove (GtkTextView *text_view,
5936 GtkWidget *child)
5937{
5938 GtkTextViewPrivate *priv = text_view->priv;
5939 AnchoredChild *ac;
5940
5941 if (GTK_IS_TEXT_VIEW_CHILD (ptr: child))
5942 {
5943 GtkTextViewChild *vc = GTK_TEXT_VIEW_CHILD (ptr: child);
5944 GtkTextViewChild **vcp;
5945
5946 if (vc == priv->left_child)
5947 vcp = &priv->left_child;
5948 else if (vc == priv->right_child)
5949 vcp = &priv->right_child;
5950 else if (vc == priv->top_child)
5951 vcp = &priv->top_child;
5952 else if (vc == priv->bottom_child)
5953 vcp = &priv->bottom_child;
5954 else if (vc == priv->center_child)
5955 vcp = &priv->center_child;
5956 else
5957 vcp = NULL;
5958
5959 if (vcp)
5960 {
5961 *vcp = NULL;
5962 gtk_widget_unparent (widget: child);
5963 g_object_unref (object: child);
5964 return;
5965 }
5966 }
5967
5968 ac = g_object_get_qdata (G_OBJECT (child), quark: quark_text_view_child);
5969
5970 if (ac == NULL)
5971 {
5972 g_warning ("%s is not a child of %s",
5973 G_OBJECT_TYPE_NAME (child),
5974 G_OBJECT_TYPE_NAME (text_view));
5975 return;
5976 }
5977
5978 g_queue_unlink (queue: &priv->anchored_children, link_: &ac->link);
5979 gtk_widget_unparent (widget: ac->widget);
5980 anchored_child_free (child: ac);
5981}
5982
5983#define CURSOR_ON_MULTIPLIER 2
5984#define CURSOR_OFF_MULTIPLIER 1
5985#define CURSOR_PEND_MULTIPLIER 3
5986#define CURSOR_DIVIDER 3
5987
5988static gboolean
5989cursor_blinks (GtkTextView *text_view)
5990{
5991 GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (text_view));
5992 gboolean blink;
5993
5994#ifdef DEBUG_VALIDATION_AND_SCROLLING
5995 return FALSE;
5996#endif
5997
5998 g_object_get (object: settings, first_property_name: "gtk-cursor-blink", &blink, NULL);
5999
6000 if (!blink)
6001 return FALSE;
6002
6003 if (text_view->priv->editable)
6004 {
6005 GtkTextMark *insert;
6006 GtkTextIter iter;
6007
6008 insert = gtk_text_buffer_get_insert (buffer: get_buffer (text_view));
6009 gtk_text_buffer_get_iter_at_mark (buffer: get_buffer (text_view), iter: &iter, mark: insert);
6010
6011 if (gtk_text_iter_editable (iter: &iter, default_setting: text_view->priv->editable))
6012 return blink;
6013 }
6014
6015 return FALSE;
6016}
6017
6018static gboolean
6019cursor_visible (GtkTextView *text_view)
6020{
6021 GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (text_view));
6022 gboolean use_caret;
6023
6024 g_object_get (object: settings, first_property_name: "gtk-keynav-use-caret", &use_caret, NULL);
6025
6026 return use_caret || text_view->priv->cursor_visible;
6027}
6028
6029static gboolean
6030get_middle_click_paste (GtkTextView *text_view)
6031{
6032 GtkSettings *settings;
6033 gboolean paste;
6034
6035 settings = gtk_widget_get_settings (GTK_WIDGET (text_view));
6036 g_object_get (object: settings, first_property_name: "gtk-enable-primary-paste", &paste, NULL);
6037
6038 return paste;
6039}
6040
6041static int
6042get_cursor_time (GtkTextView *text_view)
6043{
6044 GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (text_view));
6045 int time;
6046
6047 g_object_get (object: settings, first_property_name: "gtk-cursor-blink-time", &time, NULL);
6048
6049 return time;
6050}
6051
6052static int
6053get_cursor_blink_timeout (GtkTextView *text_view)
6054{
6055 GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (text_view));
6056 int time;
6057
6058 g_object_get (object: settings, first_property_name: "gtk-cursor-blink-timeout", &time, NULL);
6059
6060 return time;
6061}
6062
6063
6064/*
6065 * Blink!
6066 */
6067
6068typedef struct {
6069 guint64 start;
6070 guint64 end;
6071} BlinkData;
6072
6073static gboolean blink_cb (GtkWidget *widget,
6074 GdkFrameClock *clock,
6075 gpointer user_data);
6076
6077
6078static void
6079add_blink_timeout (GtkTextView *self,
6080 gboolean delay)
6081{
6082 GtkTextViewPrivate *priv = self->priv;
6083 BlinkData *data;
6084 int blink_time;
6085
6086 priv->blink_start_time = g_get_monotonic_time ();
6087 priv->cursor_alpha = 1.0;
6088
6089 blink_time = get_cursor_time (text_view: self);
6090
6091 data = g_new (BlinkData, 1);
6092 data->start = priv->blink_start_time;
6093 if (delay)
6094 data->start += blink_time * 1000 / 2;
6095 data->end = data->start + blink_time * 1000;
6096
6097 priv->blink_tick = gtk_widget_add_tick_callback (GTK_WIDGET (self),
6098 callback: blink_cb,
6099 user_data: data,
6100 notify: g_free);
6101}
6102
6103static void
6104remove_blink_timeout (GtkTextView *self)
6105{
6106 GtkTextViewPrivate *priv = self->priv;
6107
6108 if (priv->blink_tick)
6109 {
6110 gtk_widget_remove_tick_callback (GTK_WIDGET (self), id: priv->blink_tick);
6111 priv->blink_tick = 0;
6112 }
6113}
6114
6115static float
6116blink_alpha (float phase)
6117{
6118 /* keep it simple, and split the blink cycle evenly
6119 * into visible, fading out, invisible, fading in
6120 */
6121 if (phase < 0.25)
6122 return 1;
6123 else if (phase < 0.5)
6124 return 1 - 4 * (phase - 0.25);
6125 else if (phase < 0.75)
6126 return 0;
6127 else
6128 return 4 * (phase - 0.75);
6129}
6130
6131static gboolean
6132blink_cb (GtkWidget *widget,
6133 GdkFrameClock *clock,
6134 gpointer user_data)
6135{
6136 GtkTextView *text_view = GTK_TEXT_VIEW (widget);
6137 GtkTextViewPrivate *priv = text_view->priv;
6138 BlinkData *data = user_data;
6139 int blink_timeout;
6140 int blink_time;
6141 guint64 now;
6142 float phase;
6143 float alpha;
6144
6145 g_assert (priv->layout);
6146 g_assert (cursor_visible (text_view));
6147
6148 blink_timeout = get_cursor_blink_timeout (text_view);
6149 blink_time = get_cursor_time (text_view);
6150
6151 now = g_get_monotonic_time ();
6152
6153 if (now > priv->blink_start_time + blink_timeout * 1000000)
6154 {
6155 /* we've blinked enough without the user doing anything, stop blinking */
6156 priv->cursor_alpha = 1.0;
6157 remove_blink_timeout (self: text_view);
6158 gtk_widget_queue_draw (widget);
6159
6160 return G_SOURCE_REMOVE;
6161 }
6162
6163 phase = (now - data->start) / (float) (data->end - data->start);
6164
6165 if (now >= data->end)
6166 {
6167 data->start = data->end;
6168 data->end = data->start + blink_time * 1000;
6169 }
6170
6171 alpha = blink_alpha (phase);
6172
6173 if (priv->cursor_alpha != alpha)
6174 {
6175 priv->cursor_alpha = alpha;
6176 gtk_widget_queue_draw (widget);
6177 }
6178
6179 return G_SOURCE_CONTINUE;
6180}
6181
6182
6183static void
6184gtk_text_view_stop_cursor_blink (GtkTextView *text_view)
6185{
6186 remove_blink_timeout (self: text_view);
6187}
6188
6189static void
6190gtk_text_view_check_cursor_blink (GtkTextView *text_view)
6191{
6192 GtkTextViewPrivate *priv = text_view->priv;
6193
6194 if (cursor_blinks (text_view) && cursor_visible (text_view))
6195 {
6196 if (!priv->blink_tick)
6197 add_blink_timeout (self: text_view, FALSE);
6198 }
6199 else
6200 {
6201 if (priv->blink_tick)
6202 remove_blink_timeout (self: text_view);
6203 }
6204}
6205
6206static void
6207gtk_text_view_pend_cursor_blink (GtkTextView *text_view)
6208{
6209 if (cursor_blinks (text_view) && cursor_visible (text_view))
6210 {
6211 remove_blink_timeout (self: text_view);
6212 add_blink_timeout (self: text_view, TRUE);
6213 }
6214}
6215
6216static void
6217gtk_text_view_reset_blink_time (GtkTextView *text_view)
6218{
6219 GtkTextViewPrivate *priv = text_view->priv;
6220
6221 priv->blink_start_time = g_get_monotonic_time ();
6222}
6223
6224
6225/*
6226 * Key binding handlers
6227 */
6228
6229static gboolean
6230gtk_text_view_move_iter_by_lines (GtkTextView *text_view,
6231 GtkTextIter *newplace,
6232 int count)
6233{
6234 gboolean ret = TRUE;
6235
6236 while (count < 0)
6237 {
6238 ret = gtk_text_layout_move_iter_to_previous_line (layout: text_view->priv->layout, iter: newplace);
6239 count++;
6240 }
6241
6242 while (count > 0)
6243 {
6244 ret = gtk_text_layout_move_iter_to_next_line (layout: text_view->priv->layout, iter: newplace);
6245 count--;
6246 }
6247
6248 return ret;
6249}
6250
6251static void
6252move_cursor (GtkTextView *text_view,
6253 const GtkTextIter *new_location,
6254 gboolean extend_selection)
6255{
6256 if (extend_selection)
6257 gtk_text_buffer_move_mark_by_name (buffer: get_buffer (text_view),
6258 name: "insert",
6259 where: new_location);
6260 else
6261 gtk_text_buffer_place_cursor (buffer: get_buffer (text_view),
6262 where: new_location);
6263 gtk_text_view_check_cursor_blink (text_view);
6264}
6265
6266static gboolean
6267iter_line_is_rtl (const GtkTextIter *iter)
6268{
6269 GtkTextIter start, end;
6270 char *text;
6271 PangoDirection direction;
6272
6273 start = end = *iter;
6274 gtk_text_iter_set_line_offset (iter: &start, char_on_line: 0);
6275 gtk_text_iter_forward_line (iter: &end);
6276 text = gtk_text_iter_get_visible_text (start: &start, end: &end);
6277 direction = gdk_find_base_dir (text, len: -1);
6278
6279 g_free (mem: text);
6280
6281 return direction == PANGO_DIRECTION_RTL;
6282}
6283
6284static void
6285gtk_text_view_move_cursor (GtkTextView *text_view,
6286 GtkMovementStep step,
6287 int count,
6288 gboolean extend_selection)
6289{
6290 GtkTextViewPrivate *priv;
6291 GtkTextIter insert;
6292 GtkTextIter newplace;
6293 gboolean cancel_selection = FALSE;
6294 int cursor_x_pos = 0;
6295 GtkDirectionType leave_direction = -1;
6296
6297 priv = text_view->priv;
6298
6299 if (!cursor_visible (text_view))
6300 {
6301 GtkScrollStep scroll_step;
6302 double old_xpos, old_ypos;
6303
6304 switch (step)
6305 {
6306 case GTK_MOVEMENT_VISUAL_POSITIONS:
6307 leave_direction = count > 0 ? GTK_DIR_RIGHT : GTK_DIR_LEFT;
6308 G_GNUC_FALLTHROUGH;
6309 case GTK_MOVEMENT_LOGICAL_POSITIONS:
6310 case GTK_MOVEMENT_WORDS:
6311 scroll_step = GTK_SCROLL_HORIZONTAL_STEPS;
6312 break;
6313 case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
6314 scroll_step = GTK_SCROLL_HORIZONTAL_ENDS;
6315 break;
6316 case GTK_MOVEMENT_DISPLAY_LINES:
6317 leave_direction = count > 0 ? GTK_DIR_DOWN : GTK_DIR_UP;
6318 G_GNUC_FALLTHROUGH;
6319 case GTK_MOVEMENT_PARAGRAPHS:
6320 case GTK_MOVEMENT_PARAGRAPH_ENDS:
6321 scroll_step = GTK_SCROLL_STEPS;
6322 break;
6323 case GTK_MOVEMENT_PAGES:
6324 scroll_step = GTK_SCROLL_PAGES;
6325 break;
6326 case GTK_MOVEMENT_HORIZONTAL_PAGES:
6327 scroll_step = GTK_SCROLL_HORIZONTAL_PAGES;
6328 break;
6329 case GTK_MOVEMENT_BUFFER_ENDS:
6330 scroll_step = GTK_SCROLL_ENDS;
6331 break;
6332 default:
6333 scroll_step = GTK_SCROLL_PAGES;
6334 break;
6335 }
6336
6337 old_xpos = gtk_adjustment_get_value (adjustment: priv->hadjustment);
6338 old_ypos = gtk_adjustment_get_value (adjustment: priv->vadjustment);
6339 gtk_text_view_move_viewport (text_view, step: scroll_step, count);
6340 if ((old_xpos == gtk_adjustment_get_target_value (adjustment: priv->hadjustment) &&
6341 old_ypos == gtk_adjustment_get_target_value (adjustment: priv->vadjustment)) &&
6342 leave_direction != (GtkDirectionType)-1 &&
6343 !gtk_widget_keynav_failed (GTK_WIDGET (text_view),
6344 direction: leave_direction))
6345 {
6346 g_signal_emit_by_name (instance: text_view, detailed_signal: "move-focus", leave_direction);
6347 }
6348
6349 return;
6350 }
6351
6352 if (step == GTK_MOVEMENT_PAGES)
6353 {
6354 if (!gtk_text_view_scroll_pages (text_view, count, extend_selection))
6355 gtk_widget_error_bell (GTK_WIDGET (text_view));
6356
6357 gtk_text_view_check_cursor_blink (text_view);
6358 gtk_text_view_pend_cursor_blink (text_view);
6359 return;
6360 }
6361 else if (step == GTK_MOVEMENT_HORIZONTAL_PAGES)
6362 {
6363 if (!gtk_text_view_scroll_hpages (text_view, count, extend_selection))
6364 gtk_widget_error_bell (GTK_WIDGET (text_view));
6365
6366 gtk_text_view_check_cursor_blink (text_view);
6367 gtk_text_view_pend_cursor_blink (text_view);
6368 return;
6369 }
6370
6371 gtk_text_buffer_get_iter_at_mark (buffer: get_buffer (text_view), iter: &insert,
6372 mark: gtk_text_buffer_get_insert (buffer: get_buffer (text_view)));
6373
6374 if (! extend_selection)
6375 {
6376 gboolean move_forward = count > 0;
6377 GtkTextIter sel_bound;
6378
6379 gtk_text_buffer_get_iter_at_mark (buffer: get_buffer (text_view), iter: &sel_bound,
6380 mark: gtk_text_buffer_get_selection_bound (buffer: get_buffer (text_view)));
6381
6382 if (iter_line_is_rtl (iter: &insert))
6383 move_forward = !move_forward;
6384
6385 /* if we move forward, assume the cursor is at the end of the selection;
6386 * if we move backward, assume the cursor is at the start
6387 */
6388 if (move_forward)
6389 gtk_text_iter_order (first: &sel_bound, second: &insert);
6390 else
6391 gtk_text_iter_order (first: &insert, second: &sel_bound);
6392
6393 /* if we actually have a selection, just move *to* the beginning/end
6394 * of the selection and not *from* there on LOGICAL_POSITIONS
6395 * and VISUAL_POSITIONS movement
6396 */
6397 if (! gtk_text_iter_equal (lhs: &sel_bound, rhs: &insert))
6398 cancel_selection = TRUE;
6399 }
6400
6401 newplace = insert;
6402
6403 if (step == GTK_MOVEMENT_DISPLAY_LINES)
6404 gtk_text_view_get_virtual_cursor_pos (text_view, cursor: &insert, x: &cursor_x_pos, NULL);
6405
6406 switch (step)
6407 {
6408 case GTK_MOVEMENT_LOGICAL_POSITIONS:
6409 if (! cancel_selection)
6410 gtk_text_iter_forward_visible_cursor_positions (iter: &newplace, count);
6411 break;
6412
6413 case GTK_MOVEMENT_VISUAL_POSITIONS:
6414 if (! cancel_selection)
6415 gtk_text_layout_move_iter_visually (layout: priv->layout,
6416 iter: &newplace, count);
6417 break;
6418
6419 case GTK_MOVEMENT_WORDS:
6420 if (iter_line_is_rtl (iter: &newplace))
6421 count *= -1;
6422
6423 if (count < 0)
6424 gtk_text_iter_backward_visible_word_starts (iter: &newplace, count: -count);
6425 else if (count > 0)
6426 {
6427 if (!gtk_text_iter_forward_visible_word_ends (iter: &newplace, count))
6428 gtk_text_iter_forward_to_line_end (iter: &newplace);
6429 }
6430 break;
6431
6432 case GTK_MOVEMENT_DISPLAY_LINES:
6433 if (count < 0)
6434 {
6435 leave_direction = GTK_DIR_UP;
6436
6437 if (gtk_text_view_move_iter_by_lines (text_view, newplace: &newplace, count))
6438 gtk_text_layout_move_iter_to_x (layout: priv->layout, iter: &newplace, x: cursor_x_pos);
6439 else
6440 gtk_text_iter_set_line_offset (iter: &newplace, char_on_line: 0);
6441 }
6442 if (count > 0)
6443 {
6444 leave_direction = GTK_DIR_DOWN;
6445
6446 if (gtk_text_view_move_iter_by_lines (text_view, newplace: &newplace, count))
6447 gtk_text_layout_move_iter_to_x (layout: priv->layout, iter: &newplace, x: cursor_x_pos);
6448 else
6449 gtk_text_iter_forward_to_line_end (iter: &newplace);
6450 }
6451 break;
6452
6453 case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
6454 if (count > 1)
6455 gtk_text_view_move_iter_by_lines (text_view, newplace: &newplace, count: --count);
6456 else if (count < -1)
6457 gtk_text_view_move_iter_by_lines (text_view, newplace: &newplace, count: ++count);
6458
6459 if (count != 0)
6460 gtk_text_layout_move_iter_to_line_end (layout: priv->layout, iter: &newplace, direction: count);
6461 break;
6462
6463 case GTK_MOVEMENT_PARAGRAPHS:
6464 if (count > 0)
6465 {
6466 if (!gtk_text_iter_ends_line (iter: &newplace))
6467 {
6468 gtk_text_iter_forward_to_line_end (iter: &newplace);
6469 --count;
6470 }
6471 gtk_text_iter_forward_visible_lines (iter: &newplace, count);
6472 gtk_text_iter_forward_to_line_end (iter: &newplace);
6473 }
6474 else if (count < 0)
6475 {
6476 if (gtk_text_iter_get_line_offset (iter: &newplace) > 0)
6477 gtk_text_iter_set_line_offset (iter: &newplace, char_on_line: 0);
6478 gtk_text_iter_forward_visible_lines (iter: &newplace, count);
6479 gtk_text_iter_set_line_offset (iter: &newplace, char_on_line: 0);
6480 }
6481 break;
6482
6483 case GTK_MOVEMENT_PARAGRAPH_ENDS:
6484 if (count > 0)
6485 {
6486 if (!gtk_text_iter_ends_line (iter: &newplace))
6487 gtk_text_iter_forward_to_line_end (iter: &newplace);
6488 }
6489 else if (count < 0)
6490 {
6491 gtk_text_iter_set_line_offset (iter: &newplace, char_on_line: 0);
6492 }
6493 break;
6494
6495 case GTK_MOVEMENT_BUFFER_ENDS:
6496 if (count > 0)
6497 gtk_text_buffer_get_end_iter (buffer: get_buffer (text_view), iter: &newplace);
6498 else if (count < 0)
6499 gtk_text_buffer_get_iter_at_offset (buffer: get_buffer (text_view), iter: &newplace, char_offset: 0);
6500 break;
6501
6502 case GTK_MOVEMENT_PAGES:
6503 case GTK_MOVEMENT_HORIZONTAL_PAGES:
6504 /* We handle these cases above and return early from them. */
6505 default:
6506 g_assert_not_reached ();
6507 break;
6508 }
6509
6510 /* call move_cursor() even if the cursor hasn't moved, since it
6511 cancels the selection
6512 */
6513 move_cursor (text_view, new_location: &newplace, extend_selection);
6514
6515 DV(g_print (G_STRLOC": scrolling onscreen\n"));
6516 gtk_text_view_scroll_mark_onscreen (text_view,
6517 mark: gtk_text_buffer_get_insert (buffer: get_buffer (text_view)));
6518
6519 if (step == GTK_MOVEMENT_DISPLAY_LINES)
6520 gtk_text_view_set_virtual_cursor_pos (text_view, x: cursor_x_pos, y: -1);
6521
6522 if (gtk_text_iter_equal (lhs: &insert, rhs: &newplace))
6523 {
6524 if (leave_direction != (GtkDirectionType)-1)
6525 {
6526 if (!gtk_widget_keynav_failed (GTK_WIDGET (text_view), direction: leave_direction))
6527 g_signal_emit_by_name (instance: text_view, detailed_signal: "move-focus", leave_direction);
6528 }
6529 else if (!cancel_selection)
6530 gtk_widget_error_bell (GTK_WIDGET (text_view));
6531 }
6532
6533 gtk_text_view_check_cursor_blink (text_view);
6534 gtk_text_view_pend_cursor_blink (text_view);
6535
6536 priv->need_im_reset = TRUE;
6537 gtk_text_view_reset_im_context (text_view);
6538}
6539
6540static void
6541gtk_text_view_move_viewport (GtkTextView *text_view,
6542 GtkScrollStep step,
6543 int count)
6544{
6545 GtkAdjustment *adjustment;
6546 double increment;
6547
6548 switch (step)
6549 {
6550 case GTK_SCROLL_STEPS:
6551 case GTK_SCROLL_PAGES:
6552 case GTK_SCROLL_ENDS:
6553 adjustment = text_view->priv->vadjustment;
6554 break;
6555 case GTK_SCROLL_HORIZONTAL_STEPS:
6556 case GTK_SCROLL_HORIZONTAL_PAGES:
6557 case GTK_SCROLL_HORIZONTAL_ENDS:
6558 adjustment = text_view->priv->hadjustment;
6559 break;
6560 default:
6561 adjustment = text_view->priv->vadjustment;
6562 break;
6563 }
6564
6565 switch (step)
6566 {
6567 case GTK_SCROLL_STEPS:
6568 case GTK_SCROLL_HORIZONTAL_STEPS:
6569 increment = gtk_adjustment_get_step_increment (adjustment);
6570 break;
6571 case GTK_SCROLL_PAGES:
6572 case GTK_SCROLL_HORIZONTAL_PAGES:
6573 increment = gtk_adjustment_get_page_increment (adjustment);
6574 break;
6575 case GTK_SCROLL_ENDS:
6576 case GTK_SCROLL_HORIZONTAL_ENDS:
6577 increment = gtk_adjustment_get_upper (adjustment) - gtk_adjustment_get_lower (adjustment);
6578 break;
6579 default:
6580 increment = 0.0;
6581 break;
6582 }
6583
6584 gtk_adjustment_animate_to_value (adjustment, value: gtk_adjustment_get_value (adjustment) + count * increment);
6585}
6586
6587static void
6588gtk_text_view_set_anchor (GtkTextView *text_view)
6589{
6590 GtkTextIter insert;
6591
6592 gtk_text_buffer_get_iter_at_mark (buffer: get_buffer (text_view), iter: &insert,
6593 mark: gtk_text_buffer_get_insert (buffer: get_buffer (text_view)));
6594
6595 gtk_text_buffer_create_mark (buffer: get_buffer (text_view), mark_name: "anchor", where: &insert, TRUE);
6596}
6597
6598static gboolean
6599gtk_text_view_scroll_pages (GtkTextView *text_view,
6600 int count,
6601 gboolean extend_selection)
6602{
6603 GtkTextViewPrivate *priv;
6604 GtkAdjustment *adjustment;
6605 int cursor_x_pos, cursor_y_pos;
6606 GtkTextMark *insert_mark;
6607 GtkTextIter old_insert;
6608 GtkTextIter new_insert;
6609 GtkTextIter anchor;
6610 double newval;
6611 double oldval;
6612 int y0, y1;
6613
6614 priv = text_view->priv;
6615
6616 g_return_val_if_fail (priv->vadjustment != NULL, FALSE);
6617
6618 adjustment = priv->vadjustment;
6619
6620 insert_mark = gtk_text_buffer_get_insert (buffer: get_buffer (text_view));
6621
6622 /* Make sure we start from the current cursor position, even
6623 * if it was offscreen, but don't queue more scrolls if we're
6624 * already behind.
6625 */
6626 if (priv->pending_scroll)
6627 cancel_pending_scroll (text_view);
6628 else
6629 gtk_text_view_scroll_mark_onscreen (text_view, mark: insert_mark);
6630
6631 /* Validate the region that will be brought into view by the cursor motion
6632 */
6633 gtk_text_buffer_get_iter_at_mark (buffer: get_buffer (text_view),
6634 iter: &old_insert, mark: insert_mark);
6635
6636 if (count < 0)
6637 {
6638 gtk_text_view_get_first_para_iter (text_view, iter: &anchor);
6639 y0 = gtk_adjustment_get_page_size (adjustment);
6640 y1 = gtk_adjustment_get_page_size (adjustment) + count * gtk_adjustment_get_page_increment (adjustment);
6641 }
6642 else
6643 {
6644 gtk_text_view_get_first_para_iter (text_view, iter: &anchor);
6645 y0 = count * gtk_adjustment_get_page_increment (adjustment) + gtk_adjustment_get_page_size (adjustment);
6646 y1 = 0;
6647 }
6648
6649 gtk_text_layout_validate_yrange (layout: priv->layout, anchor_line: &anchor, y0_: y0, y1_: y1);
6650 /* FIXME do we need to update the adjustment ranges here? */
6651
6652 new_insert = old_insert;
6653
6654 if (count < 0 && gtk_adjustment_get_value (adjustment) <= (gtk_adjustment_get_lower (adjustment) + 1e-12))
6655 {
6656 /* already at top, just be sure we are at offset 0 */
6657 gtk_text_buffer_get_start_iter (buffer: get_buffer (text_view), iter: &new_insert);
6658 move_cursor (text_view, new_location: &new_insert, extend_selection);
6659 }
6660 else if (count > 0 && gtk_adjustment_get_value (adjustment) >= (gtk_adjustment_get_upper (adjustment) - gtk_adjustment_get_page_size (adjustment) - 1e-12))
6661 {
6662 /* already at bottom, just be sure we are at the end */
6663 gtk_text_buffer_get_end_iter (buffer: get_buffer (text_view), iter: &new_insert);
6664 move_cursor (text_view, new_location: &new_insert, extend_selection);
6665 }
6666 else
6667 {
6668 gtk_text_view_get_virtual_cursor_pos (text_view, NULL, x: &cursor_x_pos, y: &cursor_y_pos);
6669
6670 oldval = newval = gtk_adjustment_get_target_value (adjustment);
6671 newval += count * gtk_adjustment_get_page_increment (adjustment);
6672
6673 gtk_adjustment_animate_to_value (adjustment, value: newval);
6674 cursor_y_pos += newval - oldval;
6675
6676 gtk_text_layout_get_iter_at_pixel (layout: priv->layout, iter: &new_insert, x: cursor_x_pos, y: cursor_y_pos);
6677
6678 move_cursor (text_view, new_location: &new_insert, extend_selection);
6679
6680 gtk_text_view_set_virtual_cursor_pos (text_view, x: cursor_x_pos, y: cursor_y_pos);
6681 }
6682
6683 /* Adjust to have the cursor _entirely_ onscreen, move_mark_onscreen
6684 * only guarantees 1 pixel onscreen.
6685 */
6686 DV(g_print (G_STRLOC": scrolling onscreen\n"));
6687
6688 return !gtk_text_iter_equal (lhs: &old_insert, rhs: &new_insert);
6689}
6690
6691static gboolean
6692gtk_text_view_scroll_hpages (GtkTextView *text_view,
6693 int count,
6694 gboolean extend_selection)
6695{
6696 GtkTextViewPrivate *priv;
6697 GtkAdjustment *adjustment;
6698 int cursor_x_pos, cursor_y_pos;
6699 GtkTextMark *insert_mark;
6700 GtkTextIter old_insert;
6701 GtkTextIter new_insert;
6702 double newval;
6703 double oldval;
6704 int y, height;
6705
6706 priv = text_view->priv;
6707
6708 g_return_val_if_fail (priv->hadjustment != NULL, FALSE);
6709
6710 adjustment = priv->hadjustment;
6711
6712 insert_mark = gtk_text_buffer_get_insert (buffer: get_buffer (text_view));
6713
6714 /* Make sure we start from the current cursor position, even
6715 * if it was offscreen, but don't queue more scrolls if we're
6716 * already behind.
6717 */
6718 if (priv->pending_scroll)
6719 cancel_pending_scroll (text_view);
6720 else
6721 gtk_text_view_scroll_mark_onscreen (text_view, mark: insert_mark);
6722
6723 /* Validate the line that we're moving within.
6724 */
6725 gtk_text_buffer_get_iter_at_mark (buffer: get_buffer (text_view),
6726 iter: &old_insert, mark: insert_mark);
6727
6728 gtk_text_layout_get_line_yrange (layout: priv->layout, iter: &old_insert, y: &y, height: &height);
6729 gtk_text_layout_validate_yrange (layout: priv->layout, anchor_line: &old_insert, y0_: y, y1_: y + height);
6730 /* FIXME do we need to update the adjustment ranges here? */
6731
6732 new_insert = old_insert;
6733
6734 if (count < 0 && gtk_adjustment_get_value (adjustment) <= (gtk_adjustment_get_lower (adjustment) + 1e-12))
6735 {
6736 /* already at far left, just be sure we are at offset 0 */
6737 gtk_text_iter_set_line_offset (iter: &new_insert, char_on_line: 0);
6738 move_cursor (text_view, new_location: &new_insert, extend_selection);
6739 }
6740 else if (count > 0 && gtk_adjustment_get_value (adjustment) >= (gtk_adjustment_get_upper (adjustment) - gtk_adjustment_get_page_size (adjustment) - 1e-12))
6741 {
6742 /* already at far right, just be sure we are at the end */
6743 if (!gtk_text_iter_ends_line (iter: &new_insert))
6744 gtk_text_iter_forward_to_line_end (iter: &new_insert);
6745 move_cursor (text_view, new_location: &new_insert, extend_selection);
6746 }
6747 else
6748 {
6749 gtk_text_view_get_virtual_cursor_pos (text_view, NULL, x: &cursor_x_pos, y: &cursor_y_pos);
6750
6751 oldval = newval = gtk_adjustment_get_target_value (adjustment);
6752 newval += count * gtk_adjustment_get_page_increment (adjustment);
6753
6754 gtk_adjustment_animate_to_value (adjustment, value: newval);
6755 cursor_x_pos += newval - oldval;
6756
6757 gtk_text_layout_get_iter_at_pixel (layout: priv->layout, iter: &new_insert, x: cursor_x_pos, y: cursor_y_pos);
6758 move_cursor (text_view, new_location: &new_insert, extend_selection);
6759
6760 gtk_text_view_set_virtual_cursor_pos (text_view, x: cursor_x_pos, y: cursor_y_pos);
6761 }
6762
6763 /* FIXME for lines shorter than the overall widget width, this results in a
6764 * "bounce" effect as we scroll to the right of the widget, then scroll
6765 * back to get the end of the line onscreen.
6766 * http://bugzilla.gnome.org/show_bug.cgi?id=68963
6767 */
6768
6769 /* Adjust to have the cursor _entirely_ onscreen, move_mark_onscreen
6770 * only guarantees 1 pixel onscreen.
6771 */
6772 DV(g_print (G_STRLOC": scrolling onscreen\n"));
6773
6774 return !gtk_text_iter_equal (lhs: &old_insert, rhs: &new_insert);
6775}
6776
6777static gboolean
6778whitespace (gunichar ch, gpointer user_data)
6779{
6780 return (ch == ' ' || ch == '\t');
6781}
6782
6783static gboolean
6784not_whitespace (gunichar ch, gpointer user_data)
6785{
6786 return !whitespace (ch, user_data);
6787}
6788
6789static gboolean
6790find_whitepace_region (const GtkTextIter *center,
6791 GtkTextIter *start, GtkTextIter *end)
6792{
6793 *start = *center;
6794 *end = *center;
6795
6796 if (gtk_text_iter_backward_find_char (iter: start, pred: not_whitespace, NULL, NULL))
6797 gtk_text_iter_forward_char (iter: start); /* we want the first whitespace... */
6798 if (whitespace (ch: gtk_text_iter_get_char (iter: end), NULL))
6799 gtk_text_iter_forward_find_char (iter: end, pred: not_whitespace, NULL, NULL);
6800
6801 return !gtk_text_iter_equal (lhs: start, rhs: end);
6802}
6803
6804static void
6805gtk_text_view_insert_at_cursor (GtkTextView *text_view,
6806 const char *str)
6807{
6808 if (!gtk_text_buffer_insert_interactive_at_cursor (buffer: get_buffer (text_view), text: str, len: -1,
6809 default_editable: text_view->priv->editable))
6810 {
6811 gtk_widget_error_bell (GTK_WIDGET (text_view));
6812 }
6813}
6814
6815static void
6816gtk_text_view_delete_from_cursor (GtkTextView *text_view,
6817 GtkDeleteType type,
6818 int count)
6819{
6820 GtkTextViewPrivate *priv;
6821 GtkTextIter insert;
6822 GtkTextIter start;
6823 GtkTextIter end;
6824 gboolean leave_one = FALSE;
6825
6826 priv = text_view->priv;
6827
6828 if (type == GTK_DELETE_CHARS)
6829 {
6830 /* Char delete deletes the selection, if one exists */
6831 if (gtk_text_buffer_delete_selection (buffer: get_buffer (text_view), TRUE,
6832 default_editable: priv->editable))
6833 {
6834 priv->need_im_reset = TRUE;
6835 gtk_text_view_reset_im_context (text_view);
6836 return;
6837 }
6838 }
6839
6840 gtk_text_buffer_get_iter_at_mark (buffer: get_buffer (text_view), iter: &insert,
6841 mark: gtk_text_buffer_get_insert (buffer: get_buffer (text_view)));
6842
6843 start = insert;
6844 end = insert;
6845
6846 switch (type)
6847 {
6848 case GTK_DELETE_CHARS:
6849 gtk_text_iter_forward_cursor_positions (iter: &end, count);
6850 break;
6851
6852 case GTK_DELETE_WORD_ENDS:
6853 if (count > 0)
6854 gtk_text_iter_forward_word_ends (iter: &end, count);
6855 else if (count < 0)
6856 gtk_text_iter_backward_word_starts (iter: &start, count: 0 - count);
6857 break;
6858
6859 case GTK_DELETE_WORDS:
6860 break;
6861
6862 case GTK_DELETE_DISPLAY_LINE_ENDS:
6863 break;
6864
6865 case GTK_DELETE_DISPLAY_LINES:
6866 break;
6867
6868 case GTK_DELETE_PARAGRAPH_ENDS:
6869 if (count > 0)
6870 {
6871 /* If we're already at a newline, we need to
6872 * simply delete that newline, instead of
6873 * moving to the next one.
6874 */
6875 if (gtk_text_iter_ends_line (iter: &end))
6876 {
6877 gtk_text_iter_forward_line (iter: &end);
6878 --count;
6879 }
6880
6881 while (count > 0)
6882 {
6883 if (!gtk_text_iter_forward_to_line_end (iter: &end))
6884 break;
6885
6886 --count;
6887 }
6888 }
6889 else if (count < 0)
6890 {
6891 if (gtk_text_iter_starts_line (iter: &start))
6892 {
6893 gtk_text_iter_backward_line (iter: &start);
6894 if (!gtk_text_iter_ends_line (iter: &end))
6895 gtk_text_iter_forward_to_line_end (iter: &start);
6896 }
6897 else
6898 {
6899 gtk_text_iter_set_line_offset (iter: &start, char_on_line: 0);
6900 }
6901 ++count;
6902
6903 gtk_text_iter_backward_lines (iter: &start, count: -count);
6904 }
6905 break;
6906
6907 case GTK_DELETE_PARAGRAPHS:
6908 if (count > 0)
6909 {
6910 gtk_text_iter_set_line_offset (iter: &start, char_on_line: 0);
6911 gtk_text_iter_forward_to_line_end (iter: &end);
6912
6913 /* Do the lines beyond the first. */
6914 while (count > 1)
6915 {
6916 gtk_text_iter_forward_to_line_end (iter: &end);
6917
6918 --count;
6919 }
6920 }
6921
6922 /* FIXME negative count? */
6923
6924 break;
6925
6926 case GTK_DELETE_WHITESPACE:
6927 {
6928 find_whitepace_region (center: &insert, start: &start, end: &end);
6929 }
6930 break;
6931
6932 default:
6933 break;
6934 }
6935
6936 if (!gtk_text_iter_equal (lhs: &start, rhs: &end))
6937 {
6938 gtk_text_buffer_begin_user_action (buffer: get_buffer (text_view));
6939
6940 if (gtk_text_buffer_delete_interactive (buffer: get_buffer (text_view), start_iter: &start, end_iter: &end,
6941 default_editable: priv->editable))
6942 {
6943 if (leave_one)
6944 gtk_text_buffer_insert_interactive_at_cursor (buffer: get_buffer (text_view),
6945 text: " ", len: 1,
6946 default_editable: priv->editable);
6947 }
6948 else
6949 {
6950 gtk_widget_error_bell (GTK_WIDGET (text_view));
6951 }
6952
6953 gtk_text_buffer_end_user_action (buffer: get_buffer (text_view));
6954 gtk_text_view_set_virtual_cursor_pos (text_view, x: -1, y: -1);
6955
6956 DV(g_print (G_STRLOC": scrolling onscreen\n"));
6957 gtk_text_view_scroll_mark_onscreen (text_view,
6958 mark: gtk_text_buffer_get_insert (buffer: get_buffer (text_view)));
6959 }
6960 else
6961 {
6962 gtk_widget_error_bell (GTK_WIDGET (text_view));
6963 }
6964
6965 priv->need_im_reset = TRUE;
6966 gtk_text_view_reset_im_context (text_view);
6967}
6968
6969static void
6970gtk_text_view_backspace (GtkTextView *text_view)
6971{
6972 GtkTextViewPrivate *priv;
6973 GtkTextIter insert;
6974
6975 priv = text_view->priv;
6976
6977 /* Backspace deletes the selection, if one exists */
6978 if (gtk_text_buffer_delete_selection (buffer: get_buffer (text_view), TRUE,
6979 default_editable: priv->editable))
6980 {
6981 priv->need_im_reset = TRUE;
6982 gtk_text_view_reset_im_context (text_view);
6983 return;
6984 }
6985
6986 gtk_text_buffer_get_iter_at_mark (buffer: get_buffer (text_view),
6987 iter: &insert,
6988 mark: gtk_text_buffer_get_insert (buffer: get_buffer (text_view)));
6989
6990 if (gtk_text_buffer_backspace (buffer: get_buffer (text_view), iter: &insert,
6991 TRUE, default_editable: priv->editable))
6992 {
6993 gtk_text_view_set_virtual_cursor_pos (text_view, x: -1, y: -1);
6994 DV(g_print (G_STRLOC": scrolling onscreen\n"));
6995 gtk_text_view_scroll_mark_onscreen (text_view,
6996 mark: gtk_text_buffer_get_insert (buffer: get_buffer (text_view)));
6997
6998 priv->need_im_reset = TRUE;
6999 gtk_text_view_reset_im_context (text_view);
7000 }
7001 else
7002 {
7003 gtk_widget_error_bell (GTK_WIDGET (text_view));
7004 }
7005}
7006
7007static void
7008gtk_text_view_cut_clipboard (GtkTextView *text_view)
7009{
7010 GdkClipboard *clipboard = gtk_widget_get_clipboard (GTK_WIDGET (text_view));
7011
7012 gtk_text_buffer_cut_clipboard (buffer: get_buffer (text_view),
7013 clipboard,
7014 default_editable: text_view->priv->editable);
7015 DV(g_print (G_STRLOC": scrolling onscreen\n"));
7016 gtk_text_view_scroll_mark_onscreen (text_view,
7017 mark: gtk_text_buffer_get_insert (buffer: get_buffer (text_view)));
7018 gtk_text_view_selection_bubble_popup_unset (text_view);
7019}
7020
7021static void
7022gtk_text_view_copy_clipboard (GtkTextView *text_view)
7023{
7024 GdkClipboard *clipboard = gtk_widget_get_clipboard (GTK_WIDGET (text_view));
7025
7026 gtk_text_buffer_copy_clipboard (buffer: get_buffer (text_view), clipboard);
7027
7028 /* on copy do not scroll, we are already onscreen */
7029}
7030
7031static void
7032gtk_text_view_paste_clipboard (GtkTextView *text_view)
7033{
7034 GdkClipboard *clipboard = gtk_widget_get_clipboard (GTK_WIDGET (text_view));
7035
7036 text_view->priv->scroll_after_paste = TRUE;
7037
7038 gtk_text_buffer_paste_clipboard (buffer: get_buffer (text_view),
7039 clipboard,
7040 NULL,
7041 default_editable: text_view->priv->editable);
7042}
7043
7044static void
7045gtk_text_view_paste_done_handler (GtkTextBuffer *buffer,
7046 GdkClipboard *clipboard,
7047 gpointer data)
7048{
7049 GtkTextView *text_view = data;
7050 GtkTextViewPrivate *priv;
7051
7052 priv = text_view->priv;
7053
7054 if (priv->scroll_after_paste)
7055 {
7056 DV(g_print (G_STRLOC": scrolling onscreen\n"));
7057 gtk_text_view_scroll_mark_onscreen (text_view, mark: gtk_text_buffer_get_insert (buffer));
7058 }
7059
7060 priv->scroll_after_paste = FALSE;
7061}
7062
7063static void
7064gtk_text_view_buffer_changed_handler (GtkTextBuffer *buffer,
7065 gpointer data)
7066{
7067 GtkTextView *text_view = data;
7068
7069 gtk_text_view_update_handles (text_view);
7070}
7071
7072static void
7073gtk_text_view_toggle_overwrite (GtkTextView *text_view)
7074{
7075 GtkTextViewPrivate *priv = text_view->priv;
7076
7077 priv->overwrite_mode = !priv->overwrite_mode;
7078
7079 if (priv->layout)
7080 gtk_text_layout_set_overwrite_mode (layout: priv->layout,
7081 overwrite: priv->overwrite_mode && priv->editable);
7082
7083 gtk_widget_queue_draw (GTK_WIDGET (text_view));
7084
7085 gtk_text_view_pend_cursor_blink (text_view);
7086
7087 g_object_notify (G_OBJECT (text_view), property_name: "overwrite");
7088}
7089
7090/**
7091 * gtk_text_view_get_overwrite: (attributes org.gtk.Method.get_property=overwrite)
7092 * @text_view: a `GtkTextView`
7093 *
7094 * Returns whether the `GtkTextView` is in overwrite mode or not.
7095 *
7096 * Returns: whether @text_view is in overwrite mode or not.
7097 */
7098gboolean
7099gtk_text_view_get_overwrite (GtkTextView *text_view)
7100{
7101 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), FALSE);
7102
7103 return text_view->priv->overwrite_mode;
7104}
7105
7106/**
7107 * gtk_text_view_set_overwrite: (attributes org.gtk.Method.set_property=overwrite)
7108 * @text_view: a `GtkTextView`
7109 * @overwrite: %TRUE to turn on overwrite mode, %FALSE to turn it off
7110 *
7111 * Changes the `GtkTextView` overwrite mode.
7112 */
7113void
7114gtk_text_view_set_overwrite (GtkTextView *text_view,
7115 gboolean overwrite)
7116{
7117 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
7118 overwrite = overwrite != FALSE;
7119
7120 if (text_view->priv->overwrite_mode != overwrite)
7121 gtk_text_view_toggle_overwrite (text_view);
7122}
7123
7124/**
7125 * gtk_text_view_set_accepts_tab: (attributes org.gtk.Method.set_property=accepts-tab)
7126 * @text_view: A `GtkTextView`
7127 * @accepts_tab: %TRUE if pressing the Tab key should insert a tab
7128 * character, %FALSE, if pressing the Tab key should move the
7129 * keyboard focus.
7130 *
7131 * Sets the behavior of the text widget when the <kbd>Tab</kbd> key is pressed.
7132 *
7133 * If @accepts_tab is %TRUE, a tab character is inserted. If @accepts_tab
7134 * is %FALSE the keyboard focus is moved to the next widget in the focus
7135 * chain.
7136 */
7137void
7138gtk_text_view_set_accepts_tab (GtkTextView *text_view,
7139 gboolean accepts_tab)
7140{
7141 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
7142
7143 accepts_tab = accepts_tab != FALSE;
7144
7145 if (text_view->priv->accepts_tab != accepts_tab)
7146 {
7147 text_view->priv->accepts_tab = accepts_tab;
7148
7149 g_object_notify (G_OBJECT (text_view), property_name: "accepts-tab");
7150 }
7151}
7152
7153/**
7154 * gtk_text_view_get_accepts_tab: (attributes org.gtk.Method.get_property=accepts-tab)
7155 * @text_view: A `GtkTextView`
7156 *
7157 * Returns whether pressing the <kbd>Tab</kbd> key inserts a tab characters.
7158 *
7159 * See [method@Gtk.TextView.set_accepts_tab].
7160 *
7161 * Returns: %TRUE if pressing the Tab key inserts a tab character,
7162 * %FALSE if pressing the Tab key moves the keyboard focus.
7163 */
7164gboolean
7165gtk_text_view_get_accepts_tab (GtkTextView *text_view)
7166{
7167 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), FALSE);
7168
7169 return text_view->priv->accepts_tab;
7170}
7171
7172/*
7173 * Selections
7174 */
7175
7176static void
7177gtk_text_view_unselect (GtkTextView *text_view)
7178{
7179 GtkTextIter insert;
7180
7181 gtk_text_buffer_get_iter_at_mark (buffer: get_buffer (text_view), iter: &insert,
7182 mark: gtk_text_buffer_get_insert (buffer: get_buffer (text_view)));
7183
7184 gtk_text_buffer_move_mark (buffer: get_buffer (text_view),
7185 mark: gtk_text_buffer_get_selection_bound (buffer: get_buffer (text_view)),
7186 where: &insert);
7187}
7188
7189static void
7190move_mark_to_pointer_and_scroll (GtkTextView *text_view,
7191 const char *mark_name)
7192{
7193 GtkTextIter newplace;
7194 GtkTextBuffer *buffer;
7195 GtkTextMark *mark;
7196
7197 buffer = get_buffer (text_view);
7198 get_iter_from_gesture (text_view, gesture: text_view->priv->drag_gesture,
7199 iter: &newplace, NULL, NULL);
7200
7201 mark = gtk_text_buffer_get_mark (buffer, name: mark_name);
7202
7203 /* This may invalidate the layout */
7204 DV(g_print (G_STRLOC": move mark\n"));
7205
7206 gtk_text_buffer_move_mark (buffer, mark, where: &newplace);
7207
7208 DV(g_print (G_STRLOC": scrolling onscreen\n"));
7209 gtk_text_view_scroll_mark_onscreen (text_view, mark);
7210
7211 DV (g_print ("first validate idle leaving %s is %d\n",
7212 G_STRLOC, text_view->priv->first_validate_idle));
7213}
7214
7215static gboolean
7216selection_scan_timeout (gpointer data)
7217{
7218 GtkTextView *text_view;
7219
7220 text_view = GTK_TEXT_VIEW (data);
7221
7222 gtk_text_view_scroll_mark_onscreen (text_view,
7223 mark: gtk_text_buffer_get_insert (buffer: get_buffer (text_view)));
7224
7225 return TRUE; /* remain installed. */
7226}
7227
7228static void
7229extend_selection (GtkTextView *text_view,
7230 SelectionGranularity granularity,
7231 const GtkTextIter *location,
7232 GtkTextIter *start,
7233 GtkTextIter *end)
7234{
7235 GtkTextExtendSelection extend_selection_granularity;
7236 gboolean handled = FALSE;
7237
7238 switch (granularity)
7239 {
7240 case SELECT_CHARACTERS:
7241 *start = *location;
7242 *end = *location;
7243 return;
7244
7245 case SELECT_WORDS:
7246 extend_selection_granularity = GTK_TEXT_EXTEND_SELECTION_WORD;
7247 break;
7248
7249 case SELECT_LINES:
7250 extend_selection_granularity = GTK_TEXT_EXTEND_SELECTION_LINE;
7251 break;
7252
7253 default:
7254 g_assert_not_reached ();
7255 }
7256
7257 g_signal_emit (instance: text_view,
7258 signal_id: signals[EXTEND_SELECTION], detail: 0,
7259 extend_selection_granularity,
7260 location,
7261 start,
7262 end,
7263 &handled);
7264
7265 if (!handled)
7266 {
7267 *start = *location;
7268 *end = *location;
7269 }
7270}
7271
7272static gboolean
7273gtk_text_view_extend_selection (GtkTextView *text_view,
7274 GtkTextExtendSelection granularity,
7275 const GtkTextIter *location,
7276 GtkTextIter *start,
7277 GtkTextIter *end)
7278{
7279 *start = *location;
7280 *end = *location;
7281
7282 switch (granularity)
7283 {
7284 case GTK_TEXT_EXTEND_SELECTION_WORD:
7285 if (gtk_text_iter_inside_word (iter: start))
7286 {
7287 if (!gtk_text_iter_starts_word (iter: start))
7288 gtk_text_iter_backward_visible_word_start (iter: start);
7289
7290 if (!gtk_text_iter_ends_word (iter: end))
7291 {
7292 if (!gtk_text_iter_forward_visible_word_end (iter: end))
7293 gtk_text_iter_forward_to_end (iter: end);
7294 }
7295 }
7296 else
7297 {
7298 GtkTextIter tmp;
7299
7300 /* @start is not contained in a word: the selection is extended to all
7301 * the white spaces between the end of the word preceding @start and
7302 * the start of the one following.
7303 */
7304
7305 tmp = *start;
7306 if (gtk_text_iter_backward_visible_word_start (iter: &tmp))
7307 gtk_text_iter_forward_visible_word_end (iter: &tmp);
7308
7309 if (gtk_text_iter_get_line (iter: &tmp) == gtk_text_iter_get_line (iter: start))
7310 *start = tmp;
7311 else
7312 gtk_text_iter_set_line_offset (iter: start, char_on_line: 0);
7313
7314 tmp = *end;
7315 if (!gtk_text_iter_forward_visible_word_end (iter: &tmp))
7316 gtk_text_iter_forward_to_end (iter: &tmp);
7317
7318 if (gtk_text_iter_ends_word (iter: &tmp))
7319 gtk_text_iter_backward_visible_word_start (iter: &tmp);
7320
7321 if (gtk_text_iter_get_line (iter: &tmp) == gtk_text_iter_get_line (iter: end))
7322 *end = tmp;
7323 }
7324 break;
7325
7326 case GTK_TEXT_EXTEND_SELECTION_LINE:
7327 if (gtk_text_view_starts_display_line (text_view, iter: start))
7328 {
7329 /* If on a display line boundary, we assume the user
7330 * clicked off the end of a line and we therefore select
7331 * the line before the boundary.
7332 */
7333 gtk_text_view_backward_display_line_start (text_view, iter: start);
7334 }
7335 else
7336 {
7337 /* start isn't on the start of a line, so we move it to the
7338 * start, and move end to the end unless it's already there.
7339 */
7340 gtk_text_view_backward_display_line_start (text_view, iter: start);
7341
7342 if (!gtk_text_view_starts_display_line (text_view, iter: end))
7343 gtk_text_view_forward_display_line_end (text_view, iter: end);
7344 }
7345 break;
7346
7347 default:
7348 g_return_val_if_reached (GDK_EVENT_STOP);
7349 }
7350
7351 return GDK_EVENT_STOP;
7352}
7353
7354typedef struct
7355{
7356 SelectionGranularity granularity;
7357 GtkTextMark *orig_start;
7358 GtkTextMark *orig_end;
7359 GtkTextBuffer *buffer;
7360} SelectionData;
7361
7362static void
7363selection_data_free (SelectionData *data)
7364{
7365 if (data->orig_start != NULL)
7366 gtk_text_buffer_delete_mark (buffer: data->buffer, mark: data->orig_start);
7367
7368 if (data->orig_end != NULL)
7369 gtk_text_buffer_delete_mark (buffer: data->buffer, mark: data->orig_end);
7370
7371 g_object_unref (object: data->buffer);
7372
7373 g_slice_free (SelectionData, data);
7374}
7375
7376static gboolean
7377drag_gesture_get_text_surface_coords (GtkGestureDrag *gesture,
7378 GtkTextView *text_view,
7379 int *start_x,
7380 int *start_y,
7381 int *x,
7382 int *y)
7383{
7384 double sx, sy, ox, oy;
7385
7386 if (!gtk_gesture_drag_get_start_point (gesture, x: &sx, y: &sy) ||
7387 !gtk_gesture_drag_get_offset (gesture, x: &ox, y: &oy))
7388 return FALSE;
7389
7390 *start_x = sx;
7391 *start_y = sy;
7392 _widget_to_text_surface_coords (text_view, x: start_x, y: start_y);
7393
7394 *x = sx + ox;
7395 *y = sy + oy;
7396 _widget_to_text_surface_coords (text_view, x, y);
7397
7398 return TRUE;
7399}
7400
7401static void
7402gtk_text_view_drag_gesture_update (GtkGestureDrag *gesture,
7403 double offset_x,
7404 double offset_y,
7405 GtkTextView *text_view)
7406{
7407 int start_x, start_y, x, y;
7408 GdkEventSequence *sequence;
7409 gboolean is_touchscreen;
7410 GdkEvent *event;
7411 SelectionData *data;
7412 GdkDevice *device;
7413 GtkTextIter cursor;
7414
7415 data = g_object_get_qdata (G_OBJECT (gesture), quark: quark_text_selection_data);
7416 sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
7417 event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
7418
7419 if (!drag_gesture_get_text_surface_coords (gesture, text_view,
7420 start_x: &start_x, start_y: &start_y, x: &x, y: &y))
7421 return;
7422
7423 device = gdk_event_get_device (event);
7424
7425 is_touchscreen = gtk_simulate_touchscreen () ||
7426 gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN;
7427
7428 get_iter_from_gesture (text_view, gesture: text_view->priv->drag_gesture,
7429 iter: &cursor, NULL, NULL);
7430
7431 if (!data)
7432 {
7433 /* If no data is attached, the initial press happened within the current
7434 * text selection, check for drag and drop to be initiated.
7435 */
7436 if (gtk_drag_check_threshold_double (GTK_WIDGET (text_view), start_x: 0, start_y: 0, current_x: offset_x, current_y: offset_y))
7437 {
7438 if (!is_touchscreen)
7439 {
7440 GtkTextIter iter;
7441 int buffer_x, buffer_y;
7442
7443 gtk_text_view_window_to_buffer_coords (text_view,
7444 win: GTK_TEXT_WINDOW_TEXT,
7445 window_x: start_x, window_y: start_y,
7446 buffer_x: &buffer_x,
7447 buffer_y: &buffer_y);
7448
7449 gtk_text_layout_get_iter_at_pixel (layout: text_view->priv->layout,
7450 iter: &iter, x: buffer_x, y: buffer_y);
7451
7452 gtk_text_view_start_selection_dnd (text_view, iter: &iter, event,
7453 x: start_x, y: start_y);
7454
7455 /* Deny the gesture so we don't get further updates */
7456 gtk_gesture_set_state (gesture: text_view->priv->drag_gesture,
7457 state: GTK_EVENT_SEQUENCE_DENIED);
7458 return;
7459 }
7460 else
7461 {
7462 gtk_text_view_start_selection_drag (text_view, iter: &cursor,
7463 granularity: SELECT_WORDS, TRUE);
7464 data = g_object_get_qdata (G_OBJECT (gesture), quark: quark_text_selection_data);
7465 }
7466 }
7467 else
7468 return;
7469 }
7470
7471 g_assert (data != NULL);
7472
7473 /* Text selection */
7474 if (data->granularity == SELECT_CHARACTERS)
7475 {
7476 move_mark_to_pointer_and_scroll (text_view, mark_name: "insert");
7477 }
7478 else
7479 {
7480 GtkTextIter start, end;
7481 GtkTextIter orig_start, orig_end;
7482 GtkTextBuffer *buffer;
7483
7484 buffer = get_buffer (text_view);
7485
7486 gtk_text_buffer_get_iter_at_mark (buffer, iter: &orig_start, mark: data->orig_start);
7487 gtk_text_buffer_get_iter_at_mark (buffer, iter: &orig_end, mark: data->orig_end);
7488
7489 get_iter_from_gesture (text_view, gesture: text_view->priv->drag_gesture,
7490 iter: &cursor, NULL, NULL);
7491
7492 extend_selection (text_view, granularity: data->granularity, location: &cursor, start: &start, end: &end);
7493
7494 /* either the selection extends to the front, or end (or not) */
7495 if (gtk_text_iter_compare (lhs: &orig_start, rhs: &start) < 0)
7496 start = orig_start;
7497 if (gtk_text_iter_compare (lhs: &orig_end, rhs: &end) > 0)
7498 end = orig_end;
7499 gtk_text_buffer_select_range (buffer, ins: &start, bound: &end);
7500
7501 gtk_text_view_scroll_mark_onscreen (text_view,
7502 mark: gtk_text_buffer_get_insert (buffer));
7503 }
7504
7505 /* If we had to scroll offscreen, insert a timeout to do so
7506 * again. Note that in the timeout, even if the mouse doesn't
7507 * move, due to this scroll xoffset/yoffset will have changed
7508 * and we'll need to scroll again.
7509 */
7510 if (text_view->priv->scroll_timeout != 0) /* reset on every motion event */
7511 g_source_remove (tag: text_view->priv->scroll_timeout);
7512
7513 text_view->priv->scroll_timeout = g_timeout_add (interval: 50, function: selection_scan_timeout, data: text_view);
7514 gdk_source_set_static_name_by_id (tag: text_view->priv->scroll_timeout, name: "[gtk] selection_scan_timeout");
7515
7516 gtk_text_view_selection_bubble_popup_unset (text_view);
7517
7518 if (is_touchscreen)
7519 {
7520 text_view->priv->text_handles_enabled = TRUE;
7521 gtk_text_view_update_handles (text_view);
7522 gtk_text_view_show_magnifier (text_view, iter: &cursor, x, y);
7523 }
7524}
7525
7526static void
7527gtk_text_view_drag_gesture_end (GtkGestureDrag *gesture,
7528 double offset_x,
7529 double offset_y,
7530 GtkTextView *text_view)
7531{
7532 gboolean is_touchscreen, clicked_in_selection;
7533 int start_x, start_y, x, y;
7534 GdkEventSequence *sequence;
7535 GtkTextViewPrivate *priv;
7536 GdkEvent *event;
7537 GdkDevice *device;
7538
7539 priv = text_view->priv;
7540 sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
7541
7542 clicked_in_selection =
7543 g_object_get_qdata (G_OBJECT (gesture), quark: quark_text_selection_data) == NULL;
7544 g_object_set_qdata (G_OBJECT (gesture), quark: quark_text_selection_data, NULL);
7545 gtk_text_view_unobscure_mouse_cursor (text_view);
7546
7547 if (priv->scroll_timeout != 0)
7548 {
7549 g_source_remove (tag: priv->scroll_timeout);
7550 priv->scroll_timeout = 0;
7551 }
7552
7553 if (priv->magnifier_popover)
7554 gtk_widget_hide (widget: priv->magnifier_popover);
7555
7556 if (!drag_gesture_get_text_surface_coords (gesture, text_view,
7557 start_x: &start_x, start_y: &start_y, x: &x, y: &y))
7558 return;
7559
7560 /* Check whether the drag was cancelled rather than finished */
7561 if (!gtk_gesture_handles_sequence (GTK_GESTURE (gesture), sequence))
7562 return;
7563
7564 event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
7565 device = gdk_event_get_device (event);
7566 is_touchscreen = gtk_simulate_touchscreen () ||
7567 gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN;
7568
7569 if ((is_touchscreen || clicked_in_selection) &&
7570 !gtk_drag_check_threshold_double (GTK_WIDGET (text_view), start_x: 0, start_y: 0, current_x: offset_x, current_y: offset_y))
7571 {
7572 GtkTextIter iter;
7573
7574 /* Unselect everything; we clicked inside selection, but
7575 * didn't move by the drag threshold, so just clear selection
7576 * and place cursor.
7577 */
7578 gtk_text_layout_get_iter_at_pixel (layout: priv->layout, iter: &iter,
7579 x: x + priv->xoffset, y: y + priv->yoffset);
7580
7581 gtk_text_buffer_place_cursor (buffer: get_buffer (text_view), where: &iter);
7582 gtk_text_view_check_cursor_blink (text_view);
7583
7584 gtk_text_view_update_handles (text_view);
7585 }
7586}
7587
7588static void
7589gtk_text_view_start_selection_drag (GtkTextView *text_view,
7590 const GtkTextIter *iter,
7591 SelectionGranularity granularity,
7592 gboolean extend)
7593{
7594 GtkTextViewPrivate *priv;
7595 GtkTextIter cursor, ins, bound;
7596 GtkTextIter orig_start, orig_end;
7597 GtkTextBuffer *buffer;
7598 SelectionData *data;
7599
7600 priv = text_view->priv;
7601 data = g_slice_new0 (SelectionData);
7602 data->granularity = granularity;
7603
7604 buffer = get_buffer (text_view);
7605
7606 cursor = *iter;
7607 extend_selection (text_view, granularity: data->granularity, location: &cursor, start: &ins, end: &bound);
7608
7609 orig_start = ins;
7610 orig_end = bound;
7611
7612 if (extend)
7613 {
7614 /* Extend selection */
7615 GtkTextIter old_ins, old_bound;
7616 GtkTextIter old_start, old_end;
7617
7618 gtk_text_buffer_get_iter_at_mark (buffer, iter: &old_ins, mark: gtk_text_buffer_get_insert (buffer));
7619 gtk_text_buffer_get_iter_at_mark (buffer, iter: &old_bound, mark: gtk_text_buffer_get_selection_bound (buffer));
7620 old_start = old_ins;
7621 old_end = old_bound;
7622 gtk_text_iter_order (first: &old_start, second: &old_end);
7623
7624 /* move the front cursor, if the mouse is in front of the selection. Should the
7625 * cursor however be inside the selection (this happens on triple click) then we
7626 * move the side which was last moved (current insert mark) */
7627 if (gtk_text_iter_compare (lhs: &cursor, rhs: &old_start) <= 0 ||
7628 (gtk_text_iter_compare (lhs: &cursor, rhs: &old_end) < 0 &&
7629 gtk_text_iter_compare (lhs: &old_ins, rhs: &old_bound) <= 0))
7630 {
7631 bound = old_end;
7632 }
7633 else
7634 {
7635 ins = bound;
7636 bound = old_start;
7637 }
7638
7639 /* Store any previous selection */
7640 if (gtk_text_iter_compare (lhs: &old_start, rhs: &old_end) != 0)
7641 {
7642 orig_start = old_ins;
7643 orig_end = old_bound;
7644 }
7645 }
7646
7647 gtk_text_buffer_select_range (buffer, ins: &ins, bound: &bound);
7648
7649 gtk_text_iter_order (first: &orig_start, second: &orig_end);
7650 data->orig_start = gtk_text_buffer_create_mark (buffer, NULL,
7651 where: &orig_start, TRUE);
7652 data->orig_end = gtk_text_buffer_create_mark (buffer, NULL,
7653 where: &orig_end, TRUE);
7654 data->buffer = g_object_ref (buffer);
7655 gtk_text_view_check_cursor_blink (text_view);
7656
7657 g_object_set_qdata_full (G_OBJECT (priv->drag_gesture),
7658 quark: quark_text_selection_data,
7659 data, destroy: (GDestroyNotify) selection_data_free);
7660// gtk_gesture_set_state (priv->drag_gesture,
7661// GTK_EVENT_SEQUENCE_CLAIMED);
7662}
7663
7664/* returns whether we were really dragging */
7665static gboolean
7666gtk_text_view_end_selection_drag (GtkTextView *text_view)
7667{
7668 GtkTextViewPrivate *priv;
7669
7670 priv = text_view->priv;
7671
7672 if (!gtk_gesture_is_active (gesture: priv->drag_gesture))
7673 return FALSE;
7674
7675 if (priv->scroll_timeout != 0)
7676 {
7677 g_source_remove (tag: priv->scroll_timeout);
7678 priv->scroll_timeout = 0;
7679 }
7680
7681 if (priv->magnifier_popover)
7682 gtk_widget_hide (widget: priv->magnifier_popover);
7683
7684 return TRUE;
7685}
7686
7687/*
7688 * Layout utils
7689 */
7690
7691static void
7692gtk_text_view_set_attributes_from_style (GtkTextView *text_view,
7693 GtkTextAttributes *values)
7694{
7695 GtkCssStyle *style;
7696 const GdkRGBA black = { 0, };
7697 const GdkRGBA *color;
7698 const GdkRGBA *decoration_color;
7699 GtkTextDecorationLine decoration_line;
7700 GtkTextDecorationStyle decoration_style;
7701
7702 if (!values->appearance.bg_rgba)
7703 values->appearance.bg_rgba = gdk_rgba_copy (rgba: &black);
7704 if (!values->appearance.fg_rgba)
7705 values->appearance.fg_rgba = gdk_rgba_copy (rgba: &black);
7706
7707 style = gtk_css_node_get_style (cssnode: gtk_widget_get_css_node (GTK_WIDGET (text_view)));
7708
7709 color = gtk_css_color_value_get_rgba (color: style->background->background_color);
7710 *values->appearance.bg_rgba = *color;
7711 color = gtk_css_color_value_get_rgba (color: style->core->color);
7712 *values->appearance.fg_rgba = *color;
7713
7714 if (values->font)
7715 pango_font_description_free (desc: values->font);
7716
7717 values->font = gtk_css_style_get_pango_font (style);
7718
7719 /* text-decoration */
7720
7721 decoration_line = _gtk_css_text_decoration_line_value_get (value: style->font_variant->text_decoration_line);
7722 decoration_style = _gtk_css_text_decoration_style_value_get (value: style->font_variant->text_decoration_style);
7723 decoration_color = gtk_css_color_value_get_rgba (color: style->font_variant->text_decoration_color
7724 ? style->font_variant->text_decoration_color
7725 : style->core->color);
7726
7727 if (decoration_line & GTK_CSS_TEXT_DECORATION_LINE_UNDERLINE)
7728 {
7729 switch (decoration_style)
7730 {
7731 case GTK_CSS_TEXT_DECORATION_STYLE_DOUBLE:
7732 values->appearance.underline = PANGO_UNDERLINE_DOUBLE;
7733 break;
7734 case GTK_CSS_TEXT_DECORATION_STYLE_WAVY:
7735 values->appearance.underline = PANGO_UNDERLINE_ERROR;
7736 break;
7737 case GTK_CSS_TEXT_DECORATION_STYLE_SOLID:
7738 default:
7739 values->appearance.underline = PANGO_UNDERLINE_SINGLE;
7740 break;
7741 }
7742
7743 if (values->appearance.underline_rgba)
7744 *values->appearance.underline_rgba = *decoration_color;
7745 else
7746 values->appearance.underline_rgba = gdk_rgba_copy (rgba: decoration_color);
7747 }
7748 else
7749 {
7750 values->appearance.underline = PANGO_UNDERLINE_NONE;
7751 gdk_rgba_free (rgba: values->appearance.underline_rgba);
7752 values->appearance.underline_rgba = NULL;
7753 }
7754
7755 if (decoration_line & GTK_CSS_TEXT_DECORATION_LINE_OVERLINE)
7756 {
7757 values->appearance.overline = PANGO_OVERLINE_SINGLE;
7758 if (values->appearance.overline_rgba)
7759 *values->appearance.overline_rgba = *decoration_color;
7760 else
7761 values->appearance.overline_rgba = gdk_rgba_copy (rgba: decoration_color);
7762 }
7763 else
7764 {
7765 values->appearance.overline = PANGO_OVERLINE_NONE;
7766 gdk_rgba_free (rgba: values->appearance.overline_rgba);
7767 values->appearance.overline_rgba = NULL;
7768 }
7769
7770 if (decoration_line & GTK_CSS_TEXT_DECORATION_LINE_LINE_THROUGH)
7771 {
7772 values->appearance.strikethrough = TRUE;
7773 if (values->appearance.strikethrough_rgba)
7774 *values->appearance.strikethrough_rgba = *decoration_color;
7775 else
7776 values->appearance.strikethrough_rgba = gdk_rgba_copy (rgba: decoration_color);
7777 }
7778 else
7779 {
7780 values->appearance.strikethrough = FALSE;
7781 gdk_rgba_free (rgba: values->appearance.strikethrough_rgba);
7782 values->appearance.strikethrough_rgba = NULL;
7783 }
7784
7785 /* letter-spacing */
7786 values->letter_spacing = _gtk_css_number_value_get (number: style->font->letter_spacing, one_hundred_percent: 100) * PANGO_SCALE;
7787
7788 /* line-height */
7789
7790 values->line_height = gtk_css_line_height_value_get (value: style->font->line_height);
7791 values->line_height_is_absolute = FALSE;
7792 if (values->line_height != 0.0)
7793 {
7794 if (gtk_css_number_value_get_dimension (value: style->font->line_height) == GTK_CSS_DIMENSION_LENGTH)
7795 values->line_height_is_absolute = TRUE;
7796 }
7797
7798 /* OpenType features */
7799 g_free (mem: values->font_features);
7800 values->font_features = gtk_css_style_compute_font_features (style);
7801
7802 /* text-transform */
7803 values->text_transform = gtk_css_style_get_pango_text_transform (style);
7804}
7805
7806static void
7807gtk_text_view_check_keymap_direction (GtkTextView *text_view)
7808{
7809 GtkTextViewPrivate *priv = text_view->priv;
7810 GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (text_view));
7811 GdkSeat *seat;
7812 GdkDevice *keyboard;
7813 PangoDirection direction;
7814 GtkTextDirection new_cursor_dir;
7815 GtkTextDirection new_keyboard_dir;
7816 gboolean split_cursor;
7817
7818 if (!priv->layout)
7819 return;
7820
7821 seat = gdk_display_get_default_seat (display: gtk_widget_get_display (GTK_WIDGET (text_view)));
7822 if (seat)
7823 keyboard = gdk_seat_get_keyboard (seat);
7824 else
7825 keyboard = NULL;
7826
7827 if (keyboard)
7828 direction = gdk_device_get_direction (device: keyboard);
7829 else
7830 direction = PANGO_DIRECTION_LTR;
7831
7832 g_object_get (object: settings,
7833 first_property_name: "gtk-split-cursor", &split_cursor,
7834 NULL);
7835
7836 if (direction == PANGO_DIRECTION_RTL)
7837 new_keyboard_dir = GTK_TEXT_DIR_RTL;
7838 else
7839 new_keyboard_dir = GTK_TEXT_DIR_LTR;
7840
7841 if (split_cursor)
7842 new_cursor_dir = GTK_TEXT_DIR_NONE;
7843 else
7844 new_cursor_dir = new_keyboard_dir;
7845
7846 gtk_text_layout_set_cursor_direction (layout: priv->layout, direction: new_cursor_dir);
7847 gtk_text_layout_set_keyboard_direction (layout: priv->layout, keyboard_dir: new_keyboard_dir);
7848}
7849
7850static void
7851gtk_text_view_ensure_layout (GtkTextView *text_view)
7852{
7853 GtkWidget *widget;
7854 GtkTextViewPrivate *priv;
7855
7856 widget = GTK_WIDGET (text_view);
7857 priv = text_view->priv;
7858
7859 if (priv->layout == NULL)
7860 {
7861 GtkTextAttributes *style;
7862 const GList *iter;
7863 PangoContext *ltr_context, *rtl_context;
7864
7865 DV(g_print(G_STRLOC"\n"));
7866
7867 priv->layout = gtk_text_layout_new ();
7868
7869 g_signal_connect (priv->layout,
7870 "invalidated",
7871 G_CALLBACK (invalidated_handler),
7872 text_view);
7873
7874 g_signal_connect (priv->layout,
7875 "changed",
7876 G_CALLBACK (changed_handler),
7877 text_view);
7878
7879 g_signal_connect (priv->layout,
7880 "allocate-child",
7881 G_CALLBACK (gtk_anchored_child_allocated),
7882 text_view);
7883
7884 if (get_buffer (text_view))
7885 gtk_text_layout_set_buffer (layout: priv->layout, buffer: get_buffer (text_view));
7886
7887 if ((gtk_widget_has_focus (widget) && cursor_visible (text_view)))
7888 gtk_text_view_pend_cursor_blink (text_view);
7889 else
7890 gtk_text_layout_set_cursor_visible (layout: priv->layout, FALSE);
7891
7892 gtk_text_layout_set_overwrite_mode (layout: priv->layout,
7893 overwrite: priv->overwrite_mode && priv->editable);
7894
7895 ltr_context = gtk_widget_create_pango_context (GTK_WIDGET (text_view));
7896 rtl_context = gtk_widget_create_pango_context (GTK_WIDGET (text_view));
7897 pango_context_set_base_dir (context: ltr_context, direction: PANGO_DIRECTION_LTR);
7898 pango_context_set_base_dir (context: rtl_context, direction: PANGO_DIRECTION_RTL);
7899 gtk_text_layout_set_contexts (layout: priv->layout, ltr_context, rtl_context);
7900 g_object_unref (object: ltr_context);
7901 g_object_unref (object: rtl_context);
7902
7903 gtk_text_view_update_pango_contexts (text_view);
7904
7905 gtk_text_view_check_keymap_direction (text_view);
7906
7907 style = gtk_text_attributes_new ();
7908
7909 gtk_text_view_set_attributes_from_style (text_view, values: style);
7910
7911 style->pixels_above_lines = priv->pixels_above_lines;
7912 style->pixels_below_lines = priv->pixels_below_lines;
7913 style->pixels_inside_wrap = priv->pixels_inside_wrap;
7914
7915 style->left_margin = priv->left_margin;
7916 style->right_margin = priv->right_margin;
7917 priv->layout->right_padding = priv->right_padding;
7918 priv->layout->left_padding = priv->left_padding;
7919
7920 style->indent = priv->indent;
7921 style->tabs = priv->tabs ? pango_tab_array_copy (src: priv->tabs) : NULL;
7922
7923 style->wrap_mode = priv->wrap_mode;
7924 style->justification = priv->justify;
7925 style->direction = gtk_widget_get_direction (GTK_WIDGET (text_view));
7926
7927 gtk_text_layout_set_default_style (layout: priv->layout, values: style);
7928
7929 gtk_text_attributes_unref (values: style);
7930
7931 /* Set layout for all anchored children */
7932
7933 iter = priv->anchored_children.head;
7934 while (iter != NULL)
7935 {
7936 const AnchoredChild *ac = iter->data;
7937 iter = iter->next;
7938 gtk_text_anchored_child_set_layout (child: ac->widget, layout: priv->layout);
7939 /* ac may now be invalid! */
7940 }
7941 }
7942}
7943
7944GtkTextAttributes*
7945gtk_text_view_get_default_attributes (GtkTextView *text_view)
7946{
7947 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), NULL);
7948
7949 gtk_text_view_ensure_layout (text_view);
7950
7951 return gtk_text_attributes_copy (src: text_view->priv->layout->default_style);
7952}
7953
7954static void
7955gtk_text_view_destroy_layout (GtkTextView *text_view)
7956{
7957 GtkTextViewPrivate *priv = text_view->priv;
7958
7959 if (priv->layout)
7960 {
7961 const GList *iter;
7962
7963 gtk_text_view_remove_validate_idles (text_view);
7964
7965 g_signal_handlers_disconnect_by_func (priv->layout,
7966 invalidated_handler,
7967 text_view);
7968 g_signal_handlers_disconnect_by_func (priv->layout,
7969 changed_handler,
7970 text_view);
7971
7972 iter = priv->anchored_children.head;
7973 while (iter != NULL)
7974 {
7975 const AnchoredChild *ac = iter->data;
7976 iter = iter->next;
7977 gtk_text_anchored_child_set_layout (child: ac->widget, NULL);
7978 /* vc may now be invalid! */
7979 }
7980
7981 gtk_text_view_stop_cursor_blink (text_view);
7982 gtk_text_view_end_selection_drag (text_view);
7983
7984 g_object_unref (object: priv->layout);
7985 priv->layout = NULL;
7986 }
7987}
7988
7989/**
7990 * gtk_text_view_reset_im_context:
7991 * @text_view: a `GtkTextView`
7992 *
7993 * Reset the input method context of the text view if needed.
7994 *
7995 * This can be necessary in the case where modifying the buffer
7996 * would confuse on-going input method behavior.
7997 */
7998void
7999gtk_text_view_reset_im_context (GtkTextView *text_view)
8000{
8001 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
8002
8003 if (text_view->priv->need_im_reset)
8004 {
8005 text_view->priv->need_im_reset = FALSE;
8006 gtk_im_context_reset (context: text_view->priv->im_context);
8007 }
8008}
8009
8010/**
8011 * gtk_text_view_im_context_filter_keypress:
8012 * @text_view: a `GtkTextView`
8013 * @event: the key event
8014 *
8015 * Allow the `GtkTextView` input method to internally handle key press
8016 * and release events.
8017 *
8018 * If this function returns %TRUE, then no further processing should be
8019 * done for this key event. See [method@Gtk.IMContext.filter_keypress].
8020 *
8021 * Note that you are expected to call this function from your handler
8022 * when overriding key event handling. This is needed in the case when
8023 * you need to insert your own key handling between the input method
8024 * and the default key event handling of the `GtkTextView`.
8025 *
8026 * ```c
8027 * static gboolean
8028 * gtk_foo_bar_key_press_event (GtkWidget *widget,
8029 * GdkEvent *event)
8030 * {
8031 * guint keyval;
8032 *
8033 * gdk_event_get_keyval ((GdkEvent*)event, &keyval);
8034 *
8035 * if (keyval == GDK_KEY_Return || keyval == GDK_KEY_KP_Enter)
8036 * {
8037 * if (gtk_text_view_im_context_filter_keypress (GTK_TEXT_VIEW (widget), event))
8038 * return TRUE;
8039 * }
8040 *
8041 * // Do some stuff
8042 *
8043 * return GTK_WIDGET_CLASS (gtk_foo_bar_parent_class)->key_press_event (widget, event);
8044 * }
8045 * ```
8046 *
8047 * Returns: %TRUE if the input method handled the key event.
8048 */
8049gboolean
8050gtk_text_view_im_context_filter_keypress (GtkTextView *text_view,
8051 GdkEvent *event)
8052{
8053 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), FALSE);
8054
8055 return gtk_im_context_filter_keypress (context: text_view->priv->im_context, event);
8056}
8057
8058/*
8059 * DND feature
8060 */
8061
8062static void
8063dnd_finished_cb (GdkDrag *drag,
8064 GtkTextView *self)
8065{
8066 GtkTextBuffer *buffer = self->priv->buffer;
8067
8068 if (self->priv->dnd_drag_begin_mark)
8069 {
8070 if (gdk_drag_get_selected_action (drag) == GDK_ACTION_MOVE)
8071 {
8072 {
8073 GtkTextIter begin, end;
8074
8075 gtk_text_buffer_get_iter_at_mark (buffer, iter: &begin, mark: self->priv->dnd_drag_begin_mark);
8076 gtk_text_buffer_get_iter_at_mark (buffer, iter: &end, mark: self->priv->dnd_drag_end_mark);
8077 gtk_text_buffer_delete (buffer, start: &begin, end: &end);
8078 }
8079 }
8080
8081 gtk_text_buffer_delete_mark (buffer, mark: self->priv->dnd_drag_begin_mark);
8082 gtk_text_buffer_delete_mark (buffer, mark: self->priv->dnd_drag_end_mark);
8083 self->priv->dnd_drag_begin_mark = NULL;
8084 self->priv->dnd_drag_end_mark = NULL;
8085 }
8086
8087 self->priv->drag = NULL;
8088}
8089
8090static void
8091dnd_cancel_cb (GdkDrag *drag,
8092 GdkDragCancelReason reason,
8093 GtkTextView *self)
8094{
8095 GtkTextBuffer *buffer = self->priv->buffer;
8096
8097 if (self->priv->dnd_drag_begin_mark)
8098 {
8099 gtk_text_buffer_delete_mark (buffer, mark: self->priv->dnd_drag_begin_mark);
8100 gtk_text_buffer_delete_mark (buffer, mark: self->priv->dnd_drag_end_mark);
8101 self->priv->dnd_drag_begin_mark = NULL;
8102 self->priv->dnd_drag_end_mark = NULL;
8103 }
8104
8105 self->priv->drag = NULL;
8106}
8107
8108static void
8109gtk_text_view_start_selection_dnd (GtkTextView *text_view,
8110 const GtkTextIter *iter,
8111 GdkEvent *event,
8112 int x,
8113 int y)
8114{
8115 GtkWidget *widget = GTK_WIDGET (text_view);
8116 GtkTextBuffer *buffer = gtk_text_view_get_buffer (text_view);
8117 GdkContentProvider *content;
8118 GtkTextIter start, end;
8119 GdkDragAction actions;
8120 GdkSurface *surface;
8121 GdkDevice *device;
8122 GdkDrag *drag;
8123
8124 if (text_view->priv->editable)
8125 actions = GDK_ACTION_COPY | GDK_ACTION_MOVE;
8126 else
8127 actions = GDK_ACTION_COPY;
8128
8129 content = gtk_text_buffer_get_selection_content (buffer);
8130
8131 surface = gdk_event_get_surface (event);
8132 device = gdk_event_get_device (event);
8133 drag = gdk_drag_begin (surface, device, content, actions, dx: x, dy: y);
8134
8135 g_object_unref (object: content);
8136
8137 g_signal_connect (drag, "dnd-finished", G_CALLBACK (dnd_finished_cb), text_view);
8138 g_signal_connect (drag, "cancel", G_CALLBACK (dnd_cancel_cb), text_view);
8139
8140 if (gtk_text_buffer_get_selection_bounds (buffer, start: &start, end: &end))
8141 {
8142 GdkPaintable *paintable;
8143 paintable = gtk_text_util_create_rich_drag_icon (widget, buffer, start: &start, end: &end);
8144 gtk_drag_icon_set_from_paintable (drag, paintable, hot_x: 0, hot_y: 0);
8145 g_object_unref (object: paintable);
8146
8147 text_view->priv->dnd_drag_begin_mark = gtk_text_buffer_create_mark (buffer, NULL, where: &start, TRUE);
8148 text_view->priv->dnd_drag_end_mark = gtk_text_buffer_create_mark (buffer, NULL, where: &end, TRUE);
8149 }
8150
8151 text_view->priv->drag = drag;
8152
8153 g_object_unref (object: drag);
8154}
8155
8156static void
8157gtk_text_view_drag_leave (GtkDropTarget *dest,
8158 GtkTextView *text_view)
8159{
8160 GtkTextViewPrivate *priv = text_view->priv;
8161
8162 gtk_text_mark_set_visible (mark: priv->dnd_mark, FALSE);
8163}
8164
8165static GdkDragAction
8166gtk_text_view_drag_motion (GtkDropTarget *dest,
8167 double x,
8168 double y,
8169 GtkTextView *text_view)
8170{
8171 GtkTextViewPrivate *priv = text_view->priv;
8172 GtkTextIter newplace;
8173 GtkTextIter start;
8174 GtkTextIter end;
8175 int bx, by;
8176 gboolean can_accept = FALSE;
8177
8178 gtk_text_view_window_to_buffer_coords (text_view,
8179 win: GTK_TEXT_WINDOW_WIDGET,
8180 window_x: x, window_y: y,
8181 buffer_x: &bx, buffer_y: &by);
8182
8183 gtk_text_layout_get_iter_at_pixel (layout: priv->layout,
8184 iter: &newplace,
8185 x: bx, y: by);
8186
8187 if (gtk_text_buffer_get_selection_bounds (buffer: get_buffer (text_view),
8188 start: &start, end: &end) &&
8189 gtk_text_iter_compare (lhs: &newplace, rhs: &start) >= 0 &&
8190 gtk_text_iter_compare (lhs: &newplace, rhs: &end) <= 0)
8191 {
8192 /* We're inside the selection. */
8193 }
8194 else
8195 {
8196 can_accept = gtk_text_iter_can_insert (iter: &newplace, default_editability: priv->editable);
8197 }
8198
8199 if (can_accept)
8200 {
8201 gtk_text_mark_set_visible (mark: priv->dnd_mark, setting: cursor_visible (text_view));
8202 if (text_view->priv->drag)
8203 return GDK_ACTION_MOVE;
8204 else
8205 return GDK_ACTION_COPY;
8206 }
8207 else
8208 {
8209 gtk_text_mark_set_visible (mark: priv->dnd_mark, FALSE);
8210 return 0;
8211 }
8212}
8213
8214static gboolean
8215gtk_text_view_drag_drop (GtkDropTarget *dest,
8216 const GValue *value,
8217 double x,
8218 double y,
8219 GtkTextView *text_view)
8220{
8221 GtkTextViewPrivate *priv = text_view->priv;
8222 GtkTextBuffer *buffer;
8223 GtkTextIter drop_point;
8224
8225 buffer = get_buffer (text_view);
8226 gtk_text_buffer_get_iter_at_mark (buffer, iter: &drop_point, mark: priv->dnd_mark);
8227
8228 if (!gtk_text_iter_can_insert (iter: &drop_point, default_editability: priv->editable))
8229 return FALSE;
8230
8231 gtk_text_buffer_begin_user_action (buffer);
8232
8233 if (!gtk_text_buffer_insert_interactive (buffer,
8234 iter: &drop_point, text: (char *) g_value_get_string (value), len: -1,
8235 default_editable: text_view->priv->editable))
8236 gtk_widget_error_bell (GTK_WIDGET (text_view));
8237
8238 gtk_text_buffer_get_iter_at_mark (buffer, iter: &drop_point, mark: priv->dnd_mark);
8239 gtk_text_buffer_place_cursor (buffer, where: &drop_point);
8240
8241 gtk_text_buffer_end_user_action (buffer);
8242
8243 return TRUE;
8244}
8245
8246static void
8247gtk_text_view_set_hadjustment (GtkTextView *text_view,
8248 GtkAdjustment *adjustment)
8249{
8250 GtkTextViewPrivate *priv = text_view->priv;
8251
8252 if (adjustment && priv->hadjustment == adjustment)
8253 return;
8254
8255 if (priv->hadjustment != NULL)
8256 {
8257 g_signal_handlers_disconnect_by_func (priv->hadjustment,
8258 gtk_text_view_value_changed,
8259 text_view);
8260 g_object_unref (object: priv->hadjustment);
8261 }
8262
8263 if (adjustment == NULL)
8264 adjustment = gtk_adjustment_new (value: 0.0, lower: 0.0, upper: 0.0, step_increment: 0.0, page_increment: 0.0, page_size: 0.0);
8265
8266 g_signal_connect (adjustment, "value-changed",
8267 G_CALLBACK (gtk_text_view_value_changed), text_view);
8268 priv->hadjustment = g_object_ref_sink (adjustment);
8269 gtk_text_view_set_hadjustment_values (text_view);
8270
8271 g_object_notify (G_OBJECT (text_view), property_name: "hadjustment");
8272}
8273
8274static void
8275gtk_text_view_set_vadjustment (GtkTextView *text_view,
8276 GtkAdjustment *adjustment)
8277{
8278 GtkTextViewPrivate *priv = text_view->priv;
8279
8280 if (adjustment && priv->vadjustment == adjustment)
8281 return;
8282
8283 if (priv->vadjustment != NULL)
8284 {
8285 g_signal_handlers_disconnect_by_func (priv->vadjustment,
8286 gtk_text_view_value_changed,
8287 text_view);
8288 g_object_unref (object: priv->vadjustment);
8289 }
8290
8291 if (adjustment == NULL)
8292 adjustment = gtk_adjustment_new (value: 0.0, lower: 0.0, upper: 0.0, step_increment: 0.0, page_increment: 0.0, page_size: 0.0);
8293
8294 g_signal_connect (adjustment, "value-changed",
8295 G_CALLBACK (gtk_text_view_value_changed), text_view);
8296 priv->vadjustment = g_object_ref_sink (adjustment);
8297 gtk_text_view_set_vadjustment_values (text_view);
8298
8299 g_object_notify (G_OBJECT (text_view), property_name: "vadjustment");
8300}
8301
8302static void
8303gtk_text_view_set_hadjustment_values (GtkTextView *text_view)
8304{
8305 GtkTextViewPrivate *priv;
8306 int screen_width;
8307 double old_value;
8308 double new_value;
8309 double new_upper;
8310
8311 priv = text_view->priv;
8312
8313 screen_width = SCREEN_WIDTH (text_view);
8314 old_value = gtk_adjustment_get_value (adjustment: priv->hadjustment);
8315 new_upper = MAX (screen_width, priv->width);
8316
8317 g_object_set (object: priv->hadjustment,
8318 first_property_name: "lower", 0.0,
8319 "upper", new_upper,
8320 "page-size", (double)screen_width,
8321 "step-increment", screen_width * 0.1,
8322 "page-increment", screen_width * 0.9,
8323 NULL);
8324
8325 new_value = CLAMP (old_value, 0, new_upper - screen_width);
8326 if (new_value != old_value)
8327 gtk_adjustment_set_value (adjustment: priv->hadjustment, value: new_value);
8328}
8329
8330static void
8331gtk_text_view_set_vadjustment_values (GtkTextView *text_view)
8332{
8333 GtkTextViewPrivate *priv;
8334 GtkTextIter first_para;
8335 int screen_height;
8336 int y;
8337 double old_value;
8338 double new_value;
8339 double new_upper;
8340
8341 priv = text_view->priv;
8342
8343 screen_height = SCREEN_HEIGHT (text_view);
8344 old_value = gtk_adjustment_get_value (adjustment: priv->vadjustment);
8345 new_upper = MAX (screen_height, priv->height);
8346
8347 g_object_set (object: priv->vadjustment,
8348 first_property_name: "lower", 0.0,
8349 "upper", new_upper,
8350 "page-size", (double)screen_height,
8351 "step-increment", screen_height * 0.1,
8352 "page-increment", screen_height * 0.9,
8353 NULL);
8354
8355 /* Now adjust the value of the adjustment to keep the cursor at the
8356 * same place in the buffer */
8357 gtk_text_view_ensure_layout (text_view);
8358 gtk_text_view_get_first_para_iter (text_view, iter: &first_para);
8359 gtk_text_layout_get_line_yrange (layout: priv->layout, iter: &first_para, y: &y, NULL);
8360
8361 y += priv->first_para_pixels;
8362
8363 new_value = CLAMP (y, 0, new_upper - screen_height);
8364 if (new_value != old_value)
8365 gtk_adjustment_set_value (adjustment: priv->vadjustment, value: new_value);
8366}
8367
8368static void
8369gtk_text_view_value_changed (GtkAdjustment *adjustment,
8370 GtkTextView *text_view)
8371{
8372 GtkTextViewPrivate *priv;
8373 GtkTextIter iter;
8374 int line_top;
8375 int dx = 0;
8376 int dy = 0;
8377
8378 priv = text_view->priv;
8379
8380 /* Note that we oddly call this function with adjustment == NULL
8381 * sometimes
8382 */
8383
8384 priv->onscreen_validated = FALSE;
8385
8386 DV(g_print(">Scroll offset changed %s/%g, onscreen_validated = FALSE ("G_STRLOC")\n",
8387 adjustment == priv->hadjustment ? "hadjustment" : adjustment == priv->vadjustment ? "vadjustment" : "none",
8388 adjustment ? gtk_adjustment_get_value (adjustment) : 0.0));
8389
8390 if (adjustment == priv->hadjustment)
8391 {
8392 dx = priv->xoffset - (int)gtk_adjustment_get_value (adjustment);
8393 priv->xoffset = (int)gtk_adjustment_get_value (adjustment) - priv->left_padding;
8394 }
8395 else if (adjustment == priv->vadjustment)
8396 {
8397 dy = priv->yoffset - (int)gtk_adjustment_get_value (adjustment) + priv->top_margin ;
8398 priv->yoffset -= dy;
8399
8400 if (priv->layout)
8401 {
8402 gtk_text_layout_get_line_at_y (layout: priv->layout, target_iter: &iter, y: gtk_adjustment_get_value (adjustment), line_top: &line_top);
8403
8404 gtk_text_buffer_move_mark (buffer: get_buffer (text_view), mark: priv->first_para_mark, where: &iter);
8405
8406 priv->first_para_pixels = gtk_adjustment_get_value (adjustment) - line_top;
8407 }
8408 }
8409
8410 if (dx != 0 || dy != 0)
8411 {
8412 if (gtk_widget_get_realized (GTK_WIDGET (text_view)))
8413 {
8414 if (priv->selection_bubble)
8415 gtk_widget_hide (widget: priv->selection_bubble);
8416 }
8417 }
8418
8419 /* This could result in invalidation, which would install the
8420 * first_validate_idle, which would validate onscreen;
8421 * but we're going to go ahead and validate here, so
8422 * first_validate_idle shouldn't have anything to do.
8423 */
8424 gtk_text_view_update_layout_width (text_view);
8425
8426 /* We also update the IM spot location here, since the IM context
8427 * might do something that leads to validation.
8428 */
8429 gtk_text_view_update_im_spot_location (text_view);
8430
8431 /* note that validation of onscreen could invoke this function
8432 * recursively, by scrolling to maintain first_para, or in response
8433 * to updating the layout width, however there is no problem with
8434 * that, or shouldn't be.
8435 */
8436 gtk_text_view_validate_onscreen (text_view);
8437
8438 /* If this got installed, get rid of it, it's just a waste of time. */
8439 if (priv->first_validate_idle != 0)
8440 {
8441 g_source_remove (tag: priv->first_validate_idle);
8442 priv->first_validate_idle = 0;
8443 }
8444
8445 /* Allow to extend selection with mouse scrollwheel. Bug 710612 */
8446 if (gtk_gesture_is_active (gesture: priv->drag_gesture))
8447 {
8448 GdkEvent *current_event;
8449 current_event = gtk_event_controller_get_current_event (GTK_EVENT_CONTROLLER (priv->drag_gesture));
8450 if (current_event != NULL)
8451 {
8452 if (gdk_event_get_event_type (event: current_event) == GDK_SCROLL)
8453 move_mark_to_pointer_and_scroll (text_view, mark_name: "insert");
8454 }
8455 }
8456
8457 /* Finally we update the IM cursor location again, to ensure any
8458 * changes made by the validation are pushed through.
8459 */
8460 gtk_text_view_update_im_spot_location (text_view);
8461
8462 gtk_text_view_update_handles (text_view);
8463
8464 if (priv->anchored_children.length > 0)
8465 gtk_widget_queue_allocate (GTK_WIDGET (text_view));
8466 else
8467 gtk_widget_queue_draw (GTK_WIDGET (text_view));
8468
8469 DV(g_print(">End scroll offset changed handler ("G_STRLOC")\n"));
8470}
8471
8472static void
8473gtk_text_view_commit_handler (GtkIMContext *context,
8474 const char *str,
8475 GtkTextView *text_view)
8476{
8477 gtk_text_view_commit_text (text_view, text: str);
8478 gtk_text_view_reset_blink_time (text_view);
8479 gtk_text_view_pend_cursor_blink (text_view);
8480}
8481
8482static void
8483gtk_text_view_commit_text (GtkTextView *text_view,
8484 const char *str)
8485{
8486 GtkTextViewPrivate *priv;
8487 gboolean had_selection;
8488 GtkTextIter begin, end;
8489 guint length;
8490
8491 priv = text_view->priv;
8492
8493 gtk_text_view_obscure_mouse_cursor (text_view);
8494 gtk_text_buffer_begin_user_action (buffer: get_buffer (text_view));
8495
8496 had_selection = gtk_text_buffer_get_selection_bounds (buffer: get_buffer (text_view), start: &begin, end: &end);
8497 gtk_text_iter_order (first: &begin, second: &end);
8498 length = gtk_text_iter_get_offset (iter: &end) - gtk_text_iter_get_offset (iter: &begin);
8499
8500 if (gtk_text_buffer_delete_selection (buffer: get_buffer (text_view), TRUE, default_editable: priv->editable))
8501 {
8502 /* If something was deleted, create a second group for the insert. This
8503 * ensures that there are two undo operations. One for the deletion, and
8504 * one for the insertion of new text. However, if there is only a single
8505 * character overwritten, that isn't very useful, just keep the single
8506 * undo group.
8507 */
8508 if (length > 1)
8509 {
8510 gtk_text_buffer_end_user_action (buffer: get_buffer (text_view));
8511 gtk_text_buffer_begin_user_action (buffer: get_buffer (text_view));
8512 }
8513 }
8514
8515 if (!strcmp (s1: str, s2: "\n"))
8516 {
8517 if (!gtk_text_buffer_insert_interactive_at_cursor (buffer: get_buffer (text_view), text: "\n", len: 1,
8518 default_editable: priv->editable))
8519 {
8520 gtk_widget_error_bell (GTK_WIDGET (text_view));
8521 }
8522 }
8523 else
8524 {
8525 if (!had_selection && priv->overwrite_mode)
8526 {
8527 GtkTextIter insert;
8528
8529 gtk_text_buffer_get_iter_at_mark (buffer: get_buffer (text_view),
8530 iter: &insert,
8531 mark: gtk_text_buffer_get_insert (buffer: get_buffer (text_view)));
8532 if (!gtk_text_iter_ends_line (iter: &insert))
8533 gtk_text_view_delete_from_cursor (text_view, type: GTK_DELETE_CHARS, count: 1);
8534 }
8535
8536 if (!gtk_text_buffer_insert_interactive_at_cursor (buffer: get_buffer (text_view), text: str, len: -1,
8537 default_editable: priv->editable))
8538 {
8539 gtk_widget_error_bell (GTK_WIDGET (text_view));
8540 }
8541 }
8542
8543 gtk_text_buffer_end_user_action (buffer: get_buffer (text_view));
8544
8545 gtk_text_view_set_virtual_cursor_pos (text_view, x: -1, y: -1);
8546 DV(g_print (G_STRLOC": scrolling onscreen\n"));
8547 gtk_text_view_scroll_mark_onscreen (text_view,
8548 mark: gtk_text_buffer_get_insert (buffer: get_buffer (text_view)));
8549}
8550
8551static void
8552gtk_text_view_preedit_start_handler (GtkIMContext *context,
8553 GtkTextView *self)
8554{
8555 gtk_text_buffer_delete_selection (buffer: self->priv->buffer, TRUE, default_editable: self->priv->editable);
8556}
8557
8558static void
8559gtk_text_view_preedit_changed_handler (GtkIMContext *context,
8560 GtkTextView *text_view)
8561{
8562 GtkTextViewPrivate *priv;
8563 char *str;
8564 PangoAttrList *attrs;
8565 int cursor_pos;
8566 GtkTextIter iter;
8567
8568 priv = text_view->priv;
8569
8570 gtk_text_view_obscure_mouse_cursor (text_view);
8571 gtk_text_buffer_get_iter_at_mark (buffer: priv->buffer, iter: &iter,
8572 mark: gtk_text_buffer_get_insert (buffer: priv->buffer));
8573
8574 /* Keypress events are passed to input method even if cursor position is
8575 * not editable; so beep here if it's multi-key input sequence, input
8576 * method will be reset in when the event is handled by GTK.
8577 */
8578 gtk_im_context_get_preedit_string (context, str: &str, attrs: &attrs, cursor_pos: &cursor_pos);
8579
8580 if (str && str[0] && !gtk_text_iter_can_insert (iter: &iter, default_editability: priv->editable))
8581 {
8582 gtk_widget_error_bell (GTK_WIDGET (text_view));
8583 goto out;
8584 }
8585
8586 g_signal_emit (instance: text_view, signal_id: signals[PREEDIT_CHANGED], detail: 0, str);
8587
8588 if (priv->layout)
8589 gtk_text_layout_set_preedit_string (layout: priv->layout, preedit_string: str, preedit_attrs: attrs, cursor_pos);
8590 if (gtk_widget_has_focus (GTK_WIDGET (text_view)))
8591 gtk_text_view_scroll_mark_onscreen (text_view,
8592 mark: gtk_text_buffer_get_insert (buffer: get_buffer (text_view)));
8593
8594out:
8595 pango_attr_list_unref (list: attrs);
8596 g_free (mem: str);
8597}
8598
8599static gboolean
8600gtk_text_view_retrieve_surrounding_handler (GtkIMContext *context,
8601 GtkTextView *text_view)
8602{
8603 GtkTextIter start;
8604 GtkTextIter end;
8605 GtkTextIter start1;
8606 GtkTextIter end1;
8607 GtkTextIter start2;
8608 GtkTextIter end2;
8609 int cursor_pos;
8610 int anchor_pos;
8611 char *text;
8612 char *pre;
8613 char *sel;
8614 char *post;
8615 gboolean flip;
8616
8617 gtk_text_buffer_get_iter_at_mark (buffer: text_view->priv->buffer, iter: &start,
8618 mark: gtk_text_buffer_get_insert (buffer: text_view->priv->buffer));
8619 gtk_text_buffer_get_iter_at_mark (buffer: text_view->priv->buffer, iter: &end,
8620 mark: gtk_text_buffer_get_selection_bound (buffer: text_view->priv->buffer));
8621
8622 flip = gtk_text_iter_compare (lhs: &start, rhs: &end) < 0;
8623
8624 gtk_text_iter_order (first: &start, second: &end);
8625
8626 start1 = start;
8627 end1 = end;
8628
8629 gtk_text_iter_set_line_offset (iter: &start1, char_on_line: 0);
8630 gtk_text_iter_forward_to_line_end (iter: &end1);
8631
8632 start2 = start;
8633 gtk_text_iter_backward_word_starts (iter: &start2, count: 3);
8634 if (gtk_text_iter_compare (lhs: &start2, rhs: &start1) < 0)
8635 start1 = start2;
8636
8637 end2 = end;
8638 gtk_text_iter_forward_word_ends (iter: &end2, count: 3);
8639 if (gtk_text_iter_compare (lhs: &end2, rhs: &end1) > 0)
8640 end1 = end2;
8641
8642 pre = gtk_text_iter_get_slice (start: &start1, end: &start);
8643 sel = gtk_text_iter_get_slice (start: &start, end: &end);
8644 post = gtk_text_iter_get_slice (start: &end, end: &end1);
8645
8646 if (flip)
8647 {
8648 anchor_pos = strlen (s: pre);
8649 cursor_pos = anchor_pos + strlen (s: sel);
8650 }
8651 else
8652 {
8653 cursor_pos = strlen (s: pre);
8654 anchor_pos = cursor_pos + strlen (s: sel);
8655 }
8656
8657 text = g_strconcat (string1: pre, sel, post, NULL);
8658
8659 g_free (mem: pre);
8660 g_free (mem: sel);
8661 g_free (mem: post);
8662
8663 gtk_im_context_set_surrounding_with_selection (context, text, len: -1, cursor_index: cursor_pos, anchor_index: anchor_pos);
8664
8665 g_free (mem: text);
8666
8667 return TRUE;
8668}
8669
8670static gboolean
8671gtk_text_view_delete_surrounding_handler (GtkIMContext *context,
8672 int offset,
8673 int n_chars,
8674 GtkTextView *text_view)
8675{
8676 GtkTextViewPrivate *priv;
8677 GtkTextIter start;
8678 GtkTextIter end;
8679
8680 priv = text_view->priv;
8681
8682 gtk_text_buffer_get_iter_at_mark (buffer: priv->buffer, iter: &start,
8683 mark: gtk_text_buffer_get_insert (buffer: priv->buffer));
8684 end = start;
8685
8686 gtk_text_iter_forward_chars (iter: &start, count: offset);
8687 gtk_text_iter_forward_chars (iter: &end, count: offset + n_chars);
8688
8689 gtk_text_buffer_delete_interactive (buffer: priv->buffer, start_iter: &start, end_iter: &end,
8690 default_editable: priv->editable);
8691
8692 return TRUE;
8693}
8694
8695static void
8696gtk_text_view_mark_set_handler (GtkTextBuffer *buffer,
8697 const GtkTextIter *location,
8698 GtkTextMark *mark,
8699 gpointer data)
8700{
8701 GtkTextView *text_view = GTK_TEXT_VIEW (data);
8702 gboolean need_reset = FALSE;
8703 gboolean has_selection;
8704
8705 if (mark == gtk_text_buffer_get_insert (buffer))
8706 {
8707 text_view->priv->virtual_cursor_x = -1;
8708 text_view->priv->virtual_cursor_y = -1;
8709 gtk_text_view_update_im_spot_location (text_view);
8710 need_reset = TRUE;
8711 }
8712 else if (mark == gtk_text_buffer_get_selection_bound (buffer))
8713 {
8714 need_reset = TRUE;
8715 }
8716
8717 if (need_reset)
8718 {
8719 gtk_text_view_reset_im_context (text_view);
8720 gtk_text_view_update_handles (text_view);
8721
8722 has_selection = gtk_text_buffer_get_selection_bounds (buffer: get_buffer (text_view), NULL, NULL);
8723 gtk_css_node_set_visible (cssnode: text_view->priv->selection_node, visible: has_selection);
8724 }
8725}
8726
8727static void
8728gtk_text_view_get_virtual_cursor_pos (GtkTextView *text_view,
8729 GtkTextIter *cursor,
8730 int *x,
8731 int *y)
8732{
8733 GtkTextViewPrivate *priv;
8734 GtkTextIter insert;
8735 GdkRectangle pos;
8736
8737 priv = text_view->priv;
8738
8739 if (cursor)
8740 insert = *cursor;
8741 else
8742 gtk_text_buffer_get_iter_at_mark (buffer: get_buffer (text_view), iter: &insert,
8743 mark: gtk_text_buffer_get_insert (buffer: get_buffer (text_view)));
8744
8745 if ((x && priv->virtual_cursor_x == -1) ||
8746 (y && priv->virtual_cursor_y == -1))
8747 gtk_text_layout_get_cursor_locations (layout: priv->layout, iter: &insert, strong_pos: &pos, NULL);
8748
8749 if (x)
8750 {
8751 if (priv->virtual_cursor_x != -1)
8752 *x = priv->virtual_cursor_x;
8753 else
8754 *x = pos.x;
8755 }
8756
8757 if (y)
8758 {
8759 if (priv->virtual_cursor_y != -1)
8760 *y = priv->virtual_cursor_y;
8761 else
8762 *y = pos.y + pos.height / 2;
8763 }
8764}
8765
8766static void
8767gtk_text_view_set_virtual_cursor_pos (GtkTextView *text_view,
8768 int x,
8769 int y)
8770{
8771 GdkRectangle pos;
8772
8773 if (!text_view->priv->layout)
8774 return;
8775
8776 if (x == -1 || y == -1)
8777 gtk_text_view_get_cursor_locations (text_view, NULL, strong: &pos, NULL);
8778
8779 text_view->priv->virtual_cursor_x = (x == -1) ? pos.x : x;
8780 text_view->priv->virtual_cursor_y = (y == -1) ? pos.y + pos.height / 2 : y;
8781}
8782
8783static void
8784hide_selection_bubble (GtkTextView *text_view)
8785{
8786 GtkTextViewPrivate *priv = text_view->priv;
8787
8788 if (priv->selection_bubble && gtk_widget_get_visible (widget: priv->selection_bubble))
8789 gtk_widget_hide (widget: priv->selection_bubble);
8790}
8791
8792static void
8793gtk_text_view_select_all (GtkWidget *widget,
8794 gboolean select)
8795{
8796 GtkTextView *text_view = GTK_TEXT_VIEW (widget);
8797 GtkTextBuffer *buffer;
8798 GtkTextIter start_iter, end_iter, insert;
8799
8800 buffer = text_view->priv->buffer;
8801 if (select)
8802 {
8803 gtk_text_buffer_get_bounds (buffer, start: &start_iter, end: &end_iter);
8804 gtk_text_buffer_select_range (buffer, ins: &start_iter, bound: &end_iter);
8805 }
8806 else
8807 {
8808 gtk_text_buffer_get_iter_at_mark (buffer, iter: &insert,
8809 mark: gtk_text_buffer_get_insert (buffer));
8810 gtk_text_buffer_move_mark_by_name (buffer, name: "selection_bound", where: &insert);
8811 }
8812}
8813
8814
8815static gboolean
8816range_contains_editable_text (const GtkTextIter *start,
8817 const GtkTextIter *end,
8818 gboolean default_editability)
8819{
8820 GtkTextIter iter = *start;
8821
8822 while (gtk_text_iter_compare (lhs: &iter, rhs: end) < 0)
8823 {
8824 if (gtk_text_iter_editable (iter: &iter, default_setting: default_editability))
8825 return TRUE;
8826
8827 gtk_text_iter_forward_to_tag_toggle (iter: &iter, NULL);
8828 }
8829
8830 return FALSE;
8831}
8832
8833static void
8834gtk_text_view_activate_clipboard_cut (GtkWidget *widget,
8835 const char *action_name,
8836 GVariant *parameter)
8837{
8838 GtkTextView *self = GTK_TEXT_VIEW (widget);
8839 g_signal_emit_by_name (instance: self, detailed_signal: "cut-clipboard");
8840 hide_selection_bubble (text_view: self);
8841}
8842
8843static void
8844gtk_text_view_activate_clipboard_copy (GtkWidget *widget,
8845 const char *action_name,
8846 GVariant *parameter)
8847{
8848 GtkTextView *self = GTK_TEXT_VIEW (widget);
8849 g_signal_emit_by_name (instance: self, detailed_signal: "copy-clipboard");
8850 hide_selection_bubble (text_view: self);
8851}
8852
8853static void
8854gtk_text_view_activate_clipboard_paste (GtkWidget *widget,
8855 const char *action_name,
8856 GVariant *parameter)
8857{
8858 GtkTextView *self = GTK_TEXT_VIEW (widget);
8859 g_signal_emit_by_name (instance: self, detailed_signal: "paste-clipboard");
8860 hide_selection_bubble (text_view: self);
8861}
8862
8863static void
8864gtk_text_view_activate_selection_select_all (GtkWidget *widget,
8865 const char *action_name,
8866 GVariant *parameter)
8867{
8868 gtk_text_view_select_all (widget, TRUE);
8869}
8870
8871static void
8872gtk_text_view_activate_selection_delete (GtkWidget *widget,
8873 const char *action_name,
8874 GVariant *parameter)
8875{
8876 GtkTextView *text_view = GTK_TEXT_VIEW (widget);
8877
8878 gtk_text_buffer_delete_selection (buffer: get_buffer (text_view), TRUE,
8879 default_editable: text_view->priv->editable);
8880}
8881
8882static void
8883gtk_text_view_activate_misc_insert_emoji (GtkWidget *widget,
8884 const char *action_name,
8885 GVariant *parameter)
8886{
8887 gtk_text_view_insert_emoji (GTK_TEXT_VIEW (widget));
8888}
8889
8890static void
8891gtk_text_view_update_clipboard_actions (GtkTextView *text_view)
8892{
8893 GtkTextViewPrivate *priv = text_view->priv;
8894 GdkClipboard *clipboard;
8895 gboolean have_selection;
8896 gboolean can_paste, can_insert;
8897 GtkTextIter iter, sel_start, sel_end;
8898
8899 clipboard = gtk_widget_get_clipboard (GTK_WIDGET (text_view));
8900 can_paste = gdk_content_formats_contain_gtype (formats: gdk_clipboard_get_formats (clipboard), G_TYPE_STRING);
8901
8902 have_selection = gtk_text_buffer_get_selection_bounds (buffer: get_buffer (text_view),
8903 start: &sel_start, end: &sel_end);
8904
8905 gtk_text_buffer_get_iter_at_mark (buffer: get_buffer (text_view),
8906 iter: &iter,
8907 mark: gtk_text_buffer_get_insert (buffer: get_buffer (text_view)));
8908
8909 can_insert = gtk_text_iter_can_insert (iter: &iter, default_editability: priv->editable);
8910
8911 gtk_widget_action_set_enabled (GTK_WIDGET (text_view), action_name: "clipboard.cut",
8912 enabled: have_selection &&
8913 range_contains_editable_text (start: &sel_start, end: &sel_end, default_editability: priv->editable));
8914 gtk_widget_action_set_enabled (GTK_WIDGET (text_view), action_name: "clipboard.copy",
8915 enabled: have_selection);
8916 gtk_widget_action_set_enabled (GTK_WIDGET (text_view), action_name: "clipboard.paste",
8917 enabled: can_insert && can_paste);
8918 gtk_widget_action_set_enabled (GTK_WIDGET (text_view), action_name: "selection.delete",
8919 enabled: have_selection &&
8920 range_contains_editable_text (start: &sel_start, end: &sel_end, default_editability: priv->editable));
8921 gtk_widget_action_set_enabled (GTK_WIDGET (text_view), action_name: "selection.select-all",
8922 enabled: gtk_text_buffer_get_char_count (buffer: priv->buffer) > 0);
8923}
8924
8925static void
8926gtk_text_view_update_emoji_action (GtkTextView *text_view)
8927{
8928 gtk_widget_action_set_enabled (GTK_WIDGET (text_view), action_name: "misc.insert-emoji",
8929 enabled: (gtk_text_view_get_input_hints (text_view) & GTK_INPUT_HINT_NO_EMOJI) == 0 &&
8930 text_view->priv->editable);
8931}
8932
8933static GMenuModel *
8934gtk_text_view_get_menu_model (GtkTextView *text_view)
8935{
8936 GtkTextViewPrivate *priv = text_view->priv;
8937 GtkJoinedMenu *joined;
8938 GMenu *menu, *section;
8939 GMenuItem *item;
8940
8941 joined = gtk_joined_menu_new ();
8942
8943 menu = g_menu_new ();
8944
8945 section = g_menu_new ();
8946 item = g_menu_item_new (_("Cu_t"), detailed_action: "clipboard.cut");
8947 g_menu_item_set_attribute (menu_item: item, attribute: "touch-icon", format_string: "s", "edit-cut-symbolic");
8948 g_menu_append_item (menu: section, item);
8949 g_object_unref (object: item);
8950 item = g_menu_item_new (_("_Copy"), detailed_action: "clipboard.copy");
8951 g_menu_item_set_attribute (menu_item: item, attribute: "touch-icon", format_string: "s", "edit-copy-symbolic");
8952 g_menu_append_item (menu: section, item);
8953 g_object_unref (object: item);
8954 item = g_menu_item_new (_("_Paste"), detailed_action: "clipboard.paste");
8955 g_menu_item_set_attribute (menu_item: item, attribute: "touch-icon", format_string: "s", "edit-paste-symbolic");
8956 g_menu_append_item (menu: section, item);
8957 g_object_unref (object: item);
8958 item = g_menu_item_new (_("_Delete"), detailed_action: "selection.delete");
8959 g_menu_item_set_attribute (menu_item: item, attribute: "touch-icon", format_string: "s", "edit-delete-symbolic");
8960 g_menu_append_item (menu: section, item);
8961 g_object_unref (object: item);
8962 g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
8963 g_object_unref (object: section);
8964
8965 section = g_menu_new ();
8966 item = g_menu_item_new (_("_Undo"), detailed_action: "text.undo");
8967 g_menu_item_set_attribute (menu_item: item, attribute: "touch-icon", format_string: "s", "edit-undo-symbolic");
8968 g_menu_append_item (menu: section, item);
8969 g_object_unref (object: item);
8970 item = g_menu_item_new (_("_Redo"), detailed_action: "text.redo");
8971 g_menu_item_set_attribute (menu_item: item, attribute: "touch-icon", format_string: "s", "edit-redo-symbolic");
8972 g_menu_append_item (menu: section, item);
8973 g_object_unref (object: item);
8974 g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
8975 g_object_unref (object: section);
8976
8977 section = g_menu_new ();
8978
8979 item = g_menu_item_new (_("Select _All"), detailed_action: "selection.select-all");
8980 g_menu_item_set_attribute (menu_item: item, attribute: "touch-icon", format_string: "s", "edit-select-all-symbolic");
8981 g_menu_append_item (menu: section, item);
8982 g_object_unref (object: item);
8983
8984 item = g_menu_item_new ( _("Insert _Emoji"), detailed_action: "misc.insert-emoji");
8985 g_menu_item_set_attribute (menu_item: item, attribute: "hidden-when", format_string: "s", "action-disabled");
8986 g_menu_item_set_attribute (menu_item: item, attribute: "touch-icon", format_string: "s", "face-smile-symbolic");
8987 g_menu_append_item (menu: section, item);
8988 g_object_unref (object: item);
8989 g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
8990 g_object_unref (object: section);
8991
8992 gtk_joined_menu_append_menu (self: joined, G_MENU_MODEL (menu));
8993 g_object_unref (object: menu);
8994
8995 if (priv->extra_menu)
8996 gtk_joined_menu_append_menu (self: joined, model: priv->extra_menu);
8997
8998 return G_MENU_MODEL (joined);
8999}
9000
9001static void
9002gtk_text_view_do_popup (GtkTextView *text_view,
9003 GdkEvent *trigger_event)
9004{
9005 GtkTextViewPrivate *priv = text_view->priv;
9006
9007 if (!gtk_widget_get_realized (GTK_WIDGET (text_view)))
9008 return;
9009
9010 gtk_text_view_update_clipboard_actions (text_view);
9011
9012 if (!priv->popup_menu)
9013 {
9014 GMenuModel *model;
9015
9016 model = gtk_text_view_get_menu_model (text_view);
9017 priv->popup_menu = gtk_popover_menu_new_from_model (model);
9018 gtk_css_node_insert_after (parent: gtk_widget_get_css_node (GTK_WIDGET (text_view)),
9019 cssnode: gtk_widget_get_css_node (widget: priv->popup_menu),
9020 previous_sibling: priv->text_window->css_node);
9021 gtk_widget_set_parent (widget: priv->popup_menu, GTK_WIDGET (text_view));
9022 gtk_popover_set_position (GTK_POPOVER (priv->popup_menu), position: GTK_POS_BOTTOM);
9023
9024 gtk_popover_set_has_arrow (GTK_POPOVER (priv->popup_menu), FALSE);
9025 gtk_widget_set_halign (widget: priv->popup_menu, align: GTK_ALIGN_START);
9026
9027 g_object_unref (object: model);
9028 }
9029
9030 if (trigger_event && gdk_event_triggers_context_menu (event: trigger_event))
9031 {
9032 GdkDevice *device;
9033 GdkSeat *seat;
9034 GdkRectangle rect = { 0, 0, 1, 1 };
9035
9036 device = gdk_event_get_device (event: trigger_event);
9037 seat = gdk_event_get_seat (event: trigger_event);
9038
9039 if (device == gdk_seat_get_keyboard (seat))
9040 device = gdk_seat_get_pointer (seat);
9041
9042 if (device)
9043 {
9044 GtkNative *native;
9045 GdkSurface *surface;
9046 double px, py;
9047 double nx, ny;
9048
9049 native = gtk_widget_get_native (GTK_WIDGET (text_view));
9050 surface = gtk_native_get_surface (self: native);
9051 gdk_surface_get_device_position (surface, device, x: &px, y: &py, NULL);
9052 gtk_native_get_surface_transform (self: native, x: &nx, y: &ny);
9053
9054 gtk_widget_translate_coordinates (GTK_WIDGET (gtk_widget_get_native (GTK_WIDGET (text_view))),
9055 GTK_WIDGET (text_view),
9056 src_x: px - nx, src_y: py - ny,
9057 dest_x: &px, dest_y: &py);
9058 rect.x = px;
9059 rect.y = py;
9060 }
9061
9062 gtk_popover_set_pointing_to (GTK_POPOVER (priv->popup_menu), rect: &rect);
9063 }
9064 else
9065 {
9066 GtkTextBuffer *buffer;
9067 GtkTextIter iter;
9068 GdkRectangle iter_location;
9069 GdkRectangle visible_rect;
9070 gboolean is_visible;
9071
9072 buffer = get_buffer (text_view);
9073 gtk_text_buffer_get_iter_at_mark (buffer, iter: &iter,
9074 mark: gtk_text_buffer_get_insert (buffer));
9075 gtk_text_view_get_iter_location (text_view, iter: &iter, location: &iter_location);
9076 gtk_text_view_get_visible_rect (text_view, visible_rect: &visible_rect);
9077
9078 is_visible = (iter_location.x + iter_location.width > visible_rect.x &&
9079 iter_location.x < visible_rect.x + visible_rect.width &&
9080 iter_location.y + iter_location.height > visible_rect.y &&
9081 iter_location.y < visible_rect.y + visible_rect.height);
9082
9083 if (is_visible)
9084 {
9085 gtk_text_view_buffer_to_window_coords (text_view,
9086 win: GTK_TEXT_WINDOW_WIDGET,
9087 buffer_x: iter_location.x,
9088 buffer_y: iter_location.y,
9089 window_x: &iter_location.x,
9090 window_y: &iter_location.y);
9091
9092 gtk_popover_set_pointing_to (GTK_POPOVER (priv->popup_menu), rect: &iter_location);
9093 }
9094 }
9095
9096 gtk_popover_popup (GTK_POPOVER (priv->popup_menu));
9097}
9098
9099static void
9100gtk_text_view_popup_menu (GtkWidget *widget,
9101 const char *action_name,
9102 GVariant *parameters)
9103{
9104 gtk_text_view_do_popup (GTK_TEXT_VIEW (widget), NULL);
9105}
9106
9107static void
9108gtk_text_view_get_selection_rect (GtkTextView *text_view,
9109 cairo_rectangle_int_t *rect)
9110{
9111 cairo_rectangle_int_t rect_cursor, rect_bound;
9112 GtkTextIter cursor, bound;
9113 GtkTextBuffer *buffer;
9114 int x1, y1, x2, y2;
9115
9116 buffer = get_buffer (text_view);
9117 gtk_text_buffer_get_iter_at_mark (buffer, iter: &cursor,
9118 mark: gtk_text_buffer_get_insert (buffer));
9119 gtk_text_buffer_get_iter_at_mark (buffer, iter: &bound,
9120 mark: gtk_text_buffer_get_selection_bound (buffer));
9121
9122 gtk_text_view_get_cursor_locations (text_view, iter: &cursor, strong: &rect_cursor, NULL);
9123 gtk_text_view_get_cursor_locations (text_view, iter: &bound, strong: &rect_bound, NULL);
9124
9125 x1 = MIN (rect_cursor.x, rect_bound.x);
9126 x2 = MAX (rect_cursor.x, rect_bound.x);
9127 y1 = MIN (rect_cursor.y, rect_bound.y);
9128 y2 = MAX (rect_cursor.y + rect_cursor.height, rect_bound.y + rect_bound.height);
9129
9130 rect->x = x1;
9131 rect->y = y1;
9132 rect->width = x2 - x1;
9133 rect->height = y2 - y1;
9134}
9135
9136static void
9137show_or_hide_handles (GtkWidget *popover,
9138 GParamSpec *pspec,
9139 GtkTextView *text_view)
9140{
9141 gboolean visible;
9142
9143 visible = gtk_widget_get_visible (widget: popover);
9144 text_view->priv->text_handles_enabled = !visible;
9145 gtk_text_view_update_handles (text_view);
9146}
9147
9148static void
9149append_bubble_item (GtkTextView *text_view,
9150 GtkWidget *toolbar,
9151 GMenuModel *model,
9152 int index)
9153{
9154 GtkWidget *item, *image;
9155 GVariant *att;
9156 const char *icon_name;
9157 const char *action_name;
9158 GMenuModel *link;
9159 gboolean is_toggle_action = FALSE;
9160 GtkActionMuxer *muxer;
9161 gboolean enabled;
9162 const GVariantType *param_type;
9163 const GVariantType *state_type;
9164
9165 link = g_menu_model_get_item_link (model, item_index: index, link: "section");
9166 if (link)
9167 {
9168 int i;
9169 for (i = 0; i < g_menu_model_get_n_items (model: link); i++)
9170 append_bubble_item (text_view, toolbar, model: link, index: i);
9171 g_object_unref (object: link);
9172 return;
9173 }
9174
9175 att = g_menu_model_get_item_attribute_value (model, item_index: index, attribute: "touch-icon", G_VARIANT_TYPE_STRING);
9176 if (att == NULL)
9177 return;
9178
9179 icon_name = g_variant_get_string (value: att, NULL);
9180 g_variant_unref (value: att);
9181
9182 att = g_menu_model_get_item_attribute_value (model, item_index: index, attribute: "action", G_VARIANT_TYPE_STRING);
9183 if (att == NULL)
9184 return;
9185 action_name = g_variant_get_string (value: att, NULL);
9186 g_variant_unref (value: att);
9187
9188 muxer = _gtk_widget_get_action_muxer (GTK_WIDGET (text_view), FALSE);
9189 if (muxer)
9190 {
9191 gtk_action_muxer_query_action (muxer, action_name, enabled: &enabled, parameter_type: &param_type, state_type: &state_type, NULL, NULL);
9192
9193 if (!enabled)
9194 return;
9195
9196 if (param_type == NULL &&
9197 state_type != NULL &&
9198 g_variant_type_equal (type1: state_type, G_VARIANT_TYPE_BOOLEAN))
9199 is_toggle_action = TRUE;
9200 }
9201
9202 if (is_toggle_action)
9203 item = gtk_toggle_button_new ();
9204 else
9205 item = gtk_button_new ();
9206 gtk_widget_set_focus_on_click (widget: item, FALSE);
9207 image = gtk_image_new_from_icon_name (icon_name);
9208 gtk_button_set_child (GTK_BUTTON (item), child: image);
9209 gtk_widget_add_css_class (widget: item, css_class: "image-button");
9210 gtk_actionable_set_action_name (GTK_ACTIONABLE (item), action_name);
9211
9212 gtk_box_append (GTK_BOX (toolbar), child: item);
9213}
9214
9215static gboolean
9216gtk_text_view_selection_bubble_popup_show (gpointer user_data)
9217{
9218 GtkTextView *text_view = user_data;
9219 GtkTextViewPrivate *priv = text_view->priv;
9220 cairo_rectangle_int_t rect;
9221 GtkWidget *box;
9222 GtkWidget *toolbar;
9223 GMenuModel *model;
9224 int i;
9225
9226 gtk_text_view_update_clipboard_actions (text_view);
9227
9228 priv->selection_bubble_timeout_id = 0;
9229
9230 g_clear_pointer (&priv->selection_bubble, gtk_widget_unparent);
9231
9232 priv->selection_bubble = gtk_popover_new ();
9233 gtk_widget_set_parent (widget: priv->selection_bubble, GTK_WIDGET (text_view));
9234 gtk_widget_add_css_class (widget: priv->selection_bubble, css_class: "touch-selection");
9235 gtk_popover_set_position (GTK_POPOVER (priv->selection_bubble), position: GTK_POS_BOTTOM);
9236 gtk_popover_set_autohide (GTK_POPOVER (priv->selection_bubble), FALSE);
9237 g_signal_connect (priv->selection_bubble, "notify::visible",
9238 G_CALLBACK (show_or_hide_handles), text_view);
9239
9240 box = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 5);
9241 gtk_widget_set_margin_start (widget: box, margin: 10);
9242 gtk_widget_set_margin_end (widget: box, margin: 10);
9243 gtk_widget_set_margin_top (widget: box, margin: 10);
9244 gtk_widget_set_margin_bottom (widget: box, margin: 10);
9245 toolbar = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 0);
9246 gtk_widget_add_css_class (widget: toolbar, css_class: "linked");
9247 gtk_popover_set_child (GTK_POPOVER (priv->selection_bubble), child: box);
9248 gtk_box_append (GTK_BOX (box), child: toolbar);
9249
9250 model = gtk_text_view_get_menu_model (text_view);
9251
9252 for (i = 0; i < g_menu_model_get_n_items (model); i++)
9253 append_bubble_item (text_view, toolbar, model, index: i);
9254
9255 g_object_unref (object: model);
9256
9257 gtk_text_view_get_selection_rect (text_view, rect: &rect);
9258 rect.x -= priv->xoffset;
9259 rect.y -= priv->yoffset;
9260
9261 _text_window_to_widget_coords (text_view, x: &rect.x, y: &rect.y);
9262
9263 rect.x -= 5;
9264 rect.y -= 5;
9265 rect.width += 10;
9266 rect.height += 10;
9267
9268 gtk_popover_set_pointing_to (GTK_POPOVER (priv->selection_bubble), rect: &rect);
9269 gtk_widget_show (widget: priv->selection_bubble);
9270
9271 return G_SOURCE_REMOVE;
9272}
9273
9274static void
9275gtk_text_view_selection_bubble_popup_unset (GtkTextView *text_view)
9276{
9277 GtkTextViewPrivate *priv;
9278
9279 priv = text_view->priv;
9280
9281 if (priv->selection_bubble)
9282 gtk_widget_hide (widget: priv->selection_bubble);
9283
9284 if (priv->selection_bubble_timeout_id)
9285 {
9286 g_source_remove (tag: priv->selection_bubble_timeout_id);
9287 priv->selection_bubble_timeout_id = 0;
9288 }
9289}
9290
9291static void
9292gtk_text_view_selection_bubble_popup_set (GtkTextView *text_view)
9293{
9294 GtkTextViewPrivate *priv;
9295
9296 priv = text_view->priv;
9297
9298 if (priv->selection_bubble_timeout_id)
9299 g_source_remove (tag: priv->selection_bubble_timeout_id);
9300
9301 priv->selection_bubble_timeout_id = g_timeout_add (interval: 50, function: gtk_text_view_selection_bubble_popup_show, data: text_view);
9302 gdk_source_set_static_name_by_id (tag: priv->selection_bubble_timeout_id, name: "[gtk] gtk_text_view_selection_bubble_popup_cb");
9303}
9304
9305/* Child GdkSurfaces */
9306
9307static void
9308node_style_changed_cb (GtkCssNode *node,
9309 GtkCssStyleChange *change,
9310 GtkWidget *widget)
9311{
9312 if (gtk_css_style_change_affects (change, affects: GTK_CSS_AFFECTS_SIZE))
9313 gtk_widget_queue_resize (widget);
9314 else
9315 gtk_widget_queue_draw (widget);
9316}
9317
9318static void
9319update_node_ordering (GtkWidget *widget)
9320{
9321 GtkTextViewPrivate *priv = GTK_TEXT_VIEW (widget)->priv;
9322 GtkCssNode *widget_node, *sibling, *child_node;
9323
9324 if (priv->text_window == NULL)
9325 return;
9326
9327 widget_node = gtk_widget_get_css_node (widget);
9328 sibling = priv->text_window->css_node;
9329
9330 if (priv->left_child)
9331 {
9332 child_node = gtk_widget_get_css_node (GTK_WIDGET (priv->left_child));
9333 gtk_css_node_insert_before (parent: widget_node, cssnode: child_node, next_sibling: sibling);
9334 sibling = child_node;
9335 }
9336
9337 if (priv->top_child)
9338 {
9339 child_node = gtk_widget_get_css_node (GTK_WIDGET (priv->top_child));
9340 gtk_css_node_insert_before (parent: widget_node, cssnode: child_node, next_sibling: sibling);
9341 }
9342
9343 sibling = priv->text_window->css_node;
9344
9345 if (priv->right_child)
9346 {
9347 child_node = gtk_widget_get_css_node (GTK_WIDGET (priv->right_child));
9348 gtk_css_node_insert_after (parent: widget_node, cssnode: child_node, previous_sibling: sibling);
9349 sibling = child_node;
9350 }
9351
9352 if (priv->bottom_child)
9353 {
9354 child_node = gtk_widget_get_css_node (GTK_WIDGET (priv->bottom_child));
9355 gtk_css_node_insert_after (parent: widget_node, cssnode: child_node, previous_sibling: sibling);
9356 }
9357}
9358
9359static GtkTextWindow*
9360text_window_new (GtkWidget *widget)
9361{
9362 GtkTextWindow *win;
9363 GtkCssNode *widget_node;
9364
9365 win = g_slice_new (GtkTextWindow);
9366
9367 win->type = GTK_TEXT_WINDOW_TEXT;
9368 win->widget = widget;
9369 win->allocation.width = 0;
9370 win->allocation.height = 0;
9371 win->allocation.x = 0;
9372 win->allocation.y = 0;
9373
9374 widget_node = gtk_widget_get_css_node (widget);
9375 win->css_node = gtk_css_node_new ();
9376 gtk_css_node_set_parent (cssnode: win->css_node, parent: widget_node);
9377 gtk_css_node_set_state (cssnode: win->css_node, state_flags: gtk_css_node_get_state (cssnode: widget_node));
9378 g_signal_connect_object (instance: win->css_node, detailed_signal: "style-changed", G_CALLBACK (node_style_changed_cb), gobject: widget, connect_flags: 0);
9379 gtk_css_node_set_name (cssnode: win->css_node, name: g_quark_from_static_string (string: "text"));
9380
9381 g_object_unref (object: win->css_node);
9382
9383 return win;
9384}
9385
9386static void
9387text_window_free (GtkTextWindow *win)
9388{
9389 gtk_css_node_set_parent (cssnode: win->css_node, NULL);
9390
9391 g_slice_free (GtkTextWindow, win);
9392}
9393
9394static void
9395text_window_size_allocate (GtkTextWindow *win,
9396 GdkRectangle *rect)
9397{
9398 win->allocation = *rect;
9399}
9400
9401static int
9402text_window_get_width (GtkTextWindow *win)
9403{
9404 return win->allocation.width;
9405}
9406
9407static int
9408text_window_get_height (GtkTextWindow *win)
9409{
9410 return win->allocation.height;
9411}
9412
9413/* Windows */
9414
9415/**
9416 * gtk_text_view_buffer_to_window_coords:
9417 * @text_view: a `GtkTextView`
9418 * @win: a `GtkTextWindowType`
9419 * @buffer_x: buffer x coordinate
9420 * @buffer_y: buffer y coordinate
9421 * @window_x: (out) (optional): window x coordinate return location
9422 * @window_y: (out) (optional): window y coordinate return location
9423 *
9424 * Converts buffer coordinates to window coordinates.
9425 */
9426void
9427gtk_text_view_buffer_to_window_coords (GtkTextView *text_view,
9428 GtkTextWindowType win,
9429 int buffer_x,
9430 int buffer_y,
9431 int *window_x,
9432 int *window_y)
9433{
9434 GtkTextViewPrivate *priv = text_view->priv;
9435
9436 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
9437
9438 buffer_x -= priv->xoffset;
9439 buffer_y -= priv->yoffset;
9440
9441 switch (win)
9442 {
9443 case GTK_TEXT_WINDOW_WIDGET:
9444 buffer_x += priv->border_window_size.left;
9445 buffer_y += priv->border_window_size.top;
9446 break;
9447
9448 case GTK_TEXT_WINDOW_TEXT:
9449 break;
9450
9451 case GTK_TEXT_WINDOW_LEFT:
9452 buffer_x += priv->border_window_size.left;
9453 break;
9454
9455 case GTK_TEXT_WINDOW_RIGHT:
9456 buffer_x -= text_window_get_width (win: priv->text_window);
9457 break;
9458
9459 case GTK_TEXT_WINDOW_TOP:
9460 buffer_y += priv->border_window_size.top;
9461 break;
9462
9463 case GTK_TEXT_WINDOW_BOTTOM:
9464 buffer_y -= text_window_get_height (win: priv->text_window);
9465 break;
9466
9467 default:
9468 g_warning ("%s: Unknown GtkTextWindowType", G_STRFUNC);
9469 break;
9470 }
9471
9472 if (window_x)
9473 *window_x = buffer_x;
9474 if (window_y)
9475 *window_y = buffer_y;
9476}
9477
9478/**
9479 * gtk_text_view_window_to_buffer_coords:
9480 * @text_view: a `GtkTextView`
9481 * @win: a `GtkTextWindowType`
9482 * @window_x: window x coordinate
9483 * @window_y: window y coordinate
9484 * @buffer_x: (out) (optional): buffer x coordinate return location
9485 * @buffer_y: (out) (optional): buffer y coordinate return location
9486 *
9487 * Converts coordinates on the window identified by @win to buffer
9488 * coordinates.
9489 */
9490void
9491gtk_text_view_window_to_buffer_coords (GtkTextView *text_view,
9492 GtkTextWindowType win,
9493 int window_x,
9494 int window_y,
9495 int *buffer_x,
9496 int *buffer_y)
9497{
9498 GtkTextViewPrivate *priv = text_view->priv;
9499
9500 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
9501
9502 switch (win)
9503 {
9504 case GTK_TEXT_WINDOW_WIDGET:
9505 window_x -= priv->border_window_size.left;
9506 window_y -= priv->border_window_size.top;
9507 break;
9508
9509 case GTK_TEXT_WINDOW_TEXT:
9510 break;
9511
9512 case GTK_TEXT_WINDOW_LEFT:
9513 window_x -= priv->border_window_size.left;
9514 break;
9515
9516 case GTK_TEXT_WINDOW_RIGHT:
9517 window_x += text_window_get_width (win: priv->text_window);
9518 break;
9519
9520 case GTK_TEXT_WINDOW_TOP:
9521 window_y -= priv->border_window_size.top;
9522 break;
9523
9524 case GTK_TEXT_WINDOW_BOTTOM:
9525 window_y += text_window_get_height (win: priv->text_window);
9526 break;
9527
9528 default:
9529 g_warning ("%s: Unknown GtkTextWindowType", G_STRFUNC);
9530 break;
9531 }
9532
9533 if (buffer_x)
9534 *buffer_x = window_x + priv->xoffset;
9535 if (buffer_y)
9536 *buffer_y = window_y + priv->yoffset;
9537}
9538
9539/*
9540 * Child widgets
9541 */
9542
9543static AnchoredChild *
9544anchored_child_new (GtkWidget *child,
9545 GtkTextChildAnchor *anchor,
9546 GtkTextLayout *layout)
9547{
9548 AnchoredChild *vc;
9549
9550 vc = g_slice_new0 (AnchoredChild);
9551 vc->link.data = vc;
9552 vc->widget = g_object_ref (child);
9553 vc->anchor = g_object_ref (anchor);
9554 vc->from_top_of_line = 0;
9555 vc->from_left_of_buffer = 0;
9556
9557 g_object_set_qdata (G_OBJECT (child), quark: quark_text_view_child, data: vc);
9558
9559 gtk_text_child_anchor_register_child (anchor, child, layout);
9560
9561 return vc;
9562}
9563
9564static void
9565anchored_child_free (AnchoredChild *child)
9566{
9567 g_assert (child->link.prev == NULL);
9568 g_assert (child->link.next == NULL);
9569
9570 g_object_set_qdata (G_OBJECT (child->widget), quark: quark_text_view_child, NULL);
9571
9572 gtk_text_child_anchor_unregister_child (anchor: child->anchor, child: child->widget);
9573
9574 g_object_unref (object: child->anchor);
9575 g_object_unref (object: child->widget);
9576
9577 g_slice_free (AnchoredChild, child);
9578}
9579
9580static void
9581add_child (GtkTextView *text_view,
9582 AnchoredChild *vc)
9583{
9584 GtkTextViewPrivate *priv = text_view->priv;
9585
9586 g_queue_push_head_link (queue: &priv->anchored_children, link_: &vc->link);
9587 gtk_css_node_set_parent (cssnode: gtk_widget_get_css_node (widget: vc->widget),
9588 parent: priv->text_window->css_node);
9589 gtk_widget_set_parent (widget: vc->widget, GTK_WIDGET (text_view));
9590}
9591
9592/**
9593 * gtk_text_view_add_child_at_anchor:
9594 * @text_view: a `GtkTextView`
9595 * @child: a `GtkWidget`
9596 * @anchor: a `GtkTextChildAnchor` in the `GtkTextBuffer` for @text_view
9597 *
9598 * Adds a child widget in the text buffer, at the given @anchor.
9599 */
9600void
9601gtk_text_view_add_child_at_anchor (GtkTextView *text_view,
9602 GtkWidget *child,
9603 GtkTextChildAnchor *anchor)
9604{
9605 AnchoredChild *vc;
9606
9607 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
9608 g_return_if_fail (GTK_IS_WIDGET (child));
9609 g_return_if_fail (GTK_IS_TEXT_CHILD_ANCHOR (anchor));
9610 g_return_if_fail (gtk_widget_get_parent (child) == NULL);
9611
9612 gtk_text_view_ensure_layout (text_view);
9613
9614 vc = anchored_child_new (child, anchor, layout: text_view->priv->layout);
9615
9616 add_child (text_view, vc);
9617
9618 g_assert (vc->widget == child);
9619 g_assert (gtk_widget_get_parent (child) == GTK_WIDGET (text_view));
9620}
9621
9622static void
9623ensure_child (GtkTextView *text_view,
9624 GtkTextViewChild **child,
9625 GtkTextWindowType window_type)
9626{
9627 GtkCssNode *css_node;
9628 GtkWidget *new_child;
9629
9630 if (*child != NULL)
9631 return;
9632
9633 new_child = gtk_text_view_child_new (window_type);
9634 css_node = gtk_widget_get_css_node (widget: new_child);
9635 gtk_css_node_set_parent (cssnode: css_node,
9636 parent: gtk_widget_get_css_node (GTK_WIDGET (text_view)));
9637 *child = g_object_ref (GTK_TEXT_VIEW_CHILD (new_child));
9638 gtk_widget_set_parent (GTK_WIDGET (new_child), GTK_WIDGET (text_view));
9639}
9640
9641/**
9642 * gtk_text_view_add_overlay:
9643 * @text_view: a `GtkTextView`
9644 * @child: a `GtkWidget`
9645 * @xpos: X position of child in window coordinates
9646 * @ypos: Y position of child in window coordinates
9647 *
9648 * Adds @child at a fixed coordinate in the `GtkTextView`'s text window.
9649 *
9650 * The @xpos and @ypos must be in buffer coordinates (see
9651 * [method@Gtk.TextView.get_iter_location] to convert to
9652 * buffer coordinates).
9653 *
9654 * @child will scroll with the text view.
9655 *
9656 * If instead you want a widget that will not move with the
9657 * `GtkTextView` contents see `GtkOverlay`.
9658 */
9659void
9660gtk_text_view_add_overlay (GtkTextView *text_view,
9661 GtkWidget *child,
9662 int xpos,
9663 int ypos)
9664{
9665 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
9666 g_return_if_fail (GTK_IS_WIDGET (child));
9667 g_return_if_fail (gtk_widget_get_parent (child) == NULL);
9668
9669 ensure_child (text_view,
9670 child: &text_view->priv->center_child,
9671 window_type: GTK_TEXT_WINDOW_TEXT);
9672
9673 gtk_text_view_child_add_overlay (self: text_view->priv->center_child,
9674 widget: child, xpos, ypos);
9675}
9676
9677/**
9678 * gtk_text_view_move_overlay:
9679 * @text_view: a `GtkTextView`
9680 * @child: a widget already added with [method@Gtk.TextView.add_overlay]
9681 * @xpos: new X position in buffer coordinates
9682 * @ypos: new Y position in buffer coordinates
9683 *
9684 * Updates the position of a child.
9685 *
9686 * See [method@Gtk.TextView.add_overlay].
9687 */
9688void
9689gtk_text_view_move_overlay (GtkTextView *text_view,
9690 GtkWidget *child,
9691 int xpos,
9692 int ypos)
9693{
9694 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
9695 g_return_if_fail (GTK_IS_WIDGET (child));
9696 g_return_if_fail (text_view->priv->center_child != NULL);
9697 g_return_if_fail (gtk_widget_get_parent (child) == (GtkWidget *)text_view->priv->center_child);
9698
9699 gtk_text_view_child_move_overlay (self: text_view->priv->center_child,
9700 widget: child, xpos, ypos);
9701}
9702
9703
9704/* Iterator operations */
9705
9706/**
9707 * gtk_text_view_forward_display_line:
9708 * @text_view: a `GtkTextView`
9709 * @iter: a `GtkTextIter`
9710 *
9711 * Moves the given @iter forward by one display (wrapped) line.
9712 *
9713 * A display line is different from a paragraph. Paragraphs are
9714 * separated by newlines or other paragraph separator characters.
9715 * Display lines are created by line-wrapping a paragraph. If
9716 * wrapping is turned off, display lines and paragraphs will be the
9717 * same. Display lines are divided differently for each view, since
9718 * they depend on the view’s width; paragraphs are the same in all
9719 * views, since they depend on the contents of the `GtkTextBuffer`.
9720 *
9721 * Returns: %TRUE if @iter was moved and is not on the end iterator
9722 */
9723gboolean
9724gtk_text_view_forward_display_line (GtkTextView *text_view,
9725 GtkTextIter *iter)
9726{
9727 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), FALSE);
9728 g_return_val_if_fail (iter != NULL, FALSE);
9729
9730 gtk_text_view_ensure_layout (text_view);
9731
9732 return gtk_text_layout_move_iter_to_next_line (layout: text_view->priv->layout, iter);
9733}
9734
9735/**
9736 * gtk_text_view_backward_display_line:
9737 * @text_view: a `GtkTextView`
9738 * @iter: a `GtkTextIter`
9739 *
9740 * Moves the given @iter backward by one display (wrapped) line.
9741 *
9742 * A display line is different from a paragraph. Paragraphs are
9743 * separated by newlines or other paragraph separator characters.
9744 * Display lines are created by line-wrapping a paragraph. If
9745 * wrapping is turned off, display lines and paragraphs will be the
9746 * same. Display lines are divided differently for each view, since
9747 * they depend on the view’s width; paragraphs are the same in all
9748 * views, since they depend on the contents of the `GtkTextBuffer`.
9749 *
9750 * Returns: %TRUE if @iter was moved and is not on the end iterator
9751 */
9752gboolean
9753gtk_text_view_backward_display_line (GtkTextView *text_view,
9754 GtkTextIter *iter)
9755{
9756 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), FALSE);
9757 g_return_val_if_fail (iter != NULL, FALSE);
9758
9759 gtk_text_view_ensure_layout (text_view);
9760
9761 return gtk_text_layout_move_iter_to_previous_line (layout: text_view->priv->layout, iter);
9762}
9763
9764/**
9765 * gtk_text_view_forward_display_line_end:
9766 * @text_view: a `GtkTextView`
9767 * @iter: a `GtkTextIter`
9768 *
9769 * Moves the given @iter forward to the next display line end.
9770 *
9771 * A display line is different from a paragraph. Paragraphs are
9772 * separated by newlines or other paragraph separator characters.
9773 * Display lines are created by line-wrapping a paragraph. If
9774 * wrapping is turned off, display lines and paragraphs will be the
9775 * same. Display lines are divided differently for each view, since
9776 * they depend on the view’s width; paragraphs are the same in all
9777 * views, since they depend on the contents of the `GtkTextBuffer`.
9778 *
9779 * Returns: %TRUE if @iter was moved and is not on the end iterator
9780 */
9781gboolean
9782gtk_text_view_forward_display_line_end (GtkTextView *text_view,
9783 GtkTextIter *iter)
9784{
9785 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), FALSE);
9786 g_return_val_if_fail (iter != NULL, FALSE);
9787
9788 gtk_text_view_ensure_layout (text_view);
9789
9790 return gtk_text_layout_move_iter_to_line_end (layout: text_view->priv->layout, iter, direction: 1);
9791}
9792
9793/**
9794 * gtk_text_view_backward_display_line_start:
9795 * @text_view: a `GtkTextView`
9796 * @iter: a `GtkTextIter`
9797 *
9798 * Moves the given @iter backward to the next display line start.
9799 *
9800 * A display line is different from a paragraph. Paragraphs are
9801 * separated by newlines or other paragraph separator characters.
9802 * Display lines are created by line-wrapping a paragraph. If
9803 * wrapping is turned off, display lines and paragraphs will be the
9804 * same. Display lines are divided differently for each view, since
9805 * they depend on the view’s width; paragraphs are the same in all
9806 * views, since they depend on the contents of the `GtkTextBuffer`.
9807 *
9808 * Returns: %TRUE if @iter was moved and is not on the end iterator
9809 */
9810gboolean
9811gtk_text_view_backward_display_line_start (GtkTextView *text_view,
9812 GtkTextIter *iter)
9813{
9814 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), FALSE);
9815 g_return_val_if_fail (iter != NULL, FALSE);
9816
9817 gtk_text_view_ensure_layout (text_view);
9818
9819 return gtk_text_layout_move_iter_to_line_end (layout: text_view->priv->layout, iter, direction: -1);
9820}
9821
9822/**
9823 * gtk_text_view_starts_display_line:
9824 * @text_view: a `GtkTextView`
9825 * @iter: a `GtkTextIter`
9826 *
9827 * Determines whether @iter is at the start of a display line.
9828 *
9829 * See [method@Gtk.TextView.forward_display_line] for an
9830 * explanation of display lines vs. paragraphs.
9831 *
9832 * Returns: %TRUE if @iter begins a wrapped line
9833 */
9834gboolean
9835gtk_text_view_starts_display_line (GtkTextView *text_view,
9836 const GtkTextIter *iter)
9837{
9838 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), FALSE);
9839 g_return_val_if_fail (iter != NULL, FALSE);
9840
9841 gtk_text_view_ensure_layout (text_view);
9842
9843 return gtk_text_layout_iter_starts_line (layout: text_view->priv->layout, iter);
9844}
9845
9846/**
9847 * gtk_text_view_move_visually:
9848 * @text_view: a `GtkTextView`
9849 * @iter: a `GtkTextIter`
9850 * @count: number of characters to move (negative moves left,
9851 * positive moves right)
9852 *
9853 * Move the iterator a given number of characters visually, treating
9854 * it as the strong cursor position.
9855 *
9856 * If @count is positive, then the new strong cursor position will
9857 * be @count positions to the right of the old cursor position.
9858 * If @count is negative then the new strong cursor position will
9859 * be @count positions to the left of the old cursor position.
9860 *
9861 * In the presence of bi-directional text, the correspondence
9862 * between logical and visual order will depend on the direction
9863 * of the current run, and there may be jumps when the cursor
9864 * is moved off of the end of a run.
9865 *
9866 * Returns: %TRUE if @iter moved and is not on the end iterator
9867 */
9868gboolean
9869gtk_text_view_move_visually (GtkTextView *text_view,
9870 GtkTextIter *iter,
9871 int count)
9872{
9873 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), FALSE);
9874 g_return_val_if_fail (iter != NULL, FALSE);
9875
9876 gtk_text_view_ensure_layout (text_view);
9877
9878 return gtk_text_layout_move_iter_visually (layout: text_view->priv->layout, iter, count);
9879}
9880
9881/**
9882 * gtk_text_view_set_input_purpose: (attributes org.gtk.Method.set_property=input-purpose)
9883 * @text_view: a `GtkTextView`
9884 * @purpose: the purpose
9885 *
9886 * Sets the `input-purpose` of the `GtkTextView`.
9887 *
9888 * The `input-purpose` can be used by on-screen keyboards
9889 * and other input methods to adjust their behaviour.
9890 */
9891void
9892gtk_text_view_set_input_purpose (GtkTextView *text_view,
9893 GtkInputPurpose purpose)
9894
9895{
9896 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
9897
9898 if (gtk_text_view_get_input_purpose (text_view) != purpose)
9899 {
9900 g_object_set (G_OBJECT (text_view->priv->im_context),
9901 first_property_name: "input-purpose", purpose,
9902 NULL);
9903
9904 g_object_notify (G_OBJECT (text_view), property_name: "input-purpose");
9905 }
9906}
9907
9908/**
9909 * gtk_text_view_get_input_purpose: (attributes org.gtk.Method.get_property=input-purpose)
9910 * @text_view: a `GtkTextView`
9911 *
9912 * Gets the `input-purpose` of the `GtkTextView`.
9913 */
9914GtkInputPurpose
9915gtk_text_view_get_input_purpose (GtkTextView *text_view)
9916{
9917 GtkInputPurpose purpose;
9918
9919 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), GTK_INPUT_PURPOSE_FREE_FORM);
9920
9921 g_object_get (G_OBJECT (text_view->priv->im_context),
9922 first_property_name: "input-purpose", &purpose,
9923 NULL);
9924
9925 return purpose;
9926}
9927
9928/**
9929 * gtk_text_view_set_input_hints: (attributes org.gtk.Method.set_property=input-hints)
9930 * @text_view: a `GtkTextView`
9931 * @hints: the hints
9932 *
9933 * Sets the `input-hints` of the `GtkTextView`.
9934 *
9935 * The `input-hints` allow input methods to fine-tune
9936 * their behaviour.
9937 */
9938void
9939gtk_text_view_set_input_hints (GtkTextView *text_view,
9940 GtkInputHints hints)
9941
9942{
9943 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
9944
9945 if (gtk_text_view_get_input_hints (text_view) != hints)
9946 {
9947 g_object_set (G_OBJECT (text_view->priv->im_context),
9948 first_property_name: "input-hints", hints,
9949 NULL);
9950
9951 g_object_notify (G_OBJECT (text_view), property_name: "input-hints");
9952 gtk_text_view_update_emoji_action (text_view);
9953 }
9954}
9955
9956/**
9957 * gtk_text_view_get_input_hints: (attributes org.gtk.Method.get_property=input-hints)
9958 * @text_view: a `GtkTextView`
9959 *
9960 * Gets the `input-hints` of the `GtkTextView`.
9961 */
9962GtkInputHints
9963gtk_text_view_get_input_hints (GtkTextView *text_view)
9964{
9965 GtkInputHints hints;
9966
9967 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), GTK_INPUT_HINT_NONE);
9968
9969 g_object_get (G_OBJECT (text_view->priv->im_context),
9970 first_property_name: "input-hints", &hints,
9971 NULL);
9972
9973 return hints;
9974}
9975
9976/**
9977 * gtk_text_view_set_monospace: (attributes org.gtk.Method.set_property=monospace)
9978 * @text_view: a `GtkTextView`
9979 * @monospace: %TRUE to request monospace styling
9980 *
9981 * Sets whether the `GtkTextView` should display text in
9982 * monospace styling.
9983 */
9984void
9985gtk_text_view_set_monospace (GtkTextView *text_view,
9986 gboolean monospace)
9987{
9988 gboolean has_monospace;
9989
9990 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
9991
9992 has_monospace = gtk_text_view_get_monospace (text_view);
9993
9994 if (has_monospace != monospace)
9995 {
9996 if (monospace)
9997 gtk_widget_add_css_class (GTK_WIDGET (text_view), css_class: "monospace");
9998 else
9999 gtk_widget_remove_css_class (GTK_WIDGET (text_view), css_class: "monospace");
10000
10001 g_object_notify (G_OBJECT (text_view), property_name: "monospace");
10002 }
10003}
10004
10005/**
10006 * gtk_text_view_get_monospace: (attributes org.gtk.Method.get_property=monospace)
10007 * @text_view: a `GtkTextView`
10008 *
10009 * Gets whether the `GtkTextView` uses monospace styling.
10010 *
10011 * Return: %TRUE if monospace fonts are desired
10012 */
10013gboolean
10014gtk_text_view_get_monospace (GtkTextView *text_view)
10015{
10016 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), FALSE);
10017
10018 return gtk_widget_has_css_class (GTK_WIDGET (text_view), css_class: "monospace");
10019}
10020
10021static void
10022emoji_picked (GtkEmojiChooser *chooser,
10023 const char *text,
10024 GtkTextView *self)
10025{
10026 GtkTextBuffer *buffer;
10027
10028 buffer = get_buffer (text_view: self);
10029
10030 gtk_text_buffer_begin_user_action (buffer);
10031 gtk_text_buffer_delete_selection (buffer, TRUE, TRUE);
10032 gtk_text_buffer_insert_at_cursor (buffer, text, len: -1);
10033 gtk_text_buffer_end_user_action (buffer);
10034}
10035
10036static void
10037gtk_text_view_insert_emoji (GtkTextView *text_view)
10038{
10039 GtkWidget *chooser;
10040 GtkTextIter iter;
10041 GdkRectangle rect;
10042 GdkRectangle rect2;
10043 GtkTextBuffer *buffer;
10044
10045 if (gtk_widget_get_ancestor (GTK_WIDGET (text_view), GTK_TYPE_EMOJI_CHOOSER) != NULL)
10046 return;
10047
10048 chooser = GTK_WIDGET (g_object_get_data (G_OBJECT (text_view), "gtk-emoji-chooser"));
10049 if (!chooser)
10050 {
10051 chooser = gtk_emoji_chooser_new ();
10052 g_object_set_data (G_OBJECT (text_view), key: "gtk-emoji-chooser", data: chooser);
10053
10054 gtk_widget_set_parent (widget: chooser, GTK_WIDGET (text_view));
10055 g_signal_connect (chooser, "emoji-picked", G_CALLBACK (emoji_picked), text_view);
10056 g_signal_connect_swapped (chooser, "hide", G_CALLBACK (gtk_widget_grab_focus), text_view);
10057 }
10058
10059 buffer = get_buffer (text_view);
10060
10061 gtk_text_buffer_get_iter_at_mark (buffer, iter: &iter,
10062 mark: gtk_text_buffer_get_insert (buffer));
10063
10064 gtk_text_view_get_iter_location (text_view, iter: &iter, location: (GdkRectangle *) &rect);
10065
10066 rect.width = MAX (rect.width, 1);
10067 rect.height = MAX (rect.height, 1);
10068
10069 gtk_text_view_buffer_to_window_coords (text_view, win: GTK_TEXT_WINDOW_TEXT,
10070 buffer_x: rect.x, buffer_y: rect.y, window_x: &rect.x, window_y: &rect.y);
10071 _text_window_to_widget_coords (text_view, x: &rect.x, y: &rect.y);
10072 gtk_text_view_get_visible_rect (text_view, visible_rect: &rect2);
10073 gtk_text_view_buffer_to_window_coords (text_view, win: GTK_TEXT_WINDOW_TEXT,
10074 buffer_x: rect2.x, buffer_y: rect2.y, window_x: &rect2.x, window_y: &rect2.y);
10075 _text_window_to_widget_coords (text_view, x: &rect2.x, y: &rect2.y);
10076
10077 if (!gdk_rectangle_intersect (src1: &rect2, src2: &rect, dest: &rect))
10078 {
10079 rect.x = rect2.width / 2;
10080 rect.y = rect2.height / 2;
10081 rect.width = 0;
10082 rect.height = 0;
10083 }
10084
10085 gtk_popover_set_pointing_to (GTK_POPOVER (chooser), rect: &rect);
10086
10087 gtk_popover_popup (GTK_POPOVER (chooser));
10088}
10089
10090/**
10091 * gtk_text_view_set_extra_menu: (attributes org.gtk.Method.set_property=extra-menu)
10092 * @text_view: a `GtkTextView`
10093 * @model: (nullable): a `GMenuModel`
10094 *
10095 * Sets a menu model to add when constructing the context
10096 * menu for @text_view.
10097 *
10098 * You can pass %NULL to remove a previously set extra menu.
10099 */
10100void
10101gtk_text_view_set_extra_menu (GtkTextView *text_view,
10102 GMenuModel *model)
10103{
10104 GtkTextViewPrivate *priv = text_view->priv;
10105
10106 g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
10107
10108 if (g_set_object (&priv->extra_menu, model))
10109 {
10110 g_clear_pointer (&priv->popup_menu, gtk_widget_unparent);
10111 g_object_notify (G_OBJECT (text_view), property_name: "extra-menu");
10112 }
10113}
10114
10115/**
10116 * gtk_text_view_get_extra_menu: (attributes org.gtk.Method.get_property=extra-menu)
10117 * @text_view: a `GtkTextView`
10118 *
10119 * Gets the menu model that gets added to the context menu
10120 * or %NULL if none has been set.
10121 *
10122 * Returns: (transfer none): the menu model
10123 */
10124GMenuModel *
10125gtk_text_view_get_extra_menu (GtkTextView *text_view)
10126{
10127 GtkTextViewPrivate *priv = text_view->priv;
10128
10129 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), NULL);
10130
10131 return priv->extra_menu;
10132}
10133
10134static void
10135gtk_text_view_real_undo (GtkWidget *widget,
10136 const char *action_name,
10137 GVariant *parameters)
10138{
10139 GtkTextView *text_view = GTK_TEXT_VIEW (widget);
10140
10141 if (gtk_text_view_get_editable (text_view))
10142 {
10143 gtk_text_buffer_undo (buffer: text_view->priv->buffer);
10144 gtk_text_view_scroll_mark_onscreen (text_view,
10145 mark: gtk_text_buffer_get_insert (buffer: text_view->priv->buffer));
10146 }
10147}
10148
10149static void
10150gtk_text_view_real_redo (GtkWidget *widget,
10151 const char *action_name,
10152 GVariant *parameters)
10153{
10154 GtkTextView *text_view = GTK_TEXT_VIEW (widget);
10155
10156 if (gtk_text_view_get_editable (text_view))
10157 {
10158 gtk_text_buffer_redo (buffer: text_view->priv->buffer);
10159 gtk_text_view_scroll_mark_onscreen (text_view,
10160 mark: gtk_text_buffer_get_insert (buffer: text_view->priv->buffer));
10161 }
10162}
10163
10164static void
10165gtk_text_view_buffer_notify_redo (GtkTextBuffer *buffer,
10166 GParamSpec *pspec,
10167 GtkTextView *view)
10168{
10169 gtk_widget_action_set_enabled (GTK_WIDGET (view),
10170 action_name: "text.redo",
10171 enabled: (gtk_text_view_get_editable (text_view: view) &&
10172 gtk_text_buffer_get_can_redo (buffer)));
10173}
10174
10175static void
10176gtk_text_view_buffer_notify_undo (GtkTextBuffer *buffer,
10177 GParamSpec *pspec,
10178 GtkTextView *view)
10179{
10180 gtk_widget_action_set_enabled (GTK_WIDGET (view),
10181 action_name: "text.undo",
10182 enabled: (gtk_text_view_get_editable (text_view: view) &&
10183 gtk_text_buffer_get_can_undo (buffer)));
10184}
10185
10186/**
10187 * gtk_text_view_get_ltr_context:
10188 * @text_view: a `GtkTextView`
10189 *
10190 * Gets the `PangoContext` that is used for rendering LTR directed
10191 * text layouts.
10192 *
10193 * The context may be replaced when CSS changes occur.
10194 *
10195 * Returns: (transfer none): a `PangoContext`
10196 *
10197 * Since: 4.4
10198 */
10199PangoContext *
10200gtk_text_view_get_ltr_context (GtkTextView *text_view)
10201{
10202 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), NULL);
10203
10204 gtk_text_view_ensure_layout (text_view);
10205
10206 return text_view->priv->layout->ltr_context;
10207}
10208
10209/**
10210 * gtk_text_view_get_rtl_context:
10211 * @text_view: a `GtkTextView`
10212 *
10213 * Gets the `PangoContext` that is used for rendering RTL directed
10214 * text layouts.
10215 *
10216 * The context may be replaced when CSS changes occur.
10217 *
10218 * Returns: (transfer none): a `PangoContext`
10219 *
10220 * Since: 4.4
10221 */
10222PangoContext *
10223gtk_text_view_get_rtl_context (GtkTextView *text_view)
10224{
10225 g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), NULL);
10226
10227 gtk_text_view_ensure_layout (text_view);
10228
10229 return text_view->priv->layout->rtl_context;
10230}
10231
10232GtkEventController *
10233gtk_text_view_get_key_controller (GtkTextView *text_view)
10234{
10235 return text_view->priv->key_controller;
10236}
10237

source code of gtk/gtk/gtktextview.c