summaryrefslogtreecommitdiffstats
path: root/app/tools/gimptexttool-editor.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 03:13:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 03:13:10 +0000
commit3c57dd931145d43f2b0aef96c4d178135956bf91 (patch)
tree3de698981e9f0cc2c4f9569b19a5f3595e741f6b /app/tools/gimptexttool-editor.c
parentInitial commit. (diff)
downloadgimp-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.c1871
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;
+ }
+}