GtkInputMethodFilter.cpp [plain text]
#include "config.h"
#include "GtkInputMethodFilter.h"
#include "GUniquePtrGtk.h"
#include "GtkVersioning.h"
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#include <wtf/MathExtras.h>
#include <wtf/gobject/GUniquePtr.h>
const int gCompositionEventKeyCode = GDK_KEY_VoidSymbol;
namespace WebCore {
static void handleCommitCallback(GtkIMContext*, const char* compositionString, GtkInputMethodFilter* filter)
{
filter->handleCommit(compositionString);
}
static void handlePreeditStartCallback(GtkIMContext*, GtkInputMethodFilter* filter)
{
filter->handlePreeditStart();
}
static void handlePreeditChangedCallback(GtkIMContext*, GtkInputMethodFilter* filter)
{
filter->handlePreeditChanged();
}
static void handlePreeditEndCallback(GtkIMContext*, GtkInputMethodFilter* filter)
{
filter->handlePreeditEnd();
}
static void handleWidgetRealize(GtkWidget* widget, GtkInputMethodFilter* filter)
{
GdkWindow* window = gtk_widget_get_window(widget);
ASSERT(window);
gtk_im_context_set_client_window(filter->context(), window);
}
void GtkInputMethodFilter::setWidget(GtkWidget* widget)
{
ASSERT(!m_widget);
m_widget = widget;
if (gtk_widget_get_window(m_widget))
handleWidgetRealize(m_widget, this);
else
g_signal_connect_after(widget, "realize", G_CALLBACK(handleWidgetRealize), this);
}
void GtkInputMethodFilter::setCursorRect(const IntRect& cursorRect)
{
static const int windowMovementThreshold = 10 * 10;
if (cursorRect.location().distanceSquaredToPoint(m_lastCareLocation) < windowMovementThreshold)
return;
m_lastCareLocation = cursorRect.location();
IntRect translatedRect = cursorRect;
ASSERT(m_widget);
GtkAllocation allocation;
gtk_widget_get_allocation(m_widget, &allocation);
translatedRect.move(allocation.x, allocation.y);
GdkRectangle gdkCursorRect = cursorRect;
gtk_im_context_set_cursor_location(m_context.get(), &gdkCursorRect);
}
GtkInputMethodFilter::GtkInputMethodFilter()
: m_cursorOffset(0)
, m_context(adoptGRef(gtk_im_multicontext_new()))
, m_widget(0)
, m_enabled(false)
, m_composingTextCurrently(false)
, m_filteringKeyEvent(false)
, m_preeditChanged(false)
, m_preventNextCommit(false)
, m_justSentFakeKeyUp(false)
, m_lastFilteredKeyPressCodeWithNoResults(GDK_KEY_VoidSymbol)
{
g_signal_connect(m_context.get(), "commit", G_CALLBACK(handleCommitCallback), this);
g_signal_connect(m_context.get(), "preedit-start", G_CALLBACK(handlePreeditStartCallback), this);
g_signal_connect(m_context.get(), "preedit-changed", G_CALLBACK(handlePreeditChangedCallback), this);
g_signal_connect(m_context.get(), "preedit-end", G_CALLBACK(handlePreeditEndCallback), this);
}
GtkInputMethodFilter::~GtkInputMethodFilter()
{
g_signal_handlers_disconnect_by_func(m_context.get(), reinterpret_cast<void*>(handleCommitCallback), this);
g_signal_handlers_disconnect_by_func(m_context.get(), reinterpret_cast<void*>(handlePreeditStartCallback), this);
g_signal_handlers_disconnect_by_func(m_context.get(), reinterpret_cast<void*>(handlePreeditChangedCallback), this);
g_signal_handlers_disconnect_by_func(m_context.get(), reinterpret_cast<void*>(handlePreeditEndCallback), this);
g_signal_handlers_disconnect_by_func(m_widget, reinterpret_cast<void*>(handleWidgetRealize), this);
}
void GtkInputMethodFilter::setEnabled(bool enabled)
{
m_enabled = enabled;
if (enabled)
gtk_im_context_focus_in(m_context.get());
else
gtk_im_context_focus_out(m_context.get());
}
bool GtkInputMethodFilter::filterKeyEvent(GdkEventKey* event)
{
if (!canEdit() || !m_enabled)
return sendSimpleKeyEvent(event);
m_preeditChanged = false;
m_filteringKeyEvent = true;
unsigned int lastFilteredKeyPressCodeWithNoResults = m_lastFilteredKeyPressCodeWithNoResults;
m_lastFilteredKeyPressCodeWithNoResults = GDK_KEY_VoidSymbol;
bool filtered = gtk_im_context_filter_keypress(m_context.get(), event);
m_filteringKeyEvent = false;
bool justSentFakeKeyUp = m_justSentFakeKeyUp;
m_justSentFakeKeyUp = false;
if (justSentFakeKeyUp && event->type == GDK_KEY_RELEASE)
return true;
if (filtered && !m_composingTextCurrently && !m_preeditChanged && m_confirmedComposition.length() == 1) {
bool result = sendSimpleKeyEvent(event, m_confirmedComposition);
m_confirmedComposition = String();
return result;
}
if (filtered && event->type == GDK_KEY_PRESS) {
if (!m_preeditChanged && m_confirmedComposition.isNull()) {
m_composingTextCurrently = true;
m_lastFilteredKeyPressCodeWithNoResults = event->keyval;
return true;
}
bool result = sendKeyEventWithCompositionResults(event);
if (!m_confirmedComposition.isEmpty()) {
m_composingTextCurrently = false;
m_confirmedComposition = String();
}
return result;
}
if (event->type == GDK_KEY_RELEASE && lastFilteredKeyPressCodeWithNoResults == event->keyval)
return true;
if (!m_confirmedComposition.isEmpty())
confirmComposition();
if (m_preeditChanged)
updatePreedit();
return sendSimpleKeyEvent(event);
}
void GtkInputMethodFilter::notifyMouseButtonPress()
{
if (m_composingTextCurrently)
confirmCurrentComposition();
m_composingTextCurrently = false;
cancelContextComposition();
}
void GtkInputMethodFilter::resetContext()
{
cancelCurrentComposition();
if (!m_composingTextCurrently)
return;
m_composingTextCurrently = false;
cancelContextComposition();
}
void GtkInputMethodFilter::cancelContextComposition()
{
m_preventNextCommit = !m_preedit.isEmpty();
gtk_im_context_reset(m_context.get());
m_composingTextCurrently = false;
m_justSentFakeKeyUp = false;
m_preedit = String();
m_confirmedComposition = String();
}
void GtkInputMethodFilter::notifyFocusedIn()
{
m_enabled = true;
gtk_im_context_focus_in(m_context.get());
}
void GtkInputMethodFilter::notifyFocusedOut()
{
if (!m_enabled)
return;
if (m_composingTextCurrently)
confirmCurrentComposition();
cancelContextComposition();
gtk_im_context_focus_out(m_context.get());
m_enabled = false;
}
void GtkInputMethodFilter::confirmComposition()
{
confirmCompositionText(m_confirmedComposition);
m_confirmedComposition = String();
}
void GtkInputMethodFilter::updatePreedit()
{
setPreedit(m_preedit, m_cursorOffset);
m_preeditChanged = false;
}
void GtkInputMethodFilter::sendCompositionAndPreeditWithFakeKeyEvents(ResultsToSend resultsToSend)
{
GUniquePtr<GdkEvent> event(gdk_event_new(GDK_KEY_PRESS));
event->key.time = GDK_CURRENT_TIME;
event->key.keyval = gCompositionEventKeyCode;
sendKeyEventWithCompositionResults(&event->key, resultsToSend, EventFaked);
m_confirmedComposition = String();
if (resultsToSend & Composition)
m_composingTextCurrently = false;
event->type = GDK_KEY_RELEASE;
sendSimpleKeyEvent(&event->key, String(), EventFaked);
m_justSentFakeKeyUp = true;
}
void GtkInputMethodFilter::handleCommit(const char* compositionString)
{
if (m_preventNextCommit) {
m_preventNextCommit = false;
return;
}
if (!m_enabled)
return;
m_confirmedComposition.append(String::fromUTF8(compositionString));
if (!m_filteringKeyEvent)
sendCompositionAndPreeditWithFakeKeyEvents(Composition);
}
void GtkInputMethodFilter::handlePreeditStart()
{
if (m_preventNextCommit || !m_enabled)
return;
m_preeditChanged = true;
m_preedit = "";
}
void GtkInputMethodFilter::handlePreeditChanged()
{
if (!m_enabled)
return;
GUniqueOutPtr<gchar> newPreedit;
gtk_im_context_get_preedit_string(m_context.get(), &newPreedit.outPtr(), 0, &m_cursorOffset);
if (m_preventNextCommit) {
if (strlen(newPreedit.get()) > 0)
m_preventNextCommit = false;
else
return;
}
m_preedit = String::fromUTF8(newPreedit.get());
m_cursorOffset = std::min(std::max(m_cursorOffset, 0), static_cast<int>(m_preedit.length()));
m_composingTextCurrently = !m_preedit.isEmpty();
m_preeditChanged = true;
if (!m_filteringKeyEvent)
sendCompositionAndPreeditWithFakeKeyEvents(Preedit);
}
void GtkInputMethodFilter::handlePreeditEnd()
{
if (m_preventNextCommit || !m_enabled)
return;
m_preedit = String();
m_cursorOffset = 0;
m_preeditChanged = true;
if (!m_filteringKeyEvent)
updatePreedit();
}
}