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/ui/views/tab_contents/tab_contents_view_gtk.h" | |
6 | |
7 #include <gdk/gdk.h> | |
8 #include <gtk/gtk.h> | |
9 | |
10 #include "base/string_util.h" | |
11 #include "base/utf_string_conversions.h" | |
12 #include "build/build_config.h" | |
13 #include "chrome/browser/download/download_shelf.h" | |
14 #include "chrome/browser/renderer_host/render_widget_host_view_gtk.h" | |
15 #include "chrome/browser/tab_contents/web_drag_dest_gtk.h" | |
16 #include "chrome/browser/ui/gtk/constrained_window_gtk.h" | |
17 #include "chrome/browser/ui/gtk/tab_contents_drag_source.h" | |
18 #include "chrome/browser/ui/views/sad_tab_view.h" | |
19 #include "chrome/browser/ui/views/tab_contents/render_view_context_menu_views.h" | |
20 #include "content/browser/renderer_host/render_view_host.h" | |
21 #include "content/browser/renderer_host/render_view_host_factory.h" | |
22 #include "content/browser/tab_contents/interstitial_page.h" | |
23 #include "content/browser/tab_contents/tab_contents.h" | |
24 #include "content/browser/tab_contents/tab_contents_delegate.h" | |
25 #include "ui/gfx/canvas_skia_paint.h" | |
26 #include "ui/gfx/point.h" | |
27 #include "ui/gfx/rect.h" | |
28 #include "ui/gfx/size.h" | |
29 #include "views/controls/native/native_view_host.h" | |
30 #include "views/focus/view_storage.h" | |
31 #include "views/screen.h" | |
32 #include "views/widget/root_view.h" | |
33 #include "views/widget/widget_gtk.h" | |
34 | |
35 using WebKit::WebDragOperation; | |
36 using WebKit::WebDragOperationsMask; | |
37 using WebKit::WebInputEvent; | |
38 | |
39 | |
40 namespace { | |
41 | |
42 // Called when the content view gtk widget is tabbed to, or after the call to | |
43 // gtk_widget_child_focus() in TakeFocus(). We return true | |
44 // and grab focus if we don't have it. The call to | |
45 // FocusThroughTabTraversal(bool) forwards the "move focus forward" effect to | |
46 // webkit. | |
47 gboolean OnFocus(GtkWidget* widget, GtkDirectionType focus, | |
48 TabContents* tab_contents) { | |
49 // If we already have focus, let the next widget have a shot at it. We will | |
50 // reach this situation after the call to gtk_widget_child_focus() in | |
51 // TakeFocus(). | |
52 if (gtk_widget_is_focus(widget)) | |
53 return FALSE; | |
54 | |
55 gtk_widget_grab_focus(widget); | |
56 bool reverse = focus == GTK_DIR_TAB_BACKWARD; | |
57 tab_contents->FocusThroughTabTraversal(reverse); | |
58 return TRUE; | |
59 } | |
60 | |
61 // Called when the mouse leaves the widget. We notify our delegate. | |
62 // WidgetGtk also defines OnLeaveNotify, so we use the name OnLeaveNotify2 | |
63 // here. | |
64 gboolean OnLeaveNotify2(GtkWidget* widget, GdkEventCrossing* event, | |
65 TabContents* tab_contents) { | |
66 if (tab_contents->delegate()) | |
67 tab_contents->delegate()->ContentsMouseEvent( | |
68 tab_contents, views::Screen::GetCursorScreenPoint(), false); | |
69 return FALSE; | |
70 } | |
71 | |
72 // Called when the mouse moves within the widget. | |
73 gboolean CallMouseMove(GtkWidget* widget, GdkEventMotion* event, | |
74 TabContentsViewGtk* tab_contents_view) { | |
75 return tab_contents_view->OnMouseMove(widget, event); | |
76 } | |
77 | |
78 // See tab_contents_view_gtk.cc for discussion of mouse scroll zooming. | |
79 gboolean OnMouseScroll(GtkWidget* widget, GdkEventScroll* event, | |
80 TabContents* tab_contents) { | |
81 if ((event->state & gtk_accelerator_get_default_mod_mask()) == | |
82 GDK_CONTROL_MASK) { | |
83 if (tab_contents->delegate()) { | |
84 if (event->direction == GDK_SCROLL_DOWN) { | |
85 tab_contents->delegate()->ContentsZoomChange(false); | |
86 return TRUE; | |
87 } else if (event->direction == GDK_SCROLL_UP) { | |
88 tab_contents->delegate()->ContentsZoomChange(true); | |
89 return TRUE; | |
90 } | |
91 } | |
92 } | |
93 | |
94 return FALSE; | |
95 } | |
96 | |
97 } // namespace | |
98 | |
99 // static | |
100 TabContentsView* TabContentsView::Create(TabContents* tab_contents) { | |
101 return new TabContentsViewGtk(tab_contents); | |
102 } | |
103 | |
104 TabContentsViewGtk::TabContentsViewGtk(TabContents* tab_contents) | |
105 : TabContentsView(tab_contents), | |
106 sad_tab_(NULL), | |
107 ignore_next_char_event_(false) { | |
108 drag_source_.reset(new TabContentsDragSource(this)); | |
109 last_focused_view_storage_id_ = | |
110 views::ViewStorage::GetInstance()->CreateStorageID(); | |
111 } | |
112 | |
113 TabContentsViewGtk::~TabContentsViewGtk() { | |
114 // Make sure to remove any stored view we may still have in the ViewStorage. | |
115 // | |
116 // It is possible the view went away before us, so we only do this if the | |
117 // view is registered. | |
118 views::ViewStorage* view_storage = views::ViewStorage::GetInstance(); | |
119 if (view_storage->RetrieveView(last_focused_view_storage_id_) != NULL) | |
120 view_storage->RemoveView(last_focused_view_storage_id_); | |
121 | |
122 // Just deleting the object doesn't destroy the GtkWidget. We need to do that | |
123 // manually, and synchronously, since subsequent signal handlers may expect | |
124 // to locate this object. | |
125 CloseNow(); | |
126 } | |
127 | |
128 void TabContentsViewGtk::AttachConstrainedWindow( | |
129 ConstrainedWindowGtk* constrained_window) { | |
130 DCHECK(find(constrained_windows_.begin(), constrained_windows_.end(), | |
131 constrained_window) == constrained_windows_.end()); | |
132 | |
133 constrained_windows_.push_back(constrained_window); | |
134 AddChild(constrained_window->widget()); | |
135 | |
136 gfx::Rect bounds; | |
137 GetContainerBounds(&bounds); | |
138 SetFloatingPosition(bounds.size()); | |
139 } | |
140 | |
141 void TabContentsViewGtk::RemoveConstrainedWindow( | |
142 ConstrainedWindowGtk* constrained_window) { | |
143 std::vector<ConstrainedWindowGtk*>::iterator item = | |
144 find(constrained_windows_.begin(), constrained_windows_.end(), | |
145 constrained_window); | |
146 DCHECK(item != constrained_windows_.end()); | |
147 RemoveChild((*item)->widget()); | |
148 constrained_windows_.erase(item); | |
149 } | |
150 | |
151 void TabContentsViewGtk::CreateView(const gfx::Size& initial_size) { | |
152 views::Widget::InitParams params(views::Widget::InitParams::TYPE_CONTROL); | |
153 params.delete_on_destroy = false; | |
154 params.bounds = gfx::Rect(initial_size); | |
155 GetWidget()->Init(params); | |
156 // We need to own the widget in order to attach/detach the native view | |
157 // to container. | |
158 gtk_object_ref(GTK_OBJECT(GetNativeView())); | |
159 } | |
160 | |
161 RenderWidgetHostView* TabContentsViewGtk::CreateViewForWidget( | |
162 RenderWidgetHost* render_widget_host) { | |
163 if (render_widget_host->view()) { | |
164 // During testing, the view will already be set up in most cases to the | |
165 // test view, so we don't want to clobber it with a real one. To verify that | |
166 // this actually is happening (and somebody isn't accidentally creating the | |
167 // view twice), we check for the RVH Factory, which will be set when we're | |
168 // making special ones (which go along with the special views). | |
169 DCHECK(RenderViewHostFactory::has_factory()); | |
170 return render_widget_host->view(); | |
171 } | |
172 | |
173 // If we were showing sad tab, remove it now. | |
174 if (sad_tab_ != NULL) { | |
175 SetContentsView(new views::View()); | |
176 sad_tab_ = NULL; | |
177 } | |
178 | |
179 RenderWidgetHostViewGtk* view = | |
180 new RenderWidgetHostViewGtk(render_widget_host); | |
181 view->InitAsChild(); | |
182 g_signal_connect(view->native_view(), "focus", | |
183 G_CALLBACK(OnFocus), tab_contents()); | |
184 g_signal_connect(view->native_view(), "leave-notify-event", | |
185 G_CALLBACK(OnLeaveNotify2), tab_contents()); | |
186 g_signal_connect(view->native_view(), "motion-notify-event", | |
187 G_CALLBACK(CallMouseMove), this); | |
188 g_signal_connect(view->native_view(), "scroll-event", | |
189 G_CALLBACK(OnMouseScroll), tab_contents()); | |
190 gtk_widget_add_events(view->native_view(), GDK_LEAVE_NOTIFY_MASK | | |
191 GDK_POINTER_MOTION_MASK); | |
192 | |
193 // Let widget know that the tab contents has been painted. | |
194 views::WidgetGtk::RegisterChildExposeHandler(view->native_view()); | |
195 | |
196 // Renderer target DnD. | |
197 if (tab_contents()->ShouldAcceptDragAndDrop()) | |
198 drag_dest_.reset(new WebDragDestGtk(tab_contents(), view->native_view())); | |
199 | |
200 gtk_fixed_put(GTK_FIXED(GetNativeView()), view->native_view(), 0, 0); | |
201 return view; | |
202 } | |
203 | |
204 gfx::NativeView TabContentsViewGtk::GetNativeView() const { | |
205 return WidgetGtk::GetNativeView(); | |
206 } | |
207 | |
208 gfx::NativeView TabContentsViewGtk::GetContentNativeView() const { | |
209 RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView(); | |
210 if (!rwhv) | |
211 return NULL; | |
212 return rwhv->GetNativeView(); | |
213 } | |
214 | |
215 gfx::NativeWindow TabContentsViewGtk::GetTopLevelNativeWindow() const { | |
216 GtkWidget* window = gtk_widget_get_ancestor(GetNativeView(), GTK_TYPE_WINDOW); | |
217 return window ? GTK_WINDOW(window) : NULL; | |
218 } | |
219 | |
220 void TabContentsViewGtk::GetContainerBounds(gfx::Rect* out) const { | |
221 // Callers expect the requested bounds not the actual bounds. For example, | |
222 // during init callers expect 0x0, but Gtk layout enforces a min size of 1x1. | |
223 *out = GetClientAreaScreenBounds(); | |
224 | |
225 gfx::Size size; | |
226 WidgetGtk::GetRequestedSize(&size); | |
227 out->set_size(size); | |
228 } | |
229 | |
230 void TabContentsViewGtk::StartDragging(const WebDropData& drop_data, | |
231 WebDragOperationsMask ops, | |
232 const SkBitmap& image, | |
233 const gfx::Point& image_offset) { | |
234 drag_source_->StartDragging(drop_data, ops, &last_mouse_down_, | |
235 image, image_offset); | |
236 } | |
237 | |
238 void TabContentsViewGtk::SetPageTitle(const std::wstring& title) { | |
239 // Set the window name to include the page title so it's easier to spot | |
240 // when debugging (e.g. via xwininfo -tree). | |
241 gfx::NativeView content_view = GetContentNativeView(); | |
242 if (content_view && content_view->window) | |
243 gdk_window_set_title(content_view->window, WideToUTF8(title).c_str()); | |
244 } | |
245 | |
246 void TabContentsViewGtk::OnTabCrashed(base::TerminationStatus status, | |
247 int /* error_code */) { | |
248 SadTabView::Kind kind = | |
249 status == base::TERMINATION_STATUS_PROCESS_WAS_KILLED ? | |
250 SadTabView::KILLED : SadTabView::CRASHED; | |
251 sad_tab_ = new SadTabView(tab_contents(), kind); | |
252 SetContentsView(sad_tab_); | |
253 } | |
254 | |
255 void TabContentsViewGtk::SizeContents(const gfx::Size& size) { | |
256 // TODO(brettw) this is a hack and should be removed. See tab_contents_view.h. | |
257 | |
258 // We're contained in a fixed. To have the fixed relay us out to |size|, set | |
259 // the size request, which triggers OnSizeAllocate. | |
260 gtk_widget_set_size_request(GetNativeView(), size.width(), size.height()); | |
261 | |
262 // We need to send this immediately. | |
263 RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView(); | |
264 if (rwhv) | |
265 rwhv->SetSize(size); | |
266 } | |
267 | |
268 void TabContentsViewGtk::Focus() { | |
269 if (tab_contents()->interstitial_page()) { | |
270 tab_contents()->interstitial_page()->Focus(); | |
271 return; | |
272 } | |
273 | |
274 if (tab_contents()->is_crashed() && sad_tab_ != NULL) { | |
275 sad_tab_->RequestFocus(); | |
276 return; | |
277 } | |
278 | |
279 if (constrained_windows_.size()) { | |
280 constrained_windows_.back()->FocusConstrainedWindow(); | |
281 return; | |
282 } | |
283 | |
284 RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView(); | |
285 gtk_widget_grab_focus(rwhv ? rwhv->GetNativeView() : GetNativeView()); | |
286 } | |
287 | |
288 void TabContentsViewGtk::SetInitialFocus() { | |
289 if (tab_contents()->FocusLocationBarByDefault()) | |
290 tab_contents()->SetFocusToLocationBar(false); | |
291 else | |
292 Focus(); | |
293 } | |
294 | |
295 void TabContentsViewGtk::StoreFocus() { | |
296 views::ViewStorage* view_storage = views::ViewStorage::GetInstance(); | |
297 | |
298 if (view_storage->RetrieveView(last_focused_view_storage_id_) != NULL) | |
299 view_storage->RemoveView(last_focused_view_storage_id_); | |
300 | |
301 views::FocusManager* focus_manager = | |
302 views::FocusManager::GetFocusManagerForNativeView(GetNativeView()); | |
303 if (focus_manager) { | |
304 // |focus_manager| can be NULL if the tab has been detached but still | |
305 // exists. | |
306 views::View* focused_view = focus_manager->GetFocusedView(); | |
307 if (focused_view) | |
308 view_storage->StoreView(last_focused_view_storage_id_, focused_view); | |
309 } | |
310 } | |
311 | |
312 void TabContentsViewGtk::RestoreFocus() { | |
313 views::ViewStorage* view_storage = views::ViewStorage::GetInstance(); | |
314 views::View* last_focused_view = | |
315 view_storage->RetrieveView(last_focused_view_storage_id_); | |
316 if (!last_focused_view) { | |
317 SetInitialFocus(); | |
318 } else { | |
319 views::FocusManager* focus_manager = | |
320 views::FocusManager::GetFocusManagerForNativeView(GetNativeView()); | |
321 | |
322 // If you hit this DCHECK, please report it to Jay (jcampan). | |
323 DCHECK(focus_manager != NULL) << "No focus manager when restoring focus."; | |
324 | |
325 if (last_focused_view->IsFocusableInRootView() && focus_manager && | |
326 focus_manager->ContainsView(last_focused_view)) { | |
327 last_focused_view->RequestFocus(); | |
328 } else { | |
329 // The focused view may not belong to the same window hierarchy (e.g. | |
330 // if the location bar was focused and the tab is dragged out), or it may | |
331 // no longer be focusable (e.g. if the location bar was focused and then | |
332 // we switched to fullscreen mode). In that case we default to the | |
333 // default focus. | |
334 SetInitialFocus(); | |
335 } | |
336 view_storage->RemoveView(last_focused_view_storage_id_); | |
337 } | |
338 } | |
339 | |
340 void TabContentsViewGtk::GetViewBounds(gfx::Rect* out) const { | |
341 *out = GetWindowScreenBounds(); | |
342 } | |
343 | |
344 void TabContentsViewGtk::UpdateDragCursor(WebDragOperation operation) { | |
345 if (drag_dest_.get()) | |
346 drag_dest_->UpdateDragStatus(operation); | |
347 } | |
348 | |
349 void TabContentsViewGtk::GotFocus() { | |
350 if (tab_contents()->delegate()) | |
351 tab_contents()->delegate()->TabContentsFocused(tab_contents()); | |
352 } | |
353 | |
354 void TabContentsViewGtk::TakeFocus(bool reverse) { | |
355 if (tab_contents()->delegate() && | |
356 !tab_contents()->delegate()->TakeFocus(reverse)) { | |
357 | |
358 views::FocusManager* focus_manager = | |
359 views::FocusManager::GetFocusManagerForNativeView(GetNativeView()); | |
360 | |
361 // We may not have a focus manager if the tab has been switched before this | |
362 // message arrived. | |
363 if (focus_manager) | |
364 focus_manager->AdvanceFocus(reverse); | |
365 } | |
366 } | |
367 | |
368 void TabContentsViewGtk::ShowContextMenu(const ContextMenuParams& params) { | |
369 // Allow delegates to handle the context menu operation first. | |
370 if (tab_contents()->delegate()->HandleContextMenu(params)) | |
371 return; | |
372 | |
373 context_menu_.reset(new RenderViewContextMenuViews(tab_contents(), params)); | |
374 context_menu_->Init(); | |
375 | |
376 gfx::Point screen_point(params.x, params.y); | |
377 views::View::ConvertPointToScreen(GetRootView(), &screen_point); | |
378 | |
379 // Enable recursive tasks on the message loop so we can get updates while | |
380 // the context menu is being displayed. | |
381 bool old_state = MessageLoop::current()->NestableTasksAllowed(); | |
382 MessageLoop::current()->SetNestableTasksAllowed(true); | |
383 context_menu_->RunMenuAt(screen_point.x(), screen_point.y()); | |
384 MessageLoop::current()->SetNestableTasksAllowed(old_state); | |
385 } | |
386 | |
387 void TabContentsViewGtk::ShowPopupMenu(const gfx::Rect& bounds, | |
388 int item_height, | |
389 double item_font_size, | |
390 int selected_item, | |
391 const std::vector<WebMenuItem>& items, | |
392 bool right_aligned) { | |
393 // External popup menus are only used on Mac. | |
394 NOTREACHED(); | |
395 } | |
396 | |
397 gboolean TabContentsViewGtk::OnButtonPress(GtkWidget* widget, | |
398 GdkEventButton* event) { | |
399 last_mouse_down_ = *event; | |
400 return views::WidgetGtk::OnButtonPress(widget, event); | |
401 } | |
402 | |
403 void TabContentsViewGtk::OnSizeAllocate(GtkWidget* widget, | |
404 GtkAllocation* allocation) { | |
405 gfx::Size new_size(allocation->width, allocation->height); | |
406 | |
407 // Always call WasSized() to allow checking to make sure the | |
408 // RenderWidgetHostView is the right size. | |
409 WasSized(new_size); | |
410 } | |
411 | |
412 gboolean TabContentsViewGtk::OnPaint(GtkWidget* widget, GdkEventExpose* event) { | |
413 if (tab_contents()->render_view_host() && | |
414 !tab_contents()->render_view_host()->IsRenderViewLive() && | |
415 sad_tab_) { | |
416 gfx::CanvasSkiaPaint canvas(event); | |
417 sad_tab_->Paint(&canvas); | |
418 } | |
419 return false; // False indicates other widgets should get the event as well. | |
420 } | |
421 | |
422 void TabContentsViewGtk::OnShow(GtkWidget* widget) { | |
423 WasShown(); | |
424 } | |
425 | |
426 void TabContentsViewGtk::OnHide(GtkWidget* widget) { | |
427 WasHidden(); | |
428 } | |
429 | |
430 void TabContentsViewGtk::WasHidden() { | |
431 tab_contents()->HideContents(); | |
432 } | |
433 | |
434 void TabContentsViewGtk::WasShown() { | |
435 tab_contents()->ShowContents(); | |
436 } | |
437 | |
438 void TabContentsViewGtk::WasSized(const gfx::Size& size) { | |
439 // We have to check that the RenderWidgetHostView is the proper size. | |
440 // It can be wrong in cases where the renderer has died and the host | |
441 // view needed to be recreated. | |
442 bool needs_resize = size != size_; | |
443 | |
444 if (needs_resize) { | |
445 size_ = size; | |
446 if (tab_contents()->interstitial_page()) | |
447 tab_contents()->interstitial_page()->SetSize(size); | |
448 } | |
449 | |
450 RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView(); | |
451 if (rwhv && rwhv->GetViewBounds().size() != size) | |
452 rwhv->SetSize(size); | |
453 if (sad_tab_ && sad_tab_->size() != size) | |
454 sad_tab_->SetSize(size); | |
455 | |
456 if (needs_resize) | |
457 SetFloatingPosition(size); | |
458 } | |
459 | |
460 void TabContentsViewGtk::SetFloatingPosition(const gfx::Size& size) { | |
461 // Place each ConstrainedWindow in the center of the view. | |
462 int half_view_width = size.width() / 2; | |
463 | |
464 typedef std::vector<ConstrainedWindowGtk*>::iterator iterator; | |
465 | |
466 for (iterator f = constrained_windows_.begin(), | |
467 l = constrained_windows_.end(); f != l; ++f) { | |
468 GtkWidget* widget = (*f)->widget(); | |
469 | |
470 GtkRequisition requisition; | |
471 gtk_widget_size_request(widget, &requisition); | |
472 | |
473 int child_x = std::max(half_view_width - (requisition.width / 2), 0); | |
474 PositionChild(widget, child_x, 0, 0, 0); | |
475 } | |
476 } | |
477 | |
478 // Called when the mouse moves within the widget. We notify SadTabView if it's | |
479 // not NULL, else our delegate. | |
480 gboolean TabContentsViewGtk::OnMouseMove(GtkWidget* widget, | |
481 GdkEventMotion* event) { | |
482 if (sad_tab_ != NULL) | |
483 WidgetGtk::OnMotionNotify(widget, event); | |
484 else if (tab_contents()->delegate()) | |
485 tab_contents()->delegate()->ContentsMouseEvent( | |
486 tab_contents(), views::Screen::GetCursorScreenPoint(), true); | |
487 return FALSE; | |
488 } | |
OLD | NEW |