1 /* GIMP - The GNU Image Manipulation Program
2 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3 *
4 * GimpTextTool
5 * Copyright (C) 2002-2010 Sven Neumann <sven@gimp.org>
6 * Daniel Eddeland <danedde@svn.gnome.org>
7 * Michael Natterer <mitch@gimp.org>
8 *
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 */
22
23 #include "config.h"
24
25 #include <gegl.h>
26 #include <gtk/gtk.h>
27 #include <gdk/gdkkeysyms.h>
28
29 #include "libgimpwidgets/gimpwidgets.h"
30
31 #include "tools-types.h"
32
33 #include "core/gimp.h"
34 #include "core/gimpdatafactory.h"
35 #include "core/gimpimage.h"
36 #include "core/gimptoolinfo.h"
37
38 #include "text/gimptext.h"
39 #include "text/gimptextlayout.h"
40
41 #include "widgets/gimpdialogfactory.h"
42 #include "widgets/gimpdockcontainer.h"
43 #include "widgets/gimpoverlaybox.h"
44 #include "widgets/gimpoverlayframe.h"
45 #include "widgets/gimptextbuffer.h"
46 #include "widgets/gimptexteditor.h"
47 #include "widgets/gimptextproxy.h"
48 #include "widgets/gimptextstyleeditor.h"
49 #include "widgets/gimpwidgets-utils.h"
50
51 #include "display/gimpdisplay.h"
52 #include "display/gimpdisplayshell.h"
53 #include "display/gimpdisplayshell-transform.h"
54
55 #include "gimptextoptions.h"
56 #include "gimptexttool.h"
57 #include "gimptexttool-editor.h"
58
59 #include "gimp-log.h"
60 #include "gimp-intl.h"
61
62
63 /* local function prototypes */
64
65 static void gimp_text_tool_ensure_proxy (GimpTextTool *text_tool);
66 static void gimp_text_tool_move_cursor (GimpTextTool *text_tool,
67 GtkMovementStep step,
68 gint count,
69 gboolean extend_selection);
70 static void gimp_text_tool_insert_at_cursor (GimpTextTool *text_tool,
71 const gchar *str);
72 static void gimp_text_tool_delete_from_cursor (GimpTextTool *text_tool,
73 GtkDeleteType type,
74 gint count);
75 static void gimp_text_tool_backspace (GimpTextTool *text_tool);
76 static void gimp_text_tool_toggle_overwrite (GimpTextTool *text_tool);
77 static void gimp_text_tool_select_all (GimpTextTool *text_tool,
78 gboolean select);
79 static void gimp_text_tool_change_size (GimpTextTool *text_tool,
80 gdouble amount);
81 static void gimp_text_tool_change_baseline (GimpTextTool *text_tool,
82 gdouble amount);
83 static void gimp_text_tool_change_kerning (GimpTextTool *text_tool,
84 gdouble amount);
85
86 static void gimp_text_tool_options_notify (GimpTextOptions *options,
87 GParamSpec *pspec,
88 GimpTextTool *text_tool);
89 static void gimp_text_tool_editor_dialog (GimpTextTool *text_tool);
90 static void gimp_text_tool_editor_destroy (GtkWidget *dialog,
91 GimpTextTool *text_tool);
92 static void gimp_text_tool_enter_text (GimpTextTool *text_tool,
93 const gchar *str);
94 static void gimp_text_tool_xy_to_iter (GimpTextTool *text_tool,
95 gdouble x,
96 gdouble y,
97 GtkTextIter *iter);
98
99 static void gimp_text_tool_im_preedit_start (GtkIMContext *context,
100 GimpTextTool *text_tool);
101 static void gimp_text_tool_im_preedit_end (GtkIMContext *context,
102 GimpTextTool *text_tool);
103 static void gimp_text_tool_im_preedit_changed (GtkIMContext *context,
104 GimpTextTool *text_tool);
105 static void gimp_text_tool_im_commit (GtkIMContext *context,
106 const gchar *str,
107 GimpTextTool *text_tool);
108 static gboolean gimp_text_tool_im_retrieve_surrounding
109 (GtkIMContext *context,
110 GimpTextTool *text_tool);
111 static gboolean gimp_text_tool_im_delete_surrounding
112 (GtkIMContext *context,
113 gint offset,
114 gint n_chars,
115 GimpTextTool *text_tool);
116
117 static void gimp_text_tool_im_delete_preedit (GimpTextTool *text_tool);
118
119 static void gimp_text_tool_editor_copy_selection_to_clipboard
120 (GimpTextTool *text_tool);
121
122 static void gimp_text_tool_fix_position (GimpTextTool *text_tool,
123 gdouble *x,
124 gdouble *y);
125
126 static void gimp_text_tool_convert_gdkkeyevent (GimpTextTool *text_tool,
127 GdkEventKey *kevent);
128
129
130 /* public functions */
131
132 void
gimp_text_tool_editor_init(GimpTextTool * text_tool)133 gimp_text_tool_editor_init (GimpTextTool *text_tool)
134 {
135 text_tool->im_context = gtk_im_multicontext_new ();
136 text_tool->needs_im_reset = FALSE;
137
138 text_tool->preedit_string = NULL;
139 text_tool->preedit_cursor = 0;
140 text_tool->overwrite_mode = FALSE;
141 text_tool->x_pos = -1;
142
143 g_signal_connect (text_tool->im_context, "preedit-start",
144 G_CALLBACK (gimp_text_tool_im_preedit_start),
145 text_tool);
146 g_signal_connect (text_tool->im_context, "preedit-end",
147 G_CALLBACK (gimp_text_tool_im_preedit_end),
148 text_tool);
149 g_signal_connect (text_tool->im_context, "preedit-changed",
150 G_CALLBACK (gimp_text_tool_im_preedit_changed),
151 text_tool);
152 g_signal_connect (text_tool->im_context, "commit",
153 G_CALLBACK (gimp_text_tool_im_commit),
154 text_tool);
155 g_signal_connect (text_tool->im_context, "retrieve-surrounding",
156 G_CALLBACK (gimp_text_tool_im_retrieve_surrounding),
157 text_tool);
158 g_signal_connect (text_tool->im_context, "delete-surrounding",
159 G_CALLBACK (gimp_text_tool_im_delete_surrounding),
160 text_tool);
161 }
162
163 void
gimp_text_tool_editor_finalize(GimpTextTool * text_tool)164 gimp_text_tool_editor_finalize (GimpTextTool *text_tool)
165 {
166 if (text_tool->im_context)
167 {
168 g_object_unref (text_tool->im_context);
169 text_tool->im_context = NULL;
170 }
171 }
172
173 void
gimp_text_tool_editor_start(GimpTextTool * text_tool)174 gimp_text_tool_editor_start (GimpTextTool *text_tool)
175 {
176 GimpTool *tool = GIMP_TOOL (text_tool);
177 GimpTextOptions *options = GIMP_TEXT_TOOL_GET_OPTIONS (text_tool);
178 GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
179
180 gtk_im_context_set_client_window (text_tool->im_context,
181 gtk_widget_get_window (shell->canvas));
182
183 text_tool->needs_im_reset = TRUE;
184 gimp_text_tool_reset_im_context (text_tool);
185
186 gtk_im_context_focus_in (text_tool->im_context);
187
188 if (options->use_editor)
189 gimp_text_tool_editor_dialog (text_tool);
190
191 g_signal_connect (options, "notify::use-editor",
192 G_CALLBACK (gimp_text_tool_options_notify),
193 text_tool);
194
195 if (! text_tool->style_overlay)
196 {
197 Gimp *gimp = GIMP_CONTEXT (options)->gimp;
198 GimpContainer *fonts;
199 gdouble xres = 1.0;
200 gdouble yres = 1.0;
201
202 text_tool->style_overlay = gimp_overlay_frame_new ();
203 gtk_container_set_border_width (GTK_CONTAINER (text_tool->style_overlay),
204 4);
205 gimp_display_shell_add_overlay (shell,
206 text_tool->style_overlay,
207 0, 0,
208 GIMP_HANDLE_ANCHOR_CENTER, 0, 0);
209 gimp_overlay_box_set_child_opacity (GIMP_OVERLAY_BOX (shell->canvas),
210 text_tool->style_overlay, 0.7);
211
212 if (text_tool->image)
213 gimp_image_get_resolution (text_tool->image, &xres, &yres);
214
215 fonts = gimp_data_factory_get_container (gimp->font_factory);
216
217 text_tool->style_editor = gimp_text_style_editor_new (gimp,
218 text_tool->proxy,
219 text_tool->buffer,
220 fonts,
221 xres, yres);
222 gtk_container_add (GTK_CONTAINER (text_tool->style_overlay),
223 text_tool->style_editor);
224 gtk_widget_show (text_tool->style_editor);
225 }
226
227 gimp_text_tool_editor_position (text_tool);
228 gtk_widget_show (text_tool->style_overlay);
229 }
230
231 void
gimp_text_tool_editor_position(GimpTextTool * text_tool)232 gimp_text_tool_editor_position (GimpTextTool *text_tool)
233 {
234 if (text_tool->style_overlay)
235 {
236 GimpTool *tool = GIMP_TOOL (text_tool);
237 GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
238 GtkRequisition requisition;
239 gdouble x, y;
240
241 gtk_widget_size_request (text_tool->style_overlay, &requisition);
242
243 g_object_get (text_tool->widget,
244 "x1", &x,
245 "y1", &y,
246 NULL);
247
248 gimp_display_shell_move_overlay (shell,
249 text_tool->style_overlay,
250 x, y,
251 GIMP_HANDLE_ANCHOR_SOUTH_WEST, 4, 12);
252
253 if (text_tool->image)
254 {
255 gdouble xres, yres;
256
257 gimp_image_get_resolution (text_tool->image, &xres, &yres);
258
259 g_object_set (text_tool->style_editor,
260 "resolution-x", xres,
261 "resolution-y", yres,
262 NULL);
263 }
264 }
265 }
266
267 void
gimp_text_tool_editor_halt(GimpTextTool * text_tool)268 gimp_text_tool_editor_halt (GimpTextTool *text_tool)
269 {
270 GimpTextOptions *options = GIMP_TEXT_TOOL_GET_OPTIONS (text_tool);
271
272 if (text_tool->style_overlay)
273 {
274 gtk_widget_destroy (text_tool->style_overlay);
275 text_tool->style_overlay = NULL;
276 text_tool->style_editor = NULL;
277 }
278
279 g_signal_handlers_disconnect_by_func (options,
280 gimp_text_tool_options_notify,
281 text_tool);
282
283 if (text_tool->editor_dialog)
284 {
285 g_signal_handlers_disconnect_by_func (text_tool->editor_dialog,
286 gimp_text_tool_editor_destroy,
287 text_tool);
288 gtk_widget_destroy (text_tool->editor_dialog);
289 }
290
291 if (text_tool->proxy_text_view)
292 {
293 gtk_widget_destroy (text_tool->offscreen_window);
294 text_tool->offscreen_window = NULL;
295 text_tool->proxy_text_view = NULL;
296 }
297
298 text_tool->needs_im_reset = TRUE;
299 gimp_text_tool_reset_im_context (text_tool);
300
301 gtk_im_context_focus_out (text_tool->im_context);
302
303 gtk_im_context_set_client_window (text_tool->im_context, NULL);
304 }
305
306 void
gimp_text_tool_editor_button_press(GimpTextTool * text_tool,gdouble x,gdouble y,GimpButtonPressType press_type)307 gimp_text_tool_editor_button_press (GimpTextTool *text_tool,
308 gdouble x,
309 gdouble y,
310 GimpButtonPressType press_type)
311 {
312 GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
313 GtkTextIter cursor;
314 GtkTextIter selection;
315
316 gimp_text_tool_xy_to_iter (text_tool, x, y, &cursor);
317
318 selection = cursor;
319
320 text_tool->select_start_iter = cursor;
321 text_tool->select_words = FALSE;
322 text_tool->select_lines = FALSE;
323
324 switch (press_type)
325 {
326 GtkTextIter start, end;
327
328 case GIMP_BUTTON_PRESS_NORMAL:
329 if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end) ||
330 gtk_text_iter_compare (&start, &cursor))
331 gtk_text_buffer_place_cursor (buffer, &cursor);
332 break;
333
334 case GIMP_BUTTON_PRESS_DOUBLE:
335 text_tool->select_words = TRUE;
336
337 if (! gtk_text_iter_starts_word (&cursor))
338 gtk_text_iter_backward_visible_word_starts (&cursor, 1);
339
340 if (! gtk_text_iter_ends_word (&selection) &&
341 ! gtk_text_iter_forward_visible_word_ends (&selection, 1))
342 gtk_text_iter_forward_to_line_end (&selection);
343
344 gtk_text_buffer_select_range (buffer, &cursor, &selection);
345 break;
346
347 case GIMP_BUTTON_PRESS_TRIPLE:
348 text_tool->select_lines = TRUE;
349
350 gtk_text_iter_set_line_offset (&cursor, 0);
351 gtk_text_iter_forward_to_line_end (&selection);
352
353 gtk_text_buffer_select_range (buffer, &cursor, &selection);
354 break;
355 }
356 }
357
358 void
gimp_text_tool_editor_button_release(GimpTextTool * text_tool)359 gimp_text_tool_editor_button_release (GimpTextTool *text_tool)
360 {
361 gimp_text_tool_editor_copy_selection_to_clipboard (text_tool);
362 }
363
364 void
gimp_text_tool_editor_motion(GimpTextTool * text_tool,gdouble x,gdouble y)365 gimp_text_tool_editor_motion (GimpTextTool *text_tool,
366 gdouble x,
367 gdouble y)
368 {
369 GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
370 GtkTextIter old_cursor;
371 GtkTextIter old_selection;
372 GtkTextIter cursor;
373 GtkTextIter selection;
374
375 gtk_text_buffer_get_iter_at_mark (buffer, &old_cursor,
376 gtk_text_buffer_get_insert (buffer));
377 gtk_text_buffer_get_iter_at_mark (buffer, &old_selection,
378 gtk_text_buffer_get_selection_bound (buffer));
379
380 gimp_text_tool_xy_to_iter (text_tool, x, y, &cursor);
381 selection = text_tool->select_start_iter;
382
383 if (text_tool->select_words ||
384 text_tool->select_lines)
385 {
386 GtkTextIter start;
387 GtkTextIter end;
388
389 if (gtk_text_iter_compare (&cursor, &selection) < 0)
390 {
391 start = cursor;
392 end = selection;
393 }
394 else
395 {
396 start = selection;
397 end = cursor;
398 }
399
400 if (text_tool->select_words)
401 {
402 if (! gtk_text_iter_starts_word (&start))
403 gtk_text_iter_backward_visible_word_starts (&start, 1);
404
405 if (! gtk_text_iter_ends_word (&end) &&
406 ! gtk_text_iter_forward_visible_word_ends (&end, 1))
407 gtk_text_iter_forward_to_line_end (&end);
408 }
409 else if (text_tool->select_lines)
410 {
411 gtk_text_iter_set_line_offset (&start, 0);
412 gtk_text_iter_forward_to_line_end (&end);
413 }
414
415 if (gtk_text_iter_compare (&cursor, &selection) < 0)
416 {
417 cursor = start;
418 selection = end;
419 }
420 else
421 {
422 selection = start;
423 cursor = end;
424 }
425 }
426
427 if (! gtk_text_iter_equal (&cursor, &old_cursor) ||
428 ! gtk_text_iter_equal (&selection, &old_selection))
429 {
430 gimp_draw_tool_pause (GIMP_DRAW_TOOL (text_tool));
431
432 gtk_text_buffer_select_range (buffer, &cursor, &selection);
433
434 gimp_draw_tool_resume (GIMP_DRAW_TOOL (text_tool));
435 }
436 }
437
438 gboolean
gimp_text_tool_editor_key_press(GimpTextTool * text_tool,GdkEventKey * kevent)439 gimp_text_tool_editor_key_press (GimpTextTool *text_tool,
440 GdkEventKey *kevent)
441 {
442 GimpTool *tool = GIMP_TOOL (text_tool);
443 GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
444 GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
445 GtkTextIter cursor;
446 GtkTextIter selection;
447 gboolean retval = TRUE;
448
449 if (! gtk_widget_has_focus (shell->canvas))
450 {
451 /* The focus is in the floating style editor, and the event
452 * was not handled there, focus the canvas.
453 */
454 switch (kevent->keyval)
455 {
456 case GDK_KEY_Tab:
457 case GDK_KEY_KP_Tab:
458 case GDK_KEY_ISO_Left_Tab:
459 case GDK_KEY_Escape:
460 gtk_widget_grab_focus (shell->canvas);
461 return TRUE;
462
463 default:
464 break;
465 }
466 }
467
468 if (gtk_im_context_filter_keypress (text_tool->im_context, kevent))
469 {
470 text_tool->needs_im_reset = TRUE;
471 text_tool->x_pos = -1;
472
473 return TRUE;
474 }
475
476 gimp_text_tool_convert_gdkkeyevent (text_tool, kevent);
477
478 gimp_text_tool_ensure_proxy (text_tool);
479
480 if (gtk_bindings_activate_event (GTK_OBJECT (text_tool->proxy_text_view),
481 kevent))
482 {
483 GIMP_LOG (TEXT_EDITING, "binding handled event");
484
485 return TRUE;
486 }
487
488 gtk_text_buffer_get_iter_at_mark (buffer, &cursor,
489 gtk_text_buffer_get_insert (buffer));
490 gtk_text_buffer_get_iter_at_mark (buffer, &selection,
491 gtk_text_buffer_get_selection_bound (buffer));
492
493 switch (kevent->keyval)
494 {
495 case GDK_KEY_Return:
496 case GDK_KEY_KP_Enter:
497 case GDK_KEY_ISO_Enter:
498 gimp_text_tool_reset_im_context (text_tool);
499 gimp_text_tool_enter_text (text_tool, "\n");
500 break;
501
502 case GDK_KEY_Tab:
503 case GDK_KEY_KP_Tab:
504 case GDK_KEY_ISO_Left_Tab:
505 gimp_text_tool_reset_im_context (text_tool);
506 gimp_text_tool_enter_text (text_tool, "\t");
507 break;
508
509 case GDK_KEY_Escape:
510 gimp_tool_control (GIMP_TOOL (text_tool), GIMP_TOOL_ACTION_HALT,
511 GIMP_TOOL (text_tool)->display);
512 break;
513
514 default:
515 retval = FALSE;
516 }
517
518 text_tool->x_pos = -1;
519
520 return retval;
521 }
522
523 gboolean
gimp_text_tool_editor_key_release(GimpTextTool * text_tool,GdkEventKey * kevent)524 gimp_text_tool_editor_key_release (GimpTextTool *text_tool,
525 GdkEventKey *kevent)
526 {
527 if (gtk_im_context_filter_keypress (text_tool->im_context, kevent))
528 {
529 text_tool->needs_im_reset = TRUE;
530
531 return TRUE;
532 }
533
534 gimp_text_tool_convert_gdkkeyevent (text_tool, kevent);
535
536 gimp_text_tool_ensure_proxy (text_tool);
537
538 if (gtk_bindings_activate_event (GTK_OBJECT (text_tool->proxy_text_view),
539 kevent))
540 {
541 GIMP_LOG (TEXT_EDITING, "binding handled event");
542
543 return TRUE;
544 }
545
546 return FALSE;
547 }
548
549 void
gimp_text_tool_reset_im_context(GimpTextTool * text_tool)550 gimp_text_tool_reset_im_context (GimpTextTool *text_tool)
551 {
552 if (text_tool->needs_im_reset)
553 {
554 text_tool->needs_im_reset = FALSE;
555 gtk_im_context_reset (text_tool->im_context);
556 }
557 }
558
559 void
gimp_text_tool_abort_im_context(GimpTextTool * text_tool)560 gimp_text_tool_abort_im_context (GimpTextTool *text_tool)
561 {
562 GimpTool *tool = GIMP_TOOL (text_tool);
563 GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
564
565 text_tool->needs_im_reset = TRUE;
566 gimp_text_tool_reset_im_context (text_tool);
567
568 /* Making sure preedit text is removed. */
569 gimp_text_tool_im_delete_preedit (text_tool);
570
571 /* the following lines seem to be the only way of really getting
572 * rid of any ongoing preedit state, please somebody tell me
573 * a clean way... mitch
574 */
575
576 gtk_im_context_focus_out (text_tool->im_context);
577 gtk_im_context_set_client_window (text_tool->im_context, NULL);
578
579 g_object_unref (text_tool->im_context);
580 text_tool->im_context = gtk_im_multicontext_new ();
581 gtk_im_context_set_client_window (text_tool->im_context,
582 gtk_widget_get_window (shell->canvas));
583 gtk_im_context_focus_in (text_tool->im_context);
584 g_signal_connect (text_tool->im_context, "preedit-start",
585 G_CALLBACK (gimp_text_tool_im_preedit_start),
586 text_tool);
587 g_signal_connect (text_tool->im_context, "preedit-end",
588 G_CALLBACK (gimp_text_tool_im_preedit_end),
589 text_tool);
590 g_signal_connect (text_tool->im_context, "preedit-changed",
591 G_CALLBACK (gimp_text_tool_im_preedit_changed),
592 text_tool);
593 g_signal_connect (text_tool->im_context, "commit",
594 G_CALLBACK (gimp_text_tool_im_commit),
595 text_tool);
596 g_signal_connect (text_tool->im_context, "retrieve-surrounding",
597 G_CALLBACK (gimp_text_tool_im_retrieve_surrounding),
598 text_tool);
599 g_signal_connect (text_tool->im_context, "delete-surrounding",
600 G_CALLBACK (gimp_text_tool_im_delete_surrounding),
601 text_tool);
602 }
603
604 void
gimp_text_tool_editor_get_cursor_rect(GimpTextTool * text_tool,gboolean overwrite,PangoRectangle * cursor_rect)605 gimp_text_tool_editor_get_cursor_rect (GimpTextTool *text_tool,
606 gboolean overwrite,
607 PangoRectangle *cursor_rect)
608 {
609 GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
610 PangoLayout *layout;
611 PangoContext *context;
612 gint offset_x;
613 gint offset_y;
614 GtkTextIter cursor;
615 gint cursor_index;
616
617 g_return_if_fail (GIMP_IS_TEXT_TOOL (text_tool));
618 g_return_if_fail (cursor_rect != NULL);
619
620 gtk_text_buffer_get_iter_at_mark (buffer, &cursor,
621 gtk_text_buffer_get_insert (buffer));
622 cursor_index = gimp_text_buffer_get_iter_index (text_tool->buffer, &cursor,
623 TRUE);
624
625 gimp_text_tool_ensure_layout (text_tool);
626
627 layout = gimp_text_layout_get_pango_layout (text_tool->layout);
628
629 context = pango_layout_get_context (layout);
630
631 gimp_text_layout_get_offsets (text_tool->layout, &offset_x, &offset_y);
632
633 if (overwrite)
634 {
635 pango_layout_index_to_pos (layout, cursor_index, cursor_rect);
636
637 /* pango_layout_index_to_pos() returns wrong position, if gravity is west
638 * and cursor is at end of line. Avoid this behavior. (pango 1.42.1)
639 */
640 if (pango_context_get_base_gravity (context) == PANGO_GRAVITY_WEST &&
641 cursor_rect->width == 0)
642 pango_layout_get_cursor_pos (layout, cursor_index, cursor_rect, NULL);
643 }
644 else
645 pango_layout_get_cursor_pos (layout, cursor_index, cursor_rect, NULL);
646
647 gimp_text_layout_transform_rect (text_tool->layout, cursor_rect);
648
649 switch (gimp_text_tool_get_direction (text_tool))
650 {
651 case GIMP_TEXT_DIRECTION_LTR:
652 case GIMP_TEXT_DIRECTION_RTL:
653 cursor_rect->x = PANGO_PIXELS (cursor_rect->x) + offset_x;
654 cursor_rect->y = PANGO_PIXELS (cursor_rect->y) + offset_y;
655 cursor_rect->width = PANGO_PIXELS (cursor_rect->width);
656 cursor_rect->height = PANGO_PIXELS (cursor_rect->height);
657 break;
658 case GIMP_TEXT_DIRECTION_TTB_RTL:
659 case GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT:
660 {
661 gint temp, width, height;
662
663 gimp_text_layout_get_size (text_tool->layout, &width, &height);
664
665 temp = cursor_rect->x;
666 cursor_rect->x = width - PANGO_PIXELS (cursor_rect->y) + offset_x;
667 cursor_rect->y = PANGO_PIXELS (temp) + offset_y;
668
669 temp = cursor_rect->width;
670 cursor_rect->width = PANGO_PIXELS (cursor_rect->height);
671 cursor_rect->height = PANGO_PIXELS (temp);
672 }
673 break;
674 case GIMP_TEXT_DIRECTION_TTB_LTR:
675 case GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT:
676 {
677 gint temp, width, height;
678
679 gimp_text_layout_get_size (text_tool->layout, &width, &height);
680
681 temp = cursor_rect->x;
682 cursor_rect->x = PANGO_PIXELS (cursor_rect->y) + offset_x;
683 cursor_rect->y = height - PANGO_PIXELS (temp) + offset_y;
684
685 temp = cursor_rect->width;
686 cursor_rect->width = PANGO_PIXELS (cursor_rect->height);
687 cursor_rect->height = PANGO_PIXELS (temp);
688 }
689 break;
690 }
691 }
692
693 void
gimp_text_tool_editor_update_im_cursor(GimpTextTool * text_tool)694 gimp_text_tool_editor_update_im_cursor (GimpTextTool *text_tool)
695 {
696 GimpDisplayShell *shell;
697 PangoRectangle rect = { 0, };
698 gdouble off_x, off_y;
699
700 g_return_if_fail (GIMP_IS_TEXT_TOOL (text_tool));
701
702 shell = gimp_display_get_shell (GIMP_TOOL (text_tool)->display);
703
704 if (text_tool->text)
705 gimp_text_tool_editor_get_cursor_rect (text_tool,
706 text_tool->overwrite_mode,
707 &rect);
708
709 g_object_get (text_tool->widget,
710 "x1", &off_x,
711 "y1", &off_y,
712 NULL);
713
714 rect.x += off_x;
715 rect.y += off_y;
716
717 gimp_display_shell_transform_xy (shell, rect.x, rect.y, &rect.x, &rect.y);
718
719 gtk_im_context_set_cursor_location (text_tool->im_context,
720 (GdkRectangle *) &rect);
721 }
722
723
724 /* private functions */
725
726 static void
gimp_text_tool_ensure_proxy(GimpTextTool * text_tool)727 gimp_text_tool_ensure_proxy (GimpTextTool *text_tool)
728 {
729 GimpTool *tool = GIMP_TOOL (text_tool);
730 GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
731
732 if (text_tool->offscreen_window &&
733 gtk_widget_get_screen (text_tool->offscreen_window) !=
734 gtk_widget_get_screen (GTK_WIDGET (shell)))
735 {
736 gtk_window_set_screen (GTK_WINDOW (text_tool->offscreen_window),
737 gtk_widget_get_screen (GTK_WIDGET (shell)));
738 gtk_window_move (GTK_WINDOW (text_tool->offscreen_window), -200, -200);
739 }
740 else if (! text_tool->offscreen_window)
741 {
742 text_tool->offscreen_window = gtk_window_new (GTK_WINDOW_POPUP);
743 gtk_window_set_screen (GTK_WINDOW (text_tool->offscreen_window),
744 gtk_widget_get_screen (GTK_WIDGET (shell)));
745 gtk_window_move (GTK_WINDOW (text_tool->offscreen_window), -200, -200);
746 gtk_widget_show (text_tool->offscreen_window);
747
748 text_tool->proxy_text_view = gimp_text_proxy_new ();
749 gtk_container_add (GTK_CONTAINER (text_tool->offscreen_window),
750 text_tool->proxy_text_view);
751 gtk_widget_show (text_tool->proxy_text_view);
752
753 g_signal_connect_swapped (text_tool->proxy_text_view, "move-cursor",
754 G_CALLBACK (gimp_text_tool_move_cursor),
755 text_tool);
756 g_signal_connect_swapped (text_tool->proxy_text_view, "insert-at-cursor",
757 G_CALLBACK (gimp_text_tool_insert_at_cursor),
758 text_tool);
759 g_signal_connect_swapped (text_tool->proxy_text_view, "delete-from-cursor",
760 G_CALLBACK (gimp_text_tool_delete_from_cursor),
761 text_tool);
762 g_signal_connect_swapped (text_tool->proxy_text_view, "backspace",
763 G_CALLBACK (gimp_text_tool_backspace),
764 text_tool);
765 g_signal_connect_swapped (text_tool->proxy_text_view, "cut-clipboard",
766 G_CALLBACK (gimp_text_tool_cut_clipboard),
767 text_tool);
768 g_signal_connect_swapped (text_tool->proxy_text_view, "copy-clipboard",
769 G_CALLBACK (gimp_text_tool_copy_clipboard),
770 text_tool);
771 g_signal_connect_swapped (text_tool->proxy_text_view, "paste-clipboard",
772 G_CALLBACK (gimp_text_tool_paste_clipboard),
773 text_tool);
774 g_signal_connect_swapped (text_tool->proxy_text_view, "toggle-overwrite",
775 G_CALLBACK (gimp_text_tool_toggle_overwrite),
776 text_tool);
777 g_signal_connect_swapped (text_tool->proxy_text_view, "select-all",
778 G_CALLBACK (gimp_text_tool_select_all),
779 text_tool);
780 g_signal_connect_swapped (text_tool->proxy_text_view, "change-size",
781 G_CALLBACK (gimp_text_tool_change_size),
782 text_tool);
783 g_signal_connect_swapped (text_tool->proxy_text_view, "change-baseline",
784 G_CALLBACK (gimp_text_tool_change_baseline),
785 text_tool);
786 g_signal_connect_swapped (text_tool->proxy_text_view, "change-kerning",
787 G_CALLBACK (gimp_text_tool_change_kerning),
788 text_tool);
789 }
790 }
791
792 static void
gimp_text_tool_move_cursor(GimpTextTool * text_tool,GtkMovementStep step,gint count,gboolean extend_selection)793 gimp_text_tool_move_cursor (GimpTextTool *text_tool,
794 GtkMovementStep step,
795 gint count,
796 gboolean extend_selection)
797 {
798 GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
799 GtkTextIter cursor;
800 GtkTextIter selection;
801 GtkTextIter *sel_start;
802 gboolean cancel_selection = FALSE;
803 gint x_pos = -1;
804
805 if (text_tool->pending)
806 {
807 /* If there are any pending text commits, there would be
808 * inconsistencies between the text_tool->buffer and layout.
809 * This could result in crashes. See bug 751333.
810 * Therefore we apply them first.
811 */
812 gimp_text_tool_apply (text_tool, TRUE);
813 }
814 GIMP_LOG (TEXT_EDITING, "%s count = %d, select = %s",
815 g_enum_get_value (g_type_class_ref (GTK_TYPE_MOVEMENT_STEP),
816 step)->value_name,
817 count,
818 extend_selection ? "TRUE" : "FALSE");
819
820 gtk_text_buffer_get_iter_at_mark (buffer, &cursor,
821 gtk_text_buffer_get_insert (buffer));
822 gtk_text_buffer_get_iter_at_mark (buffer, &selection,
823 gtk_text_buffer_get_selection_bound (buffer));
824
825 if (extend_selection)
826 {
827 sel_start = &selection;
828 }
829 else
830 {
831 /* when there is a selection, moving the cursor without
832 * extending it should move the cursor to the end of the
833 * selection that is in moving direction
834 */
835 if (count > 0)
836 gtk_text_iter_order (&selection, &cursor);
837 else
838 gtk_text_iter_order (&cursor, &selection);
839
840 sel_start = &cursor;
841
842 /* if we actually have a selection, just move *to* the beginning/end
843 * of the selection and not *from* there on LOGICAL_POSITIONS
844 * and VISUAL_POSITIONS movement
845 */
846 if (! gtk_text_iter_equal (&cursor, &selection))
847 cancel_selection = TRUE;
848 }
849
850 switch (step)
851 {
852 case GTK_MOVEMENT_LOGICAL_POSITIONS:
853 if (! cancel_selection)
854 gtk_text_iter_forward_visible_cursor_positions (&cursor, count);
855 break;
856
857 case GTK_MOVEMENT_VISUAL_POSITIONS:
858 if (! cancel_selection)
859 {
860 PangoLayout *layout;
861 const gchar *text;
862
863 if (! gimp_text_tool_ensure_layout (text_tool))
864 break;
865
866 layout = gimp_text_layout_get_pango_layout (text_tool->layout);
867 text = pango_layout_get_text (layout);
868
869 while (count != 0)
870 {
871 const gunichar word_joiner = 8288; /*g_utf8_get_char(WORD_JOINER);*/
872 gint index;
873 gint trailing = 0;
874 gint new_index;
875
876 index = gimp_text_buffer_get_iter_index (text_tool->buffer,
877 &cursor, TRUE);
878
879 if (count > 0)
880 {
881 if (g_utf8_get_char (text + index) == word_joiner)
882 pango_layout_move_cursor_visually (layout, TRUE,
883 index, 0, 1,
884 &new_index, &trailing);
885 else
886 new_index = index;
887
888 pango_layout_move_cursor_visually (layout, TRUE,
889 new_index, trailing, 1,
890 &new_index, &trailing);
891 count--;
892 }
893 else
894 {
895 pango_layout_move_cursor_visually (layout, TRUE,
896 index, 0, -1,
897 &new_index, &trailing);
898
899 if (new_index != -1 && new_index != G_MAXINT &&
900 g_utf8_get_char (text + new_index) == word_joiner)
901 {
902 pango_layout_move_cursor_visually (layout, TRUE,
903 new_index, trailing, -1,
904 &new_index, &trailing);
905 }
906
907 count++;
908 }
909
910 if (new_index != G_MAXINT && new_index != -1)
911 index = new_index;
912 else
913 break;
914
915 gimp_text_buffer_get_iter_at_index (text_tool->buffer,
916 &cursor, index, TRUE);
917 gtk_text_iter_forward_chars (&cursor, trailing);
918 }
919 }
920 break;
921
922 case GTK_MOVEMENT_WORDS:
923 if (count < 0)
924 {
925 gtk_text_iter_backward_visible_word_starts (&cursor, -count);
926 }
927 else if (count > 0)
928 {
929 if (! gtk_text_iter_forward_visible_word_ends (&cursor, count))
930 gtk_text_iter_forward_to_line_end (&cursor);
931 }
932 break;
933
934 case GTK_MOVEMENT_DISPLAY_LINES:
935 {
936 GtkTextIter start;
937 GtkTextIter end;
938 gint cursor_index;
939 PangoLayout *layout;
940 PangoLayoutLine *layout_line;
941 PangoLayoutIter *layout_iter;
942 PangoRectangle logical;
943 gint line;
944 gint trailing;
945 gint i;
946
947 gtk_text_buffer_get_bounds (buffer, &start, &end);
948
949 cursor_index = gimp_text_buffer_get_iter_index (text_tool->buffer,
950 &cursor, TRUE);
951
952 if (! gimp_text_tool_ensure_layout (text_tool))
953 break;
954
955 layout = gimp_text_layout_get_pango_layout (text_tool->layout);
956
957 pango_layout_index_to_line_x (layout, cursor_index, FALSE,
958 &line, &x_pos);
959
960 layout_iter = pango_layout_get_iter (layout);
961 for (i = 0; i < line; i++)
962 pango_layout_iter_next_line (layout_iter);
963
964 pango_layout_iter_get_line_extents (layout_iter, NULL, &logical);
965
966 x_pos += logical.x;
967
968 pango_layout_iter_free (layout_iter);
969
970 /* try to go to the remembered x_pos if it exists *and* we are at
971 * the beginning or at the end of the current line
972 */
973 if (text_tool->x_pos != -1 && (x_pos <= logical.x ||
974 x_pos >= logical.x + logical.width))
975 x_pos = text_tool->x_pos;
976
977 line += count;
978
979 if (line < 0)
980 {
981 cursor = start;
982 break;
983 }
984 else if (line >= pango_layout_get_line_count (layout))
985 {
986 cursor = end;
987 break;
988 }
989
990 layout_iter = pango_layout_get_iter (layout);
991 for (i = 0; i < line; i++)
992 pango_layout_iter_next_line (layout_iter);
993
994 layout_line = pango_layout_iter_get_line_readonly (layout_iter);
995 pango_layout_iter_get_line_extents (layout_iter, NULL, &logical);
996
997 pango_layout_iter_free (layout_iter);
998
999 pango_layout_line_x_to_index (layout_line, x_pos - logical.x,
1000 &cursor_index, &trailing);
1001
1002 gimp_text_buffer_get_iter_at_index (text_tool->buffer, &cursor,
1003 cursor_index, TRUE);
1004
1005 while (trailing--)
1006 gtk_text_iter_forward_char (&cursor);
1007 }
1008 break;
1009
1010 case GTK_MOVEMENT_PAGES: /* well... */
1011 case GTK_MOVEMENT_BUFFER_ENDS:
1012 if (count < 0)
1013 {
1014 gtk_text_buffer_get_start_iter (buffer, &cursor);
1015 }
1016 else if (count > 0)
1017 {
1018 gtk_text_buffer_get_end_iter (buffer, &cursor);
1019 }
1020 break;
1021
1022 case GTK_MOVEMENT_PARAGRAPH_ENDS:
1023 if (count < 0)
1024 {
1025 gtk_text_iter_set_line_offset (&cursor, 0);
1026 }
1027 else if (count > 0)
1028 {
1029 if (! gtk_text_iter_ends_line (&cursor))
1030 gtk_text_iter_forward_to_line_end (&cursor);
1031 }
1032 break;
1033
1034 case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
1035 if (count < 0)
1036 {
1037 gtk_text_iter_set_line_offset (&cursor, 0);
1038 }
1039 else if (count > 0)
1040 {
1041 if (! gtk_text_iter_ends_line (&cursor))
1042 gtk_text_iter_forward_to_line_end (&cursor);
1043 }
1044 break;
1045
1046 default:
1047 return;
1048 }
1049
1050 text_tool->x_pos = x_pos;
1051
1052 gimp_draw_tool_pause (GIMP_DRAW_TOOL (text_tool));
1053
1054 gimp_text_tool_reset_im_context (text_tool);
1055
1056 gtk_text_buffer_select_range (buffer, &cursor, sel_start);
1057 gimp_text_tool_editor_copy_selection_to_clipboard (text_tool);
1058
1059 gimp_draw_tool_resume (GIMP_DRAW_TOOL (text_tool));
1060 }
1061
1062 static void
gimp_text_tool_insert_at_cursor(GimpTextTool * text_tool,const gchar * str)1063 gimp_text_tool_insert_at_cursor (GimpTextTool *text_tool,
1064 const gchar *str)
1065 {
1066 gimp_text_buffer_insert (text_tool->buffer, str);
1067 }
1068
1069 static gboolean
is_whitespace(gunichar ch,gpointer user_data)1070 is_whitespace (gunichar ch,
1071 gpointer user_data)
1072 {
1073 return (ch == ' ' || ch == '\t');
1074 }
1075
1076 static gboolean
is_not_whitespace(gunichar ch,gpointer user_data)1077 is_not_whitespace (gunichar ch,
1078 gpointer user_data)
1079 {
1080 return ! is_whitespace (ch, user_data);
1081 }
1082
1083 static gboolean
find_whitepace_region(const GtkTextIter * center,GtkTextIter * start,GtkTextIter * end)1084 find_whitepace_region (const GtkTextIter *center,
1085 GtkTextIter *start,
1086 GtkTextIter *end)
1087 {
1088 *start = *center;
1089 *end = *center;
1090
1091 if (gtk_text_iter_backward_find_char (start, is_not_whitespace, NULL, NULL))
1092 gtk_text_iter_forward_char (start); /* we want the first whitespace... */
1093
1094 if (is_whitespace (gtk_text_iter_get_char (end), NULL))
1095 gtk_text_iter_forward_find_char (end, is_not_whitespace, NULL, NULL);
1096
1097 return ! gtk_text_iter_equal (start, end);
1098 }
1099
1100 static void
gimp_text_tool_delete_from_cursor(GimpTextTool * text_tool,GtkDeleteType type,gint count)1101 gimp_text_tool_delete_from_cursor (GimpTextTool *text_tool,
1102 GtkDeleteType type,
1103 gint count)
1104 {
1105 GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
1106 GtkTextIter cursor;
1107 GtkTextIter end;
1108
1109 GIMP_LOG (TEXT_EDITING, "%s count = %d",
1110 g_enum_get_value (g_type_class_ref (GTK_TYPE_DELETE_TYPE),
1111 type)->value_name,
1112 count);
1113
1114 gimp_text_tool_reset_im_context (text_tool);
1115
1116 gtk_text_buffer_get_iter_at_mark (buffer, &cursor,
1117 gtk_text_buffer_get_insert (buffer));
1118 end = cursor;
1119
1120 switch (type)
1121 {
1122 case GTK_DELETE_CHARS:
1123 if (gtk_text_buffer_get_has_selection (buffer))
1124 {
1125 gtk_text_buffer_delete_selection (buffer, TRUE, TRUE);
1126 return;
1127 }
1128 else
1129 {
1130 gtk_text_iter_forward_cursor_positions (&end, count);
1131 }
1132 break;
1133
1134 case GTK_DELETE_WORD_ENDS:
1135 if (count < 0)
1136 {
1137 if (! gtk_text_iter_starts_word (&cursor))
1138 gtk_text_iter_backward_visible_word_starts (&cursor, 1);
1139 }
1140 else if (count > 0)
1141 {
1142 if (! gtk_text_iter_ends_word (&end) &&
1143 ! gtk_text_iter_forward_visible_word_ends (&end, 1))
1144 gtk_text_iter_forward_to_line_end (&end);
1145 }
1146 break;
1147
1148 case GTK_DELETE_WORDS:
1149 if (! gtk_text_iter_starts_word (&cursor))
1150 gtk_text_iter_backward_visible_word_starts (&cursor, 1);
1151
1152 if (! gtk_text_iter_ends_word (&end) &&
1153 ! gtk_text_iter_forward_visible_word_ends (&end, 1))
1154 gtk_text_iter_forward_to_line_end (&end);
1155 break;
1156
1157 case GTK_DELETE_DISPLAY_LINES:
1158 break;
1159
1160 case GTK_DELETE_DISPLAY_LINE_ENDS:
1161 break;
1162
1163 case GTK_DELETE_PARAGRAPH_ENDS:
1164 if (count < 0)
1165 {
1166 gtk_text_iter_set_line_offset (&cursor, 0);
1167 }
1168 else if (count > 0)
1169 {
1170 if (! gtk_text_iter_ends_line (&end))
1171 gtk_text_iter_forward_to_line_end (&end);
1172 else
1173 gtk_text_iter_forward_cursor_positions (&end, 1);
1174 }
1175 break;
1176
1177 case GTK_DELETE_PARAGRAPHS:
1178 break;
1179
1180 case GTK_DELETE_WHITESPACE:
1181 find_whitepace_region (&cursor, &cursor, &end);
1182 break;
1183 }
1184
1185 if (! gtk_text_iter_equal (&cursor, &end))
1186 {
1187 gtk_text_buffer_delete_interactive (buffer, &cursor, &end, TRUE);
1188 }
1189 }
1190
1191 static void
gimp_text_tool_backspace(GimpTextTool * text_tool)1192 gimp_text_tool_backspace (GimpTextTool *text_tool)
1193 {
1194 GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
1195
1196 gimp_text_tool_reset_im_context (text_tool);
1197
1198 if (gtk_text_buffer_get_has_selection (buffer))
1199 {
1200 gtk_text_buffer_delete_selection (buffer, TRUE, TRUE);
1201 }
1202 else
1203 {
1204 GtkTextIter cursor;
1205
1206 gtk_text_buffer_get_iter_at_mark (buffer, &cursor,
1207 gtk_text_buffer_get_insert (buffer));
1208
1209 gtk_text_buffer_backspace (buffer, &cursor, TRUE, TRUE);
1210 }
1211 }
1212
1213 static void
gimp_text_tool_toggle_overwrite(GimpTextTool * text_tool)1214 gimp_text_tool_toggle_overwrite (GimpTextTool *text_tool)
1215 {
1216 gimp_draw_tool_pause (GIMP_DRAW_TOOL (text_tool));
1217
1218 text_tool->overwrite_mode = ! text_tool->overwrite_mode;
1219
1220 gimp_draw_tool_resume (GIMP_DRAW_TOOL (text_tool));
1221 }
1222
1223 static void
gimp_text_tool_select_all(GimpTextTool * text_tool,gboolean select)1224 gimp_text_tool_select_all (GimpTextTool *text_tool,
1225 gboolean select)
1226 {
1227 GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
1228
1229 gimp_draw_tool_pause (GIMP_DRAW_TOOL (text_tool));
1230
1231 if (select)
1232 {
1233 GtkTextIter start, end;
1234
1235 gtk_text_buffer_get_bounds (buffer, &start, &end);
1236 gtk_text_buffer_select_range (buffer, &start, &end);
1237 }
1238 else
1239 {
1240 GtkTextIter cursor;
1241
1242 gtk_text_buffer_get_iter_at_mark (buffer, &cursor,
1243 gtk_text_buffer_get_insert (buffer));
1244 gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &cursor);
1245 }
1246
1247 gimp_draw_tool_resume (GIMP_DRAW_TOOL (text_tool));
1248 }
1249
1250 static void
gimp_text_tool_change_size(GimpTextTool * text_tool,gdouble amount)1251 gimp_text_tool_change_size (GimpTextTool *text_tool,
1252 gdouble amount)
1253 {
1254 GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
1255 GtkTextIter start;
1256 GtkTextIter end;
1257
1258 if (! gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
1259 {
1260 return;
1261 }
1262
1263 gtk_text_iter_order (&start, &end);
1264 gimp_text_buffer_change_size (text_tool->buffer, &start, &end,
1265 amount * PANGO_SCALE);
1266 }
1267
1268 static void
gimp_text_tool_change_baseline(GimpTextTool * text_tool,gdouble amount)1269 gimp_text_tool_change_baseline (GimpTextTool *text_tool,
1270 gdouble amount)
1271 {
1272 GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
1273 GtkTextIter start;
1274 GtkTextIter end;
1275
1276 if (! gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
1277 {
1278 gtk_text_buffer_get_iter_at_mark (buffer, &start,
1279 gtk_text_buffer_get_insert (buffer));
1280 gtk_text_buffer_get_end_iter (buffer, &end);
1281 }
1282
1283 gtk_text_iter_order (&start, &end);
1284 gimp_text_buffer_change_baseline (text_tool->buffer, &start, &end,
1285 amount * PANGO_SCALE);
1286 }
1287
1288 static void
gimp_text_tool_change_kerning(GimpTextTool * text_tool,gdouble amount)1289 gimp_text_tool_change_kerning (GimpTextTool *text_tool,
1290 gdouble amount)
1291 {
1292 GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
1293 GtkTextIter start;
1294 GtkTextIter end;
1295
1296 if (! gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
1297 {
1298 gtk_text_buffer_get_iter_at_mark (buffer, &start,
1299 gtk_text_buffer_get_insert (buffer));
1300 end = start;
1301 gtk_text_iter_forward_char (&end);
1302 }
1303
1304 gtk_text_iter_order (&start, &end);
1305 gimp_text_buffer_change_kerning (text_tool->buffer, &start, &end,
1306 amount * PANGO_SCALE);
1307 }
1308
1309 static void
gimp_text_tool_options_notify(GimpTextOptions * options,GParamSpec * pspec,GimpTextTool * text_tool)1310 gimp_text_tool_options_notify (GimpTextOptions *options,
1311 GParamSpec *pspec,
1312 GimpTextTool *text_tool)
1313 {
1314 const gchar *param_name = g_param_spec_get_name (pspec);
1315
1316 if (! strcmp (param_name, "use-editor"))
1317 {
1318 if (options->use_editor)
1319 {
1320 gimp_text_tool_editor_dialog (text_tool);
1321 }
1322 else
1323 {
1324 if (text_tool->editor_dialog)
1325 gtk_widget_destroy (text_tool->editor_dialog);
1326 }
1327 }
1328 }
1329
1330 static void
gimp_text_tool_editor_dialog(GimpTextTool * text_tool)1331 gimp_text_tool_editor_dialog (GimpTextTool *text_tool)
1332 {
1333 GimpTool *tool = GIMP_TOOL (text_tool);
1334 GimpTextOptions *options = GIMP_TEXT_TOOL_GET_OPTIONS (text_tool);
1335 GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
1336 GimpImageWindow *image_window;
1337 GimpDialogFactory *dialog_factory;
1338 GtkWindow *parent = NULL;
1339 gdouble xres = 1.0;
1340 gdouble yres = 1.0;
1341
1342 if (text_tool->editor_dialog)
1343 {
1344 gtk_window_present (GTK_WINDOW (text_tool->editor_dialog));
1345 return;
1346 }
1347
1348 image_window = gimp_display_shell_get_window (shell);
1349 dialog_factory = gimp_dock_container_get_dialog_factory (GIMP_DOCK_CONTAINER (image_window));
1350
1351 if (text_tool->image)
1352 gimp_image_get_resolution (text_tool->image, &xres, &yres);
1353
1354 text_tool->editor_dialog =
1355 gimp_text_options_editor_new (parent, tool->tool_info->gimp, options,
1356 gimp_dialog_factory_get_menu_factory (dialog_factory),
1357 _("GIMP Text Editor"),
1358 text_tool->proxy, text_tool->buffer,
1359 xres, yres);
1360
1361 g_object_add_weak_pointer (G_OBJECT (text_tool->editor_dialog),
1362 (gpointer) &text_tool->editor_dialog);
1363
1364 gimp_dialog_factory_add_foreign (dialog_factory,
1365 "gimp-text-tool-dialog",
1366 text_tool->editor_dialog,
1367 gtk_widget_get_screen (GTK_WIDGET (image_window)),
1368 gimp_widget_get_monitor (GTK_WIDGET (image_window)));
1369
1370 g_signal_connect (text_tool->editor_dialog, "destroy",
1371 G_CALLBACK (gimp_text_tool_editor_destroy),
1372 text_tool);
1373
1374 gtk_widget_show (text_tool->editor_dialog);
1375 }
1376
1377 static void
gimp_text_tool_editor_destroy(GtkWidget * dialog,GimpTextTool * text_tool)1378 gimp_text_tool_editor_destroy (GtkWidget *dialog,
1379 GimpTextTool *text_tool)
1380 {
1381 GimpTextOptions *options = GIMP_TEXT_TOOL_GET_OPTIONS (text_tool);
1382
1383 g_object_set (options,
1384 "use-editor", FALSE,
1385 NULL);
1386 }
1387
1388 static void
gimp_text_tool_enter_text(GimpTextTool * text_tool,const gchar * str)1389 gimp_text_tool_enter_text (GimpTextTool *text_tool,
1390 const gchar *str)
1391 {
1392 GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
1393 GList *insert_tags = NULL;
1394 GList *remove_tags = NULL;
1395 gboolean had_selection;
1396
1397 had_selection = gtk_text_buffer_get_has_selection (buffer);
1398
1399 gtk_text_buffer_begin_user_action (buffer);
1400
1401 if (had_selection && text_tool->style_editor)
1402 insert_tags = gimp_text_style_editor_list_tags (GIMP_TEXT_STYLE_EDITOR (text_tool->style_editor),
1403 &remove_tags);
1404
1405 gimp_text_tool_delete_selection (text_tool);
1406
1407 if (! had_selection && text_tool->overwrite_mode && strcmp (str, "\n"))
1408 {
1409 GtkTextIter cursor;
1410
1411 gtk_text_buffer_get_iter_at_mark (buffer, &cursor,
1412 gtk_text_buffer_get_insert (buffer));
1413
1414 if (! gtk_text_iter_ends_line (&cursor))
1415 gimp_text_tool_delete_from_cursor (text_tool, GTK_DELETE_CHARS, 1);
1416 }
1417
1418 if (had_selection && text_tool->style_editor)
1419 gimp_text_buffer_set_insert_tags (text_tool->buffer,
1420 insert_tags, remove_tags);
1421
1422 gimp_text_buffer_insert (text_tool->buffer, str);
1423
1424 gtk_text_buffer_end_user_action (buffer);
1425 }
1426
1427 static void
gimp_text_tool_xy_to_iter(GimpTextTool * text_tool,gdouble x,gdouble y,GtkTextIter * iter)1428 gimp_text_tool_xy_to_iter (GimpTextTool *text_tool,
1429 gdouble x,
1430 gdouble y,
1431 GtkTextIter *iter)
1432 {
1433 PangoLayout *layout;
1434 gint offset_x;
1435 gint offset_y;
1436 gint index;
1437 gint trailing;
1438
1439 gimp_text_tool_ensure_layout (text_tool);
1440
1441 gimp_text_layout_untransform_point (text_tool->layout, &x, &y);
1442
1443 gimp_text_layout_get_offsets (text_tool->layout, &offset_x, &offset_y);
1444 x -= offset_x;
1445 y -= offset_y;
1446
1447 layout = gimp_text_layout_get_pango_layout (text_tool->layout);
1448
1449 gimp_text_tool_fix_position (text_tool, &x, &y);
1450
1451 pango_layout_xy_to_index (layout,
1452 x * PANGO_SCALE,
1453 y * PANGO_SCALE,
1454 &index, &trailing);
1455
1456 gimp_text_buffer_get_iter_at_index (text_tool->buffer, iter, index, TRUE);
1457
1458 if (trailing)
1459 gtk_text_iter_forward_char (iter);
1460 }
1461
1462 static void
gimp_text_tool_im_preedit_start(GtkIMContext * context,GimpTextTool * text_tool)1463 gimp_text_tool_im_preedit_start (GtkIMContext *context,
1464 GimpTextTool *text_tool)
1465 {
1466 GIMP_LOG (TEXT_EDITING, "preedit start");
1467
1468 text_tool->preedit_active = TRUE;
1469 }
1470
1471 static void
gimp_text_tool_im_preedit_end(GtkIMContext * context,GimpTextTool * text_tool)1472 gimp_text_tool_im_preedit_end (GtkIMContext *context,
1473 GimpTextTool *text_tool)
1474 {
1475 gimp_text_tool_delete_selection (text_tool);
1476
1477 text_tool->preedit_active = FALSE;
1478
1479 GIMP_LOG (TEXT_EDITING, "preedit end");
1480 }
1481
1482 static void
gimp_text_tool_im_preedit_changed(GtkIMContext * context,GimpTextTool * text_tool)1483 gimp_text_tool_im_preedit_changed (GtkIMContext *context,
1484 GimpTextTool *text_tool)
1485 {
1486 GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
1487 PangoAttrList *attrs;
1488
1489 GIMP_LOG (TEXT_EDITING, "preedit changed");
1490
1491 gtk_text_buffer_begin_user_action (buffer);
1492
1493 gimp_text_tool_im_delete_preedit (text_tool);
1494
1495 gimp_text_tool_delete_selection (text_tool);
1496
1497 gtk_im_context_get_preedit_string (context,
1498 &text_tool->preedit_string, &attrs,
1499 &text_tool->preedit_cursor);
1500
1501 if (text_tool->preedit_string && *text_tool->preedit_string)
1502 {
1503 PangoAttrIterator *attr_iter;
1504 GtkTextIter iter;
1505 gint i;
1506
1507 /* Save the preedit start position. */
1508 gtk_text_buffer_get_iter_at_mark (buffer, &iter,
1509 gtk_text_buffer_get_insert (buffer));
1510 text_tool->preedit_start = gtk_text_buffer_create_mark (buffer,
1511 "preedit-start",
1512 &iter, TRUE);
1513
1514 /* Loop through chunks of preedit text with different attributes. */
1515 attr_iter = pango_attr_list_get_iterator (attrs);
1516 do
1517 {
1518 gint attr_start;
1519 gint attr_end;
1520
1521 pango_attr_iterator_range (attr_iter, &attr_start, &attr_end);
1522 if (attr_start < strlen (text_tool->preedit_string))
1523 {
1524 GSList *attrs_pos;
1525 GtkTextMark *start_mark;
1526 GtkTextIter start;
1527 GtkTextIter end;
1528
1529 gtk_text_buffer_get_iter_at_mark (buffer, &start,
1530 gtk_text_buffer_get_insert (buffer));
1531 start_mark = gtk_text_buffer_create_mark (buffer,
1532 NULL,
1533 &start, TRUE);
1534
1535 gtk_text_buffer_begin_user_action (buffer);
1536
1537 /* Insert the preedit chunk at current cursor position. */
1538 gtk_text_buffer_insert_at_cursor (GTK_TEXT_BUFFER (text_tool->buffer),
1539 text_tool->preedit_string + attr_start,
1540 attr_end - attr_start);
1541 gtk_text_buffer_get_iter_at_mark (buffer, &start,
1542 start_mark);
1543 gtk_text_buffer_delete_mark (buffer, start_mark);
1544 gtk_text_buffer_get_iter_at_mark (buffer, &end,
1545 gtk_text_buffer_get_insert (buffer));
1546
1547 /* Apply text attributes to preedit text. */
1548 attrs_pos = pango_attr_iterator_get_attrs (attr_iter);
1549 while (attrs_pos)
1550 {
1551 PangoAttribute *attr = attrs_pos->data;
1552
1553 if (attr)
1554 {
1555 switch (attr->klass->type)
1556 {
1557 case PANGO_ATTR_UNDERLINE:
1558 gtk_text_buffer_apply_tag (buffer,
1559 text_tool->buffer->preedit_underline_tag,
1560 &start, &end);
1561 break;
1562 case PANGO_ATTR_BACKGROUND:
1563 case PANGO_ATTR_FOREGROUND:
1564 {
1565 PangoAttrColor *color_attr = (PangoAttrColor *) attr;
1566 GimpRGB color;
1567
1568 color.r = (gdouble) color_attr->color.red / 65535.0;
1569 color.g = (gdouble) color_attr->color.green / 65535.0;
1570 color.b = (gdouble) color_attr->color.blue / 65535.0;
1571
1572 if (attr->klass->type == PANGO_ATTR_BACKGROUND)
1573 {
1574 gimp_text_buffer_set_preedit_bg_color (text_tool->buffer,
1575 &start, &end,
1576 &color);
1577 }
1578 else
1579 {
1580 gimp_text_buffer_set_preedit_color (text_tool->buffer,
1581 &start, &end,
1582 &color);
1583 }
1584 }
1585 break;
1586 default:
1587 /* Unsupported tags. */
1588 break;
1589 }
1590 }
1591
1592 attrs_pos = attrs_pos->next;
1593 }
1594
1595 gtk_text_buffer_end_user_action (buffer);
1596 }
1597 }
1598 while (pango_attr_iterator_next (attr_iter));
1599
1600 /* Save the preedit end position. */
1601 gtk_text_buffer_get_iter_at_mark (buffer, &iter,
1602 gtk_text_buffer_get_insert (buffer));
1603 text_tool->preedit_end = gtk_text_buffer_create_mark (buffer,
1604 "preedit-end",
1605 &iter, FALSE);
1606
1607 /* Move the cursor to the expected location. */
1608 gtk_text_buffer_get_iter_at_mark (buffer, &iter, text_tool->preedit_start);
1609 for (i = 0; i < text_tool->preedit_cursor; i++)
1610 gtk_text_iter_forward_char (&iter);
1611 gtk_text_buffer_place_cursor (buffer, &iter);
1612
1613 pango_attr_iterator_destroy (attr_iter);
1614 }
1615
1616 pango_attr_list_unref (attrs);
1617
1618 gtk_text_buffer_end_user_action (buffer);
1619 }
1620
1621 static void
gimp_text_tool_im_commit(GtkIMContext * context,const gchar * str,GimpTextTool * text_tool)1622 gimp_text_tool_im_commit (GtkIMContext *context,
1623 const gchar *str,
1624 GimpTextTool *text_tool)
1625 {
1626 gboolean preedit_active = text_tool->preedit_active;
1627
1628 gimp_text_tool_im_delete_preedit (text_tool);
1629
1630 /* Some IMEs would emit a preedit-commit before preedit-end.
1631 * To keep undo consistency, we fake and end then immediate restart of
1632 * preediting.
1633 */
1634 if (preedit_active)
1635 gimp_text_tool_im_preedit_end (context, text_tool);
1636
1637 gimp_text_tool_enter_text (text_tool, str);
1638
1639 if (preedit_active)
1640 gimp_text_tool_im_preedit_start (context, text_tool);
1641 }
1642
1643 static gboolean
gimp_text_tool_im_retrieve_surrounding(GtkIMContext * context,GimpTextTool * text_tool)1644 gimp_text_tool_im_retrieve_surrounding (GtkIMContext *context,
1645 GimpTextTool *text_tool)
1646 {
1647 GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
1648 GtkTextIter start;
1649 GtkTextIter end;
1650 gint pos;
1651 gchar *text;
1652
1653 gtk_text_buffer_get_iter_at_mark (buffer, &start,
1654 gtk_text_buffer_get_insert (buffer));
1655 end = start;
1656
1657 pos = gtk_text_iter_get_line_index (&start);
1658 gtk_text_iter_set_line_offset (&start, 0);
1659 gtk_text_iter_forward_to_line_end (&end);
1660
1661 text = gtk_text_iter_get_slice (&start, &end);
1662 gtk_im_context_set_surrounding (context, text, -1, pos);
1663 g_free (text);
1664
1665 return TRUE;
1666 }
1667
1668 static gboolean
gimp_text_tool_im_delete_surrounding(GtkIMContext * context,gint offset,gint n_chars,GimpTextTool * text_tool)1669 gimp_text_tool_im_delete_surrounding (GtkIMContext *context,
1670 gint offset,
1671 gint n_chars,
1672 GimpTextTool *text_tool)
1673 {
1674 GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
1675 GtkTextIter start;
1676 GtkTextIter end;
1677
1678 gtk_text_buffer_get_iter_at_mark (buffer, &start,
1679 gtk_text_buffer_get_insert (buffer));
1680 end = start;
1681
1682 gtk_text_iter_forward_chars (&start, offset);
1683 gtk_text_iter_forward_chars (&end, offset + n_chars);
1684
1685 gtk_text_buffer_delete_interactive (buffer, &start, &end, TRUE);
1686
1687 return TRUE;
1688 }
1689
1690 static void
gimp_text_tool_im_delete_preedit(GimpTextTool * text_tool)1691 gimp_text_tool_im_delete_preedit (GimpTextTool *text_tool)
1692 {
1693 if (text_tool->preedit_string)
1694 {
1695 if (*text_tool->preedit_string)
1696 {
1697 GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
1698 GtkTextIter start;
1699 GtkTextIter end;
1700
1701 gtk_text_buffer_get_iter_at_mark (buffer, &start,
1702 text_tool->preedit_start);
1703 gtk_text_buffer_get_iter_at_mark (buffer, &end,
1704 text_tool->preedit_end);
1705
1706 gtk_text_buffer_delete_interactive (buffer, &start, &end, TRUE);
1707
1708 gtk_text_buffer_delete_mark (buffer, text_tool->preedit_start);
1709 gtk_text_buffer_delete_mark (buffer, text_tool->preedit_end);
1710 text_tool->preedit_start = NULL;
1711 text_tool->preedit_end = NULL;
1712 }
1713
1714 g_clear_pointer (&text_tool->preedit_string, g_free);
1715 }
1716 }
1717
1718 static void
gimp_text_tool_editor_copy_selection_to_clipboard(GimpTextTool * text_tool)1719 gimp_text_tool_editor_copy_selection_to_clipboard (GimpTextTool *text_tool)
1720 {
1721 GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
1722
1723 if (! text_tool->editor_dialog &&
1724 gtk_text_buffer_get_has_selection (buffer))
1725 {
1726 GimpTool *tool = GIMP_TOOL (text_tool);
1727 GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
1728 GtkClipboard *clipboard;
1729
1730 clipboard = gtk_widget_get_clipboard (GTK_WIDGET (shell),
1731 GDK_SELECTION_PRIMARY);
1732
1733 gtk_text_buffer_copy_clipboard (buffer, clipboard);
1734 }
1735 }
1736
1737 static void
gimp_text_tool_fix_position(GimpTextTool * text_tool,gdouble * x,gdouble * y)1738 gimp_text_tool_fix_position (GimpTextTool *text_tool,
1739 gdouble *x,
1740 gdouble *y)
1741 {
1742 gint temp, width, height;
1743
1744 gimp_text_layout_get_size (text_tool->layout, &width, &height);
1745 switch (gimp_text_tool_get_direction(text_tool))
1746 {
1747 case GIMP_TEXT_DIRECTION_RTL:
1748 case GIMP_TEXT_DIRECTION_LTR:
1749 break;
1750 case GIMP_TEXT_DIRECTION_TTB_RTL:
1751 case GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT:
1752 temp = width - *x;
1753 *x = *y;
1754 *y = temp;
1755 break;
1756 case GIMP_TEXT_DIRECTION_TTB_LTR:
1757 case GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT:
1758 temp = *x;
1759 *x = height - *y;
1760 *y = temp;
1761 break;
1762 }
1763 }
1764
1765 static void
gimp_text_tool_convert_gdkkeyevent(GimpTextTool * text_tool,GdkEventKey * kevent)1766 gimp_text_tool_convert_gdkkeyevent (GimpTextTool *text_tool,
1767 GdkEventKey *kevent)
1768 {
1769 switch (gimp_text_tool_get_direction (text_tool))
1770 {
1771 case GIMP_TEXT_DIRECTION_LTR:
1772 case GIMP_TEXT_DIRECTION_RTL:
1773 break;
1774
1775 case GIMP_TEXT_DIRECTION_TTB_RTL:
1776 case GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT:
1777 #ifdef _WIN32
1778 switch (kevent->keyval)
1779 {
1780 case GDK_KEY_Up:
1781 kevent->hardware_keycode = 0x25;/* VK_LEFT */
1782 kevent->keyval = GDK_KEY_Left;
1783 break;
1784 case GDK_KEY_Down:
1785 kevent->hardware_keycode = 0x27;/* VK_RIGHT */
1786 kevent->keyval = GDK_KEY_Right;
1787 break;
1788 case GDK_KEY_Left:
1789 kevent->hardware_keycode = 0x28;/* VK_DOWN */
1790 kevent->keyval = GDK_KEY_Down;
1791 break;
1792 case GDK_KEY_Right:
1793 kevent->hardware_keycode = 0x26;/* VK_UP */
1794 kevent->keyval = GDK_KEY_Up;
1795 break;
1796 }
1797 #else
1798 switch (kevent->keyval)
1799 {
1800 case GDK_KEY_Up:
1801 kevent->hardware_keycode = 113;
1802 kevent->keyval = GDK_KEY_Left;
1803 break;
1804 case GDK_KEY_Down:
1805 kevent->hardware_keycode = 114;
1806 kevent->keyval = GDK_KEY_Right;
1807 break;
1808 case GDK_KEY_Left:
1809 kevent->hardware_keycode = 116;
1810 kevent->keyval = GDK_KEY_Down;
1811 break;
1812 case GDK_KEY_Right:
1813 kevent->hardware_keycode = 111;
1814 kevent->keyval = GDK_KEY_Up;
1815 break;
1816 }
1817 #endif
1818 break;
1819 case GIMP_TEXT_DIRECTION_TTB_LTR:
1820 case GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT:
1821 #ifdef _WIN32
1822 switch (kevent->keyval)
1823 {
1824 case GDK_KEY_Up:
1825 kevent->hardware_keycode = 0x26;/* VK_UP */
1826 kevent->keyval = GDK_KEY_Up;
1827 break;
1828 case GDK_KEY_Down:
1829 kevent->hardware_keycode = 0x28;/* VK_DOWN */
1830 kevent->keyval = GDK_KEY_Down;
1831 break;
1832 case GDK_KEY_Left:
1833 kevent->hardware_keycode = 0x25;/* VK_LEFT */
1834 kevent->keyval = GDK_KEY_Left;
1835 break;
1836 case GDK_KEY_Right:
1837 kevent->hardware_keycode = 0x27;/* VK_RIGHT */
1838 kevent->keyval = GDK_KEY_Right;
1839 break;
1840 }
1841 #else
1842 switch (kevent->keyval)
1843 {
1844 case GDK_KEY_Up:
1845 kevent->hardware_keycode = 114;
1846 kevent->keyval = GDK_KEY_Right;
1847 break;
1848 case GDK_KEY_Down:
1849 kevent->hardware_keycode = 113;
1850 kevent->keyval = GDK_KEY_Left;
1851 break;
1852 case GDK_KEY_Left:
1853 kevent->hardware_keycode = 111;
1854 kevent->keyval = GDK_KEY_Up;
1855 break;
1856 case GDK_KEY_Right:
1857 kevent->hardware_keycode = 116;
1858 kevent->keyval = GDK_KEY_Down;
1859 break;
1860 }
1861 #endif
1862 break;
1863 }
1864 }
1865