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