root/Source/web/WebInputEventFactoryGtk.cpp

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. shouldForgetPreviousClick
  2. resetClickCountState
  3. isKeyPadKeyval
  4. gdkEventTimeToWebEventTime
  5. gdkStateToWebEventModifiers
  6. gdkEventToWindowsKeyCode
  7. normalizeEventState
  8. getControlCharacter
  9. keyboardEvent
  10. isSystemKeyEvent
  11. keyboardEvent
  12. mouseEvent
  13. mouseEvent
  14. mouseEvent
  15. mouseWheelEvent

/*
 * Copyright (C) 2006-2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "WebInputEventFactory.h"

#include "platform/KeyCodeConversion.h"
#include "platform/KeyboardCodes.h"

#include "WebInputEvent.h"

#include <gdk/gdk.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#include <stdlib.h>

#include "wtf/Assertions.h"

namespace {

// For click count tracking.
static int gNumClicks = 0;
static GdkWindow* gLastClickEventWindow = 0;
static gint gLastClickTime = 0;
static gint gLastClickX = 0;
static gint gLastClickY = 0;
static blink::WebMouseEvent::Button gLastClickButton = blink::WebMouseEvent::ButtonNone;

bool shouldForgetPreviousClick(GdkWindow* window, gint time, gint x, gint y)
{
    static GtkSettings* settings = gtk_settings_get_default();

    if (window != gLastClickEventWindow)
      return true;

    gint doubleClickTime = 250;
    gint doubleClickDistance = 5;
    g_object_get(G_OBJECT(settings),
                 "gtk-double-click-time", &doubleClickTime,
                 "gtk-double-click-distance", &doubleClickDistance, NULL);
    return (time - gLastClickTime) > doubleClickTime
           || abs(x - gLastClickX) > doubleClickDistance
           || abs(y - gLastClickY) > doubleClickDistance;
}

void resetClickCountState()
{
    gNumClicks = 0;
    gLastClickEventWindow = 0;
    gLastClickTime = 0;
    gLastClickX = 0;
    gLastClickY = 0;
    gLastClickButton = blink::WebMouseEvent::ButtonNone;
}

bool isKeyPadKeyval(guint keyval)
{
    // Keypad keyvals all fall into one range.
    return keyval >= GDK_KP_Space && keyval <= GDK_KP_9;
}

}  // namespace

namespace blink {

static double gdkEventTimeToWebEventTime(guint32 time)
{
    // Convert from time in ms to time in sec.
    return time / 1000.0;
}

static int gdkStateToWebEventModifiers(guint state)
{
    int modifiers = 0;
    if (state & GDK_SHIFT_MASK)
        modifiers |= WebInputEvent::ShiftKey;
    if (state & GDK_CONTROL_MASK)
        modifiers |= WebInputEvent::ControlKey;
    if (state & GDK_MOD1_MASK)
        modifiers |= WebInputEvent::AltKey;
    if (state & GDK_META_MASK)
        modifiers |= WebInputEvent::MetaKey;
    if (state & GDK_BUTTON1_MASK)
        modifiers |= WebInputEvent::LeftButtonDown;
    if (state & GDK_BUTTON2_MASK)
        modifiers |= WebInputEvent::MiddleButtonDown;
    if (state & GDK_BUTTON3_MASK)
        modifiers |= WebInputEvent::RightButtonDown;
    if (state & GDK_LOCK_MASK)
        modifiers |= WebInputEvent::CapsLockOn;
    if (state & GDK_MOD2_MASK)
        modifiers |= WebInputEvent::NumLockOn;
    return modifiers;
}

static int gdkEventToWindowsKeyCode(const GdkEventKey* event)
{
    static const unsigned hardwareCodeToGDKKeyval[] = {
        0,                 // 0x00:
        0,                 // 0x01:
        0,                 // 0x02:
        0,                 // 0x03:
        0,                 // 0x04:
        0,                 // 0x05:
        0,                 // 0x06:
        0,                 // 0x07:
        0,                 // 0x08:
        0,                 // 0x09: GDK_Escape
        GDK_1,             // 0x0A: GDK_1
        GDK_2,             // 0x0B: GDK_2
        GDK_3,             // 0x0C: GDK_3
        GDK_4,             // 0x0D: GDK_4
        GDK_5,             // 0x0E: GDK_5
        GDK_6,             // 0x0F: GDK_6
        GDK_7,             // 0x10: GDK_7
        GDK_8,             // 0x11: GDK_8
        GDK_9,             // 0x12: GDK_9
        GDK_0,             // 0x13: GDK_0
        GDK_minus,         // 0x14: GDK_minus
        GDK_equal,         // 0x15: GDK_equal
        0,                 // 0x16: GDK_BackSpace
        0,                 // 0x17: GDK_Tab
        GDK_q,             // 0x18: GDK_q
        GDK_w,             // 0x19: GDK_w
        GDK_e,             // 0x1A: GDK_e
        GDK_r,             // 0x1B: GDK_r
        GDK_t,             // 0x1C: GDK_t
        GDK_y,             // 0x1D: GDK_y
        GDK_u,             // 0x1E: GDK_u
        GDK_i,             // 0x1F: GDK_i
        GDK_o,             // 0x20: GDK_o
        GDK_p,             // 0x21: GDK_p
        GDK_bracketleft,   // 0x22: GDK_bracketleft
        GDK_bracketright,  // 0x23: GDK_bracketright
        0,                 // 0x24: GDK_Return
        0,                 // 0x25: GDK_Control_L
        GDK_a,             // 0x26: GDK_a
        GDK_s,             // 0x27: GDK_s
        GDK_d,             // 0x28: GDK_d
        GDK_f,             // 0x29: GDK_f
        GDK_g,             // 0x2A: GDK_g
        GDK_h,             // 0x2B: GDK_h
        GDK_j,             // 0x2C: GDK_j
        GDK_k,             // 0x2D: GDK_k
        GDK_l,             // 0x2E: GDK_l
        GDK_semicolon,     // 0x2F: GDK_semicolon
        GDK_apostrophe,    // 0x30: GDK_apostrophe
        GDK_grave,         // 0x31: GDK_grave
        0,                 // 0x32: GDK_Shift_L
        GDK_backslash,     // 0x33: GDK_backslash
        GDK_z,             // 0x34: GDK_z
        GDK_x,             // 0x35: GDK_x
        GDK_c,             // 0x36: GDK_c
        GDK_v,             // 0x37: GDK_v
        GDK_b,             // 0x38: GDK_b
        GDK_n,             // 0x39: GDK_n
        GDK_m,             // 0x3A: GDK_m
        GDK_comma,         // 0x3B: GDK_comma
        GDK_period,        // 0x3C: GDK_period
        GDK_slash,         // 0x3D: GDK_slash
        0,                 // 0x3E: GDK_Shift_R
        0,                 // 0x3F:
        0,                 // 0x40:
        0,                 // 0x41:
        0,                 // 0x42:
        0,                 // 0x43:
        0,                 // 0x44:
        0,                 // 0x45:
        0,                 // 0x46:
        0,                 // 0x47:
        0,                 // 0x48:
        0,                 // 0x49:
        0,                 // 0x4A:
        0,                 // 0x4B:
        0,                 // 0x4C:
        0,                 // 0x4D:
        0,                 // 0x4E:
        0,                 // 0x4F:
        0,                 // 0x50:
        0,                 // 0x51:
        0,                 // 0x52:
        0,                 // 0x53:
        0,                 // 0x54:
        0,                 // 0x55:
        0,                 // 0x56:
        0,                 // 0x57:
        0,                 // 0x58:
        0,                 // 0x59:
        0,                 // 0x5A:
        0,                 // 0x5B:
        0,                 // 0x5C:
        0,                 // 0x5D:
        0,                 // 0x5E:
        0,                 // 0x5F:
        0,                 // 0x60:
        0,                 // 0x61:
        0,                 // 0x62:
        0,                 // 0x63:
        0,                 // 0x64:
        0,                 // 0x65:
        0,                 // 0x66:
        0,                 // 0x67:
        0,                 // 0x68:
        0,                 // 0x69:
        0,                 // 0x6A:
        0,                 // 0x6B:
        0,                 // 0x6C:
        0,                 // 0x6D:
        0,                 // 0x6E:
        0,                 // 0x6F:
        0,                 // 0x70:
        0,                 // 0x71:
        0,                 // 0x72:
        GDK_Super_L,       // 0x73: GDK_Super_L
        GDK_Super_R,       // 0x74: GDK_Super_R
    };

    // |windowsKeyCode| has to include a valid virtual-key code even when we
    // use non-US layouts, e.g. even when we type an 'A' key of a US keyboard
    // on the Hebrew layout, |windowsKeyCode| should be VK_A.
    // On the other hand, |event->keyval| value depends on the current
    // GdkKeymap object, i.e. when we type an 'A' key of a US keyboard on
    // the Hebrew layout, |event->keyval| becomes GDK_hebrew_shin and this
    // WebCore::windowsKeyCodeForKeyEvent() call returns 0.
    // To improve compatibilty with Windows, we use |event->hardware_keycode|
    // for retrieving its Windows key-code for the keys when the
    // WebCore::windowsKeyCodeForEvent() call returns 0.
    // We shouldn't use |event->hardware_keycode| for keys that GdkKeymap
    // objects cannot change because |event->hardware_keycode| doesn't change
    // even when we change the layout options, e.g. when we swap a control
    // key and a caps-lock key, GTK doesn't swap their
    // |event->hardware_keycode| values but swap their |event->keyval| values.
    int windowsKeyCode = WebCore::windowsKeyCodeForKeyEvent(event->keyval);
    if (windowsKeyCode)
        return windowsKeyCode;

    const int tableSize = sizeof(hardwareCodeToGDKKeyval) / sizeof(hardwareCodeToGDKKeyval[0]);
    if (event->hardware_keycode < tableSize) {
        int keyval = hardwareCodeToGDKKeyval[event->hardware_keycode];
        if (keyval)
            return WebCore::windowsKeyCodeForKeyEvent(keyval);
    }

    // This key is one that keyboard-layout drivers cannot change.
    // Use |event->keyval| to retrieve its |windowsKeyCode| value.
    return WebCore::windowsKeyCodeForKeyEvent(event->keyval);
}

// Normalizes event->state to make it Windows/Mac compatible. Since the way
// of setting modifier mask on X is very different than Windows/Mac as shown
// in http://crbug.com/127142#c8, the normalization is necessary.
static guint normalizeEventState(const GdkEventKey* event)
{
    guint mask = 0;
    switch (gdkEventToWindowsKeyCode(event)) {
    case WebCore::VKEY_CONTROL:
    case WebCore::VKEY_LCONTROL:
    case WebCore::VKEY_RCONTROL:
        mask = GDK_CONTROL_MASK;
        break;
    case WebCore::VKEY_SHIFT:
    case WebCore::VKEY_LSHIFT:
    case WebCore::VKEY_RSHIFT:
        mask = GDK_SHIFT_MASK;
        break;
    case WebCore::VKEY_MENU:
    case WebCore::VKEY_LMENU:
    case WebCore::VKEY_RMENU:
        mask = GDK_MOD1_MASK;
        break;
    case WebCore::VKEY_CAPITAL:
        mask = GDK_LOCK_MASK;
        break;
    default:
        return event->state;
    }
    if (event->type == GDK_KEY_PRESS)
        return event->state | mask;
    return event->state & ~mask;
}

// Gets the corresponding control character of a specified key code. See:
// http://en.wikipedia.org/wiki/Control_characters
// We emulate Windows behavior here.
static WebUChar getControlCharacter(int windowsKeyCode, bool shift)
{
    if (windowsKeyCode >= WebCore::VKEY_A && windowsKeyCode <= WebCore::VKEY_Z) {
        // ctrl-A ~ ctrl-Z map to \x01 ~ \x1A
        return windowsKeyCode - WebCore::VKEY_A + 1;
    }
    if (shift) {
        // following graphics chars require shift key to input.
        switch (windowsKeyCode) {
        // ctrl-@ maps to \x00 (Null byte)
        case WebCore::VKEY_2:
            return 0;
        // ctrl-^ maps to \x1E (Record separator, Information separator two)
        case WebCore::VKEY_6:
            return 0x1E;
        // ctrl-_ maps to \x1F (Unit separator, Information separator one)
        case WebCore::VKEY_OEM_MINUS:
            return 0x1F;
        // Returns 0 for all other keys to avoid inputting unexpected chars.
        default:
            return 0;
        }
    } else {
        switch (windowsKeyCode) {
        // ctrl-[ maps to \x1B (Escape)
        case WebCore::VKEY_OEM_4:
            return 0x1B;
        // ctrl-\ maps to \x1C (File separator, Information separator four)
        case WebCore::VKEY_OEM_5:
            return 0x1C;
        // ctrl-] maps to \x1D (Group separator, Information separator three)
        case WebCore::VKEY_OEM_6:
            return 0x1D;
        // ctrl-Enter maps to \x0A (Line feed)
        case WebCore::VKEY_RETURN:
            return 0x0A;
        // Returns 0 for all other keys to avoid inputting unexpected chars.
        default:
            return 0;
        }
    }
}

// WebKeyboardEvent -----------------------------------------------------------

WebKeyboardEvent WebInputEventFactory::keyboardEvent(const GdkEventKey* event)
{
    WebKeyboardEvent result;

    result.timeStampSeconds = gdkEventTimeToWebEventTime(event->time);
    result.modifiers = gdkStateToWebEventModifiers(normalizeEventState(event));

    switch (event->type) {
    case GDK_KEY_RELEASE:
        result.type = WebInputEvent::KeyUp;
        break;
    case GDK_KEY_PRESS:
        result.type = WebInputEvent::RawKeyDown;
        break;
    default:
        ASSERT_NOT_REACHED();
    }

    result.isSystemKey = WebInputEventFactory::isSystemKeyEvent(result);

    // The key code tells us which physical key was pressed (for example, the
    // A key went down or up).  It does not determine whether A should be lower
    // or upper case.  This is what text does, which should be the keyval.
    int windowsKeyCode = gdkEventToWindowsKeyCode(event);
    result.windowsKeyCode = WebKeyboardEvent::windowsKeyCodeWithoutLocation(windowsKeyCode);
    result.modifiers |= WebKeyboardEvent::locationModifiersFromWindowsKeyCode(windowsKeyCode);
    result.nativeKeyCode = event->hardware_keycode;

    if (result.windowsKeyCode == WebCore::VKEY_RETURN)
        // We need to treat the enter key as a key press of character \r.  This
        // is apparently just how webkit handles it and what it expects.
        result.unmodifiedText[0] = '\r';
    else
        // FIXME: fix for non BMP chars
        result.unmodifiedText[0] =
            static_cast<WebUChar>(gdk_keyval_to_unicode(event->keyval));

    // If ctrl key is pressed down, then control character shall be input.
    if (result.modifiers & WebInputEvent::ControlKey)
        result.text[0] = getControlCharacter(
            result.windowsKeyCode, result.modifiers & WebInputEvent::ShiftKey);
    else
        result.text[0] = result.unmodifiedText[0];

    result.setKeyIdentifierFromWindowsKeyCode();

    // FIXME: Do we need to set IsAutoRepeat?
    if (isKeyPadKeyval(event->keyval))
        result.modifiers |= WebInputEvent::IsKeyPad;

    return result;
}

bool WebInputEventFactory::isSystemKeyEvent(const WebKeyboardEvent& event)
{
    // On Windows all keys with Alt modifier will be marked as system key.
    // We keep the same behavior on Linux and everywhere non-Mac.
    return event.modifiers & WebInputEvent::AltKey;
}

WebKeyboardEvent WebInputEventFactory::keyboardEvent(wchar_t character, int state, double timeStampSeconds)
{
    // keyboardEvent(const GdkEventKey*) depends on the GdkEventKey object and
    // it is hard to use/ it from signal handlers which don't use GdkEventKey
    // objects (e.g. GtkIMContext signal handlers.) For such handlers, this
    // function creates a WebInputEvent::Char event without using a
    // GdkEventKey object.
    WebKeyboardEvent result;
    result.type = blink::WebInputEvent::Char;
    result.timeStampSeconds = timeStampSeconds;
    result.modifiers = gdkStateToWebEventModifiers(state);
    result.windowsKeyCode = character;
    result.nativeKeyCode = character;
    result.text[0] = character;
    result.unmodifiedText[0] = character;

    // According to MSDN:
    // http://msdn.microsoft.com/en-us/library/ms646286(VS.85).aspx
    // Key events with Alt modifier and F10 are system key events.
    // We just emulate this behavior. It's necessary to prevent webkit from
    // processing keypress event generated by alt-d, etc.
    // F10 is not special on Linux, so don't treat it as system key.
    if (result.modifiers & WebInputEvent::AltKey)
        result.isSystemKey = true;

    return result;
}

// WebMouseEvent --------------------------------------------------------------

WebMouseEvent WebInputEventFactory::mouseEvent(const GdkEventButton* event)
{
    WebMouseEvent result;

    result.timeStampSeconds = gdkEventTimeToWebEventTime(event->time);

    result.modifiers = gdkStateToWebEventModifiers(event->state);
    result.x = static_cast<int>(event->x);
    result.y = static_cast<int>(event->y);
    result.windowX = result.x;
    result.windowY = result.y;
    result.globalX = static_cast<int>(event->x_root);
    result.globalY = static_cast<int>(event->y_root);
    result.clickCount = 0;

    switch (event->type) {
    case GDK_BUTTON_PRESS:
        result.type = WebInputEvent::MouseDown;
        break;
    case GDK_BUTTON_RELEASE:
        result.type = WebInputEvent::MouseUp;
        break;
    case GDK_3BUTTON_PRESS:
    case GDK_2BUTTON_PRESS:
    default:
        ASSERT_NOT_REACHED();
    };

    result.button = WebMouseEvent::ButtonNone;
    if (event->button == 1)
        result.button = WebMouseEvent::ButtonLeft;
    else if (event->button == 2)
        result.button = WebMouseEvent::ButtonMiddle;
    else if (event->button == 3)
        result.button = WebMouseEvent::ButtonRight;

    if (result.type == WebInputEvent::MouseDown) {
        bool forgetPreviousClick = shouldForgetPreviousClick(event->window, event->time, event->x, event->y);

        if (!forgetPreviousClick && result.button == gLastClickButton)
            ++gNumClicks;
        else {
            gNumClicks = 1;

            gLastClickEventWindow = event->window;
            gLastClickX = event->x;
            gLastClickY = event->y;
            gLastClickButton = result.button;
        }
        gLastClickTime = event->time;
    }
    result.clickCount = gNumClicks;

    return result;
}

WebMouseEvent WebInputEventFactory::mouseEvent(const GdkEventMotion* event)
{
    WebMouseEvent result;

    result.timeStampSeconds = gdkEventTimeToWebEventTime(event->time);
    result.modifiers = gdkStateToWebEventModifiers(event->state);
    result.x = static_cast<int>(event->x);
    result.y = static_cast<int>(event->y);
    result.windowX = result.x;
    result.windowY = result.y;
    result.globalX = static_cast<int>(event->x_root);
    result.globalY = static_cast<int>(event->y_root);

    switch (event->type) {
    case GDK_MOTION_NOTIFY:
        result.type = WebInputEvent::MouseMove;
        break;
    default:
        ASSERT_NOT_REACHED();
    }

    result.button = WebMouseEvent::ButtonNone;
    if (event->state & GDK_BUTTON1_MASK)
        result.button = WebMouseEvent::ButtonLeft;
    else if (event->state & GDK_BUTTON2_MASK)
        result.button = WebMouseEvent::ButtonMiddle;
    else if (event->state & GDK_BUTTON3_MASK)
        result.button = WebMouseEvent::ButtonRight;

    if (shouldForgetPreviousClick(event->window, event->time, event->x, event->y))
        resetClickCountState();

    return result;
}

WebMouseEvent WebInputEventFactory::mouseEvent(const GdkEventCrossing* event)
{
    WebMouseEvent result;

    result.timeStampSeconds = gdkEventTimeToWebEventTime(event->time);
    result.modifiers = gdkStateToWebEventModifiers(event->state);
    result.x = static_cast<int>(event->x);
    result.y = static_cast<int>(event->y);
    result.windowX = result.x;
    result.windowY = result.y;
    result.globalX = static_cast<int>(event->x_root);
    result.globalY = static_cast<int>(event->y_root);

    switch (event->type) {
    case GDK_ENTER_NOTIFY:
    case GDK_LEAVE_NOTIFY:
        // Note that if we sent MouseEnter or MouseLeave to WebKit, it
        // wouldn't work - they don't result in the proper JavaScript events.
        // MouseMove does the right thing.
        result.type = WebInputEvent::MouseMove;
        break;
    default:
        ASSERT_NOT_REACHED();
    }

    result.button = WebMouseEvent::ButtonNone;
    if (event->state & GDK_BUTTON1_MASK)
        result.button = WebMouseEvent::ButtonLeft;
    else if (event->state & GDK_BUTTON2_MASK)
        result.button = WebMouseEvent::ButtonMiddle;
    else if (event->state & GDK_BUTTON3_MASK)
        result.button = WebMouseEvent::ButtonRight;

    if (shouldForgetPreviousClick(event->window, event->time, event->x, event->y))
        resetClickCountState();

    return result;
}

// WebMouseWheelEvent ---------------------------------------------------------

WebMouseWheelEvent WebInputEventFactory::mouseWheelEvent(const GdkEventScroll* event)
{
    WebMouseWheelEvent result;

    result.type = WebInputEvent::MouseWheel;
    result.button = WebMouseEvent::ButtonNone;

    result.timeStampSeconds = gdkEventTimeToWebEventTime(event->time);
    result.modifiers = gdkStateToWebEventModifiers(event->state);
    result.x = static_cast<int>(event->x);
    result.y = static_cast<int>(event->y);
    result.windowX = result.x;
    result.windowY = result.y;
    result.globalX = static_cast<int>(event->x_root);
    result.globalY = static_cast<int>(event->y_root);

    // How much should we scroll per mouse wheel event?
    // - Windows uses 3 lines by default and obeys a system setting.
    // - Mozilla has a pref that lets you either use the "system" number of lines
    //   to scroll, or lets the user override it.
    //   For the "system" number of lines, it appears they've hardcoded 3.
    //   See case NS_MOUSE_SCROLL in content/events/src/nsEventStateManager.cpp
    //   and InitMouseScrollEvent in widget/src/gtk2/nsCommonWidget.cpp .
    // - Gtk makes the scroll amount a function of the size of the scroll bar,
    //   which is not available to us here.
    // Instead, we pick a number that empirically matches Firefox's behavior.
    static const float scrollbarPixelsPerTick = 160.0f / 3.0f;

    switch (event->direction) {
    case GDK_SCROLL_UP:
        result.deltaY = scrollbarPixelsPerTick;
        result.wheelTicksY = 1;
        break;
    case GDK_SCROLL_DOWN:
        result.deltaY = -scrollbarPixelsPerTick;
        result.wheelTicksY = -1;
        break;
    case GDK_SCROLL_LEFT:
        result.deltaX = scrollbarPixelsPerTick;
        result.wheelTicksX = 1;
        break;
    case GDK_SCROLL_RIGHT:
        result.deltaX = -scrollbarPixelsPerTick;
        result.wheelTicksX = -1;
        break;
    }

    return result;
}

} // namespace blink

/* [<][>][^][v][top][bottom][index][help] */