diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 03:13:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 03:13:10 +0000 |
commit | 3c57dd931145d43f2b0aef96c4d178135956bf91 (patch) | |
tree | 3de698981e9f0cc2c4f9569b19a5f3595e741f6b /app/tools/gimptexttool-editor.c | |
parent | Initial commit. (diff) | |
download | gimp-3c57dd931145d43f2b0aef96c4d178135956bf91.tar.xz gimp-3c57dd931145d43f2b0aef96c4d178135956bf91.zip |
Adding upstream version 2.10.36.upstream/2.10.36
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'app/tools/gimptexttool-editor.c')
-rw-r--r-- | app/tools/gimptexttool-editor.c | 1871 |
1 files changed, 1871 insertions, 0 deletions
diff --git a/app/tools/gimptexttool-editor.c b/app/tools/gimptexttool-editor.c new file mode 100644 index 0000000..addfdda --- /dev/null +++ b/app/tools/gimptexttool-editor.c @@ -0,0 +1,1871 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpTextTool + * Copyright (C) 2002-2010 Sven Neumann <sven@gimp.org> + * Daniel Eddeland <danedde@svn.gnome.org> + * Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <gegl.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> + +#include "libgimpwidgets/gimpwidgets.h" + +#include "tools-types.h" + +#include "core/gimp.h" +#include "core/gimpdatafactory.h" +#include "core/gimpimage.h" +#include "core/gimptoolinfo.h" + +#include "text/gimptext.h" +#include "text/gimptextlayout.h" + +#include "widgets/gimpdialogfactory.h" +#include "widgets/gimpdockcontainer.h" +#include "widgets/gimpoverlaybox.h" +#include "widgets/gimpoverlayframe.h" +#include "widgets/gimptextbuffer.h" +#include "widgets/gimptexteditor.h" +#include "widgets/gimptextproxy.h" +#include "widgets/gimptextstyleeditor.h" +#include "widgets/gimpwidgets-utils.h" + +#include "display/gimpdisplay.h" +#include "display/gimpdisplayshell.h" +#include "display/gimpdisplayshell-transform.h" + +#include "gimptextoptions.h" +#include "gimptexttool.h" +#include "gimptexttool-editor.h" + +#include "gimp-log.h" +#include "gimp-intl.h" + + +/* local function prototypes */ + +static void gimp_text_tool_ensure_proxy (GimpTextTool *text_tool); +static void gimp_text_tool_move_cursor (GimpTextTool *text_tool, + GtkMovementStep step, + gint count, + gboolean extend_selection); +static void gimp_text_tool_insert_at_cursor (GimpTextTool *text_tool, + const gchar *str); +static void gimp_text_tool_delete_from_cursor (GimpTextTool *text_tool, + GtkDeleteType type, + gint count); +static void gimp_text_tool_backspace (GimpTextTool *text_tool); +static void gimp_text_tool_toggle_overwrite (GimpTextTool *text_tool); +static void gimp_text_tool_select_all (GimpTextTool *text_tool, + gboolean select); +static void gimp_text_tool_change_size (GimpTextTool *text_tool, + gdouble amount); +static void gimp_text_tool_change_baseline (GimpTextTool *text_tool, + gdouble amount); +static void gimp_text_tool_change_kerning (GimpTextTool *text_tool, + gdouble amount); + +static void gimp_text_tool_options_notify (GimpTextOptions *options, + GParamSpec *pspec, + GimpTextTool *text_tool); +static void gimp_text_tool_editor_dialog (GimpTextTool *text_tool); +static void gimp_text_tool_editor_destroy (GtkWidget *dialog, + GimpTextTool *text_tool); +static void gimp_text_tool_enter_text (GimpTextTool *text_tool, + const gchar *str); +static void gimp_text_tool_xy_to_iter (GimpTextTool *text_tool, + gdouble x, + gdouble y, + GtkTextIter *iter); + +static void gimp_text_tool_im_preedit_start (GtkIMContext *context, + GimpTextTool *text_tool); +static void gimp_text_tool_im_preedit_end (GtkIMContext *context, + GimpTextTool *text_tool); +static void gimp_text_tool_im_preedit_changed (GtkIMContext *context, + GimpTextTool *text_tool); +static void gimp_text_tool_im_commit (GtkIMContext *context, + const gchar *str, + GimpTextTool *text_tool); +static gboolean gimp_text_tool_im_retrieve_surrounding + (GtkIMContext *context, + GimpTextTool *text_tool); +static gboolean gimp_text_tool_im_delete_surrounding + (GtkIMContext *context, + gint offset, + gint n_chars, + GimpTextTool *text_tool); + +static void gimp_text_tool_im_delete_preedit (GimpTextTool *text_tool); + +static void gimp_text_tool_editor_copy_selection_to_clipboard + (GimpTextTool *text_tool); + +static void gimp_text_tool_fix_position (GimpTextTool *text_tool, + gdouble *x, + gdouble *y); + +static void gimp_text_tool_convert_gdkkeyevent (GimpTextTool *text_tool, + GdkEventKey *kevent); + + +/* public functions */ + +void +gimp_text_tool_editor_init (GimpTextTool *text_tool) +{ + text_tool->im_context = gtk_im_multicontext_new (); + text_tool->needs_im_reset = FALSE; + + text_tool->preedit_string = NULL; + text_tool->preedit_cursor = 0; + text_tool->overwrite_mode = FALSE; + text_tool->x_pos = -1; + + g_signal_connect (text_tool->im_context, "preedit-start", + G_CALLBACK (gimp_text_tool_im_preedit_start), + text_tool); + g_signal_connect (text_tool->im_context, "preedit-end", + G_CALLBACK (gimp_text_tool_im_preedit_end), + text_tool); + g_signal_connect (text_tool->im_context, "preedit-changed", + G_CALLBACK (gimp_text_tool_im_preedit_changed), + text_tool); + g_signal_connect (text_tool->im_context, "commit", + G_CALLBACK (gimp_text_tool_im_commit), + text_tool); + g_signal_connect (text_tool->im_context, "retrieve-surrounding", + G_CALLBACK (gimp_text_tool_im_retrieve_surrounding), + text_tool); + g_signal_connect (text_tool->im_context, "delete-surrounding", + G_CALLBACK (gimp_text_tool_im_delete_surrounding), + text_tool); +} + +void +gimp_text_tool_editor_finalize (GimpTextTool *text_tool) +{ + if (text_tool->im_context) + { + g_object_unref (text_tool->im_context); + text_tool->im_context = NULL; + } +} + +void +gimp_text_tool_editor_start (GimpTextTool *text_tool) +{ + GimpTool *tool = GIMP_TOOL (text_tool); + GimpTextOptions *options = GIMP_TEXT_TOOL_GET_OPTIONS (text_tool); + GimpDisplayShell *shell = gimp_display_get_shell (tool->display); + + gtk_im_context_set_client_window (text_tool->im_context, + gtk_widget_get_window (shell->canvas)); + + text_tool->needs_im_reset = TRUE; + gimp_text_tool_reset_im_context (text_tool); + + gtk_im_context_focus_in (text_tool->im_context); + + if (options->use_editor) + gimp_text_tool_editor_dialog (text_tool); + + g_signal_connect (options, "notify::use-editor", + G_CALLBACK (gimp_text_tool_options_notify), + text_tool); + + if (! text_tool->style_overlay) + { + Gimp *gimp = GIMP_CONTEXT (options)->gimp; + GimpContainer *fonts; + gdouble xres = 1.0; + gdouble yres = 1.0; + + text_tool->style_overlay = gimp_overlay_frame_new (); + gtk_container_set_border_width (GTK_CONTAINER (text_tool->style_overlay), + 4); + gimp_display_shell_add_overlay (shell, + text_tool->style_overlay, + 0, 0, + GIMP_HANDLE_ANCHOR_CENTER, 0, 0); + gimp_overlay_box_set_child_opacity (GIMP_OVERLAY_BOX (shell->canvas), + text_tool->style_overlay, 0.7); + + if (text_tool->image) + gimp_image_get_resolution (text_tool->image, &xres, &yres); + + fonts = gimp_data_factory_get_container (gimp->font_factory); + + text_tool->style_editor = gimp_text_style_editor_new (gimp, + text_tool->proxy, + text_tool->buffer, + fonts, + xres, yres); + gtk_container_add (GTK_CONTAINER (text_tool->style_overlay), + text_tool->style_editor); + gtk_widget_show (text_tool->style_editor); + } + + gimp_text_tool_editor_position (text_tool); + gtk_widget_show (text_tool->style_overlay); +} + +void +gimp_text_tool_editor_position (GimpTextTool *text_tool) +{ + if (text_tool->style_overlay) + { + GimpTool *tool = GIMP_TOOL (text_tool); + GimpDisplayShell *shell = gimp_display_get_shell (tool->display); + GtkRequisition requisition; + gdouble x, y; + + gtk_widget_size_request (text_tool->style_overlay, &requisition); + + g_object_get (text_tool->widget, + "x1", &x, + "y1", &y, + NULL); + + gimp_display_shell_move_overlay (shell, + text_tool->style_overlay, + x, y, + GIMP_HANDLE_ANCHOR_SOUTH_WEST, 4, 12); + + if (text_tool->image) + { + gdouble xres, yres; + + gimp_image_get_resolution (text_tool->image, &xres, &yres); + + g_object_set (text_tool->style_editor, + "resolution-x", xres, + "resolution-y", yres, + NULL); + } + } +} + +void +gimp_text_tool_editor_halt (GimpTextTool *text_tool) +{ + GimpTextOptions *options = GIMP_TEXT_TOOL_GET_OPTIONS (text_tool); + + if (text_tool->style_overlay) + { + gtk_widget_destroy (text_tool->style_overlay); + text_tool->style_overlay = NULL; + text_tool->style_editor = NULL; + } + + g_signal_handlers_disconnect_by_func (options, + gimp_text_tool_options_notify, + text_tool); + + if (text_tool->editor_dialog) + { + g_signal_handlers_disconnect_by_func (text_tool->editor_dialog, + gimp_text_tool_editor_destroy, + text_tool); + gtk_widget_destroy (text_tool->editor_dialog); + } + + if (text_tool->proxy_text_view) + { + gtk_widget_destroy (text_tool->offscreen_window); + text_tool->offscreen_window = NULL; + text_tool->proxy_text_view = NULL; + } + + text_tool->needs_im_reset = TRUE; + gimp_text_tool_reset_im_context (text_tool); + + gtk_im_context_focus_out (text_tool->im_context); + + gtk_im_context_set_client_window (text_tool->im_context, NULL); +} + +void +gimp_text_tool_editor_button_press (GimpTextTool *text_tool, + gdouble x, + gdouble y, + GimpButtonPressType press_type) +{ + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer); + GtkTextIter cursor; + GtkTextIter selection; + + gimp_text_tool_xy_to_iter (text_tool, x, y, &cursor); + + selection = cursor; + + text_tool->select_start_iter = cursor; + text_tool->select_words = FALSE; + text_tool->select_lines = FALSE; + + switch (press_type) + { + GtkTextIter start, end; + + case GIMP_BUTTON_PRESS_NORMAL: + if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end) || + gtk_text_iter_compare (&start, &cursor)) + gtk_text_buffer_place_cursor (buffer, &cursor); + break; + + case GIMP_BUTTON_PRESS_DOUBLE: + text_tool->select_words = TRUE; + + if (! gtk_text_iter_starts_word (&cursor)) + gtk_text_iter_backward_visible_word_starts (&cursor, 1); + + if (! gtk_text_iter_ends_word (&selection) && + ! gtk_text_iter_forward_visible_word_ends (&selection, 1)) + gtk_text_iter_forward_to_line_end (&selection); + + gtk_text_buffer_select_range (buffer, &cursor, &selection); + break; + + case GIMP_BUTTON_PRESS_TRIPLE: + text_tool->select_lines = TRUE; + + gtk_text_iter_set_line_offset (&cursor, 0); + gtk_text_iter_forward_to_line_end (&selection); + + gtk_text_buffer_select_range (buffer, &cursor, &selection); + break; + } +} + +void +gimp_text_tool_editor_button_release (GimpTextTool *text_tool) +{ + gimp_text_tool_editor_copy_selection_to_clipboard (text_tool); +} + +void +gimp_text_tool_editor_motion (GimpTextTool *text_tool, + gdouble x, + gdouble y) +{ + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer); + GtkTextIter old_cursor; + GtkTextIter old_selection; + GtkTextIter cursor; + GtkTextIter selection; + + gtk_text_buffer_get_iter_at_mark (buffer, &old_cursor, + gtk_text_buffer_get_insert (buffer)); + gtk_text_buffer_get_iter_at_mark (buffer, &old_selection, + gtk_text_buffer_get_selection_bound (buffer)); + + gimp_text_tool_xy_to_iter (text_tool, x, y, &cursor); + selection = text_tool->select_start_iter; + + if (text_tool->select_words || + text_tool->select_lines) + { + GtkTextIter start; + GtkTextIter end; + + if (gtk_text_iter_compare (&cursor, &selection) < 0) + { + start = cursor; + end = selection; + } + else + { + start = selection; + end = cursor; + } + + if (text_tool->select_words) + { + if (! gtk_text_iter_starts_word (&start)) + gtk_text_iter_backward_visible_word_starts (&start, 1); + + if (! gtk_text_iter_ends_word (&end) && + ! gtk_text_iter_forward_visible_word_ends (&end, 1)) + gtk_text_iter_forward_to_line_end (&end); + } + else if (text_tool->select_lines) + { + gtk_text_iter_set_line_offset (&start, 0); + gtk_text_iter_forward_to_line_end (&end); + } + + if (gtk_text_iter_compare (&cursor, &selection) < 0) + { + cursor = start; + selection = end; + } + else + { + selection = start; + cursor = end; + } + } + + if (! gtk_text_iter_equal (&cursor, &old_cursor) || + ! gtk_text_iter_equal (&selection, &old_selection)) + { + gimp_draw_tool_pause (GIMP_DRAW_TOOL (text_tool)); + + gtk_text_buffer_select_range (buffer, &cursor, &selection); + + gimp_draw_tool_resume (GIMP_DRAW_TOOL (text_tool)); + } +} + +gboolean +gimp_text_tool_editor_key_press (GimpTextTool *text_tool, + GdkEventKey *kevent) +{ + GimpTool *tool = GIMP_TOOL (text_tool); + GimpDisplayShell *shell = gimp_display_get_shell (tool->display); + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer); + GtkTextIter cursor; + GtkTextIter selection; + gboolean retval = TRUE; + + if (! gtk_widget_has_focus (shell->canvas)) + { + /* The focus is in the floating style editor, and the event + * was not handled there, focus the canvas. + */ + switch (kevent->keyval) + { + case GDK_KEY_Tab: + case GDK_KEY_KP_Tab: + case GDK_KEY_ISO_Left_Tab: + case GDK_KEY_Escape: + gtk_widget_grab_focus (shell->canvas); + return TRUE; + + default: + break; + } + } + + if (gtk_im_context_filter_keypress (text_tool->im_context, kevent)) + { + text_tool->needs_im_reset = TRUE; + text_tool->x_pos = -1; + + return TRUE; + } + + gimp_text_tool_convert_gdkkeyevent (text_tool, kevent); + + gimp_text_tool_ensure_proxy (text_tool); + + if (gtk_bindings_activate_event (GTK_OBJECT (text_tool->proxy_text_view), + kevent)) + { + GIMP_LOG (TEXT_EDITING, "binding handled event"); + + return TRUE; + } + + gtk_text_buffer_get_iter_at_mark (buffer, &cursor, + gtk_text_buffer_get_insert (buffer)); + gtk_text_buffer_get_iter_at_mark (buffer, &selection, + gtk_text_buffer_get_selection_bound (buffer)); + + switch (kevent->keyval) + { + case GDK_KEY_Return: + case GDK_KEY_KP_Enter: + case GDK_KEY_ISO_Enter: + gimp_text_tool_reset_im_context (text_tool); + gimp_text_tool_enter_text (text_tool, "\n"); + break; + + case GDK_KEY_Tab: + case GDK_KEY_KP_Tab: + case GDK_KEY_ISO_Left_Tab: + gimp_text_tool_reset_im_context (text_tool); + gimp_text_tool_enter_text (text_tool, "\t"); + break; + + case GDK_KEY_Escape: + gimp_tool_control (GIMP_TOOL (text_tool), GIMP_TOOL_ACTION_HALT, + GIMP_TOOL (text_tool)->display); + break; + + default: + retval = FALSE; + } + + text_tool->x_pos = -1; + + return retval; +} + +gboolean +gimp_text_tool_editor_key_release (GimpTextTool *text_tool, + GdkEventKey *kevent) +{ + if (gtk_im_context_filter_keypress (text_tool->im_context, kevent)) + { + text_tool->needs_im_reset = TRUE; + + return TRUE; + } + + gimp_text_tool_convert_gdkkeyevent (text_tool, kevent); + + gimp_text_tool_ensure_proxy (text_tool); + + if (gtk_bindings_activate_event (GTK_OBJECT (text_tool->proxy_text_view), + kevent)) + { + GIMP_LOG (TEXT_EDITING, "binding handled event"); + + return TRUE; + } + + return FALSE; +} + +void +gimp_text_tool_reset_im_context (GimpTextTool *text_tool) +{ + if (text_tool->needs_im_reset) + { + text_tool->needs_im_reset = FALSE; + gtk_im_context_reset (text_tool->im_context); + } +} + +void +gimp_text_tool_abort_im_context (GimpTextTool *text_tool) +{ + GimpTool *tool = GIMP_TOOL (text_tool); + GimpDisplayShell *shell = gimp_display_get_shell (tool->display); + + text_tool->needs_im_reset = TRUE; + gimp_text_tool_reset_im_context (text_tool); + + /* Making sure preedit text is removed. */ + gimp_text_tool_im_delete_preedit (text_tool); + + /* the following lines seem to be the only way of really getting + * rid of any ongoing preedit state, please somebody tell me + * a clean way... mitch + */ + + gtk_im_context_focus_out (text_tool->im_context); + gtk_im_context_set_client_window (text_tool->im_context, NULL); + + g_object_unref (text_tool->im_context); + text_tool->im_context = gtk_im_multicontext_new (); + gtk_im_context_set_client_window (text_tool->im_context, + gtk_widget_get_window (shell->canvas)); + gtk_im_context_focus_in (text_tool->im_context); + g_signal_connect (text_tool->im_context, "preedit-start", + G_CALLBACK (gimp_text_tool_im_preedit_start), + text_tool); + g_signal_connect (text_tool->im_context, "preedit-end", + G_CALLBACK (gimp_text_tool_im_preedit_end), + text_tool); + g_signal_connect (text_tool->im_context, "preedit-changed", + G_CALLBACK (gimp_text_tool_im_preedit_changed), + text_tool); + g_signal_connect (text_tool->im_context, "commit", + G_CALLBACK (gimp_text_tool_im_commit), + text_tool); + g_signal_connect (text_tool->im_context, "retrieve-surrounding", + G_CALLBACK (gimp_text_tool_im_retrieve_surrounding), + text_tool); + g_signal_connect (text_tool->im_context, "delete-surrounding", + G_CALLBACK (gimp_text_tool_im_delete_surrounding), + text_tool); +} + +void +gimp_text_tool_editor_get_cursor_rect (GimpTextTool *text_tool, + gboolean overwrite, + PangoRectangle *cursor_rect) +{ + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer); + PangoLayout *layout; + PangoContext *context; + gint offset_x; + gint offset_y; + GtkTextIter cursor; + gint cursor_index; + + g_return_if_fail (GIMP_IS_TEXT_TOOL (text_tool)); + g_return_if_fail (cursor_rect != NULL); + + gtk_text_buffer_get_iter_at_mark (buffer, &cursor, + gtk_text_buffer_get_insert (buffer)); + cursor_index = gimp_text_buffer_get_iter_index (text_tool->buffer, &cursor, + TRUE); + + gimp_text_tool_ensure_layout (text_tool); + + layout = gimp_text_layout_get_pango_layout (text_tool->layout); + + context = pango_layout_get_context (layout); + + gimp_text_layout_get_offsets (text_tool->layout, &offset_x, &offset_y); + + if (overwrite) + { + pango_layout_index_to_pos (layout, cursor_index, cursor_rect); + + /* pango_layout_index_to_pos() returns wrong position, if gravity is west + * and cursor is at end of line. Avoid this behavior. (pango 1.42.1) + */ + if (pango_context_get_base_gravity (context) == PANGO_GRAVITY_WEST && + cursor_rect->width == 0) + pango_layout_get_cursor_pos (layout, cursor_index, cursor_rect, NULL); + } + else + pango_layout_get_cursor_pos (layout, cursor_index, cursor_rect, NULL); + + gimp_text_layout_transform_rect (text_tool->layout, cursor_rect); + + switch (gimp_text_tool_get_direction (text_tool)) + { + case GIMP_TEXT_DIRECTION_LTR: + case GIMP_TEXT_DIRECTION_RTL: + cursor_rect->x = PANGO_PIXELS (cursor_rect->x) + offset_x; + cursor_rect->y = PANGO_PIXELS (cursor_rect->y) + offset_y; + cursor_rect->width = PANGO_PIXELS (cursor_rect->width); + cursor_rect->height = PANGO_PIXELS (cursor_rect->height); + break; + case GIMP_TEXT_DIRECTION_TTB_RTL: + case GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT: + { + gint temp, width, height; + + gimp_text_layout_get_size (text_tool->layout, &width, &height); + + temp = cursor_rect->x; + cursor_rect->x = width - PANGO_PIXELS (cursor_rect->y) + offset_x; + cursor_rect->y = PANGO_PIXELS (temp) + offset_y; + + temp = cursor_rect->width; + cursor_rect->width = PANGO_PIXELS (cursor_rect->height); + cursor_rect->height = PANGO_PIXELS (temp); + } + break; + case GIMP_TEXT_DIRECTION_TTB_LTR: + case GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT: + { + gint temp, width, height; + + gimp_text_layout_get_size (text_tool->layout, &width, &height); + + temp = cursor_rect->x; + cursor_rect->x = PANGO_PIXELS (cursor_rect->y) + offset_x; + cursor_rect->y = height - PANGO_PIXELS (temp) + offset_y; + + temp = cursor_rect->width; + cursor_rect->width = PANGO_PIXELS (cursor_rect->height); + cursor_rect->height = PANGO_PIXELS (temp); + } + break; + } +} + +void +gimp_text_tool_editor_update_im_cursor (GimpTextTool *text_tool) +{ + GimpDisplayShell *shell; + PangoRectangle rect = { 0, }; + gdouble off_x, off_y; + + g_return_if_fail (GIMP_IS_TEXT_TOOL (text_tool)); + + shell = gimp_display_get_shell (GIMP_TOOL (text_tool)->display); + + if (text_tool->text) + gimp_text_tool_editor_get_cursor_rect (text_tool, + text_tool->overwrite_mode, + &rect); + + g_object_get (text_tool->widget, + "x1", &off_x, + "y1", &off_y, + NULL); + + rect.x += off_x; + rect.y += off_y; + + gimp_display_shell_transform_xy (shell, rect.x, rect.y, &rect.x, &rect.y); + + gtk_im_context_set_cursor_location (text_tool->im_context, + (GdkRectangle *) &rect); +} + + +/* private functions */ + +static void +gimp_text_tool_ensure_proxy (GimpTextTool *text_tool) +{ + GimpTool *tool = GIMP_TOOL (text_tool); + GimpDisplayShell *shell = gimp_display_get_shell (tool->display); + + if (text_tool->offscreen_window && + gtk_widget_get_screen (text_tool->offscreen_window) != + gtk_widget_get_screen (GTK_WIDGET (shell))) + { + gtk_window_set_screen (GTK_WINDOW (text_tool->offscreen_window), + gtk_widget_get_screen (GTK_WIDGET (shell))); + gtk_window_move (GTK_WINDOW (text_tool->offscreen_window), -200, -200); + } + else if (! text_tool->offscreen_window) + { + text_tool->offscreen_window = gtk_window_new (GTK_WINDOW_POPUP); + gtk_window_set_screen (GTK_WINDOW (text_tool->offscreen_window), + gtk_widget_get_screen (GTK_WIDGET (shell))); + gtk_window_move (GTK_WINDOW (text_tool->offscreen_window), -200, -200); + gtk_widget_show (text_tool->offscreen_window); + + text_tool->proxy_text_view = gimp_text_proxy_new (); + gtk_container_add (GTK_CONTAINER (text_tool->offscreen_window), + text_tool->proxy_text_view); + gtk_widget_show (text_tool->proxy_text_view); + + g_signal_connect_swapped (text_tool->proxy_text_view, "move-cursor", + G_CALLBACK (gimp_text_tool_move_cursor), + text_tool); + g_signal_connect_swapped (text_tool->proxy_text_view, "insert-at-cursor", + G_CALLBACK (gimp_text_tool_insert_at_cursor), + text_tool); + g_signal_connect_swapped (text_tool->proxy_text_view, "delete-from-cursor", + G_CALLBACK (gimp_text_tool_delete_from_cursor), + text_tool); + g_signal_connect_swapped (text_tool->proxy_text_view, "backspace", + G_CALLBACK (gimp_text_tool_backspace), + text_tool); + g_signal_connect_swapped (text_tool->proxy_text_view, "cut-clipboard", + G_CALLBACK (gimp_text_tool_cut_clipboard), + text_tool); + g_signal_connect_swapped (text_tool->proxy_text_view, "copy-clipboard", + G_CALLBACK (gimp_text_tool_copy_clipboard), + text_tool); + g_signal_connect_swapped (text_tool->proxy_text_view, "paste-clipboard", + G_CALLBACK (gimp_text_tool_paste_clipboard), + text_tool); + g_signal_connect_swapped (text_tool->proxy_text_view, "toggle-overwrite", + G_CALLBACK (gimp_text_tool_toggle_overwrite), + text_tool); + g_signal_connect_swapped (text_tool->proxy_text_view, "select-all", + G_CALLBACK (gimp_text_tool_select_all), + text_tool); + g_signal_connect_swapped (text_tool->proxy_text_view, "change-size", + G_CALLBACK (gimp_text_tool_change_size), + text_tool); + g_signal_connect_swapped (text_tool->proxy_text_view, "change-baseline", + G_CALLBACK (gimp_text_tool_change_baseline), + text_tool); + g_signal_connect_swapped (text_tool->proxy_text_view, "change-kerning", + G_CALLBACK (gimp_text_tool_change_kerning), + text_tool); + } +} + +static void +gimp_text_tool_move_cursor (GimpTextTool *text_tool, + GtkMovementStep step, + gint count, + gboolean extend_selection) +{ + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer); + GtkTextIter cursor; + GtkTextIter selection; + GtkTextIter *sel_start; + gboolean cancel_selection = FALSE; + gint x_pos = -1; + + if (text_tool->pending) + { + /* If there are any pending text commits, there would be + * inconsistencies between the text_tool->buffer and layout. + * This could result in crashes. See bug 751333. + * Therefore we apply them first. + */ + gimp_text_tool_apply (text_tool, TRUE); + } + GIMP_LOG (TEXT_EDITING, "%s count = %d, select = %s", + g_enum_get_value (g_type_class_ref (GTK_TYPE_MOVEMENT_STEP), + step)->value_name, + count, + extend_selection ? "TRUE" : "FALSE"); + + gtk_text_buffer_get_iter_at_mark (buffer, &cursor, + gtk_text_buffer_get_insert (buffer)); + gtk_text_buffer_get_iter_at_mark (buffer, &selection, + gtk_text_buffer_get_selection_bound (buffer)); + + if (extend_selection) + { + sel_start = &selection; + } + else + { + /* when there is a selection, moving the cursor without + * extending it should move the cursor to the end of the + * selection that is in moving direction + */ + if (count > 0) + gtk_text_iter_order (&selection, &cursor); + else + gtk_text_iter_order (&cursor, &selection); + + sel_start = &cursor; + + /* if we actually have a selection, just move *to* the beginning/end + * of the selection and not *from* there on LOGICAL_POSITIONS + * and VISUAL_POSITIONS movement + */ + if (! gtk_text_iter_equal (&cursor, &selection)) + cancel_selection = TRUE; + } + + switch (step) + { + case GTK_MOVEMENT_LOGICAL_POSITIONS: + if (! cancel_selection) + gtk_text_iter_forward_visible_cursor_positions (&cursor, count); + break; + + case GTK_MOVEMENT_VISUAL_POSITIONS: + if (! cancel_selection) + { + PangoLayout *layout; + const gchar *text; + + if (! gimp_text_tool_ensure_layout (text_tool)) + break; + + layout = gimp_text_layout_get_pango_layout (text_tool->layout); + text = pango_layout_get_text (layout); + + while (count != 0) + { + const gunichar word_joiner = 8288; /*g_utf8_get_char(WORD_JOINER);*/ + gint index; + gint trailing = 0; + gint new_index; + + index = gimp_text_buffer_get_iter_index (text_tool->buffer, + &cursor, TRUE); + + if (count > 0) + { + if (g_utf8_get_char (text + index) == word_joiner) + pango_layout_move_cursor_visually (layout, TRUE, + index, 0, 1, + &new_index, &trailing); + else + new_index = index; + + pango_layout_move_cursor_visually (layout, TRUE, + new_index, trailing, 1, + &new_index, &trailing); + count--; + } + else + { + pango_layout_move_cursor_visually (layout, TRUE, + index, 0, -1, + &new_index, &trailing); + + if (new_index != -1 && new_index != G_MAXINT && + g_utf8_get_char (text + new_index) == word_joiner) + { + pango_layout_move_cursor_visually (layout, TRUE, + new_index, trailing, -1, + &new_index, &trailing); + } + + count++; + } + + if (new_index != G_MAXINT && new_index != -1) + index = new_index; + else + break; + + gimp_text_buffer_get_iter_at_index (text_tool->buffer, + &cursor, index, TRUE); + gtk_text_iter_forward_chars (&cursor, trailing); + } + } + break; + + case GTK_MOVEMENT_WORDS: + if (count < 0) + { + gtk_text_iter_backward_visible_word_starts (&cursor, -count); + } + else if (count > 0) + { + if (! gtk_text_iter_forward_visible_word_ends (&cursor, count)) + gtk_text_iter_forward_to_line_end (&cursor); + } + break; + + case GTK_MOVEMENT_DISPLAY_LINES: + { + GtkTextIter start; + GtkTextIter end; + gint cursor_index; + PangoLayout *layout; + PangoLayoutLine *layout_line; + PangoLayoutIter *layout_iter; + PangoRectangle logical; + gint line; + gint trailing; + gint i; + + gtk_text_buffer_get_bounds (buffer, &start, &end); + + cursor_index = gimp_text_buffer_get_iter_index (text_tool->buffer, + &cursor, TRUE); + + if (! gimp_text_tool_ensure_layout (text_tool)) + break; + + layout = gimp_text_layout_get_pango_layout (text_tool->layout); + + pango_layout_index_to_line_x (layout, cursor_index, FALSE, + &line, &x_pos); + + layout_iter = pango_layout_get_iter (layout); + for (i = 0; i < line; i++) + pango_layout_iter_next_line (layout_iter); + + pango_layout_iter_get_line_extents (layout_iter, NULL, &logical); + + x_pos += logical.x; + + pango_layout_iter_free (layout_iter); + + /* try to go to the remembered x_pos if it exists *and* we are at + * the beginning or at the end of the current line + */ + if (text_tool->x_pos != -1 && (x_pos <= logical.x || + x_pos >= logical.x + logical.width)) + x_pos = text_tool->x_pos; + + line += count; + + if (line < 0) + { + cursor = start; + break; + } + else if (line >= pango_layout_get_line_count (layout)) + { + cursor = end; + break; + } + + layout_iter = pango_layout_get_iter (layout); + for (i = 0; i < line; i++) + pango_layout_iter_next_line (layout_iter); + + layout_line = pango_layout_iter_get_line_readonly (layout_iter); + pango_layout_iter_get_line_extents (layout_iter, NULL, &logical); + + pango_layout_iter_free (layout_iter); + + pango_layout_line_x_to_index (layout_line, x_pos - logical.x, + &cursor_index, &trailing); + + gimp_text_buffer_get_iter_at_index (text_tool->buffer, &cursor, + cursor_index, TRUE); + + while (trailing--) + gtk_text_iter_forward_char (&cursor); + } + break; + + case GTK_MOVEMENT_PAGES: /* well... */ + case GTK_MOVEMENT_BUFFER_ENDS: + if (count < 0) + { + gtk_text_buffer_get_start_iter (buffer, &cursor); + } + else if (count > 0) + { + gtk_text_buffer_get_end_iter (buffer, &cursor); + } + break; + + case GTK_MOVEMENT_PARAGRAPH_ENDS: + if (count < 0) + { + gtk_text_iter_set_line_offset (&cursor, 0); + } + else if (count > 0) + { + if (! gtk_text_iter_ends_line (&cursor)) + gtk_text_iter_forward_to_line_end (&cursor); + } + break; + + case GTK_MOVEMENT_DISPLAY_LINE_ENDS: + if (count < 0) + { + gtk_text_iter_set_line_offset (&cursor, 0); + } + else if (count > 0) + { + if (! gtk_text_iter_ends_line (&cursor)) + gtk_text_iter_forward_to_line_end (&cursor); + } + break; + + default: + return; + } + + text_tool->x_pos = x_pos; + + gimp_draw_tool_pause (GIMP_DRAW_TOOL (text_tool)); + + gimp_text_tool_reset_im_context (text_tool); + + gtk_text_buffer_select_range (buffer, &cursor, sel_start); + gimp_text_tool_editor_copy_selection_to_clipboard (text_tool); + + gimp_draw_tool_resume (GIMP_DRAW_TOOL (text_tool)); +} + +static void +gimp_text_tool_insert_at_cursor (GimpTextTool *text_tool, + const gchar *str) +{ + gimp_text_buffer_insert (text_tool->buffer, str); +} + +static gboolean +is_whitespace (gunichar ch, + gpointer user_data) +{ + return (ch == ' ' || ch == '\t'); +} + +static gboolean +is_not_whitespace (gunichar ch, + gpointer user_data) +{ + return ! is_whitespace (ch, user_data); +} + +static gboolean +find_whitepace_region (const GtkTextIter *center, + GtkTextIter *start, + GtkTextIter *end) +{ + *start = *center; + *end = *center; + + if (gtk_text_iter_backward_find_char (start, is_not_whitespace, NULL, NULL)) + gtk_text_iter_forward_char (start); /* we want the first whitespace... */ + + if (is_whitespace (gtk_text_iter_get_char (end), NULL)) + gtk_text_iter_forward_find_char (end, is_not_whitespace, NULL, NULL); + + return ! gtk_text_iter_equal (start, end); +} + +static void +gimp_text_tool_delete_from_cursor (GimpTextTool *text_tool, + GtkDeleteType type, + gint count) +{ + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer); + GtkTextIter cursor; + GtkTextIter end; + + GIMP_LOG (TEXT_EDITING, "%s count = %d", + g_enum_get_value (g_type_class_ref (GTK_TYPE_DELETE_TYPE), + type)->value_name, + count); + + gimp_text_tool_reset_im_context (text_tool); + + gtk_text_buffer_get_iter_at_mark (buffer, &cursor, + gtk_text_buffer_get_insert (buffer)); + end = cursor; + + switch (type) + { + case GTK_DELETE_CHARS: + if (gtk_text_buffer_get_has_selection (buffer)) + { + gtk_text_buffer_delete_selection (buffer, TRUE, TRUE); + return; + } + else + { + gtk_text_iter_forward_cursor_positions (&end, count); + } + break; + + case GTK_DELETE_WORD_ENDS: + if (count < 0) + { + if (! gtk_text_iter_starts_word (&cursor)) + gtk_text_iter_backward_visible_word_starts (&cursor, 1); + } + else if (count > 0) + { + if (! gtk_text_iter_ends_word (&end) && + ! gtk_text_iter_forward_visible_word_ends (&end, 1)) + gtk_text_iter_forward_to_line_end (&end); + } + break; + + case GTK_DELETE_WORDS: + if (! gtk_text_iter_starts_word (&cursor)) + gtk_text_iter_backward_visible_word_starts (&cursor, 1); + + if (! gtk_text_iter_ends_word (&end) && + ! gtk_text_iter_forward_visible_word_ends (&end, 1)) + gtk_text_iter_forward_to_line_end (&end); + break; + + case GTK_DELETE_DISPLAY_LINES: + break; + + case GTK_DELETE_DISPLAY_LINE_ENDS: + break; + + case GTK_DELETE_PARAGRAPH_ENDS: + if (count < 0) + { + gtk_text_iter_set_line_offset (&cursor, 0); + } + else if (count > 0) + { + if (! gtk_text_iter_ends_line (&end)) + gtk_text_iter_forward_to_line_end (&end); + else + gtk_text_iter_forward_cursor_positions (&end, 1); + } + break; + + case GTK_DELETE_PARAGRAPHS: + break; + + case GTK_DELETE_WHITESPACE: + find_whitepace_region (&cursor, &cursor, &end); + break; + } + + if (! gtk_text_iter_equal (&cursor, &end)) + { + gtk_text_buffer_delete_interactive (buffer, &cursor, &end, TRUE); + } +} + +static void +gimp_text_tool_backspace (GimpTextTool *text_tool) +{ + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer); + + gimp_text_tool_reset_im_context (text_tool); + + if (gtk_text_buffer_get_has_selection (buffer)) + { + gtk_text_buffer_delete_selection (buffer, TRUE, TRUE); + } + else + { + GtkTextIter cursor; + + gtk_text_buffer_get_iter_at_mark (buffer, &cursor, + gtk_text_buffer_get_insert (buffer)); + + gtk_text_buffer_backspace (buffer, &cursor, TRUE, TRUE); + } +} + +static void +gimp_text_tool_toggle_overwrite (GimpTextTool *text_tool) +{ + gimp_draw_tool_pause (GIMP_DRAW_TOOL (text_tool)); + + text_tool->overwrite_mode = ! text_tool->overwrite_mode; + + gimp_draw_tool_resume (GIMP_DRAW_TOOL (text_tool)); +} + +static void +gimp_text_tool_select_all (GimpTextTool *text_tool, + gboolean select) +{ + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer); + + gimp_draw_tool_pause (GIMP_DRAW_TOOL (text_tool)); + + if (select) + { + GtkTextIter start, end; + + gtk_text_buffer_get_bounds (buffer, &start, &end); + gtk_text_buffer_select_range (buffer, &start, &end); + } + else + { + GtkTextIter cursor; + + gtk_text_buffer_get_iter_at_mark (buffer, &cursor, + gtk_text_buffer_get_insert (buffer)); + gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &cursor); + } + + gimp_draw_tool_resume (GIMP_DRAW_TOOL (text_tool)); +} + +static void +gimp_text_tool_change_size (GimpTextTool *text_tool, + gdouble amount) +{ + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer); + GtkTextIter start; + GtkTextIter end; + + if (! gtk_text_buffer_get_selection_bounds (buffer, &start, &end)) + { + return; + } + + gtk_text_iter_order (&start, &end); + gimp_text_buffer_change_size (text_tool->buffer, &start, &end, + amount * PANGO_SCALE); +} + +static void +gimp_text_tool_change_baseline (GimpTextTool *text_tool, + gdouble amount) +{ + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer); + GtkTextIter start; + GtkTextIter end; + + if (! gtk_text_buffer_get_selection_bounds (buffer, &start, &end)) + { + gtk_text_buffer_get_iter_at_mark (buffer, &start, + gtk_text_buffer_get_insert (buffer)); + gtk_text_buffer_get_end_iter (buffer, &end); + } + + gtk_text_iter_order (&start, &end); + gimp_text_buffer_change_baseline (text_tool->buffer, &start, &end, + amount * PANGO_SCALE); +} + +static void +gimp_text_tool_change_kerning (GimpTextTool *text_tool, + gdouble amount) +{ + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer); + GtkTextIter start; + GtkTextIter end; + + if (! gtk_text_buffer_get_selection_bounds (buffer, &start, &end)) + { + gtk_text_buffer_get_iter_at_mark (buffer, &start, + gtk_text_buffer_get_insert (buffer)); + end = start; + gtk_text_iter_forward_char (&end); + } + + gtk_text_iter_order (&start, &end); + gimp_text_buffer_change_kerning (text_tool->buffer, &start, &end, + amount * PANGO_SCALE); +} + +static void +gimp_text_tool_options_notify (GimpTextOptions *options, + GParamSpec *pspec, + GimpTextTool *text_tool) +{ + const gchar *param_name = g_param_spec_get_name (pspec); + + if (! strcmp (param_name, "use-editor")) + { + if (options->use_editor) + { + gimp_text_tool_editor_dialog (text_tool); + } + else + { + if (text_tool->editor_dialog) + gtk_widget_destroy (text_tool->editor_dialog); + } + } +} + +static void +gimp_text_tool_editor_dialog (GimpTextTool *text_tool) +{ + GimpTool *tool = GIMP_TOOL (text_tool); + GimpTextOptions *options = GIMP_TEXT_TOOL_GET_OPTIONS (text_tool); + GimpDisplayShell *shell = gimp_display_get_shell (tool->display); + GimpImageWindow *image_window; + GimpDialogFactory *dialog_factory; + GtkWindow *parent = NULL; + gdouble xres = 1.0; + gdouble yres = 1.0; + + if (text_tool->editor_dialog) + { + gtk_window_present (GTK_WINDOW (text_tool->editor_dialog)); + return; + } + + image_window = gimp_display_shell_get_window (shell); + dialog_factory = gimp_dock_container_get_dialog_factory (GIMP_DOCK_CONTAINER (image_window)); + + if (text_tool->image) + gimp_image_get_resolution (text_tool->image, &xres, &yres); + + text_tool->editor_dialog = + gimp_text_options_editor_new (parent, tool->tool_info->gimp, options, + gimp_dialog_factory_get_menu_factory (dialog_factory), + _("GIMP Text Editor"), + text_tool->proxy, text_tool->buffer, + xres, yres); + + g_object_add_weak_pointer (G_OBJECT (text_tool->editor_dialog), + (gpointer) &text_tool->editor_dialog); + + gimp_dialog_factory_add_foreign (dialog_factory, + "gimp-text-tool-dialog", + text_tool->editor_dialog, + gtk_widget_get_screen (GTK_WIDGET (image_window)), + gimp_widget_get_monitor (GTK_WIDGET (image_window))); + + g_signal_connect (text_tool->editor_dialog, "destroy", + G_CALLBACK (gimp_text_tool_editor_destroy), + text_tool); + + gtk_widget_show (text_tool->editor_dialog); +} + +static void +gimp_text_tool_editor_destroy (GtkWidget *dialog, + GimpTextTool *text_tool) +{ + GimpTextOptions *options = GIMP_TEXT_TOOL_GET_OPTIONS (text_tool); + + g_object_set (options, + "use-editor", FALSE, + NULL); +} + +static void +gimp_text_tool_enter_text (GimpTextTool *text_tool, + const gchar *str) +{ + GtkTextBuffer *buffer; + gboolean had_selection; + + buffer = GTK_TEXT_BUFFER (text_tool->buffer); + had_selection = gtk_text_buffer_get_has_selection (buffer); + + gtk_text_buffer_begin_user_action (buffer); + + if (! had_selection && text_tool->overwrite_mode && strcmp (str, "\n")) + { + GtkTextIter cursor; + + gtk_text_buffer_get_iter_at_mark (buffer, &cursor, + gtk_text_buffer_get_insert (buffer)); + + if (! gtk_text_iter_ends_line (&cursor)) + gimp_text_tool_delete_from_cursor (text_tool, GTK_DELETE_CHARS, 1); + } + + if (had_selection) + { + GtkTextIter start, end; + GList *insert_tags = NULL; + GList *remove_tags = NULL; + + gtk_text_buffer_get_selection_bounds (buffer, &start, &end); + gtk_text_iter_order (&start, &end); + + insert_tags = gimp_text_buffer_get_tags_on_iter (text_tool->buffer, &start); + + gimp_text_tool_delete_selection (text_tool); + + remove_tags = gimp_text_buffer_get_all_tags (text_tool->buffer); + gimp_text_buffer_set_insert_tags (text_tool->buffer, + insert_tags, remove_tags); + } + + gimp_text_buffer_insert (text_tool->buffer, str); + + gtk_text_buffer_end_user_action (buffer); +} + +static void +gimp_text_tool_xy_to_iter (GimpTextTool *text_tool, + gdouble x, + gdouble y, + GtkTextIter *iter) +{ + PangoLayout *layout; + gint offset_x; + gint offset_y; + gint index; + gint trailing; + + gimp_text_tool_ensure_layout (text_tool); + + gimp_text_layout_untransform_point (text_tool->layout, &x, &y); + + gimp_text_layout_get_offsets (text_tool->layout, &offset_x, &offset_y); + x -= offset_x; + y -= offset_y; + + layout = gimp_text_layout_get_pango_layout (text_tool->layout); + + gimp_text_tool_fix_position (text_tool, &x, &y); + + pango_layout_xy_to_index (layout, + x * PANGO_SCALE, + y * PANGO_SCALE, + &index, &trailing); + + gimp_text_buffer_get_iter_at_index (text_tool->buffer, iter, index, TRUE); + + if (trailing) + gtk_text_iter_forward_char (iter); +} + +static void +gimp_text_tool_im_preedit_start (GtkIMContext *context, + GimpTextTool *text_tool) +{ + GIMP_LOG (TEXT_EDITING, "preedit start"); + + text_tool->preedit_active = TRUE; +} + +static void +gimp_text_tool_im_preedit_end (GtkIMContext *context, + GimpTextTool *text_tool) +{ + gimp_text_tool_delete_selection (text_tool); + + text_tool->preedit_active = FALSE; + + GIMP_LOG (TEXT_EDITING, "preedit end"); +} + +static void +gimp_text_tool_im_preedit_changed (GtkIMContext *context, + GimpTextTool *text_tool) +{ + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer); + PangoAttrList *attrs; + + GIMP_LOG (TEXT_EDITING, "preedit changed"); + + gtk_text_buffer_begin_user_action (buffer); + + gimp_text_tool_im_delete_preedit (text_tool); + + gimp_text_tool_delete_selection (text_tool); + + gtk_im_context_get_preedit_string (context, + &text_tool->preedit_string, &attrs, + &text_tool->preedit_cursor); + + if (text_tool->preedit_string && *text_tool->preedit_string) + { + PangoAttrIterator *attr_iter; + GtkTextIter iter; + gint i; + + /* Save the preedit start position. */ + gtk_text_buffer_get_iter_at_mark (buffer, &iter, + gtk_text_buffer_get_insert (buffer)); + text_tool->preedit_start = gtk_text_buffer_create_mark (buffer, + "preedit-start", + &iter, TRUE); + + /* Loop through chunks of preedit text with different attributes. */ + attr_iter = pango_attr_list_get_iterator (attrs); + do + { + gint attr_start; + gint attr_end; + + pango_attr_iterator_range (attr_iter, &attr_start, &attr_end); + if (attr_start < strlen (text_tool->preedit_string)) + { + GSList *attrs_pos; + GtkTextMark *start_mark; + GtkTextIter start; + GtkTextIter end; + + gtk_text_buffer_get_iter_at_mark (buffer, &start, + gtk_text_buffer_get_insert (buffer)); + start_mark = gtk_text_buffer_create_mark (buffer, + NULL, + &start, TRUE); + + gtk_text_buffer_begin_user_action (buffer); + + /* Insert the preedit chunk at current cursor position. */ + gtk_text_buffer_insert_at_cursor (GTK_TEXT_BUFFER (text_tool->buffer), + text_tool->preedit_string + attr_start, + attr_end - attr_start); + gtk_text_buffer_get_iter_at_mark (buffer, &start, + start_mark); + gtk_text_buffer_delete_mark (buffer, start_mark); + gtk_text_buffer_get_iter_at_mark (buffer, &end, + gtk_text_buffer_get_insert (buffer)); + + /* Apply text attributes to preedit text. */ + attrs_pos = pango_attr_iterator_get_attrs (attr_iter); + while (attrs_pos) + { + PangoAttribute *attr = attrs_pos->data; + + if (attr) + { + switch (attr->klass->type) + { + case PANGO_ATTR_UNDERLINE: + gtk_text_buffer_apply_tag (buffer, + text_tool->buffer->preedit_underline_tag, + &start, &end); + break; + case PANGO_ATTR_BACKGROUND: + case PANGO_ATTR_FOREGROUND: + { + PangoAttrColor *color_attr = (PangoAttrColor *) attr; + GimpRGB color; + + color.r = (gdouble) color_attr->color.red / 65535.0; + color.g = (gdouble) color_attr->color.green / 65535.0; + color.b = (gdouble) color_attr->color.blue / 65535.0; + + if (attr->klass->type == PANGO_ATTR_BACKGROUND) + { + gimp_text_buffer_set_preedit_bg_color (text_tool->buffer, + &start, &end, + &color); + } + else + { + gimp_text_buffer_set_preedit_color (text_tool->buffer, + &start, &end, + &color); + } + } + break; + default: + /* Unsupported tags. */ + break; + } + } + + attrs_pos = attrs_pos->next; + } + + gtk_text_buffer_end_user_action (buffer); + } + } + while (pango_attr_iterator_next (attr_iter)); + + /* Save the preedit end position. */ + gtk_text_buffer_get_iter_at_mark (buffer, &iter, + gtk_text_buffer_get_insert (buffer)); + text_tool->preedit_end = gtk_text_buffer_create_mark (buffer, + "preedit-end", + &iter, FALSE); + + /* Move the cursor to the expected location. */ + gtk_text_buffer_get_iter_at_mark (buffer, &iter, text_tool->preedit_start); + for (i = 0; i < text_tool->preedit_cursor; i++) + gtk_text_iter_forward_char (&iter); + gtk_text_buffer_place_cursor (buffer, &iter); + + pango_attr_iterator_destroy (attr_iter); + } + + pango_attr_list_unref (attrs); + + gtk_text_buffer_end_user_action (buffer); +} + +static void +gimp_text_tool_im_commit (GtkIMContext *context, + const gchar *str, + GimpTextTool *text_tool) +{ + gboolean preedit_active = text_tool->preedit_active; + + gimp_text_tool_im_delete_preedit (text_tool); + + /* Some IMEs would emit a preedit-commit before preedit-end. + * To keep undo consistency, we fake and end then immediate restart of + * preediting. + */ + if (preedit_active) + gimp_text_tool_im_preedit_end (context, text_tool); + + gimp_text_tool_enter_text (text_tool, str); + + if (preedit_active) + gimp_text_tool_im_preedit_start (context, text_tool); +} + +static gboolean +gimp_text_tool_im_retrieve_surrounding (GtkIMContext *context, + GimpTextTool *text_tool) +{ + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer); + GtkTextIter start; + GtkTextIter end; + gint pos; + gchar *text; + + gtk_text_buffer_get_iter_at_mark (buffer, &start, + gtk_text_buffer_get_insert (buffer)); + end = start; + + pos = gtk_text_iter_get_line_index (&start); + gtk_text_iter_set_line_offset (&start, 0); + gtk_text_iter_forward_to_line_end (&end); + + text = gtk_text_iter_get_slice (&start, &end); + gtk_im_context_set_surrounding (context, text, -1, pos); + g_free (text); + + return TRUE; +} + +static gboolean +gimp_text_tool_im_delete_surrounding (GtkIMContext *context, + gint offset, + gint n_chars, + GimpTextTool *text_tool) +{ + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer); + GtkTextIter start; + GtkTextIter end; + + gtk_text_buffer_get_iter_at_mark (buffer, &start, + gtk_text_buffer_get_insert (buffer)); + end = start; + + gtk_text_iter_forward_chars (&start, offset); + gtk_text_iter_forward_chars (&end, offset + n_chars); + + gtk_text_buffer_delete_interactive (buffer, &start, &end, TRUE); + + return TRUE; +} + +static void +gimp_text_tool_im_delete_preedit (GimpTextTool *text_tool) +{ + if (text_tool->preedit_string) + { + if (*text_tool->preedit_string) + { + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer); + GtkTextIter start; + GtkTextIter end; + + gtk_text_buffer_get_iter_at_mark (buffer, &start, + text_tool->preedit_start); + gtk_text_buffer_get_iter_at_mark (buffer, &end, + text_tool->preedit_end); + + gtk_text_buffer_delete_interactive (buffer, &start, &end, TRUE); + + gtk_text_buffer_delete_mark (buffer, text_tool->preedit_start); + gtk_text_buffer_delete_mark (buffer, text_tool->preedit_end); + text_tool->preedit_start = NULL; + text_tool->preedit_end = NULL; + } + + g_clear_pointer (&text_tool->preedit_string, g_free); + } +} + +static void +gimp_text_tool_editor_copy_selection_to_clipboard (GimpTextTool *text_tool) +{ + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer); + + if (! text_tool->editor_dialog && + gtk_text_buffer_get_has_selection (buffer)) + { + GimpTool *tool = GIMP_TOOL (text_tool); + GimpDisplayShell *shell = gimp_display_get_shell (tool->display); + GtkClipboard *clipboard; + + clipboard = gtk_widget_get_clipboard (GTK_WIDGET (shell), + GDK_SELECTION_PRIMARY); + + gtk_text_buffer_copy_clipboard (buffer, clipboard); + } +} + +static void +gimp_text_tool_fix_position (GimpTextTool *text_tool, + gdouble *x, + gdouble *y) +{ + gint temp, width, height; + + gimp_text_layout_get_size (text_tool->layout, &width, &height); + switch (gimp_text_tool_get_direction(text_tool)) + { + case GIMP_TEXT_DIRECTION_RTL: + case GIMP_TEXT_DIRECTION_LTR: + break; + case GIMP_TEXT_DIRECTION_TTB_RTL: + case GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT: + temp = width - *x; + *x = *y; + *y = temp; + break; + case GIMP_TEXT_DIRECTION_TTB_LTR: + case GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT: + temp = *x; + *x = height - *y; + *y = temp; + break; + } +} + +static void +gimp_text_tool_convert_gdkkeyevent (GimpTextTool *text_tool, + GdkEventKey *kevent) +{ + switch (gimp_text_tool_get_direction (text_tool)) + { + case GIMP_TEXT_DIRECTION_LTR: + case GIMP_TEXT_DIRECTION_RTL: + break; + + case GIMP_TEXT_DIRECTION_TTB_RTL: + case GIMP_TEXT_DIRECTION_TTB_RTL_UPRIGHT: +#ifdef _WIN32 + switch (kevent->keyval) + { + case GDK_KEY_Up: + kevent->hardware_keycode = 0x25;/* VK_LEFT */ + kevent->keyval = GDK_KEY_Left; + break; + case GDK_KEY_Down: + kevent->hardware_keycode = 0x27;/* VK_RIGHT */ + kevent->keyval = GDK_KEY_Right; + break; + case GDK_KEY_Left: + kevent->hardware_keycode = 0x28;/* VK_DOWN */ + kevent->keyval = GDK_KEY_Down; + break; + case GDK_KEY_Right: + kevent->hardware_keycode = 0x26;/* VK_UP */ + kevent->keyval = GDK_KEY_Up; + break; + } +#else + switch (kevent->keyval) + { + case GDK_KEY_Up: + kevent->hardware_keycode = 113; + kevent->keyval = GDK_KEY_Left; + break; + case GDK_KEY_Down: + kevent->hardware_keycode = 114; + kevent->keyval = GDK_KEY_Right; + break; + case GDK_KEY_Left: + kevent->hardware_keycode = 116; + kevent->keyval = GDK_KEY_Down; + break; + case GDK_KEY_Right: + kevent->hardware_keycode = 111; + kevent->keyval = GDK_KEY_Up; + break; + } +#endif + break; + case GIMP_TEXT_DIRECTION_TTB_LTR: + case GIMP_TEXT_DIRECTION_TTB_LTR_UPRIGHT: +#ifdef _WIN32 + switch (kevent->keyval) + { + case GDK_KEY_Up: + kevent->hardware_keycode = 0x26;/* VK_UP */ + kevent->keyval = GDK_KEY_Up; + break; + case GDK_KEY_Down: + kevent->hardware_keycode = 0x28;/* VK_DOWN */ + kevent->keyval = GDK_KEY_Down; + break; + case GDK_KEY_Left: + kevent->hardware_keycode = 0x25;/* VK_LEFT */ + kevent->keyval = GDK_KEY_Left; + break; + case GDK_KEY_Right: + kevent->hardware_keycode = 0x27;/* VK_RIGHT */ + kevent->keyval = GDK_KEY_Right; + break; + } +#else + switch (kevent->keyval) + { + case GDK_KEY_Up: + kevent->hardware_keycode = 114; + kevent->keyval = GDK_KEY_Right; + break; + case GDK_KEY_Down: + kevent->hardware_keycode = 113; + kevent->keyval = GDK_KEY_Left; + break; + case GDK_KEY_Left: + kevent->hardware_keycode = 111; + kevent->keyval = GDK_KEY_Up; + break; + case GDK_KEY_Right: + kevent->hardware_keycode = 116; + kevent->keyval = GDK_KEY_Down; + break; + } +#endif + break; + } +} |