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.
5 #include "build/build_config.h"
7 // TODO(pkasting): Port Mac to use this.
8 #if defined(TOOLKIT_VIEWS) || defined(TOOLKIT_GTK)
10 #include "chrome/browser/infobars/infobar_container.h"
14 #include "base/logging.h"
15 #include "chrome/browser/infobars/infobar.h"
16 #include "chrome/browser/infobars/infobar_delegate.h"
17 #include "chrome/browser/infobars/infobar_service.h"
18 #include "chrome/browser/ui/search/instant_overlay_model.h"
19 #include "chrome/browser/ui/search/search_model.h"
20 #include "chrome/common/chrome_notification_types.h"
21 #include "content/public/browser/notification_details.h"
22 #include "content/public/browser/notification_source.h"
23 #include "ui/base/animation/slide_animation.h"
25 InfoBarContainer::Delegate::~Delegate() {
28 InfoBarContainer::InfoBarContainer(Delegate
* delegate
,
29 SearchModel
* search_model
)
30 : delegate_(delegate
),
31 infobar_service_(NULL
),
32 infobars_shown_(true),
33 search_model_(search_model
),
34 top_arrow_target_height_(InfoBar::kDefaultArrowTargetHeight
) {
36 search_model_
->AddObserver(this);
39 InfoBarContainer::~InfoBarContainer() {
40 // RemoveAllInfoBarsForDestruction() should have already cleared our infobars.
41 DCHECK(infobars_
.empty());
43 search_model_
->RemoveObserver(this);
46 void InfoBarContainer::ChangeInfoBarService(InfoBarService
* infobar_service
) {
47 // We have to call HideAllInfoBars() here and not after the early exit below,
48 // to handle the case we're switching from a tab with visible infobars to one
49 // where the SearchModel directs us to hide infobars.
52 infobar_service_
= infobar_service
;
54 if (search_model_
&& !search_model_
->top_bars_visible())
57 // Note that HideAllInfoBars() sets |infobars_shown_| to false, because that's
58 // what the other, Instant-related callers want; but here we actually
59 // explicitly want to reset this variable to true. So do that after calling
61 infobars_shown_
= true;
62 infobars_shown_time_
= base::TimeTicks();
64 if (infobar_service_
) {
65 content::Source
<InfoBarService
> source(infobar_service_
);
66 registrar_
.Add(this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_ADDED
,
68 registrar_
.Add(this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED
,
70 registrar_
.Add(this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REPLACED
,
73 for (size_t i
= 0; i
< infobar_service_
->GetInfoBarCount(); ++i
) {
74 // As when we removed the infobars above, we prevent callbacks to
75 // OnInfoBarAnimated() for each infobar.
77 infobar_service_
->GetInfoBarDelegateAt(i
)->CreateInfoBar(
79 i
, false, NO_CALLBACK
);
83 // Now that everything is up to date, signal the delegate to re-layout.
84 OnInfoBarStateChanged(false);
87 int InfoBarContainer::GetVerticalOverlap(int* total_height
) {
88 // Our |total_height| is the sum of the preferred heights of the InfoBars
89 // contained within us plus the |vertical_overlap|.
90 int vertical_overlap
= 0;
91 int next_infobar_y
= 0;
93 for (InfoBars::iterator
i(infobars_
.begin()); i
!= infobars_
.end(); ++i
) {
94 InfoBar
* infobar
= *i
;
95 next_infobar_y
-= infobar
->arrow_height();
96 vertical_overlap
= std::max(vertical_overlap
, -next_infobar_y
);
97 next_infobar_y
+= infobar
->total_height();
101 *total_height
= next_infobar_y
+ vertical_overlap
;
102 return vertical_overlap
;
105 void InfoBarContainer::SetMaxTopArrowHeight(int height
) {
106 // Decrease the height by the arrow stroke thickness, which is the separator
107 // line height, because the infobar arrow target heights are without-stroke.
108 top_arrow_target_height_
= std::min(
109 std::max(height
- InfoBar::kSeparatorLineHeight
, 0),
110 InfoBar::kMaximumArrowTargetHeight
);
111 UpdateInfoBarArrowTargetHeights();
114 void InfoBarContainer::OnInfoBarStateChanged(bool is_animating
) {
116 delegate_
->InfoBarContainerStateChanged(is_animating
);
117 UpdateInfoBarArrowTargetHeights();
118 PlatformSpecificInfoBarStateChanged(is_animating
);
121 void InfoBarContainer::RemoveInfoBar(InfoBar
* infobar
) {
122 infobar
->set_container(NULL
);
123 InfoBars::iterator
i(std::find(infobars_
.begin(), infobars_
.end(), infobar
));
124 DCHECK(i
!= infobars_
.end());
125 PlatformSpecificRemoveInfoBar(infobar
);
129 void InfoBarContainer::RemoveAllInfoBarsForDestruction() {
130 // Before we remove any children, we reset |delegate_|, so that no removals
131 // will result in us trying to call
132 // delegate_->InfoBarContainerStateChanged(). This is important because at
133 // this point |delegate_| may be shutting down, and it's at best unimportant
134 // and at worst disastrous to call that.
137 for (size_t i
= infobars_
.size(); i
> 0; --i
)
138 infobars_
[i
- 1]->CloseSoon();
140 ChangeInfoBarService(NULL
);
143 void InfoBarContainer::Observe(int type
,
144 const content::NotificationSource
& source
,
145 const content::NotificationDetails
& details
) {
146 // When infobars are hidden, we shouldn't be listening for notifications.
147 DCHECK(infobars_shown_
);
150 case chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_ADDED
:
152 content::Details
<InfoBarAddedDetails
>(details
)->CreateInfoBar(
154 infobars_
.size(), true, WANT_CALLBACK
);
157 case chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED
: {
158 InfoBarRemovedDetails
* removed_details
=
159 content::Details
<InfoBarRemovedDetails
>(details
).ptr();
160 HideInfoBar(removed_details
->first
, removed_details
->second
);
164 case chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REPLACED
: {
165 InfoBarReplacedDetails
* replaced_details
=
166 content::Details
<InfoBarReplacedDetails
>(details
).ptr();
167 AddInfoBar(replaced_details
->second
->CreateInfoBar(infobar_service_
),
168 HideInfoBar(replaced_details
->first
, false), false, WANT_CALLBACK
);
178 void InfoBarContainer::ModelChanged(const SearchModel::State
& old_state
,
179 const SearchModel::State
& new_state
) {
180 if (!SearchModel::ShouldChangeTopBarsVisibility(old_state
, new_state
))
183 if (new_state
.top_bars_visible
&& !infobars_shown_
) {
184 ChangeInfoBarService(infobar_service_
);
185 infobars_shown_time_
= base::TimeTicks::Now();
186 } else if (!new_state
.top_bars_visible
&& infobars_shown_
) {
188 OnInfoBarStateChanged(false);
192 size_t InfoBarContainer::HideInfoBar(InfoBarDelegate
* delegate
,
193 bool use_animation
) {
194 bool should_animate
= use_animation
&&
195 ((base::TimeTicks::Now() - infobars_shown_time_
) >
196 base::TimeDelta::FromMilliseconds(50));
198 // Search for the infobar associated with |delegate|. We cannot search for
199 // |delegate| in |tab_helper_|, because an InfoBar remains alive until its
200 // close animation completes, while the delegate is removed from the tab
202 for (InfoBars::iterator
i(infobars_
.begin()); i
!= infobars_
.end(); ++i
) {
203 InfoBar
* infobar
= *i
;
204 if (infobar
->delegate() == delegate
) {
205 size_t position
= i
- infobars_
.begin();
206 // We merely need hide the infobar; it will call back to RemoveInfoBar()
207 // itself once it's hidden.
208 infobar
->Hide(should_animate
);
209 infobar
->CloseSoon();
210 UpdateInfoBarArrowTargetHeights();
215 return infobars_
.size();
218 void InfoBarContainer::HideAllInfoBars() {
219 registrar_
.RemoveAll();
221 infobars_shown_
= false;
222 while (!infobars_
.empty()) {
223 InfoBar
* infobar
= infobars_
.front();
224 // Inform the infobar that it's hidden. If it was already closing, this
225 // closes its delegate.
226 infobar
->Hide(false);
230 void InfoBarContainer::AddInfoBar(InfoBar
* infobar
,
233 CallbackStatus callback_status
) {
234 DCHECK(std::find(infobars_
.begin(), infobars_
.end(), infobar
) ==
236 DCHECK_LE(position
, infobars_
.size());
237 infobars_
.insert(infobars_
.begin() + position
, infobar
);
238 UpdateInfoBarArrowTargetHeights();
239 PlatformSpecificAddInfoBar(infobar
, position
);
240 if (callback_status
== WANT_CALLBACK
)
241 infobar
->set_container(this);
242 infobar
->Show(animate
);
243 if (callback_status
== NO_CALLBACK
)
244 infobar
->set_container(this);
247 void InfoBarContainer::UpdateInfoBarArrowTargetHeights() {
248 for (size_t i
= 0; i
< infobars_
.size(); ++i
)
249 infobars_
[i
]->SetArrowTargetHeight(ArrowTargetHeightForInfoBar(i
));
252 int InfoBarContainer::ArrowTargetHeightForInfoBar(size_t infobar_index
) const {
253 if (!delegate_
|| !delegate_
->DrawInfoBarArrows(NULL
))
255 if (infobar_index
== 0)
256 return top_arrow_target_height_
;
257 const ui::SlideAnimation
& first_infobar_animation
=
258 const_cast<const InfoBar
*>(infobars_
.front())->animation();
259 if ((infobar_index
> 1) || first_infobar_animation
.IsShowing())
260 return InfoBar::kDefaultArrowTargetHeight
;
261 // When the first infobar is animating closed, we animate the second infobar's
262 // arrow target height from the default to the top target height. Note that
263 // the animation values here are going from 1.0 -> 0.0 as the top bar closes.
264 return top_arrow_target_height_
+ static_cast<int>(
265 (InfoBar::kDefaultArrowTargetHeight
- top_arrow_target_height_
) *
266 first_infobar_animation
.GetCurrentValue());
269 #endif // TOOLKIT_VIEWS || defined(TOOLKIT_GTK)