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