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 |