// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "views/events/event.h"

#include <gdk/gdk.h>

#include "base/logging.h"
#include "ui/base/keycodes/keyboard_code_conversion_gtk.h"
#include "ui/gfx/point.h"

namespace {

unsigned int GetGdkStateFromNative(GdkEvent* gdk_event) {
  switch (gdk_event->type) {
    case GDK_KEY_PRESS:
    case GDK_KEY_RELEASE:
      return gdk_event->key.state;
    case GDK_BUTTON_PRESS:
    case GDK_2BUTTON_PRESS:
    case GDK_3BUTTON_PRESS:
    case GDK_BUTTON_RELEASE:
      return gdk_event->button.state;
    case GDK_SCROLL:
      return gdk_event->scroll.state;
    case GDK_MOTION_NOTIFY:
      return gdk_event->motion.state;
    case GDK_ENTER_NOTIFY:
    case GDK_LEAVE_NOTIFY:
      return gdk_event->crossing.state;
    default:
      NOTREACHED();
      break;
  }
  return 0;
}

int GetFlagsFromGdkState(unsigned int state) {
  int flags = 0;
  flags |= (state & GDK_LOCK_MASK) ? ui::EF_CAPS_LOCK_DOWN : 0;
  flags |= (state & GDK_CONTROL_MASK) ? ui::EF_CONTROL_DOWN : 0;
  flags |= (state & GDK_SHIFT_MASK) ? ui::EF_SHIFT_DOWN : 0;
  flags |= (state & GDK_MOD1_MASK) ? ui::EF_ALT_DOWN : 0;
  flags |= (state & GDK_BUTTON1_MASK) ? ui::EF_LEFT_BUTTON_DOWN : 0;
  flags |= (state & GDK_BUTTON2_MASK) ? ui::EF_MIDDLE_BUTTON_DOWN : 0;
  flags |= (state & GDK_BUTTON3_MASK) ? ui::EF_RIGHT_BUTTON_DOWN : 0;
  return flags;
}

////////////////////////////////////////////////////////////////////////////////
// These functions mirror ui/base/events.h, but GTK is in the midst of removal.

ui::EventType EventTypeFromNative(GdkEvent* gdk_event) {
  // Add new event types as necessary.
  switch (gdk_event->type) {
    case GDK_2BUTTON_PRESS:
    case GDK_3BUTTON_PRESS:
    case GDK_BUTTON_PRESS:
      return ui::ET_MOUSE_PRESSED;
    case GDK_BUTTON_RELEASE:
      return ui::ET_MOUSE_RELEASED;
    case GDK_DRAG_MOTION:
      return ui::ET_MOUSE_DRAGGED;
    case GDK_ENTER_NOTIFY:
      return ui::ET_MOUSE_ENTERED;
    case GDK_KEY_PRESS:
      return ui::ET_KEY_PRESSED;
    case GDK_KEY_RELEASE:
      return ui::ET_KEY_RELEASED;
    case GDK_LEAVE_NOTIFY:
      return ui::ET_MOUSE_EXITED;
    case GDK_MOTION_NOTIFY:
      if (gdk_event->motion.state & GDK_BUTTON1_MASK ||
          gdk_event->motion.state & GDK_BUTTON2_MASK ||
          gdk_event->motion.state & GDK_BUTTON3_MASK ||
          gdk_event->motion.state & GDK_BUTTON4_MASK ||
          gdk_event->motion.state & GDK_BUTTON5_MASK) {
        return ui::ET_MOUSE_DRAGGED;
      }
      return ui::ET_MOUSE_MOVED;
    case GDK_SCROLL:
      return ui::ET_MOUSEWHEEL;
    default:
      NOTREACHED();
      break;
  }
  return ui::ET_UNKNOWN;
}

int EventFlagsFromNative(GdkEvent* gdk_event) {
  int flags = GetFlagsFromGdkState(GetGdkStateFromNative(gdk_event));
  if (gdk_event->type == GDK_2BUTTON_PRESS)
    flags |= ui::EF_IS_DOUBLE_CLICK;
  if (gdk_event->type == GDK_BUTTON_PRESS ||
      gdk_event->type == GDK_2BUTTON_PRESS ||
      gdk_event->type == GDK_3BUTTON_PRESS ||
      gdk_event->type == GDK_BUTTON_RELEASE) {
    switch (gdk_event->button.button) {
      case 1:
        return flags | ui::EF_LEFT_BUTTON_DOWN;
      case 2:
        return flags | ui::EF_MIDDLE_BUTTON_DOWN;
      case 3:
        return flags | ui::EF_RIGHT_BUTTON_DOWN;
    }
  }
  return flags;
}

gfx::Point EventLocationFromNative(GdkEvent* gdk_event) {
  double x = 0, y = 0;
  if (gdk_event_get_coords(gdk_event, &x, &y))
    return gfx::Point(static_cast<int>(x), static_cast<int>(y));
  return gfx::Point();
}

ui::KeyboardCode KeyboardCodeFromNative(GdkEvent* gdk_event) {
  DCHECK(gdk_event->type == GDK_KEY_PRESS ||
         gdk_event->type == GDK_KEY_RELEASE);
  return ui::KeyboardCodeFromGdkEventKey(&gdk_event->key);
}

bool IsMouseEvent(GdkEvent* gdk_event) {
  return gdk_event->type == GDK_MOTION_NOTIFY ||
         gdk_event->type == GDK_BUTTON_PRESS ||
         gdk_event->type == GDK_2BUTTON_PRESS ||
         gdk_event->type == GDK_3BUTTON_PRESS ||
         gdk_event->type == GDK_BUTTON_RELEASE;
}

int GetMouseWheelOffset(GdkEvent* gdk_event) {
  DCHECK(gdk_event->type == GDK_SCROLL);
  int offset = (gdk_event->scroll.direction == GDK_SCROLL_UP ||
                gdk_event->scroll.direction == GDK_SCROLL_LEFT) ? 1 : -1;
  return offset;
}

}  // namespace

namespace views {

////////////////////////////////////////////////////////////////////////////////
// Event, protected:

Event::Event(GdkEvent* gdk_event, ui::EventType type, int flags)
    : native_event_(NULL),
      gdk_event_(gdk_event),
      type_(type),
      time_stamp_(base::Time::NowFromSystemTime()),
      flags_(flags) {
}

////////////////////////////////////////////////////////////////////////////////
// LocatedEvent, protected:

LocatedEvent::LocatedEvent(GdkEvent* gdk_event)
    : Event(gdk_event,
            EventTypeFromNative(gdk_event),
            EventFlagsFromNative(gdk_event)),
      location_(EventLocationFromNative(gdk_event)) {
}

////////////////////////////////////////////////////////////////////////////////
// KeyEvent, public:

KeyEvent::KeyEvent(GdkEvent* gdk_event)
    : Event(gdk_event,
            EventTypeFromNative(gdk_event),
            EventFlagsFromNative(gdk_event)),
      key_code_(KeyboardCodeFromNative(gdk_event)),
      character_(0),
      unmodified_character_(0) {
}

////////////////////////////////////////////////////////////////////////////////
// MouseEvent, public:

MouseEvent::MouseEvent(GdkEvent* gdk_event)
    : LocatedEvent(gdk_event) {
}

////////////////////////////////////////////////////////////////////////////////
// MouseWheelEvent, public:

MouseWheelEvent::MouseWheelEvent(GdkEvent* gdk_event)
    : MouseEvent(gdk_event),
      offset_(kWheelDelta * GetMouseWheelOffset(gdk_event)) {
}

}  // namespace views