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/tab_contents/tab_contents_view_gtk.h"
6
7 #include <gdk/gdk.h>
8 #include <gdk/gdkkeysyms.h>
9 #include <gtk/gtk.h>
10
11 #include <algorithm>
12
13 #include "base/string_util.h"
14 #include "base/utf_string_conversions.h"
15 #include "build/build_config.h"
16 #include "chrome/browser/download/download_shelf.h"
17 #include "chrome/browser/renderer_host/render_widget_host_view_gtk.h"
18 #include "chrome/browser/tab_contents/render_view_context_menu_gtk.h"
19 #include "chrome/browser/tab_contents/web_drag_dest_gtk.h"
20 #include "chrome/browser/ui/gtk/browser_window_gtk.h"
21 #include "chrome/browser/ui/gtk/constrained_window_gtk.h"
22 #include "chrome/browser/ui/gtk/gtk_expanded_container.h"
23 #include "chrome/browser/ui/gtk/gtk_floating_container.h"
24 #include "chrome/browser/ui/gtk/gtk_util.h"
25 #include "chrome/browser/ui/gtk/sad_tab_gtk.h"
26 #include "chrome/browser/ui/gtk/tab_contents_drag_source.h"
27 #include "content/browser/renderer_host/render_process_host.h"
28 #include "content/browser/renderer_host/render_view_host.h"
29 #include "content/browser/renderer_host/render_view_host_factory.h"
30 #include "content/browser/tab_contents/interstitial_page.h"
31 #include "content/browser/tab_contents/tab_contents.h"
32 #include "content/browser/tab_contents/tab_contents_delegate.h"
33 #include "content/common/notification_source.h"
34 #include "content/common/notification_type.h"
35 #include "ui/gfx/point.h"
36 #include "ui/gfx/rect.h"
37 #include "ui/gfx/size.h"
38 #include "webkit/glue/webdropdata.h"
39
40 using WebKit::WebDragOperation;
41 using WebKit::WebDragOperationsMask;
42
43 namespace {
44
45 // Called when the mouse leaves the widget. We notify our delegate.
OnLeaveNotify(GtkWidget * widget,GdkEventCrossing * event,TabContents * tab_contents)46 gboolean OnLeaveNotify(GtkWidget* widget, GdkEventCrossing* event,
47 TabContents* tab_contents) {
48 if (tab_contents->delegate())
49 tab_contents->delegate()->ContentsMouseEvent(
50 tab_contents, gfx::Point(event->x_root, event->y_root), false);
51 return FALSE;
52 }
53
54 // Called when the mouse moves within the widget. We notify our delegate.
OnMouseMove(GtkWidget * widget,GdkEventMotion * event,TabContents * tab_contents)55 gboolean OnMouseMove(GtkWidget* widget, GdkEventMotion* event,
56 TabContents* tab_contents) {
57 if (tab_contents->delegate())
58 tab_contents->delegate()->ContentsMouseEvent(
59 tab_contents, gfx::Point(event->x_root, event->y_root), true);
60 return FALSE;
61 }
62
63 // See tab_contents_view_views.cc for discussion of mouse scroll zooming.
OnMouseScroll(GtkWidget * widget,GdkEventScroll * event,TabContents * tab_contents)64 gboolean OnMouseScroll(GtkWidget* widget, GdkEventScroll* event,
65 TabContents* tab_contents) {
66 if ((event->state & gtk_accelerator_get_default_mod_mask()) ==
67 GDK_CONTROL_MASK) {
68 if (event->direction == GDK_SCROLL_DOWN) {
69 tab_contents->delegate()->ContentsZoomChange(false);
70 return TRUE;
71 } else if (event->direction == GDK_SCROLL_UP) {
72 tab_contents->delegate()->ContentsZoomChange(true);
73 return TRUE;
74 }
75 }
76
77 return FALSE;
78 }
79
80 } // namespace
81
82 // static
Create(TabContents * tab_contents)83 TabContentsView* TabContentsView::Create(TabContents* tab_contents) {
84 return new TabContentsViewGtk(tab_contents);
85 }
86
TabContentsViewGtk(TabContents * tab_contents)87 TabContentsViewGtk::TabContentsViewGtk(TabContents* tab_contents)
88 : TabContentsView(tab_contents),
89 floating_(gtk_floating_container_new()),
90 expanded_(gtk_expanded_container_new()),
91 constrained_window_(NULL) {
92 gtk_widget_set_name(expanded_, "chrome-tab-contents-view");
93 g_signal_connect(expanded_, "size-allocate",
94 G_CALLBACK(OnSizeAllocateThunk), this);
95 g_signal_connect(expanded_, "child-size-request",
96 G_CALLBACK(OnChildSizeRequestThunk), this);
97 g_signal_connect(floating_.get(), "set-floating-position",
98 G_CALLBACK(OnSetFloatingPositionThunk), this);
99
100 gtk_container_add(GTK_CONTAINER(floating_.get()), expanded_);
101 gtk_widget_show(expanded_);
102 gtk_widget_show(floating_.get());
103 registrar_.Add(this, NotificationType::TAB_CONTENTS_CONNECTED,
104 Source<TabContents>(tab_contents));
105 drag_source_.reset(new TabContentsDragSource(this));
106 }
107
~TabContentsViewGtk()108 TabContentsViewGtk::~TabContentsViewGtk() {
109 floating_.Destroy();
110 }
111
AttachConstrainedWindow(ConstrainedWindowGtk * constrained_window)112 void TabContentsViewGtk::AttachConstrainedWindow(
113 ConstrainedWindowGtk* constrained_window) {
114 DCHECK(constrained_window_ == NULL);
115
116 constrained_window_ = constrained_window;
117 gtk_floating_container_add_floating(GTK_FLOATING_CONTAINER(floating_.get()),
118 constrained_window->widget());
119 }
120
RemoveConstrainedWindow(ConstrainedWindowGtk * constrained_window)121 void TabContentsViewGtk::RemoveConstrainedWindow(
122 ConstrainedWindowGtk* constrained_window) {
123 DCHECK(constrained_window == constrained_window_);
124
125 constrained_window_ = NULL;
126 gtk_container_remove(GTK_CONTAINER(floating_.get()),
127 constrained_window->widget());
128 }
129
CreateView(const gfx::Size & initial_size)130 void TabContentsViewGtk::CreateView(const gfx::Size& initial_size) {
131 requested_size_ = initial_size;
132 }
133
CreateViewForWidget(RenderWidgetHost * render_widget_host)134 RenderWidgetHostView* TabContentsViewGtk::CreateViewForWidget(
135 RenderWidgetHost* render_widget_host) {
136 if (render_widget_host->view()) {
137 // During testing, the view will already be set up in most cases to the
138 // test view, so we don't want to clobber it with a real one. To verify that
139 // this actually is happening (and somebody isn't accidentally creating the
140 // view twice), we check for the RVH Factory, which will be set when we're
141 // making special ones (which go along with the special views).
142 DCHECK(RenderViewHostFactory::has_factory());
143 return render_widget_host->view();
144 }
145
146 RenderWidgetHostViewGtk* view =
147 new RenderWidgetHostViewGtk(render_widget_host);
148 view->InitAsChild();
149 gfx::NativeView content_view = view->native_view();
150 g_signal_connect(content_view, "focus", G_CALLBACK(OnFocusThunk), this);
151 g_signal_connect(content_view, "leave-notify-event",
152 G_CALLBACK(OnLeaveNotify), tab_contents());
153 g_signal_connect(content_view, "motion-notify-event",
154 G_CALLBACK(OnMouseMove), tab_contents());
155 g_signal_connect(content_view, "scroll-event",
156 G_CALLBACK(OnMouseScroll), tab_contents());
157 gtk_widget_add_events(content_view, GDK_LEAVE_NOTIFY_MASK |
158 GDK_POINTER_MOTION_MASK);
159 InsertIntoContentArea(content_view);
160
161 // Renderer target DnD.
162 drag_dest_.reset(new WebDragDestGtk(tab_contents(), content_view));
163
164 return view;
165 }
166
GetNativeView() const167 gfx::NativeView TabContentsViewGtk::GetNativeView() const {
168 return floating_.get();
169 }
170
GetContentNativeView() const171 gfx::NativeView TabContentsViewGtk::GetContentNativeView() const {
172 RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView();
173 if (!rwhv)
174 return NULL;
175 return rwhv->GetNativeView();
176 }
177
GetTopLevelNativeWindow() const178 gfx::NativeWindow TabContentsViewGtk::GetTopLevelNativeWindow() const {
179 GtkWidget* window = gtk_widget_get_ancestor(GetNativeView(), GTK_TYPE_WINDOW);
180 return window ? GTK_WINDOW(window) : NULL;
181 }
182
GetContainerBounds(gfx::Rect * out) const183 void TabContentsViewGtk::GetContainerBounds(gfx::Rect* out) const {
184 // This is used for positioning the download shelf arrow animation,
185 // as well as sizing some other widgets in Windows. In GTK the size is
186 // managed for us, so it appears to be only used for the download shelf
187 // animation.
188 int x = 0;
189 int y = 0;
190 if (expanded_->window)
191 gdk_window_get_origin(expanded_->window, &x, &y);
192 out->SetRect(x + expanded_->allocation.x, y + expanded_->allocation.y,
193 requested_size_.width(), requested_size_.height());
194 }
195
SetPageTitle(const std::wstring & title)196 void TabContentsViewGtk::SetPageTitle(const std::wstring& title) {
197 // Set the window name to include the page title so it's easier to spot
198 // when debugging (e.g. via xwininfo -tree).
199 gfx::NativeView content_view = GetContentNativeView();
200 if (content_view && content_view->window)
201 gdk_window_set_title(content_view->window, WideToUTF8(title).c_str());
202 }
203
OnTabCrashed(base::TerminationStatus status,int error_code)204 void TabContentsViewGtk::OnTabCrashed(base::TerminationStatus status,
205 int error_code) {
206 if (tab_contents() != NULL && !sad_tab_.get()) {
207 sad_tab_.reset(new SadTabGtk(
208 tab_contents(),
209 status == base::TERMINATION_STATUS_PROCESS_WAS_KILLED ?
210 SadTabGtk::KILLED : SadTabGtk::CRASHED));
211 InsertIntoContentArea(sad_tab_->widget());
212 gtk_widget_show(sad_tab_->widget());
213 }
214 }
215
SizeContents(const gfx::Size & size)216 void TabContentsViewGtk::SizeContents(const gfx::Size& size) {
217 // We don't need to manually set the size of of widgets in GTK+, but we do
218 // need to pass the sizing information on to the RWHV which will pass the
219 // sizing information on to the renderer.
220 requested_size_ = size;
221 RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView();
222 if (rwhv)
223 rwhv->SetSize(size);
224 }
225
Focus()226 void TabContentsViewGtk::Focus() {
227 if (tab_contents()->showing_interstitial_page()) {
228 tab_contents()->interstitial_page()->Focus();
229 } else if (!constrained_window_) {
230 GtkWidget* widget = GetContentNativeView();
231 if (widget)
232 gtk_widget_grab_focus(widget);
233 }
234 }
235
SetInitialFocus()236 void TabContentsViewGtk::SetInitialFocus() {
237 if (tab_contents()->FocusLocationBarByDefault())
238 tab_contents()->SetFocusToLocationBar(false);
239 else
240 Focus();
241 }
242
StoreFocus()243 void TabContentsViewGtk::StoreFocus() {
244 focus_store_.Store(GetNativeView());
245 }
246
RestoreFocus()247 void TabContentsViewGtk::RestoreFocus() {
248 if (focus_store_.widget())
249 gtk_widget_grab_focus(focus_store_.widget());
250 else
251 SetInitialFocus();
252 }
253
GetViewBounds(gfx::Rect * out) const254 void TabContentsViewGtk::GetViewBounds(gfx::Rect* out) const {
255 if (!floating_->window) {
256 out->SetRect(0, 0, requested_size_.width(), requested_size_.height());
257 return;
258 }
259 int x = 0, y = 0, w, h;
260 gdk_window_get_geometry(floating_->window, &x, &y, &w, &h, NULL);
261 out->SetRect(x, y, w, h);
262 }
263
SetFocusedWidget(GtkWidget * widget)264 void TabContentsViewGtk::SetFocusedWidget(GtkWidget* widget) {
265 focus_store_.SetWidget(widget);
266 }
267
UpdateDragCursor(WebDragOperation operation)268 void TabContentsViewGtk::UpdateDragCursor(WebDragOperation operation) {
269 drag_dest_->UpdateDragStatus(operation);
270 }
271
GotFocus()272 void TabContentsViewGtk::GotFocus() {
273 // This is only used in the views FocusManager stuff but it bleeds through
274 // all subclasses. http://crbug.com/21875
275 }
276
277 // This is called when we the renderer asks us to take focus back (i.e., it has
278 // iterated past the last focusable element on the page).
TakeFocus(bool reverse)279 void TabContentsViewGtk::TakeFocus(bool reverse) {
280 if (!tab_contents()->delegate()->TakeFocus(reverse)) {
281 gtk_widget_child_focus(GTK_WIDGET(GetTopLevelNativeWindow()),
282 reverse ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD);
283 }
284 }
285
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)286 void TabContentsViewGtk::Observe(NotificationType type,
287 const NotificationSource& source,
288 const NotificationDetails& details) {
289 switch (type.value) {
290 case NotificationType::TAB_CONTENTS_CONNECTED: {
291 // No need to remove the SadTabGtk's widget from the container since
292 // the new RenderWidgetHostViewGtk instance already removed all the
293 // vbox's children.
294 sad_tab_.reset();
295 break;
296 }
297 default:
298 NOTREACHED() << "Got a notification we didn't register for.";
299 break;
300 }
301 }
302
ShowContextMenu(const ContextMenuParams & params)303 void TabContentsViewGtk::ShowContextMenu(const ContextMenuParams& params) {
304 // Find out the RenderWidgetHostView that corresponds to the render widget on
305 // which this context menu is showed, so that we can retrieve the last mouse
306 // down event on the render widget and use it as the timestamp of the
307 // activation event to show the context menu.
308 RenderWidgetHostView* view = NULL;
309 if (params.custom_context.render_widget_id !=
310 webkit_glue::CustomContextMenuContext::kCurrentRenderWidget) {
311 IPC::Channel::Listener* listener =
312 tab_contents()->render_view_host()->process()->GetListenerByID(
313 params.custom_context.render_widget_id);
314 if (!listener) {
315 NOTREACHED();
316 return;
317 }
318 view = static_cast<RenderWidgetHost*>(listener)->view();
319 } else {
320 view = tab_contents()->GetRenderWidgetHostView();
321 }
322 RenderWidgetHostViewGtk* view_gtk =
323 static_cast<RenderWidgetHostViewGtk*>(view);
324 if (!view_gtk || !view_gtk->last_mouse_down())
325 return;
326
327 context_menu_.reset(new RenderViewContextMenuGtk(
328 tab_contents(), params, view_gtk->last_mouse_down()->time));
329 context_menu_->Init();
330
331 gfx::Rect bounds;
332 GetContainerBounds(&bounds);
333 gfx::Point point = bounds.origin();
334 point.Offset(params.x, params.y);
335 context_menu_->Popup(point);
336 }
337
ShowPopupMenu(const gfx::Rect & bounds,int item_height,double item_font_size,int selected_item,const std::vector<WebMenuItem> & items,bool right_aligned)338 void TabContentsViewGtk::ShowPopupMenu(const gfx::Rect& bounds,
339 int item_height,
340 double item_font_size,
341 int selected_item,
342 const std::vector<WebMenuItem>& items,
343 bool right_aligned) {
344 // We are not using external popup menus on Linux, they are rendered by
345 // WebKit.
346 NOTREACHED();
347 }
348
349 // Render view DnD -------------------------------------------------------------
350
StartDragging(const WebDropData & drop_data,WebDragOperationsMask ops,const SkBitmap & image,const gfx::Point & image_offset)351 void TabContentsViewGtk::StartDragging(const WebDropData& drop_data,
352 WebDragOperationsMask ops,
353 const SkBitmap& image,
354 const gfx::Point& image_offset) {
355 DCHECK(GetContentNativeView());
356
357 RenderWidgetHostViewGtk* view_gtk = static_cast<RenderWidgetHostViewGtk*>(
358 tab_contents()->GetRenderWidgetHostView());
359 if (!view_gtk || !view_gtk->last_mouse_down())
360 return;
361
362 drag_source_->StartDragging(drop_data, ops, view_gtk->last_mouse_down(),
363 image, image_offset);
364 }
365
366 // -----------------------------------------------------------------------------
367
InsertIntoContentArea(GtkWidget * widget)368 void TabContentsViewGtk::InsertIntoContentArea(GtkWidget* widget) {
369 gtk_container_add(GTK_CONTAINER(expanded_), widget);
370 }
371
372 // Called when the content view gtk widget is tabbed to, or after the call to
373 // gtk_widget_child_focus() in TakeFocus(). We return true
374 // and grab focus if we don't have it. The call to
375 // FocusThroughTabTraversal(bool) forwards the "move focus forward" effect to
376 // webkit.
OnFocus(GtkWidget * widget,GtkDirectionType focus)377 gboolean TabContentsViewGtk::OnFocus(GtkWidget* widget,
378 GtkDirectionType focus) {
379 // If we are showing a constrained window, don't allow the native view to take
380 // focus.
381 if (constrained_window_) {
382 // If we return false, it will revert to the default handler, which will
383 // take focus. We don't want that. But if we return true, the event will
384 // stop being propagated, leaving focus wherever it is currently. That is
385 // also bad. So we return false to let the default handler run, but take
386 // focus first so as to trick it into thinking the view was already focused
387 // and allowing the event to propagate.
388 gtk_widget_grab_focus(widget);
389 return FALSE;
390 }
391
392 // If we already have focus, let the next widget have a shot at it. We will
393 // reach this situation after the call to gtk_widget_child_focus() in
394 // TakeFocus().
395 if (gtk_widget_is_focus(widget))
396 return FALSE;
397
398 gtk_widget_grab_focus(widget);
399 bool reverse = focus == GTK_DIR_TAB_BACKWARD;
400 tab_contents()->FocusThroughTabTraversal(reverse);
401 return TRUE;
402 }
403
OnChildSizeRequest(GtkWidget * widget,GtkWidget * child,GtkRequisition * requisition)404 void TabContentsViewGtk::OnChildSizeRequest(GtkWidget* widget,
405 GtkWidget* child,
406 GtkRequisition* requisition) {
407 if (tab_contents()->delegate()) {
408 requisition->height +=
409 tab_contents()->delegate()->GetExtraRenderViewHeight();
410 }
411 }
412
OnSizeAllocate(GtkWidget * widget,GtkAllocation * allocation)413 void TabContentsViewGtk::OnSizeAllocate(GtkWidget* widget,
414 GtkAllocation* allocation) {
415 int width = allocation->width;
416 int height = allocation->height;
417 // |delegate()| can be NULL here during browser teardown.
418 if (tab_contents()->delegate())
419 height += tab_contents()->delegate()->GetExtraRenderViewHeight();
420 gfx::Size size(width, height);
421 requested_size_ = size;
422
423 // We manually tell our RWHV to resize the renderer content. This avoids
424 // spurious resizes from GTK+.
425 RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView();
426 if (rwhv)
427 rwhv->SetSize(size);
428 if (tab_contents()->interstitial_page())
429 tab_contents()->interstitial_page()->SetSize(size);
430 }
431
OnSetFloatingPosition(GtkWidget * floating_container,GtkAllocation * allocation)432 void TabContentsViewGtk::OnSetFloatingPosition(
433 GtkWidget* floating_container, GtkAllocation* allocation) {
434 if (!constrained_window_)
435 return;
436
437 // Place each ConstrainedWindow in the center of the view.
438 GtkWidget* widget = constrained_window_->widget();
439 DCHECK(widget->parent == floating_.get());
440
441 GtkRequisition requisition;
442 gtk_widget_size_request(widget, &requisition);
443
444 GValue value = { 0, };
445 g_value_init(&value, G_TYPE_INT);
446
447 int child_x = std::max((allocation->width - requisition.width) / 2, 0);
448 g_value_set_int(&value, child_x);
449 gtk_container_child_set_property(GTK_CONTAINER(floating_container),
450 widget, "x", &value);
451
452 int child_y = std::max((allocation->height - requisition.height) / 2, 0);
453 g_value_set_int(&value, child_y);
454 gtk_container_child_set_property(GTK_CONTAINER(floating_container),
455 widget, "y", &value);
456 g_value_unset(&value);
457 }
458