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