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 "build/build_config.h"
6
7// TODO(pkasting): Port Mac to use this.
8#if defined(TOOLKIT_VIEWS) || defined(TOOLKIT_GTK) || defined(OS_ANDROID)
9
10#include "chrome/browser/infobars/infobar_container.h"
11
12#include <algorithm>
13
14#include "base/logging.h"
15#include "chrome/browser/chrome_notification_types.h"
16#include "chrome/browser/infobars/infobar.h"
17#include "chrome/browser/infobars/infobar_delegate.h"
18#include "chrome/browser/infobars/infobar_service.h"
19#include "content/public/browser/notification_details.h"
20#include "content/public/browser/notification_source.h"
21#include "ui/base/animation/slide_animation.h"
22
23InfoBarContainer::Delegate::~Delegate() {
24}
25
26InfoBarContainer::InfoBarContainer(Delegate* delegate)
27    : delegate_(delegate),
28      infobar_service_(NULL),
29      top_arrow_target_height_(InfoBar::kDefaultArrowTargetHeight) {
30}
31
32InfoBarContainer::~InfoBarContainer() {
33  // RemoveAllInfoBarsForDestruction() should have already cleared our infobars.
34  DCHECK(infobars_.empty());
35}
36
37void InfoBarContainer::ChangeInfoBarService(InfoBarService* infobar_service) {
38  HideAllInfoBars();
39
40  infobar_service_ = infobar_service;
41  if (infobar_service_) {
42    content::Source<InfoBarService> source(infobar_service_);
43    registrar_.Add(this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_ADDED,
44                   source);
45    registrar_.Add(this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
46                   source);
47    registrar_.Add(this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REPLACED,
48                   source);
49
50    for (size_t i = 0; i < infobar_service_->infobar_count(); ++i) {
51      // As when we removed the infobars above, we prevent callbacks to
52      // OnInfoBarAnimated() for each infobar.
53      AddInfoBar(
54          infobar_service_->infobar_at(i)->CreateInfoBar(infobar_service_),
55          i, false, NO_CALLBACK);
56    }
57  }
58
59  // Now that everything is up to date, signal the delegate to re-layout.
60  OnInfoBarStateChanged(false);
61}
62
63int InfoBarContainer::GetVerticalOverlap(int* total_height) {
64  // Our |total_height| is the sum of the preferred heights of the InfoBars
65  // contained within us plus the |vertical_overlap|.
66  int vertical_overlap = 0;
67  int next_infobar_y = 0;
68
69  for (InfoBars::iterator i(infobars_.begin()); i != infobars_.end(); ++i) {
70    InfoBar* infobar = *i;
71    next_infobar_y -= infobar->arrow_height();
72    vertical_overlap = std::max(vertical_overlap, -next_infobar_y);
73    next_infobar_y += infobar->total_height();
74  }
75
76  if (total_height)
77    *total_height = next_infobar_y + vertical_overlap;
78  return vertical_overlap;
79}
80
81void InfoBarContainer::SetMaxTopArrowHeight(int height) {
82  // Decrease the height by the arrow stroke thickness, which is the separator
83  // line height, because the infobar arrow target heights are without-stroke.
84  top_arrow_target_height_ = std::min(
85      std::max(height - InfoBar::kSeparatorLineHeight, 0),
86      InfoBar::kMaximumArrowTargetHeight);
87  UpdateInfoBarArrowTargetHeights();
88}
89
90void InfoBarContainer::OnInfoBarStateChanged(bool is_animating) {
91  if (delegate_)
92    delegate_->InfoBarContainerStateChanged(is_animating);
93  UpdateInfoBarArrowTargetHeights();
94  PlatformSpecificInfoBarStateChanged(is_animating);
95}
96
97void InfoBarContainer::RemoveInfoBar(InfoBar* infobar) {
98  infobar->set_container(NULL);
99  InfoBars::iterator i(std::find(infobars_.begin(), infobars_.end(), infobar));
100  DCHECK(i != infobars_.end());
101  PlatformSpecificRemoveInfoBar(infobar);
102  infobars_.erase(i);
103}
104
105void InfoBarContainer::RemoveAllInfoBarsForDestruction() {
106  // Before we remove any children, we reset |delegate_|, so that no removals
107  // will result in us trying to call
108  // delegate_->InfoBarContainerStateChanged().  This is important because at
109  // this point |delegate_| may be shutting down, and it's at best unimportant
110  // and at worst disastrous to call that.
111  delegate_ = NULL;
112
113  // TODO(pkasting): Remove this once InfoBarService calls CloseSoon().
114  for (size_t i = infobars_.size(); i > 0; --i)
115    infobars_[i - 1]->CloseSoon();
116
117  ChangeInfoBarService(NULL);
118}
119
120void InfoBarContainer::Observe(int type,
121                               const content::NotificationSource& source,
122                               const content::NotificationDetails& details) {
123  switch (type) {
124    case chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_ADDED:
125      AddInfoBar(
126          content::Details<InfoBarAddedDetails>(details)->CreateInfoBar(
127              infobar_service_),
128          infobars_.size(), true, WANT_CALLBACK);
129      break;
130
131    case chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED: {
132      InfoBarRemovedDetails* removed_details =
133          content::Details<InfoBarRemovedDetails>(details).ptr();
134      HideInfoBar(FindInfoBar(removed_details->first), removed_details->second);
135      break;
136    }
137
138    case chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REPLACED: {
139      InfoBarReplacedDetails* replaced_details =
140          content::Details<InfoBarReplacedDetails>(details).ptr();
141      ReplaceInfoBar(replaced_details->first, replaced_details->second);
142      break;
143    }
144
145    default:
146      NOTREACHED();
147      break;
148  }
149}
150
151void InfoBarContainer::ReplaceInfoBar(InfoBarDelegate* old_delegate,
152                                      InfoBarDelegate* new_delegate) {
153  InfoBar* new_infobar = new_delegate->CreateInfoBar(infobar_service_);
154  InfoBar* old_infobar = FindInfoBar(old_delegate);
155#if defined(OS_ANDROID)
156  PlatformSpecificReplaceInfoBar(old_infobar, new_infobar);
157#endif
158  AddInfoBar(
159      new_infobar, HideInfoBar(old_infobar, false), false, WANT_CALLBACK);
160}
161
162InfoBar* InfoBarContainer::FindInfoBar(InfoBarDelegate* delegate) {
163  // Search for the infobar associated with |delegate|.  We cannot search for
164  // |delegate| in |tab_helper_|, because an InfoBar remains alive until its
165  // close animation completes, while the delegate is removed from the tab
166  // immediately.
167  for (InfoBars::iterator i(infobars_.begin()); i != infobars_.end(); ++i) {
168    InfoBar* infobar = *i;
169    if (infobar->delegate() == delegate)
170      return infobar;
171  }
172  NOTREACHED();
173  return NULL;
174}
175
176size_t InfoBarContainer::HideInfoBar(InfoBar* infobar, bool use_animation) {
177  InfoBars::iterator it =
178      std::find(infobars_.begin(), infobars_.end(), infobar);
179  DCHECK(it != infobars_.end());
180  size_t position = it - infobars_.begin();
181  // We merely need hide the infobar; it will call back to RemoveInfoBar()
182  // itself once it's hidden.
183  infobar->Hide(use_animation);
184  infobar->CloseSoon();
185  UpdateInfoBarArrowTargetHeights();
186  return position;
187}
188
189void InfoBarContainer::HideAllInfoBars() {
190  registrar_.RemoveAll();
191
192  while (!infobars_.empty()) {
193    InfoBar* infobar = infobars_.front();
194    // Inform the infobar that it's hidden.  If it was already closing, this
195    // closes its delegate.
196    infobar->Hide(false);
197  }
198}
199
200void InfoBarContainer::AddInfoBar(InfoBar* infobar,
201                                  size_t position,
202                                  bool animate,
203                                  CallbackStatus callback_status) {
204  DCHECK(std::find(infobars_.begin(), infobars_.end(), infobar) ==
205      infobars_.end());
206  DCHECK_LE(position, infobars_.size());
207  infobars_.insert(infobars_.begin() + position, infobar);
208  UpdateInfoBarArrowTargetHeights();
209  PlatformSpecificAddInfoBar(infobar, position);
210  if (callback_status == WANT_CALLBACK)
211    infobar->set_container(this);
212  infobar->Show(animate);
213  if (callback_status == NO_CALLBACK)
214    infobar->set_container(this);
215}
216
217void InfoBarContainer::UpdateInfoBarArrowTargetHeights() {
218  for (size_t i = 0; i < infobars_.size(); ++i)
219    infobars_[i]->SetArrowTargetHeight(ArrowTargetHeightForInfoBar(i));
220}
221
222int InfoBarContainer::ArrowTargetHeightForInfoBar(size_t infobar_index) const {
223  if (!delegate_ || !delegate_->DrawInfoBarArrows(NULL))
224    return 0;
225  if (infobar_index == 0)
226    return top_arrow_target_height_;
227  const ui::SlideAnimation& first_infobar_animation =
228      const_cast<const InfoBar*>(infobars_.front())->animation();
229  if ((infobar_index > 1) || first_infobar_animation.IsShowing())
230    return InfoBar::kDefaultArrowTargetHeight;
231  // When the first infobar is animating closed, we animate the second infobar's
232  // arrow target height from the default to the top target height.  Note that
233  // the animation values here are going from 1.0 -> 0.0 as the top bar closes.
234  return top_arrow_target_height_ + static_cast<int>(
235      (InfoBar::kDefaultArrowTargetHeight - top_arrow_target_height_) *
236          first_infobar_animation.GetCurrentValue());
237}
238
239#endif  // TOOLKIT_VIEWS || defined(TOOLKIT_GTK) || defined(OS_ANDROID)
240