app: fix various undo- and preedit-related text tool issues
In the text tool editor code, connect to GtkIMContext::preedit-start and introduce a boolean text_tool->preedit_active which indicates that a preedit is going on. Remove the new preedit-removal code from gimp_text_tool_reset_im_context() because it was not reflecting the IM's internal state and made things worse. Instead, added gimp_text_tool_abort_im_context() which really gets rid of any ongoing preedit by force. In the main text tool code, check for preedit_active and if TRUE, apply any edits directly widhout pushing undo steps. Factored out gimp_text_tool_apply_list() for that purpose in order not do duplicate a lot of code. On undo and on button_press, force-abort any ongoing preedit. This is the right thing to do on undo, but not really on button_press, but I don't see another way to keep states consistent.
This commit is contained in:
@ -95,6 +95,8 @@ static void gimp_text_tool_xy_to_iter (GimpTextTool *text_tool,
|
||||
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,
|
||||
@ -127,6 +129,9 @@ gimp_text_tool_editor_init (GimpTextTool *text_tool)
|
||||
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);
|
||||
@ -535,9 +540,6 @@ gimp_text_tool_editor_key_release (GimpTextTool *text_tool,
|
||||
void
|
||||
gimp_text_tool_reset_im_context (GimpTextTool *text_tool)
|
||||
{
|
||||
/* Cancel any ungoing preedit on reset. */
|
||||
gimp_text_tool_im_delete_preedit (text_tool);
|
||||
|
||||
if (text_tool->needs_im_reset)
|
||||
{
|
||||
text_tool->needs_im_reset = FALSE;
|
||||
@ -545,6 +547,28 @@ gimp_text_tool_reset_im_context (GimpTextTool *text_tool)
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
/* 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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void
|
||||
gimp_text_tool_editor_get_cursor_rect (GimpTextTool *text_tool,
|
||||
gboolean overwrite,
|
||||
@ -1335,11 +1359,24 @@ gimp_text_tool_xy_to_iter (GimpTextTool *text_tool,
|
||||
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
|
||||
@ -1349,6 +1386,10 @@ gimp_text_tool_im_preedit_changed (GtkIMContext *context,
|
||||
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);
|
||||
@ -1473,6 +1514,8 @@ gimp_text_tool_im_preedit_changed (GtkIMContext *context,
|
||||
}
|
||||
|
||||
pango_attr_list_unref (attrs);
|
||||
|
||||
gtk_text_buffer_end_user_action (buffer);
|
||||
}
|
||||
|
||||
static void
|
||||
|
||||
@ -45,6 +45,7 @@ gboolean gimp_text_tool_editor_key_release (GimpTextTool *text_too
|
||||
GdkEventKey *kevent);
|
||||
|
||||
void gimp_text_tool_reset_im_context (GimpTextTool *text_tool);
|
||||
void gimp_text_tool_abort_im_context (GimpTextTool *text_tool);
|
||||
|
||||
void gimp_text_tool_editor_get_cursor_rect (GimpTextTool *text_tool,
|
||||
gboolean overwrite,
|
||||
|
||||
@ -148,6 +148,8 @@ static void gimp_text_tool_text_changed (GimpText *text,
|
||||
|
||||
static gboolean gimp_text_tool_apply (GimpTextTool *text_tool,
|
||||
gboolean push_undo);
|
||||
static void gimp_text_tool_apply_list (GimpTextTool *text_tool,
|
||||
GList *pspecs);
|
||||
|
||||
static void gimp_text_tool_create_layer (GimpTextTool *text_tool,
|
||||
GimpText *text);
|
||||
@ -366,7 +368,14 @@ gimp_text_tool_button_press (GimpTool *tool,
|
||||
{
|
||||
gimp_tool_control_activate (tool->control);
|
||||
|
||||
gimp_text_tool_reset_im_context (text_tool);
|
||||
/* clicking anywhere while a preedit is going on aborts the
|
||||
* preedit, this is ugly but at least leaves everything in
|
||||
* a consistent state
|
||||
*/
|
||||
if (text_tool->preedit_active)
|
||||
gimp_text_tool_abort_im_context (text_tool);
|
||||
else
|
||||
gimp_text_tool_reset_im_context (text_tool);
|
||||
|
||||
text_tool->selecting = FALSE;
|
||||
|
||||
@ -1130,17 +1139,59 @@ gimp_text_tool_proxy_notify (GimpText *text,
|
||||
if ((pspec->flags & G_PARAM_READWRITE) == G_PARAM_READWRITE &&
|
||||
pspec->owner_type == GIMP_TYPE_TEXT)
|
||||
{
|
||||
gimp_text_tool_block_drawing (text_tool);
|
||||
if (text_tool->preedit_active)
|
||||
{
|
||||
/* if there is a preedit going on, don't queue pending
|
||||
* changes to be idle-applied with undo; instead, flush the
|
||||
* pending queue (happens only when preedit starts), and
|
||||
* apply the changes to text_tool->text directly. Preedit
|
||||
* will *always* end by removing the preedit string, and if
|
||||
* the preedit was committed, it will insert the resulting
|
||||
* text, which will not trigger this if() any more.
|
||||
*/
|
||||
|
||||
text_tool->pending = g_list_append (text_tool->pending, (gpointer) pspec);
|
||||
GList *list = NULL;
|
||||
|
||||
if (text_tool->idle_id)
|
||||
g_source_remove (text_tool->idle_id);
|
||||
/* if there are pending changes, apply them before applying
|
||||
* preedit stuff directly (bypassing undo)
|
||||
*/
|
||||
if (text_tool->pending)
|
||||
{
|
||||
gimp_text_tool_block_drawing (text_tool);
|
||||
gimp_text_tool_apply (text_tool, TRUE);
|
||||
}
|
||||
|
||||
text_tool->idle_id =
|
||||
g_idle_add_full (G_PRIORITY_LOW,
|
||||
gimp_text_tool_apply_idle, text_tool,
|
||||
NULL);
|
||||
gimp_text_tool_block_drawing (text_tool);
|
||||
|
||||
list = g_list_append (list, (gpointer) pspec);
|
||||
gimp_text_tool_apply_list (text_tool, list);
|
||||
g_list_free (list);
|
||||
|
||||
gimp_text_tool_frame_item (text_tool);
|
||||
|
||||
gimp_image_flush (gimp_item_get_image (GIMP_ITEM (text_tool->layer)));
|
||||
|
||||
gimp_text_tool_unblock_drawing (text_tool);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* else queue the property change for normal processing,
|
||||
* including undo
|
||||
*/
|
||||
|
||||
gimp_text_tool_block_drawing (text_tool);
|
||||
|
||||
text_tool->pending = g_list_append (text_tool->pending,
|
||||
(gpointer) pspec);
|
||||
|
||||
if (text_tool->idle_id)
|
||||
g_source_remove (text_tool->idle_id);
|
||||
|
||||
text_tool->idle_id =
|
||||
g_idle_add_full (G_PRIORITY_LOW,
|
||||
gimp_text_tool_apply_idle, text_tool,
|
||||
NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1151,6 +1202,10 @@ gimp_text_tool_text_notify (GimpText *text,
|
||||
{
|
||||
g_return_if_fail (text == text_tool->text);
|
||||
|
||||
/* an undo cancels all preedit operations */
|
||||
if (text_tool->preedit_active)
|
||||
gimp_text_tool_abort_im_context (text_tool);
|
||||
|
||||
gimp_text_tool_block_drawing (text_tool);
|
||||
|
||||
if ((pspec->flags & G_PARAM_READWRITE) == G_PARAM_READWRITE)
|
||||
@ -1219,8 +1274,6 @@ gimp_text_tool_apply (GimpTextTool *text_tool,
|
||||
const GParamSpec *pspec = NULL;
|
||||
GimpImage *image;
|
||||
GimpTextLayer *layer;
|
||||
GObject *src;
|
||||
GObject *dest;
|
||||
GList *list;
|
||||
gboolean undo_group = FALSE;
|
||||
|
||||
@ -1299,48 +1352,11 @@ gimp_text_tool_apply (GimpTextTool *text_tool,
|
||||
gimp_image_undo_push_text_layer (image, NULL, layer, pspec);
|
||||
}
|
||||
|
||||
src = G_OBJECT (text_tool->proxy);
|
||||
dest = G_OBJECT (text_tool->text);
|
||||
|
||||
g_signal_handlers_block_by_func (dest,
|
||||
gimp_text_tool_text_notify,
|
||||
text_tool);
|
||||
g_signal_handlers_block_by_func (dest,
|
||||
gimp_text_tool_text_changed,
|
||||
text_tool);
|
||||
|
||||
g_object_freeze_notify (dest);
|
||||
|
||||
for (; list; list = g_list_next (list))
|
||||
{
|
||||
GValue value = G_VALUE_INIT;
|
||||
|
||||
/* look ahead and compress changes */
|
||||
if (list->next && list->next->data == list->data)
|
||||
continue;
|
||||
|
||||
pspec = list->data;
|
||||
|
||||
g_value_init (&value, pspec->value_type);
|
||||
|
||||
g_object_get_property (src, pspec->name, &value);
|
||||
g_object_set_property (dest, pspec->name, &value);
|
||||
|
||||
g_value_unset (&value);
|
||||
}
|
||||
gimp_text_tool_apply_list (text_tool, list);
|
||||
|
||||
g_list_free (text_tool->pending);
|
||||
text_tool->pending = NULL;
|
||||
|
||||
g_object_thaw_notify (dest);
|
||||
|
||||
g_signal_handlers_unblock_by_func (dest,
|
||||
gimp_text_tool_text_notify,
|
||||
text_tool);
|
||||
g_signal_handlers_unblock_by_func (dest,
|
||||
gimp_text_tool_text_changed,
|
||||
text_tool);
|
||||
|
||||
if (push_undo)
|
||||
{
|
||||
g_object_set (layer, "modified", FALSE, NULL);
|
||||
@ -1358,6 +1374,52 @@ gimp_text_tool_apply (GimpTextTool *text_tool,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
gimp_text_tool_apply_list (GimpTextTool *text_tool,
|
||||
GList *pspecs)
|
||||
{
|
||||
GObject *src = G_OBJECT (text_tool->proxy);
|
||||
GObject *dest = G_OBJECT (text_tool->text);
|
||||
GList *list;
|
||||
|
||||
g_signal_handlers_block_by_func (dest,
|
||||
gimp_text_tool_text_notify,
|
||||
text_tool);
|
||||
g_signal_handlers_block_by_func (dest,
|
||||
gimp_text_tool_text_changed,
|
||||
text_tool);
|
||||
|
||||
g_object_freeze_notify (dest);
|
||||
|
||||
for (list = pspecs; list; list = g_list_next (list))
|
||||
{
|
||||
const GParamSpec *pspec;
|
||||
GValue value = G_VALUE_INIT;
|
||||
|
||||
/* look ahead and compress changes */
|
||||
if (list->next && list->next->data == list->data)
|
||||
continue;
|
||||
|
||||
pspec = list->data;
|
||||
|
||||
g_value_init (&value, pspec->value_type);
|
||||
|
||||
g_object_get_property (src, pspec->name, &value);
|
||||
g_object_set_property (dest, pspec->name, &value);
|
||||
|
||||
g_value_unset (&value);
|
||||
}
|
||||
|
||||
g_object_thaw_notify (dest);
|
||||
|
||||
g_signal_handlers_unblock_by_func (dest,
|
||||
gimp_text_tool_text_notify,
|
||||
text_tool);
|
||||
g_signal_handlers_unblock_by_func (dest,
|
||||
gimp_text_tool_text_changed,
|
||||
text_tool);
|
||||
}
|
||||
|
||||
static void
|
||||
gimp_text_tool_create_layer (GimpTextTool *text_tool,
|
||||
GimpText *text)
|
||||
|
||||
@ -77,6 +77,7 @@ struct _GimpTextTool
|
||||
GtkIMContext *im_context;
|
||||
gboolean needs_im_reset;
|
||||
|
||||
gboolean preedit_active;
|
||||
gchar *preedit_string;
|
||||
gint preedit_cursor;
|
||||
GtkTextMark *preedit_start;
|
||||
|
||||
Reference in New Issue
Block a user