From 641dcce1d527f0fe654c07656aa828156cd5c373 Mon Sep 17 00:00:00 2001 From: "masayuki%d-toybox.com" Date: Tue, 14 Mar 2006 06:07:54 +0000 Subject: [PATCH] Bug 321468 [GTK2] Implement nsIKBStateControl Interface (event of accesskeys and shortcut keys are eaten by IME) r=masaki.katakai, sr=roc --- widget/src/gtk2/nsWindow.cpp | 292 ++++++++++++++++++++++++++++++----- widget/src/gtk2/nsWindow.h | 56 ++++++- 2 files changed, 310 insertions(+), 38 deletions(-) diff --git a/widget/src/gtk2/nsWindow.cpp b/widget/src/gtk2/nsWindow.cpp index 6adb483fd04..06fbc1409a2 100644 --- a/widget/src/gtk2/nsWindow.cpp +++ b/widget/src/gtk2/nsWindow.cpp @@ -22,6 +22,7 @@ * * Contributor(s): * Mats Palmgren + * Masayuki Nakano * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -237,10 +238,13 @@ static nsWindow *gIMEFocusWindow = NULL; static GdkEventKey *gKeyEvent = NULL; static PRBool gKeyEventCommitted = PR_FALSE; static PRBool gKeyEventChanged = PR_FALSE; +static PRBool gIMESuppressCommit = PR_FALSE; static void IM_commit_cb (GtkIMContext *aContext, const gchar *aString, nsWindow *aWindow); +static void IM_commit_cb_internal (const gchar *aString, + nsWindow *aWindow); static void IM_preedit_changed_cb (GtkIMContext *aContext, nsWindow *aWindow); static void IM_set_text_range (const PRInt32 aLen, @@ -250,6 +254,8 @@ static void IM_set_text_range (const PRInt32 aLen, PRUint32 *aTextRangeListLengthResult, nsTextRangeArray *aTextRangeListResult); +static nsWindow *IM_get_owning_window(MozDrawingarea *aArea); + static GtkIMContext *IM_get_input_context(MozDrawingarea *aArea); // If after selecting profile window, the startup fail, please refer to @@ -313,8 +319,7 @@ nsWindow::nsWindow() mDragMotionTimerID = 0; #ifdef USE_XIM - mIMContext = nsnull; - mComposingText = PR_FALSE; + mIMEData = nsnull; #endif #ifdef ACCESSIBILITY @@ -349,8 +354,14 @@ nsWindow::ReleaseGlobals() } } +#ifndef USE_XIM NS_IMPL_ISUPPORTS_INHERITED1(nsWindow, nsCommonWidget, nsISupportsWeakReference) +#else +NS_IMPL_ISUPPORTS_INHERITED2(nsWindow, nsCommonWidget, + nsISupportsWeakReference, + nsIKBStateControl) +#endif NS_IMETHODIMP nsWindow::Create(nsIWidget *aParent, @@ -1690,6 +1701,11 @@ nsWindow::OnButtonPressEvent(GtkWidget *aWidget, GdkEventButton *aEvent) PRUint32 eventType; nsEventStatus status; +#ifdef USE_XIM + if (gIMEFocusWindow) + gIMEFocusWindow->ResetInputStateInternal(); +#endif + // If you double click in GDK, it will actually generate a single // click event before sending the double click event, and this is // different than the DOM spec. GDK puts this in the queue @@ -1871,7 +1887,7 @@ nsWindow::OnKeyPressEvent(GtkWidget *aWidget, GdkEventKey *aEvent) // if we are in the middle of composing text, XIM gets to see it // before mozilla does. LOGIM(("key press [%p]: composing %d val %d\n", - (void *)this, mComposingText, aEvent->keyval)); + (void *)this, IMEComposingWindow() != nsnull, aEvent->keyval)); if (IMEFilterEvent(aEvent)) return TRUE; LOGIM(("sending as regular key press event\n")); @@ -4570,22 +4586,37 @@ nsChildWindow::~nsChildWindow() void nsWindow::IMEDestroyContext(void) { + if (!mIMEData) { + // Clear reference to this. + if (IMEComposingWindow() == this) + CancelIMECompositionInternal(); + if (gIMEFocusWindow == this) + gIMEFocusWindow = nsnull; + return; + } + // If this is the focus window and we have an IM context we need // to unset the focus on this window before we destroy the window. - if (gIMEFocusWindow == this) { + GtkIMContext *im = IMEGetContext(); + if (im && gIMEFocusWindow && gIMEFocusWindow->IMEGetContext() == im) { gIMEFocusWindow->IMELoseFocus(); gIMEFocusWindow = nsnull; } - if (!mIMContext) - return; + if (mIMEData->mContext) { + gtk_im_context_set_client_window(mIMEData->mContext, nsnull); + g_object_unref(G_OBJECT(mIMEData->mContext)); + } - gtk_im_context_set_client_window(mIMContext, NULL); - g_object_unref(G_OBJECT(mIMContext)); - mIMContext = nsnull; + if (mIMEData->mDummyContext) { + gtk_im_context_set_client_window(mIMEData->mDummyContext, nsnull); + g_object_unref(G_OBJECT(mIMEData->mDummyContext)); + } + + delete mIMEData; + mIMEData = nsnull; } - void nsWindow::IMESetFocus(void) { @@ -4596,6 +4627,12 @@ nsWindow::IMESetFocus(void) gtk_im_context_focus_in(im); gIMEFocusWindow = this; + + if (!IMEIsEnabled()) { + // We should release IME focus for uim and scim. + // These IMs are using snooper that is released at losing focus. + IMELoseFocus(); + } } void @@ -4614,12 +4651,17 @@ nsWindow::IMEComposeStart(void) { LOGIM(("IMEComposeStart [%p]\n", (void *)this)); - if (mComposingText) { + if (IMEComposingWindow()) { NS_WARNING("tried to re-start text composition\n"); return; } - mComposingText = PR_TRUE; + nsWindow *win = IMEGetOwningWindow(); + if (win) { + nsIMEData *data = win->mIMEData; + if (data) + data->mComposingWindow = this; + } nsCompositionEvent compEvent(PR_TRUE, NS_COMPOSITION_START, this); @@ -4650,7 +4692,7 @@ nsWindow::IMEComposeText (const PRUnichar *aText, const PangoAttrList *aFeedback) { // Send our start composition event if we need to - if (!mComposingText) + if (!IMEComposingWindow()) IMEComposeStart(); LOGIM(("IMEComposeText\n")); @@ -4694,12 +4736,17 @@ nsWindow::IMEComposeEnd(void) { LOGIM(("IMEComposeEnd [%p]\n", (void *)this)); - if (!mComposingText) { + if (!IMEComposingWindow()) { NS_WARNING("tried to end text composition before it was started"); return; } - mComposingText = PR_FALSE; + nsWindow *win = IMEGetOwningWindow(); + if (win) { + nsIMEData *data = win->mIMEData; + if (data) + data->mComposingWindow = nsnull; + } nsCompositionEvent compEvent(PR_TRUE, NS_COMPOSITION_END, this); @@ -4713,26 +4760,63 @@ nsWindow::IMEGetContext() return IM_get_input_context(this->mDrawingarea); } +PRBool +nsWindow::IMEIsEnabled(void) +{ + nsWindow *win = IMEGetOwningWindow(); + if (!win) + return PR_FALSE; + return win->mIMEData ? win->mIMEData->mEnabled : PR_FALSE; +} + +nsWindow* +nsWindow::IMEComposingWindow(void) +{ + nsWindow *win = IMEGetOwningWindow(); + if (!win) + return nsnull; + return win->mIMEData ? win->mIMEData->mComposingWindow : nsnull; +} + +nsWindow* +nsWindow::IMEGetOwningWindow(void) +{ + nsWindow *window = IM_get_owning_window(this->mDrawingarea); + return window; +} + void nsWindow::IMECreateContext(void) { - GtkIMContext *im = gtk_im_multicontext_new(); - if (!im) + mIMEData = new nsIMEData; + if (!mIMEData) return; - gtk_im_context_set_client_window(im, GTK_WIDGET(mContainer)->window); + mIMEData->mContext = gtk_im_multicontext_new(); + mIMEData->mDummyContext = gtk_im_multicontext_new(); + if (!mIMEData->mContext || !mIMEData->mDummyContext) { + NS_ERROR("failed to create IM context."); + IMEDestroyContext(); + return; + } - g_signal_connect(G_OBJECT(im), "preedit_changed", + gtk_im_context_set_client_window(mIMEData->mContext, + GTK_WIDGET(mContainer)->window); + gtk_im_context_set_client_window(mIMEData->mDummyContext, + GTK_WIDGET(mContainer)->window); + + g_signal_connect(G_OBJECT(mIMEData->mContext), "preedit_changed", G_CALLBACK(IM_preedit_changed_cb), this); - g_signal_connect(G_OBJECT(im), "commit", + g_signal_connect(G_OBJECT(mIMEData->mContext), "commit", G_CALLBACK(IM_commit_cb), this); - - mIMContext = im; } PRBool nsWindow::IMEFilterEvent(GdkEventKey *aEvent) { + if (!IMEIsEnabled()) + return FALSE; + GtkIMContext *im = IMEGetContext(); if (!im) return FALSE; @@ -4762,6 +4846,124 @@ nsWindow::IMEFilterEvent(GdkEventKey *aEvent) return retval; } +/* nsIKBStateControl */ +NS_IMETHODIMP +nsWindow::ResetInputState() +{ + // We should not implement this until bug 327003 is fixed. + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsWindow::ResetInputStateInternal() +{ + nsWindow *win = IMEComposingWindow(); + if (win) { + GtkIMContext *im = IMEGetContext(); + if (!im) + return NS_OK; + + gchar *preedit_string; + gint cursor_pos; + PangoAttrList *feedback_list; + gtk_im_context_get_preedit_string(im, &preedit_string, + &feedback_list, &cursor_pos); + if (preedit_string && *preedit_string) { + IM_commit_cb_internal(preedit_string, win); + g_free(preedit_string); + } + if (feedback_list) + pango_attr_list_unref(feedback_list); + } + + CancelIMECompositionInternal(); + + return NS_OK; +} + +NS_IMETHODIMP +nsWindow::SetIMEOpenState(PRBool aState) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsWindow::GetIMEOpenState(PRBool* aState) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsWindow::SetIMEEnabled(PRBool aState) +{ + nsWindow *window = IMEGetOwningWindow(); + if (!window || !window->mIMEData || window->mIMEData->mEnabled == aState) + return NS_OK; + + GtkIMContext *focusedIm = nsnull; + // XXX Don't we need to check gFocusWindow? + nsWindow *focusedWin = gIMEFocusWindow; + if (focusedWin) { + nsWindow *owningWin = focusedWin->IMEGetOwningWindow(); + if (owningWin && owningWin->mIMEData) + focusedIm = owningWin->mIMEData->mContext; + } + + if (focusedIm && focusedIm == window->mIMEData->mContext) { + // Release current IME focus if IME is enabled. + if (window->mIMEData->mEnabled) + focusedWin->IMELoseFocus(); + + window->mIMEData->mEnabled = aState; + + // Even when aState is not PR_TRUE, we need to set IME focus. + // Because some IMs are updating the status bar of them in this time. + focusedWin->IMESetFocus(); + } else { + if (window->mIMEData->mEnabled) + ResetInputStateInternal(); + window->mIMEData->mEnabled = aState; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWindow::GetIMEEnabled(PRBool* aState) +{ + NS_ENSURE_ARG_POINTER(aState); + *aState = IMEIsEnabled(); + return NS_OK; +} + +NS_IMETHODIMP +nsWindow::CancelIMEComposition() +{ + // We should not implement this until bug 327003 is fixed. + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsWindow::CancelIMECompositionInternal() +{ + GtkIMContext *im = IMEGetContext(); + if (!im) + return NS_OK; + + NS_ASSERTION(gIMESuppressCommit, "CancelIMEComposition is already called!"); + gIMESuppressCommit = PR_TRUE; + gtk_im_context_reset(im); + gIMESuppressCommit = PR_FALSE; + + nsWindow *win = IMEComposingWindow(); + if (win) { + win->IMEComposeText(nsnull, 0, nsnull, 0, nsnull); + win->IMEComposeEnd(); + } + + return NS_OK; +} + /* static */ void IM_preedit_changed_cb(GtkIMContext *aContext, @@ -4826,8 +5028,8 @@ IM_commit_cb (GtkIMContext *aContext, const gchar *aUtf8_str, nsWindow *aWindow) { - gunichar2 *uniStr; - glong uniStrLen; + if (gIMESuppressCommit) + return; LOGIM(("IM_commit_cb\n")); @@ -4859,9 +5061,16 @@ IM_commit_cb (GtkIMContext *aContext, } gKeyEventChanged = PR_TRUE; + IM_commit_cb_internal(aUtf8_str, window); +} - uniStr = NULL; - uniStrLen = 0; +/* static */ +void +IM_commit_cb_internal (const gchar *aUtf8_str, + nsWindow *aWindow) +{ + gunichar2 *uniStr = nsnull; + glong uniStrLen = 0; uniStr = g_utf8_to_utf16(aUtf8_str, -1, NULL, &uniStrLen, NULL); if (!uniStr) { @@ -4870,9 +5079,9 @@ IM_commit_cb (GtkIMContext *aContext, } if (uniStrLen) { - window->IMEComposeText((const PRUnichar *)uniStr, - (PRInt32)uniStrLen, NULL, 0, NULL); - window->IMEComposeEnd(); + aWindow->IMEComposeText((const PRUnichar *)uniStr, + (PRInt32)uniStrLen, nsnull, 0, nsnull); + aWindow->IMEComposeEnd(); } g_free(uniStr); @@ -5000,16 +5209,29 @@ IM_set_text_range(const PRInt32 aLen, pango_attr_iterator_destroy(aFeedbackIterator); } +/* static */ +nsWindow * +IM_get_owning_window(MozDrawingarea *aArea) +{ + if (!aArea) + return nsnull; + GtkWidget *owningWidget = + get_gtk_widget_for_gdk_window(aArea->inner_window); + if (!owningWidget) + return nsnull; + return get_window_for_gtk_widget(owningWidget); +} + /* static */ GtkIMContext * IM_get_input_context(MozDrawingarea *aArea) { - GtkWidget *owningWidget = - get_gtk_widget_for_gdk_window(aArea->inner_window); - - nsWindow *owningWindow = get_window_for_gtk_widget(owningWidget); - - return owningWindow->mIMContext; + if (!aArea) + return nsnull; + nsWindow::nsIMEData *data = IM_get_owning_window(aArea)->mIMEData; + if (!data) + return nsnull; + return data->mEnabled ? data->mContext : data->mDummyContext; } #endif diff --git a/widget/src/gtk2/nsWindow.h b/widget/src/gtk2/nsWindow.h index 2f0cbcee2d0..8b853fba616 100644 --- a/widget/src/gtk2/nsWindow.h +++ b/widget/src/gtk2/nsWindow.h @@ -21,6 +21,7 @@ * are Copyright (C) 2001 the Initial Developer. All Rights Reserved. * * Contributor(s): + * Masayuki Nakano * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -62,9 +63,14 @@ #ifdef USE_XIM #include #include "pldhash.h" +#include "nsIKBStateControl.h" #endif -class nsWindow : public nsCommonWidget, public nsSupportsWeakReference { +class nsWindow : public nsCommonWidget, public nsSupportsWeakReference +#ifdef USE_XIM + ,public nsIKBStateControl +#endif +{ public: nsWindow(); virtual ~nsWindow(); @@ -271,11 +277,55 @@ public: const PangoAttrList *aFeedback); void IMEComposeEnd (void); GtkIMContext* IMEGetContext (void); + nsWindow* IMEGetOwningWindow(void); + PRBool IMEIsEnabled (void); + nsWindow* IMEComposingWindow(void); void IMECreateContext (void); PRBool IMEFilterEvent (GdkEventKey *aEvent); - GtkIMContext *mIMContext; - PRBool mComposingText; + /* + * |mIMEData| has all IME data for the window and its children widgets. + * So, this is created only on owner widget. Therefore, when the + * focused widget needs the data, it get from its owning window. + * See |IM_get_input_context|. + */ + struct nsIMEData { + // Actual context. This is used for handling the user's input. + GtkIMContext *mContext; + // mDummyContext is a dummy context and will be used in IMESetFocus() + // when mEnabled is false. This mDummyContext IM state is always + // "off", so it works to switch conversion mode to OFF on IM status + // window. + GtkIMContext *mDummyContext; + // This mComposingWindow is set in IMEComposeStart(), when user starts + // composition, then unset in IMEComposeEnd() when user ends the + // composition. We will keep the widget where the actual composition is + // started. During the composition, we may get some events like + // ResetInputStateInternal() and CancelIMECompositionInternal() by + // changing input focus, we will use the original widget of + // mComposingWindow to commit or reset the composition. + nsWindow *mComposingWindow; + // IME enabled state in this window. + PRPackedBool mEnabled; + nsIMEData() { + mContext = nsnull; + mDummyContext = nsnull; + mComposingWindow = nsnull; + mEnabled = PR_TRUE; + } + }; + nsIMEData *mIMEData; + + // nsIKBStateControl interface + NS_IMETHOD ResetInputState(); + NS_IMETHOD SetIMEOpenState(PRBool aState); + NS_IMETHOD GetIMEOpenState(PRBool* aState); + NS_IMETHOD SetIMEEnabled(PRBool aState); + NS_IMETHOD GetIMEEnabled(PRBool* aState); + NS_IMETHOD CancelIMEComposition(); + + nsresult ResetInputStateInternal(); + nsresult CancelIMECompositionInternal(); #endif