summaryrefslogtreecommitdiffstats
path: root/views/focus/accelerator_handler_gtk.cc
blob: fb04e89bfb9ed1ee7871386746aaddaaf3fc154b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// Copyright (c) 2010 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 <gtk/gtk.h>

#include "base/keyboard_code_conversion_gtk.h"
#include "base/keyboard_codes.h"
#include "views/accelerator.h"
#include "views/focus/accelerator_handler.h"
#include "views/focus/focus_manager.h"
#include "views/widget/widget_gtk.h"

namespace views {

AcceleratorHandler::AcceleratorHandler() : only_menu_pressed_(false) {}

bool AcceleratorHandler::Dispatch(GdkEvent* event) {
  // When focus changes, we may not see a full key-press / key-release
  // pair, so clear the set of keys we think were pressed.
  if (event->type == GDK_FOCUS_CHANGE) {
    pressed_hardware_keys_.clear();
    consumed_vkeys_.clear();
    only_menu_pressed_ = false;
  }

  // Skip accelerator handling for non key events.
  bool skip = event->type != GDK_KEY_PRESS && event->type != GDK_KEY_RELEASE;

  // Skip if there is a grabbed widget and it is not a window. This is because
  // of the following two reasons:
  // 1. Widget such as pop up from GtkComboBox is a grabbed widget and use ESC
  //    as its accelerator key to dismiss itself. We will break Gtk's code if
  //    we eat this key. See http://crosbug.com/2355
  // 2. Modal dialogs in Gtk are grabbed windows and we want to have our
  //    accelerator key processing in this case. See http://crogbug.com/3701
  if (!skip) {
    GtkWidget* grabbed_widget = gtk_grab_get_current();
    skip = grabbed_widget && !GTK_IS_WINDOW(grabbed_widget);
  }

  if (skip) {
    // Let Gtk processes the event.
    gtk_main_do_event(event);
    return true;
  }

  GdkEventKey* key_event = reinterpret_cast<GdkEventKey*>(event);
  // Let's retrieve the focus manager for the GdkWindow.
  GdkWindow* window = gdk_window_get_toplevel(key_event->window);
  gpointer ptr;
  gdk_window_get_user_data(window, &ptr);
  if (!ptr && !gdk_window_is_visible(window)) {
    // The window is destroyed while we're handling key events.
    gtk_main_do_event(event);
    return true;
  }
  DCHECK(ptr);

  // The top-level window or window widget is expected to always be associated
  // with the top-level gtk widget.
  WidgetGtk* widget =
      WidgetGtk::GetViewForNative(reinterpret_cast<GtkWidget*>(ptr));
  if (!widget) {
    // During dnd we get events for windows we don't control (such as the
    // window being dragged).
    gtk_main_do_event(event);
    return true;
  }
  FocusManager* focus_manager = widget->GetFocusManager();
  if (!focus_manager) {
    NOTREACHED();
    return true;
  }

  KeyEvent view_key_event(key_event);
  int key_code = view_key_event.GetKeyCode();
  int hardware_keycode = key_event->hardware_keycode;
  if (event->type == GDK_KEY_PRESS) {
    pressed_hardware_keys_.insert(hardware_keycode);

    // If it's the Alt key, don't send it to the focus manager until release
    // (to handle focusing the menu bar).
    if (key_code == base::VKEY_MENU && pressed_hardware_keys_.size() == 1) {
      only_menu_pressed_ = true;
      return true;
    }

    if (pressed_hardware_keys_.size() != 1)
      only_menu_pressed_ = false;

    // FocusManager::OnKeyEvent will return false if this message has been
    // consumed and should not be propagated further.
    if (!focus_manager->OnKeyEvent(view_key_event)) {
      consumed_vkeys_.insert(key_code);
      return true;
    }
  }

  if (event->type == GDK_KEY_RELEASE) {
    pressed_hardware_keys_.erase(hardware_keycode);

    // Make sure to filter out the key release for a key press consumed
    // as an accelerator, to avoid calling gtk_main_do_event with an
    // unpaired key release.
    if (consumed_vkeys_.find(key_code) != consumed_vkeys_.end()) {
      consumed_vkeys_.erase(key_code);
      return true;
    }

    // Special case: the Alt key can trigger an accelerator on release
    // rather than on press, but only if no other keys were pressed.
    if (key_code == base::VKEY_MENU && only_menu_pressed_) {
      Accelerator accelerator(base::VKEY_MENU, false, false, false);
      focus_manager->ProcessAccelerator(accelerator);
      return true;
    }
  }

  // Pass the event to gtk if we didn't consume it above.
  gtk_main_do_event(event);
  return true;
}

}  // namespace views