diff options
Diffstat (limited to 'chrome/browser/chromeos/views/domui_menu_widget.cc')
-rw-r--r-- | chrome/browser/chromeos/views/domui_menu_widget.cc | 294 |
1 files changed, 294 insertions, 0 deletions
diff --git a/chrome/browser/chromeos/views/domui_menu_widget.cc b/chrome/browser/chromeos/views/domui_menu_widget.cc new file mode 100644 index 0000000..be6d309 --- /dev/null +++ b/chrome/browser/chromeos/views/domui_menu_widget.cc @@ -0,0 +1,294 @@ +// 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 "chrome/browser/chromeos/views/domui_menu_widget.h" + +#include "chrome/browser/chromeos/views/menu_locator.h" +#include "chrome/browser/chromeos/views/native_menu_domui.h" +#include "chrome/browser/chromeos/wm_ipc.h" +#include "chrome/browser/renderer_host/render_view_host.h" +#include "chrome/browser/renderer_host/render_widget_host_view.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/views/dom_view.h" +#include "cros/chromeos_wm_ipc_enums.h" +#include "gfx/canvas_skia.h" +#include "googleurl/src/gurl.h" +#include "third_party/skia/include/effects/SkGradientShader.h" +#include "views/border.h" +#include "views/layout_manager.h" +#include "views/widget/root_view.h" + +namespace { + +// Colors for menu's graident background. +const SkColor kMenuStartColor = SK_ColorWHITE; +const SkColor kMenuEndColor = 0xFFEEEEEE; + +// Rounded border for menu. This draws three types of rounded border, +// for context menu, dropdown menu and submenu. Please see +// menu_locator.cc for details. +class RoundedBorder : public views::Border { + public: + explicit RoundedBorder(chromeos::MenuLocator* locator) + : menu_locator_(locator) { + } + + private: + // views::Border implementatios. + virtual void Paint(const views::View& view, gfx::Canvas* canvas) const { + const SkScalar* corners = menu_locator_->GetCorners(); + // The menu is in off screen so no need to draw corners. + if (!corners) + return; + int w = view.width(); + int h = view.height(); + SkRect rect = {0, 0, w, h}; + SkPath path; + path.addRoundRect(rect, corners); + SkPaint paint; + paint.setStyle(SkPaint::kFill_Style); + paint.setFlags(SkPaint::kAntiAlias_Flag); + SkPoint p[2] = { {0, 0}, {0, h} }; + SkColor colors[2] = {kMenuStartColor, kMenuEndColor}; + SkShader* s = SkGradientShader::CreateLinear( + p, colors, NULL, 2, SkShader::kClamp_TileMode, NULL); + paint.setShader(s); + // Need to unref shader, otherwise never deleted. + s->unref(); + canvas->AsCanvasSkia()->drawPath(path, paint); + } + + virtual void GetInsets(gfx::Insets* insets) const { + DCHECK(insets); + menu_locator_->GetInsets(insets); + } + + chromeos::MenuLocator* menu_locator_; // not owned + + DISALLOW_COPY_AND_ASSIGN(RoundedBorder); +}; + +class InsetsLayout : public views::LayoutManager { + public: + InsetsLayout() : views::LayoutManager() {} + + private: + // views::LayoutManager implementatios. + virtual void Layout(views::View* host) { + if (host->GetChildViewCount() == 0) + return; + gfx::Insets insets = host->GetInsets(); + views::View* view = host->GetChildViewAt(0); + + view->SetBounds(insets.left(), insets.top(), + host->width() - insets.width(), + host->height() - insets.height()); + } + + virtual gfx::Size GetPreferredSize(views::View* host) { + DCHECK(host->GetChildViewCount() == 1); + gfx::Insets insets = host->GetInsets(); + gfx::Size size = host->GetChildViewAt(0)->GetPreferredSize(); + return gfx::Size(size.width() + insets.width(), + size.height() + insets.height()); + } + + DISALLOW_COPY_AND_ASSIGN(InsetsLayout); +}; + +// A gtk widget key used to test if a given WidgetGtk instance is +// DOMUIMenuWidgetKey. +const char* kDOMUIMenuWidgetKey = "__DOMUI_MENU_WIDGET__"; + +} // namespace + +namespace chromeos { + +// static +DOMUIMenuWidget* DOMUIMenuWidget::FindDOMUIMenuWidget(gfx::NativeView native) { + DCHECK(native); + native = gtk_widget_get_toplevel(native); + if (!native) + return NULL; + return static_cast<chromeos::DOMUIMenuWidget*>( + g_object_get_data(G_OBJECT(native), kDOMUIMenuWidgetKey)); +} + +/////////////////////////////////////////////////////////////////////////////// +// DOMUIMenuWidget public: + +DOMUIMenuWidget::DOMUIMenuWidget(chromeos::NativeMenuDOMUI* domui_menu, + bool root) + : views::WidgetGtk(views::WidgetGtk::TYPE_POPUP), + domui_menu_(domui_menu), + dom_view_(NULL), + did_pointer_grab_(false), + is_root_(root) { + DCHECK(domui_menu_); + MakeTransparent(); +} + +DOMUIMenuWidget::~DOMUIMenuWidget() { +} + +void DOMUIMenuWidget::Init(gfx::NativeView parent, const gfx::Rect& bounds) { + WidgetGtk::Init(parent, bounds); + gtk_window_set_destroy_with_parent(GTK_WINDOW(GetNativeView()), TRUE); + gtk_window_set_type_hint(GTK_WINDOW(GetNativeView()), + GDK_WINDOW_TYPE_HINT_MENU); + g_object_set_data(G_OBJECT(GetNativeView()), kDOMUIMenuWidgetKey, this); +} + +void DOMUIMenuWidget::Hide() { + ReleaseGrab(); + WidgetGtk::Hide(); +} + +void DOMUIMenuWidget::Close() { + // Detach the domui_menu_ which is being deleted. + domui_menu_ = NULL; + views::WidgetGtk::Close(); +} + +void DOMUIMenuWidget::ReleaseGrab() { + WidgetGtk::ReleaseGrab(); + if (did_pointer_grab_) { + did_pointer_grab_ = false; + gdk_pointer_ungrab(GDK_CURRENT_TIME); + + ClearGrabWidget(); + } +} + +gboolean DOMUIMenuWidget::OnGrabBrokeEvent(GtkWidget* widget, + GdkEvent* event) { + did_pointer_grab_ = false; + Hide(); + return WidgetGtk::OnGrabBrokeEvent(widget, event); +} + +void DOMUIMenuWidget::OnSizeAllocate(GtkWidget* widget, + GtkAllocation* allocation) { + views::WidgetGtk::OnSizeAllocate(widget, allocation); + // Adjust location when menu gets resized. + gfx::Rect bounds; + GetBounds(&bounds, false); + // Don't move until the menu gets contents. + if (bounds.height() >= 2) + menu_locator_->Move(this); +} + +gboolean MapToFocus(GtkWidget* widget, GdkEvent* event, gpointer data) { + DOMUIMenuWidget* menu_widget = DOMUIMenuWidget::FindDOMUIMenuWidget(widget); + if (menu_widget) + menu_widget->EnableInput(false); + return true; +} + +void DOMUIMenuWidget::EnableInput(bool select_item) { + DCHECK(dom_view_); + DCHECK(dom_view_->tab_contents()->render_view_host()); + DCHECK(dom_view_->tab_contents()->render_view_host()->view()); + GtkWidget* target = + dom_view_->tab_contents()->render_view_host()->view()->GetNativeView(); + DCHECK(target); + // Skip if the widget already own the input. + if (gtk_grab_get_current() == target) + return; + + ClearGrabWidget(); + + if (!GTK_WIDGET_REALIZED(target)) { + // Try again if the widget is not yet realized. + // This happens only for root which tries to open & grab input. + DCHECK(is_root_); + DCHECK(!select_item); + g_signal_connect(G_OBJECT(target), "map-event", + G_CALLBACK(&MapToFocus), NULL); + return; + } + + gtk_grab_add(target); + dom_view_->tab_contents()->Focus(); + if (select_item) { + ExecuteJavascript(L"selectItem()"); + } +} + +void DOMUIMenuWidget::ExecuteJavascript(const std::wstring& script) { + // Don't exeute there is no DOMView associated. This is fine because + // 1) selectItem make sense only when DOMView is associated. + // 2) updateModel will be called again when a DOMView is created/assigned. + if (dom_view_ == NULL) + return; + + DCHECK(dom_view_->tab_contents()->render_view_host()); + dom_view_->tab_contents()->render_view_host()-> + ExecuteJavascriptInWebFrame(std::wstring(), script); +} + +void DOMUIMenuWidget::ShowAt(chromeos::MenuLocator* locator) { + DCHECK(domui_menu_); + menu_locator_.reset(locator); + if (!dom_view_) { + dom_view_ = new DOMView(); + dom_view_->Init(domui_menu_->GetProfile(), NULL); + // TODO(oshima): remove extra view to draw rounded corner. + views::View* container = new views::View(); + container->AddChildView(dom_view_); + container->set_border(new RoundedBorder(locator)); + container->SetLayoutManager(new InsetsLayout()); + SetContentsView(container); + dom_view_->LoadURL(GURL("chrome://menu")); + } else { + domui_menu_->UpdateStates(); + dom_view_->GetParent()->set_border(new RoundedBorder(locator)); + menu_locator_->Move(this); + } + Show(); + + // The pointer grab is captured only on the top level menu, + // all mouse event events are delivered to submenu using gtk_add_grab. + if (is_root_) { + CaptureGrab(); + } +} + +void DOMUIMenuWidget::SetSize(const gfx::Size& new_size) { + DCHECK(domui_menu_); + // Ignore the empty new_size request which is called when + // menu.html is loaded. + if (new_size.IsEmpty()) return; + + menu_locator_->SetBounds(this, new_size); +} + +/////////////////////////////////////////////////////////////////////////////// +// DOMUIMenuWidget private: + +void DOMUIMenuWidget::CaptureGrab() { + // Release the current grab. + ClearGrabWidget(); + + // NOTE: we do this to ensure we get mouse events from other apps, a grab + // done with gtk_grab_add doesn't get events from other apps. + GdkGrabStatus grab_status = + gdk_pointer_grab(window_contents()->window, FALSE, + static_cast<GdkEventMask>( + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK), + NULL, NULL, GDK_CURRENT_TIME); + did_pointer_grab_ = (grab_status == GDK_GRAB_SUCCESS); + DCHECK(did_pointer_grab_); + + EnableInput(false /* no selection */); +} + +void DOMUIMenuWidget::ClearGrabWidget() { + GtkWidget* grab_widget; + while ((grab_widget = gtk_grab_get_current())) + gtk_grab_remove(grab_widget); +} + +} // namespace chromeos |