1// Copyright 2014 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 "components/infobars/core/infobar_container.h"
6
7#include <algorithm>
8
9#include "base/logging.h"
10#include "build/build_config.h"
11#include "components/infobars/core/infobar.h"
12#include "components/infobars/core/infobar_delegate.h"
13#include "ui/gfx/animation/slide_animation.h"
14
15namespace infobars {
16
17InfoBarContainer::Delegate::~Delegate() {
18}
19
20InfoBarContainer::InfoBarContainer(Delegate* delegate)
21    : delegate_(delegate),
22      infobar_manager_(NULL),
23      top_arrow_target_height_(InfoBar::kDefaultArrowTargetHeight) {
24}
25
26InfoBarContainer::~InfoBarContainer() {
27  // RemoveAllInfoBarsForDestruction() should have already cleared our infobars.
28  DCHECK(infobars_.empty());
29  if (infobar_manager_)
30    infobar_manager_->RemoveObserver(this);
31}
32
33void InfoBarContainer::ChangeInfoBarManager(InfoBarManager* infobar_manager) {
34  if (infobar_manager_)
35    infobar_manager_->RemoveObserver(this);
36
37  // Hides all infobars in this container without animation.
38  while (!infobars_.empty()) {
39    InfoBar* infobar = infobars_.front();
40    // Inform the infobar that it's hidden.  If it was already closing, this
41    // deletes it.  Otherwise, this ensures the infobar will be deleted if it's
42    // closed while it's not in an InfoBarContainer.
43    infobar->Hide(false);
44  }
45
46  infobar_manager_ = infobar_manager;
47  if (infobar_manager_) {
48    infobar_manager_->AddObserver(this);
49
50    for (size_t i = 0; i < infobar_manager_->infobar_count(); ++i) {
51      // As when we removed the infobars above, we prevent callbacks to
52      // OnInfoBarStateChanged() for each infobar.
53      AddInfoBar(infobar_manager_->infobar_at(i), i, false, NO_CALLBACK);
54    }
55  }
56
57  // Now that everything is up to date, signal the delegate to re-layout.
58  OnInfoBarStateChanged(false);
59}
60
61int InfoBarContainer::GetVerticalOverlap(int* total_height) const {
62  // Our |total_height| is the sum of the preferred heights of the InfoBars
63  // contained within us plus the |vertical_overlap|.
64  int vertical_overlap = 0;
65  int next_infobar_y = 0;
66
67  for (InfoBars::const_iterator i(infobars_.begin()); i != infobars_.end();
68       ++i) {
69    InfoBar* infobar = *i;
70    next_infobar_y -= infobar->arrow_height();
71    vertical_overlap = std::max(vertical_overlap, -next_infobar_y);
72    next_infobar_y += infobar->total_height();
73  }
74
75  if (total_height)
76    *total_height = next_infobar_y + vertical_overlap;
77  return vertical_overlap;
78}
79
80void InfoBarContainer::SetMaxTopArrowHeight(int height) {
81  // Decrease the height by the arrow stroke thickness, which is the separator
82  // line height, because the infobar arrow target heights are without-stroke.
83  top_arrow_target_height_ = std::min(
84      std::max(height - InfoBar::kSeparatorLineHeight, 0),
85      InfoBar::kMaximumArrowTargetHeight);
86  UpdateInfoBarArrowTargetHeights();
87}
88
89void InfoBarContainer::OnInfoBarStateChanged(bool is_animating) {
90  if (delegate_)
91    delegate_->InfoBarContainerStateChanged(is_animating);
92  UpdateInfoBarArrowTargetHeights();
93  PlatformSpecificInfoBarStateChanged(is_animating);
94}
95
96void InfoBarContainer::RemoveInfoBar(InfoBar* infobar) {
97  infobar->set_container(NULL);
98  InfoBars::iterator i(std::find(infobars_.begin(), infobars_.end(), infobar));
99  DCHECK(i != infobars_.end());
100  PlatformSpecificRemoveInfoBar(infobar);
101  infobars_.erase(i);
102}
103
104void InfoBarContainer::RemoveAllInfoBarsForDestruction() {
105  // Before we remove any children, we reset |delegate_|, so that no removals
106  // will result in us trying to call
107  // delegate_->InfoBarContainerStateChanged().  This is important because at
108  // this point |delegate_| may be shutting down, and it's at best unimportant
109  // and at worst disastrous to call that.
110  delegate_ = NULL;
111  ChangeInfoBarManager(NULL);
112}
113
114void InfoBarContainer::OnInfoBarAdded(InfoBar* infobar) {
115  AddInfoBar(infobar, infobars_.size(), true, WANT_CALLBACK);
116}
117
118void InfoBarContainer::OnInfoBarRemoved(InfoBar* infobar, bool animate) {
119  infobar->Hide(animate);
120  UpdateInfoBarArrowTargetHeights();
121}
122
123void InfoBarContainer::OnInfoBarReplaced(InfoBar* old_infobar,
124                                         InfoBar* new_infobar) {
125  PlatformSpecificReplaceInfoBar(old_infobar, new_infobar);
126  InfoBars::const_iterator i(std::find(infobars_.begin(), infobars_.end(),
127                                       old_infobar));
128  DCHECK(i != infobars_.end());
129  size_t position = i - infobars_.begin();
130  old_infobar->Hide(false);
131  AddInfoBar(new_infobar, position, false, WANT_CALLBACK);
132}
133
134void InfoBarContainer::OnManagerShuttingDown(InfoBarManager* manager) {
135  DCHECK_EQ(infobar_manager_, manager);
136  infobar_manager_->RemoveObserver(this);
137  infobar_manager_ = NULL;
138}
139
140void InfoBarContainer::AddInfoBar(InfoBar* infobar,
141                                  size_t position,
142                                  bool animate,
143                                  CallbackStatus callback_status) {
144  DCHECK(std::find(infobars_.begin(), infobars_.end(), infobar) ==
145      infobars_.end());
146  DCHECK_LE(position, infobars_.size());
147  infobars_.insert(infobars_.begin() + position, infobar);
148  UpdateInfoBarArrowTargetHeights();
149  PlatformSpecificAddInfoBar(infobar, position);
150  if (callback_status == WANT_CALLBACK)
151    infobar->set_container(this);
152  infobar->Show(animate);
153  if (callback_status == NO_CALLBACK)
154    infobar->set_container(this);
155}
156
157void InfoBarContainer::UpdateInfoBarArrowTargetHeights() {
158  for (size_t i = 0; i < infobars_.size(); ++i)
159    infobars_[i]->SetArrowTargetHeight(ArrowTargetHeightForInfoBar(i));
160}
161
162int InfoBarContainer::ArrowTargetHeightForInfoBar(size_t infobar_index) const {
163  if (!delegate_ || !delegate_->DrawInfoBarArrows(NULL))
164    return 0;
165  if (infobar_index == 0)
166    return top_arrow_target_height_;
167  const gfx::SlideAnimation& first_infobar_animation =
168      const_cast<const InfoBar*>(infobars_.front())->animation();
169  if ((infobar_index > 1) || first_infobar_animation.IsShowing())
170    return InfoBar::kDefaultArrowTargetHeight;
171  // When the first infobar is animating closed, we animate the second infobar's
172  // arrow target height from the default to the top target height.  Note that
173  // the animation values here are going from 1.0 -> 0.0 as the top bar closes.
174  return top_arrow_target_height_ + static_cast<int>(
175      (InfoBar::kDefaultArrowTargetHeight - top_arrow_target_height_) *
176          first_infobar_animation.GetCurrentValue());
177}
178
179}  // namespace infobars
180