summaryrefslogtreecommitdiff
path: root/tests/cefclient/browser/views_menu_bar.cc
blob: f8746a2b195111b30c1296dae37f7177a317c626 (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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
// Copyright (c) 2017 The Chromium Embedded Framework 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 "tests/cefclient/browser/views_menu_bar.h"

#include "include/cef_i18n_util.h"
#include "include/views/cef_box_layout.h"
#include "include/views/cef_window.h"
#include "tests/cefclient/browser/views_style.h"

namespace client {

namespace {

const int kMenuBarGroupId = 100;

// Convert |c| to lowercase using the current ICU locale.
// TODO(jshin): What about Turkish locale? See http://crbug.com/81719.
// If the mnemonic is capital I and the UI language is Turkish, lowercasing it
// results in 'small dotless i', which is different from a 'dotted i'. Similar
// issues may exist for az and lt locales.
char16 ToLower(char16 c) {
  CefStringUTF16 str16;
  cef_string_utf16_to_lower(&c, 1, str16.GetWritableStruct());
  return str16.length() > 0 ? str16.c_str()[0] : 0;
}

// Extract the mnemonic character from |title|. For example, if |title| is
// "&Test" then the mnemonic character is 'T'.
char16 GetMnemonic(const std::u16string& title) {
  size_t index = 0;
  do {
    index = title.find('&', index);
    if (index != std::u16string::npos) {
      if (index + 1 != title.size() && title[index + 1] != '&') {
        return ToLower(title[index + 1]);
      }
      index++;
    }
  } while (index != std::u16string::npos);
  return 0;
}

}  // namespace

ViewsMenuBar::ViewsMenuBar(Delegate* delegate, int menu_id_start)
    : delegate_(delegate),
      id_start_(menu_id_start),
      id_next_(menu_id_start),
      last_nav_with_keyboard_(false) {
  DCHECK(delegate_);
  DCHECK_GT(id_start_, 0);
}

bool ViewsMenuBar::HasMenuId(int menu_id) const {
  return menu_id >= id_start_ && menu_id < id_next_;
}

CefRefPtr<CefPanel> ViewsMenuBar::GetMenuPanel() {
  EnsureMenuPanel();
  return panel_;
}

CefRefPtr<CefMenuModel> ViewsMenuBar::CreateMenuModel(const CefString& label,
                                                      int* menu_id) {
  EnsureMenuPanel();

  // Assign the new menu ID.
  const int new_menu_id = id_next_++;
  if (menu_id) {
    *menu_id = new_menu_id;
  }

  // Create the new MenuModel.
  CefRefPtr<CefMenuModel> model = CefMenuModel::CreateMenuModel(this);
  views_style::ApplyTo(model);
  models_.push_back(model);

  // Create the new MenuButton.
  CefRefPtr<CefMenuButton> button =
      CefMenuButton::CreateMenuButton(this, label);
  button->SetID(new_menu_id);
  views_style::ApplyTo(button.get());
  button->SetInkDropEnabled(true);

  // Assign a group ID to allow focus traversal between MenuButtons using the
  // arrow keys when the menu is not displayed.
  button->SetGroupID(kMenuBarGroupId);

  // Add the new MenuButton to the Planel.
  panel_->AddChildView(button);

  // Extract the mnemonic that triggers the menu, if any.
  char16 mnemonic = GetMnemonic(label);
  if (mnemonic != 0) {
    mnemonics_.insert(std::make_pair(mnemonic, new_menu_id));
  }

  return model;
}

CefRefPtr<CefMenuModel> ViewsMenuBar::GetMenuModel(int menu_id) const {
  if (HasMenuId(menu_id)) {
    return models_[menu_id - id_start_];
  }
  return nullptr;
}

void ViewsMenuBar::SetMenuFocusable(bool focusable) {
  if (!panel_) {
    return;
  }

  for (int id = id_start_; id < id_next_; ++id) {
    panel_->GetViewForID(id)->SetFocusable(focusable);
  }

  if (focusable) {
    // Give focus to the first MenuButton.
    panel_->GetViewForID(id_start_)->RequestFocus();
  }
}

bool ViewsMenuBar::OnKeyEvent(const CefKeyEvent& event) {
  if (!panel_) {
    return false;
  }

  if (event.type != KEYEVENT_RAWKEYDOWN) {
    return false;
  }

  // Do not check mnemonics if the Alt or Ctrl modifiers are pressed. For
  // example Ctrl+<T> is an accelerator, but <T> only is a mnemonic.
  if (event.modifiers & (EVENTFLAG_ALT_DOWN | EVENTFLAG_CONTROL_DOWN)) {
    return false;
  }

  MnemonicMap::const_iterator it = mnemonics_.find(ToLower(event.character));
  if (it == mnemonics_.end()) {
    return false;
  }

  // Set status indicating that we navigated using the keyboard.
  last_nav_with_keyboard_ = true;

  // Show the selected menu.
  TriggerMenuButton(panel_->GetViewForID(it->second));

  return true;
}

void ViewsMenuBar::Reset() {
  panel_ = nullptr;
  models_.clear();
  mnemonics_.clear();
  id_next_ = id_start_;
}

void ViewsMenuBar::OnMenuButtonPressed(
    CefRefPtr<CefMenuButton> menu_button,
    const CefPoint& screen_point,
    CefRefPtr<CefMenuButtonPressedLock> button_pressed_lock) {
  CefRefPtr<CefMenuModel> menu_model = GetMenuModel(menu_button->GetID());

  // Adjust menu position to align with the button.
  CefPoint point = screen_point;
  if (CefIsRTL()) {
    point.x += menu_button->GetBounds().width - 4;
  } else {
    point.x -= menu_button->GetBounds().width - 4;
  }

  // Keep track of the current |last_nav_with_keyboard_| status and restore it
  // after displaying the new menu.
  bool cur_last_nav_with_keyboard = last_nav_with_keyboard_;

  // May result in the previous menu being closed, in which case MenuClosed will
  // be called before the new menu is displayed.
  menu_button->ShowMenu(menu_model, point, CEF_MENU_ANCHOR_TOPLEFT);

  last_nav_with_keyboard_ = cur_last_nav_with_keyboard;
}

void ViewsMenuBar::ExecuteCommand(CefRefPtr<CefMenuModel> menu_model,
                                  int command_id,
                                  cef_event_flags_t event_flags) {
  delegate_->MenuBarExecuteCommand(menu_model, command_id, event_flags);
}

void ViewsMenuBar::MouseOutsideMenu(CefRefPtr<CefMenuModel> menu_model,
                                    const CefPoint& screen_point) {
  DCHECK(panel_);

  // Retrieve the Window hosting the Panel.
  CefRefPtr<CefWindow> window = panel_->GetWindow();
  DCHECK(window);

  // Convert the point from screen to window coordinates.
  CefPoint window_point = screen_point;
  if (!window->ConvertPointFromScreen(window_point)) {
    return;
  }

  CefRect panel_bounds = panel_->GetBounds();

  if (last_nav_with_keyboard_) {
    // The user navigated last using the keyboard. Don't change menus using
    // mouse movements until the mouse exits and re-enters the Panel.
    if (panel_bounds.Contains(window_point)) {
      return;
    }
    last_nav_with_keyboard_ = false;
  }

  // Check that the point is inside the Panel.
  if (!panel_bounds.Contains(window_point)) {
    return;
  }

  const int active_menu_id = GetActiveMenuId();

  // Determine which MenuButton is under the specified point.
  for (int id = id_start_; id < id_next_; ++id) {
    // Skip the currently active MenuButton.
    if (id == active_menu_id) {
      continue;
    }

    CefRefPtr<CefView> button = panel_->GetViewForID(id);
    CefRect button_bounds = button->GetBounds();
    if (CefIsRTL()) {
      // Adjust for right-to-left button layout.
      button_bounds.x =
          panel_bounds.width - button_bounds.x - button_bounds.width;
    }
    if (button_bounds.Contains(window_point)) {
      // Trigger the hovered MenuButton.
      TriggerMenuButton(button);
      break;
    }
  }
}

void ViewsMenuBar::UnhandledOpenSubmenu(CefRefPtr<CefMenuModel> menu_model,
                                        bool is_rtl) {
  TriggerNextMenu(is_rtl ? 1 : -1);
}

void ViewsMenuBar::UnhandledCloseSubmenu(CefRefPtr<CefMenuModel> menu_model,
                                         bool is_rtl) {
  TriggerNextMenu(is_rtl ? -1 : 1);
}

void ViewsMenuBar::MenuClosed(CefRefPtr<CefMenuModel> menu_model) {
  // Reset |last_nav_with_keyboard_| status whenever the main menu closes.
  if (!menu_model->IsSubMenu() && last_nav_with_keyboard_) {
    last_nav_with_keyboard_ = false;
  }
}

void ViewsMenuBar::EnsureMenuPanel() {
  if (panel_) {
    return;
  }

  panel_ = CefPanel::CreatePanel(nullptr);
  views_style::ApplyTo(panel_);

  // Use a horizontal box layout.
  CefBoxLayoutSettings top_panel_layout_settings;
  top_panel_layout_settings.horizontal = true;
  panel_->SetToBoxLayout(top_panel_layout_settings);
}

int ViewsMenuBar::GetActiveMenuId() {
  DCHECK(panel_);

  for (int id = id_start_; id < id_next_; ++id) {
    CefRefPtr<CefButton> button = panel_->GetViewForID(id)->AsButton();
    if (button->GetState() == CEF_BUTTON_STATE_PRESSED) {
      return id;
    }
  }

  return -1;
}

void ViewsMenuBar::TriggerNextMenu(int offset) {
  DCHECK(panel_);

  const int active_menu_id = GetActiveMenuId();
  const int menu_count = id_next_ - id_start_;
  const int active_menu_index = active_menu_id - id_start_;

  // Compute the modulus to avoid negative values.
  int next_menu_index = (active_menu_index + offset) % menu_count;
  if (next_menu_index < 0) {
    next_menu_index += menu_count;
  }

  // Cancel the existing menu. MenuClosed may be called.
  panel_->GetWindow()->CancelMenu();

  // Set status indicating that we navigated using the keyboard.
  last_nav_with_keyboard_ = true;

  // Show the new menu.
  TriggerMenuButton(panel_->GetViewForID(id_start_ + next_menu_index));
}

void ViewsMenuBar::TriggerMenuButton(CefRefPtr<CefView> button) {
  CefRefPtr<CefMenuButton> menu_button =
      button->AsButton()->AsLabelButton()->AsMenuButton();
  if (menu_button->IsFocusable()) {
    menu_button->RequestFocus();
  }
  menu_button->TriggerMenu();
}

}  // namespace client