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