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/gtk/tab_contents_container_gtk.h"
6
7#include <algorithm>
8
9#include "base/i18n/rtl.h"
10#include "chrome/browser/renderer_host/render_widget_host_view_gtk.h"
11#include "chrome/browser/ui/gtk/gtk_expanded_container.h"
12#include "chrome/browser/ui/gtk/gtk_floating_container.h"
13#include "chrome/browser/ui/gtk/status_bubble_gtk.h"
14#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
15#include "content/browser/tab_contents/tab_contents.h"
16#include "content/common/notification_source.h"
17#include "ui/gfx/native_widget_types.h"
18
19TabContentsContainerGtk::TabContentsContainerGtk(StatusBubbleGtk* status_bubble)
20    : tab_(NULL),
21      preview_(NULL),
22      status_bubble_(status_bubble) {
23  Init();
24}
25
26TabContentsContainerGtk::~TabContentsContainerGtk() {
27  floating_.Destroy();
28}
29
30void TabContentsContainerGtk::Init() {
31  // A high level overview of the TabContentsContainer:
32  //
33  // +- GtkFloatingContainer |floating_| -------------------------------+
34  // |+- GtkExpandedContainer |expanded_| -----------------------------+|
35  // ||                                                                ||
36  // ||                                                                ||
37  // ||                                                                ||
38  // ||                                                                ||
39  // |+- (StatusBubble) ------+                                        ||
40  // |+                       +                                        ||
41  // |+-----------------------+----------------------------------------+|
42  // +------------------------------------------------------------------+
43
44  floating_.Own(gtk_floating_container_new());
45  gtk_widget_set_name(floating_.get(), "chrome-tab-contents-container");
46  g_signal_connect(floating_.get(), "focus", G_CALLBACK(OnFocusThunk), this);
47
48  expanded_ = gtk_expanded_container_new();
49  gtk_container_add(GTK_CONTAINER(floating_.get()), expanded_);
50
51  if (status_bubble_) {
52    gtk_floating_container_add_floating(GTK_FLOATING_CONTAINER(floating_.get()),
53                                        status_bubble_->widget());
54    g_signal_connect(floating_.get(), "set-floating-position",
55                     G_CALLBACK(OnSetFloatingPosition), this);
56  }
57
58  gtk_widget_show(expanded_);
59  gtk_widget_show(floating_.get());
60
61  ViewIDUtil::SetDelegateForWidget(widget(), this);
62}
63
64void TabContentsContainerGtk::SetTab(TabContentsWrapper* tab) {
65  HideTab(tab_);
66  if (tab_) {
67    registrar_.Remove(this, NotificationType::TAB_CONTENTS_DESTROYED,
68                      Source<TabContents>(tab_->tab_contents()));
69  }
70
71  tab_ = tab;
72
73  if (tab_ == preview_) {
74    // If the preview contents is becoming the new permanent tab contents, we
75    // just reassign some pointers.
76    preview_ = NULL;
77  } else if (tab_) {
78    // Otherwise we actually have to add it to the widget hierarchy.
79    PackTab(tab);
80    registrar_.Add(this, NotificationType::TAB_CONTENTS_DESTROYED,
81                   Source<TabContents>(tab_->tab_contents()));
82  }
83}
84
85TabContents* TabContentsContainerGtk::GetVisibleTabContents() {
86  if (preview_)
87    return preview_->tab_contents();
88  return tab_ ? tab_->tab_contents() : NULL;
89}
90
91void TabContentsContainerGtk::SetPreview(TabContentsWrapper* preview) {
92  if (preview_)
93    RemovePreview();
94  else
95    HideTab(tab_);
96
97  preview_ = preview;
98
99  PackTab(preview);
100  registrar_.Add(this, NotificationType::TAB_CONTENTS_DESTROYED,
101                 Source<TabContents>(preview_->tab_contents()));
102}
103
104void TabContentsContainerGtk::RemovePreview() {
105  if (!preview_)
106    return;
107
108  HideTab(preview_);
109
110  GtkWidget* preview_widget = preview_->tab_contents()->GetNativeView();
111  if (preview_widget)
112    gtk_container_remove(GTK_CONTAINER(expanded_), preview_widget);
113
114  registrar_.Remove(this, NotificationType::TAB_CONTENTS_DESTROYED,
115                    Source<TabContents>(preview_->tab_contents()));
116  preview_ = NULL;
117}
118
119void TabContentsContainerGtk::PopPreview() {
120  if (!preview_)
121    return;
122
123  RemovePreview();
124
125  PackTab(tab_);
126}
127
128void TabContentsContainerGtk::PackTab(TabContentsWrapper* tab) {
129  if (!tab)
130    return;
131
132  gfx::NativeView widget = tab->tab_contents()->GetNativeView();
133  if (widget) {
134    if (widget->parent != expanded_)
135      gtk_container_add(GTK_CONTAINER(expanded_), widget);
136    gtk_widget_show(widget);
137  }
138
139  // We need to make sure that we are below the findbar.
140  // Sometimes the content native view will be null.
141  if (tab->tab_contents()->GetContentNativeView()) {
142    GdkWindow* content_gdk_window =
143        tab->tab_contents()->GetContentNativeView()->window;
144    if (content_gdk_window)
145      gdk_window_lower(content_gdk_window);
146  }
147
148  tab->tab_contents()->ShowContents();
149}
150
151void TabContentsContainerGtk::HideTab(TabContentsWrapper* tab) {
152  if (!tab)
153    return;
154
155  gfx::NativeView widget = tab->tab_contents()->GetNativeView();
156  if (widget)
157    gtk_widget_hide(widget);
158
159  tab->tab_contents()->WasHidden();
160}
161
162void TabContentsContainerGtk::DetachTab(TabContentsWrapper* tab) {
163  gfx::NativeView widget = tab->tab_contents()->GetNativeView();
164
165  // It is possible to detach an unrealized, unparented TabContents if you
166  // slow things down enough in valgrind. Might happen in the real world, too.
167  if (widget && widget->parent) {
168    DCHECK_EQ(widget->parent, expanded_);
169    gtk_container_remove(GTK_CONTAINER(expanded_), widget);
170  }
171}
172
173void TabContentsContainerGtk::Observe(NotificationType type,
174                                      const NotificationSource& source,
175                                      const NotificationDetails& details) {
176  DCHECK(type == NotificationType::TAB_CONTENTS_DESTROYED);
177
178  TabContentsDestroyed(Source<TabContents>(source).ptr());
179}
180
181void TabContentsContainerGtk::TabContentsDestroyed(TabContents* contents) {
182  // Sometimes, a TabContents is destroyed before we know about it. This allows
183  // us to clean up our state in case this happens.
184  if (preview_ && contents == preview_->tab_contents())
185    PopPreview();
186  else if (tab_ && contents == tab_->tab_contents())
187    SetTab(NULL);
188  else
189    NOTREACHED();
190}
191
192// Prevent |preview_| from getting focus via the tab key. If |tab_| exists, try
193// to focus that. Otherwise, do nothing, but stop event propagation. See bug
194// http://crbug.com/63365
195gboolean TabContentsContainerGtk::OnFocus(GtkWidget* widget,
196                                          GtkDirectionType focus) {
197  if (preview_) {
198    gtk_widget_child_focus(tab_->tab_contents()->GetContentNativeView(), focus);
199    return TRUE;
200  }
201
202  // No preview contents; let the default handler run.
203  return FALSE;
204}
205
206// -----------------------------------------------------------------------------
207// ViewIDUtil::Delegate implementation
208
209GtkWidget* TabContentsContainerGtk::GetWidgetForViewID(ViewID view_id) {
210  if (view_id == VIEW_ID_TAB_CONTAINER ||
211      view_id == VIEW_ID_TAB_CONTAINER_FOCUS_VIEW) {
212    return widget();
213  }
214
215  return NULL;
216}
217
218// -----------------------------------------------------------------------------
219
220// static
221void TabContentsContainerGtk::OnSetFloatingPosition(
222    GtkFloatingContainer* floating_container, GtkAllocation* allocation,
223    TabContentsContainerGtk* tab_contents_container) {
224  StatusBubbleGtk* status = tab_contents_container->status_bubble_;
225
226  // Look at the size request of the status bubble and tell the
227  // GtkFloatingContainer where we want it positioned.
228  GtkRequisition requisition;
229  gtk_widget_size_request(status->widget(), &requisition);
230
231  bool ltr = !base::i18n::IsRTL();
232
233  GValue value = { 0, };
234  g_value_init(&value, G_TYPE_INT);
235  if (ltr ^ status->flip_horizontally())  // Is it on the left?
236    g_value_set_int(&value, 0);
237  else
238    g_value_set_int(&value, allocation->width - requisition.width);
239  gtk_container_child_set_property(GTK_CONTAINER(floating_container),
240                                   status->widget(), "x", &value);
241
242  int child_y = std::max(allocation->height - requisition.height, 0);
243  g_value_set_int(&value, child_y + status->y_offset());
244  gtk_container_child_set_property(GTK_CONTAINER(floating_container),
245                                   status->widget(), "y", &value);
246  g_value_unset(&value);
247}
248