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
35using WebKit::WebDragOperation;
36using WebKit::WebDragOperationsMask;
37using WebKit::WebInputEvent;
38
39
40namespace {
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.
47gboolean 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.
64gboolean 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.
73gboolean 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.
79gboolean 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
100TabContentsView* TabContentsView::Create(TabContents* tab_contents) {
101  return new TabContentsViewGtk(tab_contents);
102}
103
104TabContentsViewGtk::TabContentsViewGtk(TabContents* tab_contents)
105    : TabContentsView(tab_contents),
106      views::WidgetGtk(TYPE_CHILD),
107      sad_tab_(NULL),
108      ignore_next_char_event_(false) {
109  drag_source_.reset(new TabContentsDragSource(this));
110  last_focused_view_storage_id_ =
111      views::ViewStorage::GetInstance()->CreateStorageID();
112}
113
114TabContentsViewGtk::~TabContentsViewGtk() {
115  // Make sure to remove any stored view we may still have in the ViewStorage.
116  //
117  // It is possible the view went away before us, so we only do this if the
118  // view is registered.
119  views::ViewStorage* view_storage = views::ViewStorage::GetInstance();
120  if (view_storage->RetrieveView(last_focused_view_storage_id_) != NULL)
121    view_storage->RemoveView(last_focused_view_storage_id_);
122
123  // Just deleting the object doesn't destroy the GtkWidget. We need to do that
124  // manually, and synchronously, since subsequent signal handlers may expect
125  // to locate this object.
126  CloseNow();
127}
128
129void TabContentsViewGtk::AttachConstrainedWindow(
130    ConstrainedWindowGtk* constrained_window) {
131  DCHECK(find(constrained_windows_.begin(), constrained_windows_.end(),
132              constrained_window) == constrained_windows_.end());
133
134  constrained_windows_.push_back(constrained_window);
135  AddChild(constrained_window->widget());
136
137  gfx::Rect bounds;
138  GetContainerBounds(&bounds);
139  SetFloatingPosition(bounds.size());
140}
141
142void TabContentsViewGtk::RemoveConstrainedWindow(
143    ConstrainedWindowGtk* constrained_window) {
144  std::vector<ConstrainedWindowGtk*>::iterator item =
145      find(constrained_windows_.begin(), constrained_windows_.end(),
146           constrained_window);
147  DCHECK(item != constrained_windows_.end());
148  RemoveChild((*item)->widget());
149  constrained_windows_.erase(item);
150}
151
152void TabContentsViewGtk::CreateView(const gfx::Size& initial_size) {
153  set_delete_on_destroy(false);
154  WidgetGtk::Init(NULL, gfx::Rect(0, 0, initial_size.width(),
155                                  initial_size.height()));
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
161RenderWidgetHostView* 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
204gfx::NativeView TabContentsViewGtk::GetNativeView() const {
205  return WidgetGtk::GetNativeView();
206}
207
208gfx::NativeView TabContentsViewGtk::GetContentNativeView() const {
209  RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView();
210  if (!rwhv)
211    return NULL;
212  return rwhv->GetNativeView();
213}
214
215gfx::NativeWindow TabContentsViewGtk::GetTopLevelNativeWindow() const {
216  GtkWidget* window = gtk_widget_get_ancestor(GetNativeView(), GTK_TYPE_WINDOW);
217  return window ? GTK_WINDOW(window) : NULL;
218}
219
220void 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
230void 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
238void 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
246void 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
255void 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
268void 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
288void TabContentsViewGtk::SetInitialFocus() {
289  if (tab_contents()->FocusLocationBarByDefault())
290    tab_contents()->SetFocusToLocationBar(false);
291  else
292    Focus();
293}
294
295void 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
312void 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
340void TabContentsViewGtk::GetViewBounds(gfx::Rect* out) const {
341  *out = GetWindowScreenBounds();
342}
343
344void TabContentsViewGtk::UpdateDragCursor(WebDragOperation operation) {
345  if (drag_dest_.get())
346    drag_dest_->UpdateDragStatus(operation);
347}
348
349void TabContentsViewGtk::GotFocus() {
350  if (tab_contents()->delegate())
351    tab_contents()->delegate()->TabContentsFocused(tab_contents());
352}
353
354void 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
368void 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
387void 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
397gboolean TabContentsViewGtk::OnButtonPress(GtkWidget* widget,
398                                           GdkEventButton* event) {
399  last_mouse_down_ = *event;
400  return views::WidgetGtk::OnButtonPress(widget, event);
401}
402
403void 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
412gboolean 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
422void TabContentsViewGtk::OnShow(GtkWidget* widget) {
423  WasShown();
424}
425
426void TabContentsViewGtk::OnHide(GtkWidget* widget) {
427  WasHidden();
428}
429
430void TabContentsViewGtk::WasHidden() {
431  tab_contents()->HideContents();
432}
433
434void TabContentsViewGtk::WasShown() {
435  tab_contents()->ShowContents();
436}
437
438void 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
460void 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.
480gboolean 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}
489