OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "chrome/browser/chromeos/views/native_menu_webui.h" | |
6 | |
7 #include <string> | |
8 | |
9 #include "base/message_loop.h" | |
10 #include "base/string_util.h" | |
11 #include "chrome/browser/chromeos/views/menu_locator.h" | |
12 #include "chrome/browser/chromeos/views/webui_menu_widget.h" | |
13 #include "chrome/browser/chromeos/webui/menu_ui.h" | |
14 #include "chrome/browser/profiles/profile_manager.h" | |
15 #include "chrome/browser/ui/browser.h" | |
16 #include "chrome/browser/ui/browser_list.h" | |
17 #include "chrome/browser/ui/browser_window.h" | |
18 #include "chrome/common/url_constants.h" | |
19 #include "ui/base/models/menu_model.h" | |
20 #include "ui/gfx/rect.h" | |
21 #include "views/controls/menu/menu_2.h" | |
22 #include "views/controls/menu/nested_dispatcher_gtk.h" | |
23 | |
24 #if defined(TOUCH_UI) | |
25 #include "views/focus/accelerator_handler.h" | |
26 #include "views/controls/menu/native_menu_x.h" | |
27 #else | |
28 #include "views/controls/menu/native_menu_gtk.h" | |
29 #endif | |
30 | |
31 namespace { | |
32 | |
33 using chromeos::NativeMenuWebUI; | |
34 using chromeos::WebUIMenuWidget; | |
35 | |
36 // Returns true if the menu item type specified can be executed as a command. | |
37 bool MenuTypeCanExecute(ui::MenuModel::ItemType type) { | |
38 return type == ui::MenuModel::TYPE_COMMAND || | |
39 type == ui::MenuModel::TYPE_CHECK || | |
40 type == ui::MenuModel::TYPE_RADIO; | |
41 } | |
42 | |
43 gboolean Destroy(GtkWidget* widget, gpointer data) { | |
44 WebUIMenuWidget* menu_widget = static_cast<WebUIMenuWidget*>(data); | |
45 NativeMenuWebUI* webui_menu = menu_widget->webui_menu(); | |
46 // webui_menu can be NULL if widget is destroyed by signal. | |
47 if (webui_menu) | |
48 webui_menu->Hide(); | |
49 return true; | |
50 } | |
51 | |
52 // Returns the active toplevel window. | |
53 gfx::NativeWindow FindActiveToplevelWindow() { | |
54 GList* toplevels = gtk_window_list_toplevels(); | |
55 while (toplevels) { | |
56 gfx::NativeWindow window = static_cast<gfx::NativeWindow>(toplevels->data); | |
57 if (gtk_window_is_active(window)) { | |
58 return window; | |
59 } | |
60 toplevels = g_list_next(toplevels); | |
61 } | |
62 return NULL; | |
63 } | |
64 | |
65 // Currently opened menu. See RunMenuAt for reason why we need this. | |
66 NativeMenuWebUI* current_ = NULL; | |
67 | |
68 } // namespace | |
69 | |
70 namespace chromeos { | |
71 | |
72 // static | |
73 void NativeMenuWebUI::SetMenuURL(views::Menu2* menu2, const GURL& url) { | |
74 // No-op if WebUI menu is disabled. | |
75 if (!MenuUI::IsEnabled()) | |
76 return; | |
77 | |
78 gfx::NativeView native = menu2->GetNativeMenu(); | |
79 DCHECK(native); | |
80 WebUIMenuWidget* widget = WebUIMenuWidget::FindWebUIMenuWidget(native); | |
81 DCHECK(widget); | |
82 widget->webui_menu()->set_menu_url(url); | |
83 } | |
84 | |
85 //////////////////////////////////////////////////////////////////////////////// | |
86 // NativeMenuWebUI, public: | |
87 | |
88 NativeMenuWebUI::NativeMenuWebUI(ui::MenuModel* menu_model, bool root) | |
89 : parent_(NULL), | |
90 submenu_(NULL), | |
91 model_(menu_model), | |
92 menu_widget_(NULL), | |
93 menu_shown_(false), | |
94 activated_menu_(NULL), | |
95 activated_index_(-1), | |
96 menu_action_(MENU_ACTION_NONE), | |
97 menu_url_(StringPrintf("chrome://%s", chrome::kChromeUIMenu)), | |
98 on_menu_opened_called_(false), | |
99 nested_dispatcher_(NULL) { | |
100 menu_widget_ = new WebUIMenuWidget(this, root); | |
101 // Set the initial location off the screen not to show small | |
102 // window with dropshadow. | |
103 menu_widget_->Init(NULL, gfx::Rect(-10000, -10000, 1, 1)); | |
104 } | |
105 | |
106 NativeMenuWebUI::~NativeMenuWebUI() { | |
107 if (nested_dispatcher_) { | |
108 // Menu is destroyed while its in message loop. | |
109 // Let nested dispatcher know the creator is deleted. | |
110 nested_dispatcher_->CreatorDestroyed(); | |
111 Hide(); | |
112 } | |
113 if (menu_widget_) { | |
114 menu_widget_->Close(); | |
115 menu_widget_ = NULL; | |
116 } | |
117 parent_ = NULL; | |
118 } | |
119 | |
120 //////////////////////////////////////////////////////////////////////////////// | |
121 // NativeMenuWebUI, MenuWrapper implementation: | |
122 | |
123 void NativeMenuWebUI::RunMenuAt(const gfx::Point& point, int alignment) { | |
124 if (current_ != NULL) { | |
125 // This happens when there is a nested task to show menu, which is | |
126 // executed after menu is open. Since we need to enable nested task, | |
127 // this condition has to be handled here. | |
128 return; | |
129 } | |
130 current_ = this; | |
131 bool context = false; | |
132 | |
133 // TODO(oshima): This is quick hack to check if it's context menu. (in rtl) | |
134 // Fix this once we migrated. | |
135 if (alignment == views::Menu2::ALIGN_TOPLEFT) { | |
136 context = true; | |
137 } | |
138 | |
139 activated_menu_ = NULL; | |
140 activated_index_ = -1; | |
141 menu_action_ = MENU_ACTION_NONE; | |
142 | |
143 MenuLocator* locator = context ? | |
144 MenuLocator::CreateContextMenuLocator(point) : | |
145 MenuLocator::CreateDropDownMenuLocator(point); | |
146 ShowAt(locator); | |
147 DCHECK(!menu_shown_); | |
148 menu_shown_ = true; | |
149 on_menu_opened_called_ = false; | |
150 | |
151 // TODO(oshima): A menu must be deleted when parent window is | |
152 // closed. Menu2 doesn't know about the parent window, however, so | |
153 // we're using toplevel gtkwindow. This is probably sufficient, but | |
154 // I will update Menu2 to pass host view (which is necessary anyway | |
155 // to get the right position) and get a parent widnow through | |
156 // it. http://crosbug/7642 | |
157 gfx::NativeWindow parent = FindActiveToplevelWindow(); | |
158 gulong handle = 0; | |
159 if (parent) { | |
160 handle = g_signal_connect(G_OBJECT(parent), "destroy", | |
161 G_CALLBACK(&Destroy), | |
162 menu_widget_); | |
163 } | |
164 // We need to turn on nestable tasks as a renderer uses tasks internally. | |
165 // Without this, renderer cannnot finish loading page. | |
166 nested_dispatcher_ = | |
167 new views::NestedDispatcherGtk(this, true /* allow nested */); | |
168 bool deleted = nested_dispatcher_->RunAndSelfDestruct(); | |
169 current_ = NULL; // this is static and safe to access. | |
170 if (deleted) { | |
171 // The menu was destryed while menu is shown, so return immediately. | |
172 // Don't touch the instance which is already deleted. | |
173 return; | |
174 } | |
175 nested_dispatcher_ = NULL; | |
176 if (menu_shown_) { | |
177 // If this happens it means we haven't yet gotten the hide signal and | |
178 // someone else quit the message loop on us. | |
179 NOTREACHED(); | |
180 menu_shown_ = false; | |
181 } | |
182 if (handle) | |
183 g_signal_handler_disconnect(G_OBJECT(parent), handle); | |
184 | |
185 menu_widget_->Hide(); | |
186 // Close All submenus. | |
187 submenu_.reset(); | |
188 ProcessActivate(); | |
189 } | |
190 | |
191 void NativeMenuWebUI::CancelMenu() { | |
192 Hide(); | |
193 } | |
194 | |
195 void NativeMenuWebUI::Rebuild() { | |
196 activated_menu_ = NULL; | |
197 menu_widget_->ExecuteJavascript(L"modelUpdated()"); | |
198 } | |
199 | |
200 void NativeMenuWebUI::UpdateStates() { | |
201 // Update menu contnets and submenus. | |
202 Rebuild(); | |
203 } | |
204 | |
205 gfx::NativeMenu NativeMenuWebUI::GetNativeMenu() const { | |
206 return menu_widget_->GetNativeView(); | |
207 } | |
208 | |
209 NativeMenuWebUI::MenuAction NativeMenuWebUI::GetMenuAction() const { | |
210 return menu_action_; | |
211 } | |
212 | |
213 void NativeMenuWebUI::AddMenuListener(views::MenuListener* listener) { | |
214 listeners_.AddObserver(listener); | |
215 } | |
216 | |
217 void NativeMenuWebUI::RemoveMenuListener(views::MenuListener* listener) { | |
218 listeners_.RemoveObserver(listener); | |
219 } | |
220 | |
221 void NativeMenuWebUI::SetMinimumWidth(int width) { | |
222 gtk_widget_set_size_request(menu_widget_->GetNativeView(), width, 1); | |
223 } | |
224 | |
225 //////////////////////////////////////////////////////////////////////////////// | |
226 // NativeMenuWebUI, MessageLoopForUI::Dispatcher implementation: | |
227 | |
228 bool NativeMenuWebUI::Dispatch(GdkEvent* event) { | |
229 switch (event->type) { | |
230 case GDK_MOTION_NOTIFY: { | |
231 NativeMenuWebUI* target = FindMenuAt( | |
232 gfx::Point(event->motion.x_root, event->motion.y_root)); | |
233 if (target) | |
234 target->menu_widget_->EnableInput(false); | |
235 break; | |
236 } | |
237 case GDK_BUTTON_PRESS: { | |
238 NativeMenuWebUI* target = FindMenuAt( | |
239 gfx::Point(event->motion.x_root, event->motion.y_root)); | |
240 if (!target) { | |
241 Hide(); | |
242 return true; | |
243 } | |
244 break; | |
245 } | |
246 default: | |
247 break; | |
248 } | |
249 gtk_main_do_event(event); | |
250 return true; | |
251 } | |
252 | |
253 #if defined(TOUCH_UI) | |
254 base::MessagePumpGlibXDispatcher::DispatchStatus | |
255 NativeMenuWebUI::DispatchX(XEvent* xevent) { | |
256 return views::DispatchXEvent(xevent) ? | |
257 base::MessagePumpGlibXDispatcher::EVENT_PROCESSED : | |
258 base::MessagePumpGlibXDispatcher::EVENT_IGNORED; | |
259 | |
260 } | |
261 #endif | |
262 | |
263 //////////////////////////////////////////////////////////////////////////////// | |
264 // NativeMenuWebUI, MenuControl implementation: | |
265 | |
266 void NativeMenuWebUI::Activate(ui::MenuModel* model, | |
267 int index, | |
268 ActivationMode activation) { | |
269 NativeMenuWebUI* root = GetRoot(); | |
270 if (root) { | |
271 if (activation == CLOSE_AND_ACTIVATE) { | |
272 root->activated_menu_ = model; | |
273 root->activated_index_ = index; | |
274 root->menu_action_ = MENU_ACTION_SELECTED; | |
275 root->Hide(); | |
276 } else { | |
277 if (model->IsEnabledAt(index) && | |
278 MenuTypeCanExecute(model->GetTypeAt(index))) { | |
279 model->ActivatedAt(index); | |
280 } | |
281 } | |
282 } | |
283 } | |
284 | |
285 void NativeMenuWebUI::OpenSubmenu(int index, int y) { | |
286 submenu_.reset(); | |
287 // Returns the model for the submenu at the specified index. | |
288 ui::MenuModel* submenu = model_->GetSubmenuModelAt(index); | |
289 submenu_.reset(new chromeos::NativeMenuWebUI(submenu, false)); | |
290 submenu_->set_menu_url(menu_url_); | |
291 // y in menu_widget_ coordinate. | |
292 submenu_->set_parent(this); | |
293 submenu_->ShowAt( | |
294 MenuLocator::CreateSubMenuLocator( | |
295 menu_widget_, | |
296 menu_widget_->menu_locator()->GetSubmenuDirection(), | |
297 y)); | |
298 } | |
299 | |
300 void NativeMenuWebUI::CloseAll() { | |
301 NativeMenuWebUI* root = GetRoot(); | |
302 // root can be null if the submenu is detached from parent. | |
303 if (root) | |
304 root->Hide(); | |
305 } | |
306 | |
307 void NativeMenuWebUI::CloseSubmenu() { | |
308 submenu_.reset(); // This closes subsequent children. | |
309 } | |
310 | |
311 void NativeMenuWebUI::MoveInputToSubmenu() { | |
312 if (submenu_.get()) { | |
313 submenu_->menu_widget_->EnableInput(true); | |
314 } | |
315 } | |
316 | |
317 void NativeMenuWebUI::MoveInputToParent() { | |
318 if (parent_) { | |
319 parent_->menu_widget_->EnableInput(true); | |
320 } | |
321 } | |
322 | |
323 void NativeMenuWebUI::OnLoad() { | |
324 // TODO(oshima): OnLoad is no longer used, but kept in case | |
325 // we may need it. Delete this if this is not necessary to | |
326 // implement wrench/network/bookmark menus. | |
327 } | |
328 | |
329 void NativeMenuWebUI::SetSize(const gfx::Size& size) { | |
330 menu_widget_->SetSize(size); | |
331 } | |
332 | |
333 //////////////////////////////////////////////////////////////////////////////// | |
334 // NativeMenuWebUI, public: | |
335 | |
336 void NativeMenuWebUI::Hide() { | |
337 // Only root can hide and exit the message loop. | |
338 DCHECK(menu_widget_->is_root()); | |
339 DCHECK(!parent_); | |
340 if (!menu_shown_) { | |
341 // The menu has been already hidden by us and we're in the process of | |
342 // quiting the message loop.. | |
343 return; | |
344 } | |
345 CloseSubmenu(); | |
346 menu_shown_ = false; | |
347 MessageLoop::current()->Quit(); | |
348 } | |
349 | |
350 NativeMenuWebUI* NativeMenuWebUI::GetRoot() { | |
351 NativeMenuWebUI* ancestor = this; | |
352 while (ancestor->parent_) | |
353 ancestor = ancestor->parent_; | |
354 if (ancestor->menu_widget_->is_root()) | |
355 return ancestor; | |
356 else | |
357 return NULL; | |
358 } | |
359 | |
360 Profile* NativeMenuWebUI::GetProfile() { | |
361 Browser* browser = BrowserList::GetLastActive(); | |
362 // browser can be null in login screen. | |
363 if (!browser) | |
364 return ProfileManager::GetDefaultProfile(); | |
365 return browser->GetProfile(); | |
366 } | |
367 | |
368 void NativeMenuWebUI::InputIsReady() { | |
369 if (!on_menu_opened_called_) { | |
370 on_menu_opened_called_ = true; | |
371 FOR_EACH_OBSERVER(views::MenuListener, listeners_, OnMenuOpened()); | |
372 } | |
373 } | |
374 | |
375 //////////////////////////////////////////////////////////////////////////////// | |
376 // NativeMenuWebUI, private: | |
377 | |
378 void NativeMenuWebUI::ProcessActivate() { | |
379 if (activated_menu_ && | |
380 activated_menu_->IsEnabledAt(activated_index_) && | |
381 MenuTypeCanExecute(activated_menu_->GetTypeAt(activated_index_))) { | |
382 activated_menu_->ActivatedAt(activated_index_); | |
383 } | |
384 } | |
385 | |
386 void NativeMenuWebUI::ShowAt(MenuLocator* locator) { | |
387 model_->MenuWillShow(); | |
388 menu_widget_->ShowAt(locator); | |
389 } | |
390 | |
391 NativeMenuWebUI* NativeMenuWebUI::FindMenuAt(const gfx::Point& point) { | |
392 if (submenu_.get()) { | |
393 NativeMenuWebUI* found = submenu_->FindMenuAt(point); | |
394 if (found) | |
395 return found; | |
396 } | |
397 gfx::Rect bounds = menu_widget_->GetClientAreaScreenBounds(); | |
398 return bounds.Contains(point) ? this : NULL; | |
399 } | |
400 | |
401 } // namespace chromeos | |
OLD | NEW |