blob: 0cf2a8265755c66ee4f605e128cc3a1287cc883c [file] [log] [blame]
// 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 "chrome/browser/chromeos/views/native_menu_webui.h"
#include <string>
#include "base/message_loop.h"
#include "base/string_util.h"
#include "chrome/browser/chromeos/dom_ui/menu_ui.h"
#include "chrome/browser/chromeos/views/menu_locator.h"
#include "chrome/browser/chromeos/views/webui_menu_widget.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/common/url_constants.h"
#include "ui/base/models/menu_model.h"
#include "ui/gfx/rect.h"
#include "views/controls/menu/menu_2.h"
#include "views/controls/menu/nested_dispatcher_gtk.h"
#if defined(TOUCH_UI)
#include "views/focus/accelerator_handler.h"
#include "views/controls/menu/native_menu_x.h"
#else
#include "views/controls/menu/native_menu_gtk.h"
#endif
namespace {
using chromeos::NativeMenuWebUI;
using chromeos::WebUIMenuWidget;
// Returns true if the menu item type specified can be executed as a command.
bool MenuTypeCanExecute(ui::MenuModel::ItemType type) {
return type == ui::MenuModel::TYPE_COMMAND ||
type == ui::MenuModel::TYPE_CHECK ||
type == ui::MenuModel::TYPE_RADIO;
}
gboolean Destroy(GtkWidget* widget, gpointer data) {
WebUIMenuWidget* menu_widget = static_cast<WebUIMenuWidget*>(data);
NativeMenuWebUI* webui_menu = menu_widget->webui_menu();
// webui_menu can be NULL if widget is destroyed by signal.
if (webui_menu)
webui_menu->Hide();
return true;
}
// Returns the active toplevel window.
gfx::NativeWindow FindActiveToplevelWindow() {
GList* toplevels = gtk_window_list_toplevels();
while (toplevels) {
gfx::NativeWindow window = static_cast<gfx::NativeWindow>(toplevels->data);
if (gtk_window_is_active(window)) {
return window;
}
toplevels = g_list_next(toplevels);
}
return NULL;
}
// Currently opened menu. See RunMenuAt for reason why we need this.
NativeMenuWebUI* current_ = NULL;
} // namespace
namespace chromeos {
// static
void NativeMenuWebUI::SetMenuURL(views::Menu2* menu2, const GURL& url) {
// No-op if WebUI menu is disabled.
if (!MenuUI::IsEnabled())
return;
gfx::NativeView native = menu2->GetNativeMenu();
DCHECK(native);
WebUIMenuWidget* widget = WebUIMenuWidget::FindWebUIMenuWidget(native);
DCHECK(widget);
widget->webui_menu()->set_menu_url(url);
}
////////////////////////////////////////////////////////////////////////////////
// NativeMenuWebUI, public:
NativeMenuWebUI::NativeMenuWebUI(ui::MenuModel* menu_model, bool root)
: parent_(NULL),
submenu_(NULL),
model_(menu_model),
menu_widget_(NULL),
menu_shown_(false),
activated_menu_(NULL),
activated_index_(-1),
menu_action_(MENU_ACTION_NONE),
menu_url_(StringPrintf("chrome://%s", chrome::kChromeUIMenu)),
on_menu_opened_called_(false),
nested_dispatcher_(NULL) {
menu_widget_ = new WebUIMenuWidget(this, root);
// Set the initial location off the screen not to show small
// window with dropshadow.
menu_widget_->Init(NULL, gfx::Rect(-10000, -10000, 1, 1));
}
NativeMenuWebUI::~NativeMenuWebUI() {
if (nested_dispatcher_) {
// Menu is destroyed while its in message loop.
// Let nested dispatcher know the creator is deleted.
nested_dispatcher_->CreatorDestroyed();
Hide();
}
if (menu_widget_) {
menu_widget_->Close();
menu_widget_ = NULL;
}
parent_ = NULL;
}
////////////////////////////////////////////////////////////////////////////////
// NativeMenuWebUI, MenuWrapper implementation:
void NativeMenuWebUI::RunMenuAt(const gfx::Point& point, int alignment) {
if (current_ != NULL) {
// This happens when there is a nested task to show menu, which is
// executed after menu is open. Since we need to enable nested task,
// this condition has to be handled here.
return;
}
current_ = this;
bool context = false;
// TODO(oshima): This is quick hack to check if it's context menu. (in rtl)
// Fix this once we migrated.
if (alignment == views::Menu2::ALIGN_TOPLEFT) {
context = true;
}
activated_menu_ = NULL;
activated_index_ = -1;
menu_action_ = MENU_ACTION_NONE;
MenuLocator* locator = context ?
MenuLocator::CreateContextMenuLocator(point) :
MenuLocator::CreateDropDownMenuLocator(point);
ShowAt(locator);
DCHECK(!menu_shown_);
menu_shown_ = true;
on_menu_opened_called_ = false;
// TODO(oshima): A menu must be deleted when parent window is
// closed. Menu2 doesn't know about the parent window, however, so
// we're using toplevel gtkwindow. This is probably sufficient, but
// I will update Menu2 to pass host view (which is necessary anyway
// to get the right position) and get a parent widnow through
// it. http://crosbug/7642
gfx::NativeWindow parent = FindActiveToplevelWindow();
gulong handle = 0;
if (parent) {
handle = g_signal_connect(G_OBJECT(parent), "destroy",
G_CALLBACK(&Destroy),
menu_widget_);
}
// We need to turn on nestable tasks as a renderer uses tasks internally.
// Without this, renderer cannnot finish loading page.
nested_dispatcher_ =
new views::NestedDispatcherGtk(this, true /* allow nested */);
bool deleted = nested_dispatcher_->RunAndSelfDestruct();
current_ = NULL; // this is static and safe to access.
if (deleted) {
// The menu was destryed while menu is shown, so return immediately.
// Don't touch the instance which is already deleted.
return;
}
nested_dispatcher_ = NULL;
if (menu_shown_) {
// If this happens it means we haven't yet gotten the hide signal and
// someone else quit the message loop on us.
NOTREACHED();
menu_shown_ = false;
}
if (handle)
g_signal_handler_disconnect(G_OBJECT(parent), handle);
menu_widget_->Hide();
// Close All submenus.
submenu_.reset();
ProcessActivate();
}
void NativeMenuWebUI::CancelMenu() {
Hide();
}
void NativeMenuWebUI::Rebuild() {
activated_menu_ = NULL;
menu_widget_->ExecuteJavascript(L"modelUpdated()");
}
void NativeMenuWebUI::UpdateStates() {
// Update menu contnets and submenus.
Rebuild();
}
gfx::NativeMenu NativeMenuWebUI::GetNativeMenu() const {
return menu_widget_->GetNativeView();
}
NativeMenuWebUI::MenuAction NativeMenuWebUI::GetMenuAction() const {
return menu_action_;
}
void NativeMenuWebUI::AddMenuListener(views::MenuListener* listener) {
listeners_.AddObserver(listener);
}
void NativeMenuWebUI::RemoveMenuListener(views::MenuListener* listener) {
listeners_.RemoveObserver(listener);
}
void NativeMenuWebUI::SetMinimumWidth(int width) {
gtk_widget_set_size_request(menu_widget_->GetNativeView(), width, 1);
}
////////////////////////////////////////////////////////////////////////////////
// NativeMenuWebUI, MessageLoopForUI::Dispatcher implementation:
bool NativeMenuWebUI::Dispatch(GdkEvent* event) {
switch (event->type) {
case GDK_MOTION_NOTIFY: {
NativeMenuWebUI* target = FindMenuAt(
gfx::Point(event->motion.x_root, event->motion.y_root));
if (target)
target->menu_widget_->EnableInput(false);
break;
}
case GDK_BUTTON_PRESS: {
NativeMenuWebUI* target = FindMenuAt(
gfx::Point(event->motion.x_root, event->motion.y_root));
if (!target) {
Hide();
return true;
}
break;
}
default:
break;
}
gtk_main_do_event(event);
return true;
}
#if defined(TOUCH_UI)
base::MessagePumpGlibXDispatcher::DispatchStatus
NativeMenuWebUI::DispatchX(XEvent* xevent) {
return views::DispatchXEvent(xevent) ?
base::MessagePumpGlibXDispatcher::EVENT_PROCESSED :
base::MessagePumpGlibXDispatcher::EVENT_IGNORED;
}
#endif
////////////////////////////////////////////////////////////////////////////////
// NativeMenuWebUI, MenuControl implementation:
void NativeMenuWebUI::Activate(ui::MenuModel* model,
int index,
ActivationMode activation) {
NativeMenuWebUI* root = GetRoot();
if (root) {
if (activation == CLOSE_AND_ACTIVATE) {
root->activated_menu_ = model;
root->activated_index_ = index;
root->menu_action_ = MENU_ACTION_SELECTED;
root->Hide();
} else {
if (model->IsEnabledAt(index) &&
MenuTypeCanExecute(model->GetTypeAt(index))) {
model->ActivatedAt(index);
}
}
}
}
void NativeMenuWebUI::OpenSubmenu(int index, int y) {
submenu_.reset();
// Returns the model for the submenu at the specified index.
ui::MenuModel* submenu = model_->GetSubmenuModelAt(index);
submenu_.reset(new chromeos::NativeMenuWebUI(submenu, false));
submenu_->set_menu_url(menu_url_);
// y in menu_widget_ coordinate.
submenu_->set_parent(this);
submenu_->ShowAt(
MenuLocator::CreateSubMenuLocator(
menu_widget_,
menu_widget_->menu_locator()->GetSubmenuDirection(),
y));
}
void NativeMenuWebUI::CloseAll() {
NativeMenuWebUI* root = GetRoot();
// root can be null if the submenu is detached from parent.
if (root)
root->Hide();
}
void NativeMenuWebUI::CloseSubmenu() {
submenu_.reset(); // This closes subsequent children.
}
void NativeMenuWebUI::MoveInputToSubmenu() {
if (submenu_.get()) {
submenu_->menu_widget_->EnableInput(true);
}
}
void NativeMenuWebUI::MoveInputToParent() {
if (parent_) {
parent_->menu_widget_->EnableInput(true);
}
}
void NativeMenuWebUI::OnLoad() {
// TODO(oshima): OnLoad is no longer used, but kept in case
// we may need it. Delete this if this is not necessary to
// implement wrench/network/bookmark menus.
}
void NativeMenuWebUI::SetSize(const gfx::Size& size) {
menu_widget_->SetSize(size);
}
////////////////////////////////////////////////////////////////////////////////
// NativeMenuWebUI, public:
void NativeMenuWebUI::Hide() {
// Only root can hide and exit the message loop.
DCHECK(menu_widget_->is_root());
DCHECK(!parent_);
if (!menu_shown_) {
// The menu has been already hidden by us and we're in the process of
// quiting the message loop..
return;
}
CloseSubmenu();
menu_shown_ = false;
MessageLoop::current()->Quit();
}
NativeMenuWebUI* NativeMenuWebUI::GetRoot() {
NativeMenuWebUI* ancestor = this;
while (ancestor->parent_)
ancestor = ancestor->parent_;
if (ancestor->menu_widget_->is_root())
return ancestor;
else
return NULL;
}
Profile* NativeMenuWebUI::GetProfile() {
Browser* browser = BrowserList::GetLastActive();
// browser can be null in login screen.
if (!browser)
return ProfileManager::GetDefaultProfile();
return browser->GetProfile();
}
void NativeMenuWebUI::InputIsReady() {
if (!on_menu_opened_called_) {
on_menu_opened_called_ = true;
FOR_EACH_OBSERVER(views::MenuListener, listeners_, OnMenuOpened());
}
}
////////////////////////////////////////////////////////////////////////////////
// NativeMenuWebUI, private:
void NativeMenuWebUI::ProcessActivate() {
if (activated_menu_ &&
activated_menu_->IsEnabledAt(activated_index_) &&
MenuTypeCanExecute(activated_menu_->GetTypeAt(activated_index_))) {
activated_menu_->ActivatedAt(activated_index_);
}
}
void NativeMenuWebUI::ShowAt(MenuLocator* locator) {
model_->MenuWillShow();
menu_widget_->ShowAt(locator);
}
NativeMenuWebUI* NativeMenuWebUI::FindMenuAt(const gfx::Point& point) {
if (submenu_.get()) {
NativeMenuWebUI* found = submenu_->FindMenuAt(point);
if (found)
return found;
}
gfx::Rect bounds;
menu_widget_->GetBounds(&bounds, false);
return bounds.Contains(point) ? this : NULL;
}
} // namespace chromeos