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/infobars/infobar_container_gtk.h"
6
7#include <gtk/gtk.h>
8
9#include <utility>
10
11#include "base/command_line.h"
12#include "chrome/browser/platform_util.h"
13#include "chrome/browser/tab_contents/infobar_delegate.h"
14#include "chrome/browser/ui/browser_window.h"
15#include "chrome/browser/ui/gtk/browser_window_gtk.h"
16#include "chrome/browser/ui/gtk/gtk_theme_service.h"
17#include "chrome/browser/ui/gtk/gtk_util.h"
18#include "chrome/browser/ui/gtk/infobars/infobar_gtk.h"
19#include "content/browser/tab_contents/tab_contents.h"
20#include "content/common/notification_details.h"
21#include "content/common/notification_source.h"
22#include "third_party/skia/include/core/SkPaint.h"
23
24namespace {
25
26static const char* kInfoBar = "info-bar";
27
28// If |infobar_widget| matches |info_bar_delegate|, then close the infobar.
29void AnimateClosingForDelegate(GtkWidget* infobar_widget,
30                               gpointer info_bar_delegate) {
31  InfoBarDelegate* delegate =
32      static_cast<InfoBarDelegate*>(info_bar_delegate);
33  InfoBar* infobar = reinterpret_cast<InfoBar*>(
34      g_object_get_data(G_OBJECT(infobar_widget), kInfoBar));
35
36  if (!infobar) {
37    NOTREACHED();
38    return;
39  }
40
41  if (delegate == infobar->delegate())
42    infobar->AnimateClose();
43}
44
45// If |infobar_widget| matches |info_bar_delegate|, then close the infobar w/o
46// an animation.
47void ClosingForDelegate(GtkWidget* infobar_widget, gpointer info_bar_delegate) {
48  InfoBarDelegate* delegate =
49      static_cast<InfoBarDelegate*>(info_bar_delegate);
50  InfoBar* infobar = reinterpret_cast<InfoBar*>(
51      g_object_get_data(G_OBJECT(infobar_widget), kInfoBar));
52
53  if (!infobar) {
54    NOTREACHED();
55    return;
56  }
57
58  if (delegate == infobar->delegate())
59    infobar->Close();
60}
61
62// Get the height of the widget and add it to |userdata|, but only if it is in
63// the process of animating.
64void SumAnimatingBarHeight(GtkWidget* widget, gpointer userdata) {
65  int* height_sum = static_cast<int*>(userdata);
66  InfoBar* infobar = reinterpret_cast<InfoBar*>(
67      g_object_get_data(G_OBJECT(widget), kInfoBar));
68  if (infobar->IsAnimating())
69    *height_sum += widget->allocation.height;
70}
71
72}  // namespace
73
74// InfoBarContainerGtk, public: ------------------------------------------------
75
76InfoBarContainerGtk::InfoBarContainerGtk(Profile* profile)
77    : profile_(profile),
78      tab_contents_(NULL),
79      container_(gtk_vbox_new(FALSE, 0)) {
80  gtk_widget_show(widget());
81}
82
83InfoBarContainerGtk::~InfoBarContainerGtk() {
84  ChangeTabContents(NULL);
85
86  container_.Destroy();
87}
88
89void InfoBarContainerGtk::ChangeTabContents(TabContents* contents) {
90  if (tab_contents_)
91    registrar_.RemoveAll();
92
93  gtk_util::RemoveAllChildren(widget());
94  UpdateToolbarInfoBarState(NULL, false);
95
96  tab_contents_ = contents;
97  if (tab_contents_) {
98    UpdateInfoBars();
99    Source<TabContents> source(tab_contents_);
100    registrar_.Add(this, NotificationType::TAB_CONTENTS_INFOBAR_ADDED, source);
101    registrar_.Add(this, NotificationType::TAB_CONTENTS_INFOBAR_REMOVED,
102                   source);
103    registrar_.Add(this, NotificationType::TAB_CONTENTS_INFOBAR_REPLACED,
104                   source);
105  }
106}
107
108void InfoBarContainerGtk::RemoveDelegate(InfoBarDelegate* delegate) {
109  tab_contents_->RemoveInfoBar(delegate);
110}
111
112int InfoBarContainerGtk::TotalHeightOfAnimatingBars() const {
113  int sum = 0;
114  gtk_container_foreach(GTK_CONTAINER(widget()), SumAnimatingBarHeight, &sum);
115  return sum;
116}
117
118// InfoBarContainerGtk, NotificationObserver implementation: -------------------
119
120void InfoBarContainerGtk::Observe(NotificationType type,
121                                  const NotificationSource& source,
122                                  const NotificationDetails& details) {
123  if (type == NotificationType::TAB_CONTENTS_INFOBAR_ADDED) {
124    AddInfoBar(Details<InfoBarDelegate>(details).ptr(), true);
125  } else if (type == NotificationType::TAB_CONTENTS_INFOBAR_REMOVED) {
126    RemoveInfoBar(Details<InfoBarDelegate>(details).ptr(), true);
127  } else if (type == NotificationType::TAB_CONTENTS_INFOBAR_REPLACED) {
128    std::pair<InfoBarDelegate*, InfoBarDelegate*>* delegates =
129        Details<std::pair<InfoBarDelegate*, InfoBarDelegate*> >(details).ptr();
130
131    // By not animating the removal/addition, this appears to be a replace.
132    RemoveInfoBar(delegates->first, false);
133    AddInfoBar(delegates->second, false);
134  } else {
135    NOTREACHED();
136  }
137}
138
139// InfoBarContainerGtk, private: -----------------------------------------------
140
141void InfoBarContainerGtk::UpdateInfoBars() {
142  for (size_t i = 0; i < tab_contents_->infobar_count(); ++i) {
143    InfoBarDelegate* delegate = tab_contents_->GetInfoBarDelegateAt(i);
144    AddInfoBar(delegate, false);
145  }
146}
147
148void InfoBarContainerGtk::ShowArrowForDelegate(InfoBarDelegate* delegate,
149                                               bool animate) {
150  GList* children = gtk_container_get_children(GTK_CONTAINER(widget()));
151  if (!children)
152    return;
153
154  // Iterate through the infobars and find the last one that isn't closing.
155  InfoBar* last_bar = NULL;
156  InfoBar* this_bar = NULL;
157  for (GList* iter = children; iter != NULL; iter = iter->next) {
158    this_bar = reinterpret_cast<InfoBar*>(
159        g_object_get_data(G_OBJECT(iter->data), kInfoBar));
160
161    if (this_bar->delegate() == delegate)
162      break;
163
164    if (!this_bar->IsClosing())
165      last_bar = this_bar;
166
167    this_bar = NULL;
168  }
169
170  if (last_bar)
171    last_bar->ShowArrowFor(this_bar, animate);
172  else
173    UpdateToolbarInfoBarState(this_bar, animate);
174
175  g_list_free(children);
176}
177
178void InfoBarContainerGtk::AddInfoBar(InfoBarDelegate* delegate, bool animate) {
179  InfoBar* infobar = delegate->CreateInfoBar();
180  infobar->set_container(this);
181  infobar->SetThemeProvider(GtkThemeService::GetFrom(profile_));
182  gtk_box_pack_start(GTK_BOX(widget()), infobar->widget(),
183                     FALSE, FALSE, 0);
184
185  if (animate)
186    infobar->AnimateOpen();
187  else
188    infobar->Open();
189
190  ShowArrowForDelegate(delegate, animate);
191}
192
193void InfoBarContainerGtk::RemoveInfoBar(InfoBarDelegate* delegate,
194                                        bool animate) {
195  if (animate) {
196    gtk_container_foreach(GTK_CONTAINER(widget()),
197                          AnimateClosingForDelegate, delegate);
198  } else {
199    gtk_container_foreach(GTK_CONTAINER(widget()), ClosingForDelegate,
200                          delegate);
201  }
202
203  InfoBarDelegate* next_delegate = NULL;
204  for (size_t i = 1; i < tab_contents_->infobar_count(); ++i) {
205    if (tab_contents_->GetInfoBarDelegateAt(i - 1) == delegate) {
206      next_delegate = tab_contents_->GetInfoBarDelegateAt(i);
207      break;
208    }
209  }
210
211  ShowArrowForDelegate(next_delegate, animate);
212}
213
214void InfoBarContainerGtk::UpdateToolbarInfoBarState(InfoBar* infobar,
215                                                    bool animate) {
216  GtkWindow* parent = platform_util::GetTopLevel(widget());
217  BrowserWindowGtk* browser_window =
218      BrowserWindowGtk::GetBrowserWindowForNativeWindow(parent);
219  if (browser_window)
220    browser_window->SetInfoBarShowing(infobar, animate);
221}
222