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