| 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 |