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
40using WebKit::WebDragOperation;
41using WebKit::WebDragOperationsMask;
42
43namespace {
44
45// Called when the mouse leaves the widget. We notify our delegate.
46gboolean 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.
55gboolean 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.
64gboolean 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
83TabContentsView* TabContentsView::Create(TabContents* tab_contents) {
84  return new TabContentsViewGtk(tab_contents);
85}
86
87TabContentsViewGtk::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
108TabContentsViewGtk::~TabContentsViewGtk() {
109  floating_.Destroy();
110}
111
112void 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
121void 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
130void TabContentsViewGtk::CreateView(const gfx::Size& initial_size) {
131  requested_size_ = initial_size;
132}
133
134RenderWidgetHostView* 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
167gfx::NativeView TabContentsViewGtk::GetNativeView() const {
168  return floating_.get();
169}
170
171gfx::NativeView TabContentsViewGtk::GetContentNativeView() const {
172  RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView();
173  if (!rwhv)
174    return NULL;
175  return rwhv->GetNativeView();
176}
177
178gfx::NativeWindow TabContentsViewGtk::GetTopLevelNativeWindow() const {
179  GtkWidget* window = gtk_widget_get_ancestor(GetNativeView(), GTK_TYPE_WINDOW);
180  return window ? GTK_WINDOW(window) : NULL;
181}
182
183void 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
196void 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
204void 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
216void 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
226void 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
236void TabContentsViewGtk::SetInitialFocus() {
237  if (tab_contents()->FocusLocationBarByDefault())
238    tab_contents()->SetFocusToLocationBar(false);
239  else
240    Focus();
241}
242
243void TabContentsViewGtk::StoreFocus() {
244  focus_store_.Store(GetNativeView());
245}
246
247void TabContentsViewGtk::RestoreFocus() {
248  if (focus_store_.widget())
249    gtk_widget_grab_focus(focus_store_.widget());
250  else
251    SetInitialFocus();
252}
253
254void 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
264void TabContentsViewGtk::SetFocusedWidget(GtkWidget* widget) {
265  focus_store_.SetWidget(widget);
266}
267
268void TabContentsViewGtk::UpdateDragCursor(WebDragOperation operation) {
269  drag_dest_->UpdateDragStatus(operation);
270}
271
272void 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).
279void 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
286void 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
303void 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
338void 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
351void 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
368void 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.
377gboolean 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
404void 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
413void 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
432void 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