| 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/webui_menu_widget.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 | |
| 9 #include "base/stringprintf.h" | |
| 10 #include "base/singleton.h" | |
| 11 #include "base/task.h" | |
| 12 #include "base/utf_string_conversions.h" | |
| 13 #include "chrome/browser/chromeos/views/menu_locator.h" | |
| 14 #include "chrome/browser/chromeos/views/native_menu_webui.h" | |
| 15 #include "chrome/browser/chromeos/wm_ipc.h" | |
| 16 #include "chrome/browser/ui/views/dom_view.h" | |
| 17 #include "chrome/common/url_constants.h" | |
| 18 #include "content/browser/renderer_host/render_view_host.h" | |
| 19 #include "content/browser/renderer_host/render_widget_host_view.h" | |
| 20 #include "content/browser/tab_contents/tab_contents.h" | |
| 21 #include "googleurl/src/gurl.h" | |
| 22 #include "third_party/cros/chromeos_wm_ipc_enums.h" | |
| 23 #include "third_party/skia/include/effects/SkGradientShader.h" | |
| 24 #include "ui/gfx/canvas_skia.h" | |
| 25 #include "views/border.h" | |
| 26 #include "views/layout/layout_manager.h" | |
| 27 #include "views/widget/root_view.h" | |
| 28 | |
| 29 namespace { | |
| 30 | |
| 31 // Colors for the menu's gradient background. | |
| 32 const SkColor kMenuStartColor = SK_ColorWHITE; | |
| 33 const SkColor kMenuEndColor = 0xFFEEEEEE; | |
| 34 | |
| 35 // Rounded border for menu. This draws three types of rounded border, | |
| 36 // for context menu, dropdown menu and submenu. Please see | |
| 37 // menu_locator.cc for details. | |
| 38 class RoundedBorder : public views::Border { | |
| 39 public: | |
| 40 explicit RoundedBorder(chromeos::MenuLocator* locator) | |
| 41 : menu_locator_(locator) { | |
| 42 } | |
| 43 | |
| 44 private: | |
| 45 // views::Border implementations. | |
| 46 virtual void Paint(const views::View& view, gfx::Canvas* canvas) const { | |
| 47 const SkScalar* corners = menu_locator_->GetCorners(); | |
| 48 // The menu is in off screen so no need to draw corners. | |
| 49 if (!corners) | |
| 50 return; | |
| 51 int w = view.width(); | |
| 52 int h = view.height(); | |
| 53 SkRect rect = {0, 0, w, h}; | |
| 54 SkPath path; | |
| 55 path.addRoundRect(rect, corners); | |
| 56 SkPaint paint; | |
| 57 paint.setStyle(SkPaint::kFill_Style); | |
| 58 paint.setFlags(SkPaint::kAntiAlias_Flag); | |
| 59 SkPoint p[2] = { {0, 0}, {0, h} }; | |
| 60 SkColor colors[2] = {kMenuStartColor, kMenuEndColor}; | |
| 61 SkShader* s = SkGradientShader::CreateLinear( | |
| 62 p, colors, NULL, 2, SkShader::kClamp_TileMode, NULL); | |
| 63 paint.setShader(s); | |
| 64 // Need to unref shader, otherwise never deleted. | |
| 65 s->unref(); | |
| 66 canvas->AsCanvasSkia()->drawPath(path, paint); | |
| 67 } | |
| 68 | |
| 69 virtual void GetInsets(gfx::Insets* insets) const { | |
| 70 DCHECK(insets); | |
| 71 menu_locator_->GetInsets(insets); | |
| 72 } | |
| 73 | |
| 74 chromeos::MenuLocator* menu_locator_; // not owned | |
| 75 | |
| 76 DISALLOW_COPY_AND_ASSIGN(RoundedBorder); | |
| 77 }; | |
| 78 | |
| 79 class InsetsLayout : public views::LayoutManager { | |
| 80 public: | |
| 81 InsetsLayout() : views::LayoutManager() {} | |
| 82 | |
| 83 private: | |
| 84 // views::LayoutManager implementations. | |
| 85 virtual void Layout(views::View* host) { | |
| 86 if (!host->has_children()) | |
| 87 return; | |
| 88 gfx::Insets insets = host->GetInsets(); | |
| 89 views::View* view = host->GetChildViewAt(0); | |
| 90 | |
| 91 view->SetBounds(insets.left(), insets.top(), | |
| 92 host->width() - insets.width(), | |
| 93 host->height() - insets.height()); | |
| 94 } | |
| 95 | |
| 96 virtual gfx::Size GetPreferredSize(views::View* host) { | |
| 97 DCHECK(host->child_count() == 1); | |
| 98 gfx::Insets insets = host->GetInsets(); | |
| 99 gfx::Size size = host->GetChildViewAt(0)->GetPreferredSize(); | |
| 100 return gfx::Size(size.width() + insets.width(), | |
| 101 size.height() + insets.height()); | |
| 102 } | |
| 103 | |
| 104 DISALLOW_COPY_AND_ASSIGN(InsetsLayout); | |
| 105 }; | |
| 106 | |
| 107 // A gtk widget key used to test if a given WidgetGtk instance is | |
| 108 // a WebUIMenuWidgetKey. | |
| 109 const char* kWebUIMenuWidgetKey = "__WEBUI_MENU_WIDGET__"; | |
| 110 | |
| 111 } // namespace | |
| 112 | |
| 113 namespace chromeos { | |
| 114 | |
| 115 // static | |
| 116 WebUIMenuWidget* WebUIMenuWidget::FindWebUIMenuWidget(gfx::NativeView native) { | |
| 117 DCHECK(native); | |
| 118 native = gtk_widget_get_toplevel(native); | |
| 119 if (!native) | |
| 120 return NULL; | |
| 121 return static_cast<chromeos::WebUIMenuWidget*>( | |
| 122 g_object_get_data(G_OBJECT(native), kWebUIMenuWidgetKey)); | |
| 123 } | |
| 124 | |
| 125 /////////////////////////////////////////////////////////////////////////////// | |
| 126 // WebUIMenuWidget public: | |
| 127 | |
| 128 WebUIMenuWidget::WebUIMenuWidget(chromeos::NativeMenuWebUI* webui_menu, | |
| 129 bool root) | |
| 130 : views::WidgetGtk(views::WidgetGtk::TYPE_POPUP), | |
| 131 webui_menu_(webui_menu), | |
| 132 dom_view_(NULL), | |
| 133 did_input_grab_(false), | |
| 134 is_root_(root) { | |
| 135 DCHECK(webui_menu_); | |
| 136 // TODO(oshima): Disabling transparent until we migrate bookmark | |
| 137 // menus to WebUI. See crosbug.com/7718. | |
| 138 // MakeTransparent(); | |
| 139 } | |
| 140 | |
| 141 WebUIMenuWidget::~WebUIMenuWidget() { | |
| 142 } | |
| 143 | |
| 144 void WebUIMenuWidget::Init(gfx::NativeView parent, const gfx::Rect& bounds) { | |
| 145 WidgetGtk::Init(parent, bounds); | |
| 146 gtk_window_set_destroy_with_parent(GTK_WINDOW(GetNativeView()), TRUE); | |
| 147 gtk_window_set_type_hint(GTK_WINDOW(GetNativeView()), | |
| 148 GDK_WINDOW_TYPE_HINT_MENU); | |
| 149 g_object_set_data(G_OBJECT(GetNativeView()), kWebUIMenuWidgetKey, this); | |
| 150 } | |
| 151 | |
| 152 void WebUIMenuWidget::Hide() { | |
| 153 ReleaseNativeCapture(); | |
| 154 WidgetGtk::Hide(); | |
| 155 // Clears the content. | |
| 156 ExecuteJavascript(L"updateModel({'items':[]})"); | |
| 157 } | |
| 158 | |
| 159 void WebUIMenuWidget::Close() { | |
| 160 if (dom_view_ != NULL) { | |
| 161 dom_view_->parent()->RemoveChildView(dom_view_); | |
| 162 delete dom_view_; | |
| 163 dom_view_ = NULL; | |
| 164 } | |
| 165 | |
| 166 // Detach the webui_menu_ which is being deleted. | |
| 167 webui_menu_ = NULL; | |
| 168 views::WidgetGtk::Close(); | |
| 169 } | |
| 170 | |
| 171 void WebUIMenuWidget::ReleaseNativeCapture() { | |
| 172 WidgetGtk::ReleaseNativeCapture(); | |
| 173 if (did_input_grab_) { | |
| 174 did_input_grab_ = false; | |
| 175 gdk_pointer_ungrab(GDK_CURRENT_TIME); | |
| 176 gdk_keyboard_ungrab(GDK_CURRENT_TIME); | |
| 177 | |
| 178 ClearGrabWidget(); | |
| 179 } | |
| 180 } | |
| 181 | |
| 182 gboolean WebUIMenuWidget::OnGrabBrokeEvent(GtkWidget* widget, | |
| 183 GdkEvent* event) { | |
| 184 did_input_grab_ = false; | |
| 185 Hide(); | |
| 186 return WidgetGtk::OnGrabBrokeEvent(widget, event); | |
| 187 } | |
| 188 | |
| 189 void WebUIMenuWidget::OnSizeAllocate(GtkWidget* widget, | |
| 190 GtkAllocation* allocation) { | |
| 191 views::WidgetGtk::OnSizeAllocate(widget, allocation); | |
| 192 // Adjust location when menu gets resized. | |
| 193 gfx::Rect bounds = GetClientAreaScreenBounds(); | |
| 194 // Don't move until the menu gets contents. | |
| 195 if (bounds.height() > 1) { | |
| 196 menu_locator_->Move(this); | |
| 197 webui_menu_->InputIsReady(); | |
| 198 } | |
| 199 } | |
| 200 | |
| 201 gboolean MapToFocus(GtkWidget* widget, GdkEvent* event, gpointer data) { | |
| 202 WebUIMenuWidget* menu_widget = WebUIMenuWidget::FindWebUIMenuWidget(widget); | |
| 203 if (menu_widget) { | |
| 204 // See EnableInput for the meaning of data. | |
| 205 bool select_item = data != NULL; | |
| 206 menu_widget->EnableInput(select_item); | |
| 207 } | |
| 208 return true; | |
| 209 } | |
| 210 | |
| 211 void WebUIMenuWidget::EnableScroll(bool enable) { | |
| 212 ExecuteJavascript(StringPrintf( | |
| 213 L"enableScroll(%ls)", enable ? L"true" : L"false")); | |
| 214 } | |
| 215 | |
| 216 void WebUIMenuWidget::EnableInput(bool select_item) { | |
| 217 if (!dom_view_) | |
| 218 return; | |
| 219 DCHECK(dom_view_->tab_contents()->render_view_host()); | |
| 220 DCHECK(dom_view_->tab_contents()->render_view_host()->view()); | |
| 221 GtkWidget* target = | |
| 222 dom_view_->tab_contents()->render_view_host()->view()->GetNativeView(); | |
| 223 DCHECK(target); | |
| 224 // Skip if the widget already own the input. | |
| 225 if (gtk_grab_get_current() == target) | |
| 226 return; | |
| 227 | |
| 228 ClearGrabWidget(); | |
| 229 | |
| 230 if (!GTK_WIDGET_REALIZED(target)) { | |
| 231 // Wait grabbing widget if the widget is not yet realized. | |
| 232 // Using data as a flag. |select_item| is false if data is NULL, | |
| 233 // or true otherwise. | |
| 234 g_signal_connect(G_OBJECT(target), "map-event", | |
| 235 G_CALLBACK(&MapToFocus), | |
| 236 select_item ? this : NULL); | |
| 237 return; | |
| 238 } | |
| 239 | |
| 240 gtk_grab_add(target); | |
| 241 dom_view_->tab_contents()->Focus(); | |
| 242 if (select_item) { | |
| 243 ExecuteJavascript(L"selectItem()"); | |
| 244 } | |
| 245 } | |
| 246 | |
| 247 void WebUIMenuWidget::ExecuteJavascript(const std::wstring& script) { | |
| 248 // Don't execute if there is no DOMView associated. This is fine because | |
| 249 // 1) selectItem make sense only when DOMView is associated. | |
| 250 // 2) updateModel will be called again when a DOMView is created/assigned. | |
| 251 if (!dom_view_) | |
| 252 return; | |
| 253 | |
| 254 DCHECK(dom_view_->tab_contents()->render_view_host()); | |
| 255 dom_view_->tab_contents()->render_view_host()-> | |
| 256 ExecuteJavascriptInWebFrame(string16(), WideToUTF16Hack(script)); | |
| 257 } | |
| 258 | |
| 259 void WebUIMenuWidget::ShowAt(chromeos::MenuLocator* locator) { | |
| 260 DCHECK(webui_menu_); | |
| 261 menu_locator_.reset(locator); | |
| 262 if (!dom_view_) { | |
| 263 // TODO(oshima): Replace DOMView with direct use of RVH for beta. | |
| 264 // DOMView should be refactored to use RVH directly, but | |
| 265 // it'll require a lot of change and will take time. | |
| 266 dom_view_ = new DOMView(); | |
| 267 dom_view_->Init(webui_menu_->GetProfile(), NULL); | |
| 268 // TODO(oshima): remove extra view to draw rounded corner. | |
| 269 views::View* container = new views::View(); | |
| 270 container->AddChildView(dom_view_); | |
| 271 container->set_border(new RoundedBorder(locator)); | |
| 272 container->SetLayoutManager(new InsetsLayout()); | |
| 273 SetContentsView(container); | |
| 274 dom_view_->LoadURL(webui_menu_->menu_url()); | |
| 275 } else { | |
| 276 webui_menu_->UpdateStates(); | |
| 277 dom_view_->parent()->set_border(new RoundedBorder(locator)); | |
| 278 menu_locator_->Move(this); | |
| 279 } | |
| 280 Show(); | |
| 281 | |
| 282 // The pointer grab is captured only on the top level menu, | |
| 283 // all mouse event events are delivered to submenu using gtk_add_grab. | |
| 284 if (is_root_) { | |
| 285 CaptureGrab(); | |
| 286 } | |
| 287 } | |
| 288 | |
| 289 void WebUIMenuWidget::SetSize(const gfx::Size& new_size) { | |
| 290 DCHECK(webui_menu_); | |
| 291 // Ignore the empty new_size request which is called when | |
| 292 // menu.html is loaded. | |
| 293 if (new_size.IsEmpty()) | |
| 294 return; | |
| 295 int width, height; | |
| 296 gtk_widget_get_size_request(GetNativeView(), &width, &height); | |
| 297 gfx::Size real_size(std::max(new_size.width(), width), | |
| 298 new_size.height()); | |
| 299 // Ignore the size request with the same size. | |
| 300 gfx::Rect bounds = GetClientAreaScreenBounds(); | |
| 301 if (bounds.size() == real_size) | |
| 302 return; | |
| 303 menu_locator_->SetBounds(this, real_size); | |
| 304 } | |
| 305 | |
| 306 /////////////////////////////////////////////////////////////////////////////// | |
| 307 // WebUIMenuWidget private: | |
| 308 | |
| 309 void WebUIMenuWidget::CaptureGrab() { | |
| 310 // Release the current grab. | |
| 311 ClearGrabWidget(); | |
| 312 | |
| 313 // NOTE: we do this to ensure we get mouse/keyboard events from | |
| 314 // other apps, a grab done with gtk_grab_add doesn't get events from | |
| 315 // other apps. | |
| 316 GdkGrabStatus pointer_grab_status = | |
| 317 gdk_pointer_grab(window_contents()->window, FALSE, | |
| 318 static_cast<GdkEventMask>( | |
| 319 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | | |
| 320 GDK_POINTER_MOTION_MASK), | |
| 321 NULL, NULL, GDK_CURRENT_TIME); | |
| 322 GdkGrabStatus keyboard_grab_status = | |
| 323 gdk_keyboard_grab(window_contents()->window, FALSE, | |
| 324 GDK_CURRENT_TIME); | |
| 325 | |
| 326 did_input_grab_ = pointer_grab_status == GDK_GRAB_SUCCESS && | |
| 327 keyboard_grab_status == GDK_GRAB_SUCCESS; | |
| 328 DCHECK(did_input_grab_); | |
| 329 | |
| 330 EnableInput(false /* no selection */); | |
| 331 } | |
| 332 | |
| 333 void WebUIMenuWidget::ClearGrabWidget() { | |
| 334 GtkWidget* grab_widget; | |
| 335 while ((grab_widget = gtk_grab_get_current())) | |
| 336 gtk_grab_remove(grab_widget); | |
| 337 } | |
| 338 | |
| 339 } // namespace chromeos | |
| OLD | NEW |