1// Copyright (c) 2012 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/gtk/tab_contents/chrome_web_contents_view_delegate_gtk.h"
6
7#include <map>
8
9#include "base/lazy_instance.h"
10#include "chrome/browser/browser_shutdown.h"
11#include "chrome/browser/ui/gtk/constrained_window_gtk.h"
12#include "chrome/browser/ui/gtk/tab_contents/render_view_context_menu_gtk.h"
13#include "chrome/browser/ui/gtk/tab_contents/web_drag_bookmark_handler_gtk.h"
14#include "chrome/browser/ui/tab_contents/chrome_web_contents_view_delegate.h"
15#include "content/public/browser/render_process_host.h"
16#include "content/public/browser/render_view_host.h"
17#include "content/public/browser/render_widget_host_view.h"
18#include "content/public/browser/web_contents.h"
19#include "content/public/browser/web_contents_view.h"
20#include "ui/base/gtk/focus_store_gtk.h"
21#include "ui/base/gtk/gtk_floating_container.h"
22
23namespace {
24
25const char kViewDelegateUserDataKey[] = "ChromeWebContentsViewDelegateGtk";
26
27class ViewDelegateUserData : public base::SupportsUserData::Data {
28 public:
29  explicit ViewDelegateUserData(ChromeWebContentsViewDelegateGtk* view_delegate)
30      : view_delegate_(view_delegate) {}
31  virtual ~ViewDelegateUserData() {}
32  ChromeWebContentsViewDelegateGtk* view_delegate() { return view_delegate_; }
33
34 private:
35  ChromeWebContentsViewDelegateGtk* view_delegate_;  // unowned
36};
37
38}  // namespace
39
40ChromeWebContentsViewDelegateGtk* ChromeWebContentsViewDelegateGtk::GetFor(
41    content::WebContents* web_contents) {
42  ViewDelegateUserData* user_data = static_cast<ViewDelegateUserData*>(
43      web_contents->GetUserData(&kViewDelegateUserDataKey));
44
45  return user_data ? user_data->view_delegate() : NULL;
46}
47
48ChromeWebContentsViewDelegateGtk::ChromeWebContentsViewDelegateGtk(
49    content::WebContents* web_contents)
50    : floating_(gtk_floating_container_new()),
51      web_contents_modal_dialog_(NULL),
52      web_contents_(web_contents),
53      expanded_container_(NULL),
54      focus_store_(NULL) {
55  g_object_ref_sink(floating_.get());
56  gtk_widget_set_name(floating_.get(), "chrome-tab-contents-view");
57  g_signal_connect(floating_.get(), "set-floating-position",
58                   G_CALLBACK(OnSetFloatingPositionThunk), this);
59
60  // Stash this in the WebContents.
61  web_contents->SetUserData(&kViewDelegateUserDataKey,
62                            new ViewDelegateUserData(this));
63}
64
65ChromeWebContentsViewDelegateGtk::~ChromeWebContentsViewDelegateGtk() {
66}
67
68void ChromeWebContentsViewDelegateGtk::AttachWebContentsModalDialog(
69    GtkWidget* web_contents_modal_dialog) {
70  DCHECK(web_contents_modal_dialog_ == NULL);
71
72  web_contents_modal_dialog_ = web_contents_modal_dialog;
73  gtk_floating_container_add_floating(GTK_FLOATING_CONTAINER(floating_.get()),
74                                      web_contents_modal_dialog);
75}
76
77void ChromeWebContentsViewDelegateGtk::RemoveWebContentsModalDialog(
78    GtkWidget* web_contents_modal_dialog) {
79  DCHECK(web_contents_modal_dialog == web_contents_modal_dialog_);
80
81  web_contents_modal_dialog_ = NULL;
82}
83
84void ChromeWebContentsViewDelegateGtk::Initialize(
85    GtkWidget* expanded_container, ui::FocusStoreGtk* focus_store) {
86  expanded_container_ = expanded_container;
87  focus_store_ = focus_store;
88  // We install a chrome specific handler to intercept bookmark drags for the
89  // bookmark manager/extension API.
90  bookmark_handler_gtk_.reset(new WebDragBookmarkHandlerGtk);
91
92  gtk_container_add(GTK_CONTAINER(floating_.get()), expanded_container);
93  gtk_widget_show(floating_.get());
94}
95
96gfx::NativeView ChromeWebContentsViewDelegateGtk::GetNativeView() const {
97  return floating_.get();
98}
99
100void ChromeWebContentsViewDelegateGtk::Focus() {
101  if (!web_contents_modal_dialog_) {
102    GtkWidget* widget = web_contents_->GetView()->GetContentNativeView();
103    if (widget)
104      gtk_widget_grab_focus(widget);
105  }
106}
107
108gboolean ChromeWebContentsViewDelegateGtk::OnNativeViewFocusEvent(
109    GtkWidget* widget,
110    GtkDirectionType type,
111    gboolean* return_value) {
112  // If we are showing a web contents modal dialog, don't allow the native view
113  // to take focus.
114  if (web_contents_modal_dialog_) {
115    // If we return false, it will revert to the default handler, which will
116    // take focus. We don't want that. But if we return true, the event will
117    // stop being propagated, leaving focus wherever it is currently. That is
118    // also bad. So we return false to let the default handler run, but take
119    // focus first so as to trick it into thinking the view was already focused
120    // and allowing the event to propagate.
121    gtk_widget_grab_focus(widget);
122    *return_value = FALSE;
123    return TRUE;
124  }
125
126  // Let the default WebContentsViewGtk::OnFocus() behaviour run.
127  return FALSE;
128}
129
130void ChromeWebContentsViewDelegateGtk::ShowContextMenu(
131    const content::ContextMenuParams& params) {
132  // Find out the RenderWidgetHostView that corresponds to the render widget on
133  // which this context menu is showed, so that we can retrieve the last mouse
134  // down event on the render widget and use it as the timestamp of the
135  // activation event to show the context menu.
136  content::RenderWidgetHostView* view = NULL;
137  if (params.custom_context.render_widget_id !=
138      content::CustomContextMenuContext::kCurrentRenderWidget) {
139    content::RenderWidgetHost* host = content::RenderWidgetHost::FromID(
140        web_contents_->GetRenderProcessHost()->GetID(),
141        params.custom_context.render_widget_id);
142    if (!host) {
143      NOTREACHED();
144      return;
145    }
146    view = host->GetView();
147  } else {
148    view = web_contents_->GetRenderWidgetHostView();
149  }
150
151  context_menu_.reset(
152      new RenderViewContextMenuGtk(web_contents_, params, view));
153  context_menu_->Init();
154
155  // Don't show empty menus.
156  if (context_menu_->menu_model().GetItemCount() == 0)
157    return;
158
159  gfx::Rect bounds;
160  web_contents_->GetView()->GetContainerBounds(&bounds);
161  gfx::Point point = bounds.origin();
162  point.Offset(params.x, params.y);
163  context_menu_->Popup(point);
164}
165
166content::WebDragDestDelegate*
167    ChromeWebContentsViewDelegateGtk::GetDragDestDelegate() {
168  return bookmark_handler_gtk_.get();
169}
170
171void ChromeWebContentsViewDelegateGtk::OnSetFloatingPosition(
172    GtkWidget* floating_container, GtkAllocation* allocation) {
173  if (!web_contents_modal_dialog_)
174    return;
175
176  // Place each web contents modal dialog in the center of the view.
177  GtkWidget* widget = web_contents_modal_dialog_;
178  DCHECK(gtk_widget_get_parent(widget) == floating_.get());
179
180  GtkRequisition requisition;
181  gtk_widget_size_request(widget, &requisition);
182
183  GValue value = { 0, };
184  g_value_init(&value, G_TYPE_INT);
185
186  int child_x = std::max((allocation->width - requisition.width) / 2, 0);
187  g_value_set_int(&value, child_x);
188  gtk_container_child_set_property(GTK_CONTAINER(floating_container),
189                                   widget, "x", &value);
190
191  int child_y = std::max((allocation->height - requisition.height) / 2, 0);
192  g_value_set_int(&value, child_y);
193  gtk_container_child_set_property(GTK_CONTAINER(floating_container),
194                                   widget, "y", &value);
195  g_value_unset(&value);
196}
197
198namespace chrome {
199
200content::WebContentsViewDelegate* CreateWebContentsViewDelegate(
201    content::WebContents* web_contents) {
202  return new ChromeWebContentsViewDelegateGtk(web_contents);
203}
204
205}  // namespace chrome
206