content_setting_image_view.cc revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
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 "chrome/browser/ui/views/location_bar/content_setting_image_view.h" 6 7#include "base/strings/utf_string_conversions.h" 8#include "chrome/browser/content_settings/tab_specific_content_settings.h" 9#include "chrome/browser/ui/content_settings/content_setting_bubble_model.h" 10#include "chrome/browser/ui/content_settings/content_setting_image_model.h" 11#include "chrome/browser/ui/views/content_setting_bubble_contents.h" 12#include "chrome/browser/ui/views/location_bar/location_bar_view.h" 13#include "grit/theme_resources.h" 14#include "ui/base/l10n/l10n_util.h" 15#include "ui/base/resource/resource_bundle.h" 16#include "ui/gfx/color_utils.h" 17#include "ui/views/controls/image_view.h" 18#include "ui/views/controls/label.h" 19#include "ui/views/widget/widget.h" 20 21 22namespace { 23const int kBackgroundImages[] = IMAGE_GRID(IDR_OMNIBOX_CONTENT_SETTING_BUBBLE); 24const int kStayOpenTimeMS = 3200; // Time spent with animation fully open. 25} 26 27 28// static 29const int ContentSettingImageView::kOpenTimeMS = 150; 30const int ContentSettingImageView::kAnimationDurationMS = 31 (kOpenTimeMS * 2) + kStayOpenTimeMS; 32 33ContentSettingImageView::ContentSettingImageView( 34 ContentSettingsType content_type, 35 LocationBarView* parent, 36 const gfx::FontList& font_list, 37 int font_y_offset, 38 SkColor text_color, 39 SkColor parent_background_color) 40 : parent_(parent), 41 content_setting_image_model_( 42 ContentSettingImageModel::CreateContentSettingImageModel( 43 content_type)), 44 background_painter_( 45 views::Painter::CreateImageGridPainter(kBackgroundImages)), 46 icon_(new views::ImageView), 47 text_label_(new views::Label(string16(), font_list)), 48 slide_animator_(this), 49 pause_animation_(false), 50 pause_animation_state_(0.0), 51 bubble_widget_(NULL) { 52 icon_->SetHorizontalAlignment(views::ImageView::LEADING); 53 AddChildView(icon_); 54 55 text_label_->SetVisible(false); 56 text_label_->set_border( 57 views::Border::CreateEmptyBorder(font_y_offset, 0, 0, 0)); 58 text_label_->SetEnabledColor(text_color); 59 // Calculate the actual background color for the label. The background images 60 // are painted atop |parent_background_color|. We grab the color of the 61 // middle pixel of the middle image of the background, which we treat as the 62 // representative color of the entire background (reasonable, given the 63 // current appearance of these images). Then we alpha-blend it over the 64 // parent background color to determine the actual color the label text will 65 // sit atop. 66 const SkBitmap& bitmap( 67 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( 68 kBackgroundImages[4])->GetRepresentation(1.0f).sk_bitmap()); 69 SkAutoLockPixels pixel_lock(bitmap); 70 SkColor background_image_color = 71 bitmap.getColor(bitmap.width() / 2, bitmap.height() / 2); 72 // Tricky bit: We alpha blend an opaque version of |background_image_color| 73 // against |parent_background_color| using the original image grid color's 74 // alpha. This is because AlphaBlend(a, b, 255) always returns |a| unchanged 75 // even if |a| is a color with non-255 alpha. 76 text_label_->SetBackgroundColor( 77 color_utils::AlphaBlend(SkColorSetA(background_image_color, 255), 78 parent_background_color, 79 SkColorGetA(background_image_color))); 80 text_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 81 text_label_->SetElideBehavior(views::Label::NO_ELIDE); 82 AddChildView(text_label_); 83 84 LocationBarView::InitTouchableLocationBarChildView(this); 85 86 slide_animator_.SetSlideDuration(kAnimationDurationMS); 87 slide_animator_.SetTweenType(gfx::Tween::LINEAR); 88} 89 90ContentSettingImageView::~ContentSettingImageView() { 91 if (bubble_widget_) 92 bubble_widget_->RemoveObserver(this); 93} 94 95void ContentSettingImageView::UpdatePreLayout( 96 content::WebContents* web_contents) { 97 // Note: We explicitly want to call this even if |web_contents| is NULL, so we 98 // get hidden properly while the user is editing the omnibox. 99 content_setting_image_model_->UpdateFromWebContents(web_contents); 100 101 if (!content_setting_image_model_->is_visible()) { 102 SetVisible(false); 103 return; 104 } 105 106 icon_->SetImage(ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( 107 content_setting_image_model_->get_icon())); 108 icon_->SetTooltipText( 109 UTF8ToUTF16(content_setting_image_model_->get_tooltip())); 110 SetVisible(true); 111 112 // If the content blockage should be indicated to the user, start the 113 // animation and record that we indicated the blockage. 114 TabSpecificContentSettings* content_settings = web_contents ? 115 TabSpecificContentSettings::FromWebContents(web_contents) : NULL; 116 if (!content_settings || content_settings->IsBlockageIndicated( 117 content_setting_image_model_->get_content_settings_type())) 118 return; 119 120 // We just ignore this blockage if we're already showing some other string to 121 // the user. If this becomes a problem, we could design some sort of queueing 122 // mechanism to show one after the other, but it doesn't seem important now. 123 int string_id = content_setting_image_model_->explanatory_string_id(); 124 if (string_id && !background_showing()) { 125 text_label_->SetText(l10n_util::GetStringUTF16(string_id)); 126 text_label_->SetVisible(true); 127 slide_animator_.Show(); 128 } 129 // Since indicating blockage may include showing a bubble, which must be done 130 // in UpdatePostLayout() in order for the bubble to have the right anchor 131 // coordinates, we delay calling SetBlockageHasBeeenIndicated() until that 132 // function completes. 133} 134 135void ContentSettingImageView::UpdatePostLayout( 136 content::WebContents* web_contents) { 137 if (!content_setting_image_model_->is_visible()) 138 return; 139 140 TabSpecificContentSettings* content_settings = web_contents ? 141 TabSpecificContentSettings::FromWebContents(web_contents) : NULL; 142 if (!content_settings) 143 return; 144 145 if (!content_settings->IsBlockageIndicated( 146 content_setting_image_model_->get_content_settings_type())) { 147 if (content_setting_image_model_->ShouldShowBubbleOnBlockage()) 148 CreateBubble(web_contents); 149 content_settings->SetBlockageHasBeenIndicated( 150 content_setting_image_model_->get_content_settings_type()); 151 } 152} 153 154// static 155int ContentSettingImageView::GetBubbleOuterPadding(bool by_icon) { 156 return LocationBarView::GetItemPadding() - LocationBarView::kBubblePadding + 157 (by_icon ? 0 : LocationBarView::kIconInternalPadding); 158} 159 160void ContentSettingImageView::AnimationEnded(const gfx::Animation* animation) { 161 slide_animator_.Reset(); 162 if (!pause_animation_) { 163 text_label_->SetVisible(false); 164 parent_->Layout(); 165 parent_->SchedulePaint(); 166 } 167} 168 169void ContentSettingImageView::AnimationProgressed( 170 const gfx::Animation* animation) { 171 if (!pause_animation_) { 172 parent_->Layout(); 173 parent_->SchedulePaint(); 174 } 175} 176 177void ContentSettingImageView::AnimationCanceled( 178 const gfx::Animation* animation) { 179 AnimationEnded(animation); 180} 181 182gfx::Size ContentSettingImageView::GetPreferredSize() { 183 // Height will be ignored by the LocationBarView. 184 gfx::Size size(icon_->GetPreferredSize()); 185 if (background_showing()) { 186 double state = slide_animator_.GetCurrentValue(); 187 // The fraction of the animation we'll spend animating the string into view, 188 // which is also the fraction we'll spend animating it closed; total 189 // animation (slide out, show, then slide in) is 1.0. 190 const double kOpenFraction = 191 static_cast<double>(kOpenTimeMS) / kAnimationDurationMS; 192 double size_fraction = 1.0; 193 if (state < kOpenFraction) 194 size_fraction = state / kOpenFraction; 195 if (state > (1.0 - kOpenFraction)) 196 size_fraction = (1.0 - state) / kOpenFraction; 197 size.Enlarge( 198 size_fraction * (text_label_->GetPreferredSize().width() + 199 GetTotalSpacingWhileAnimating()), 0); 200 size.SetToMax(background_painter_->GetMinimumSize()); 201 } 202 return size; 203} 204 205void ContentSettingImageView::Layout() { 206 const int icon_width = icon_->GetPreferredSize().width(); 207 icon_->SetBounds( 208 std::min((width() - icon_width) / 2, GetBubbleOuterPadding(true)), 0, 209 icon_width, height()); 210 text_label_->SetBounds( 211 icon_->bounds().right() + LocationBarView::GetItemPadding(), 0, 212 std::max(width() - GetTotalSpacingWhileAnimating() - icon_width, 0), 213 text_label_->GetPreferredSize().height()); 214} 215 216bool ContentSettingImageView::OnMousePressed(const ui::MouseEvent& event) { 217 // We want to show the bubble on mouse release; that is the standard behavior 218 // for buttons. 219 return true; 220} 221 222void ContentSettingImageView::OnMouseReleased(const ui::MouseEvent& event) { 223 if (HitTestPoint(event.location())) 224 OnClick(); 225} 226 227void ContentSettingImageView::OnGestureEvent(ui::GestureEvent* event) { 228 if (event->type() == ui::ET_GESTURE_TAP) 229 OnClick(); 230 if ((event->type() == ui::ET_GESTURE_TAP) || 231 (event->type() == ui::ET_GESTURE_TAP_DOWN)) 232 event->SetHandled(); 233} 234 235void ContentSettingImageView::OnPaintBackground(gfx::Canvas* canvas) { 236 if (background_showing()) 237 background_painter_->Paint(canvas, size()); 238} 239 240void ContentSettingImageView::OnWidgetDestroying(views::Widget* widget) { 241 DCHECK_EQ(bubble_widget_, widget); 242 bubble_widget_->RemoveObserver(this); 243 bubble_widget_ = NULL; 244 245 if (pause_animation_) { 246 slide_animator_.Reset(pause_animation_state_); 247 pause_animation_ = false; 248 slide_animator_.Show(); 249 } 250} 251 252int ContentSettingImageView::GetTotalSpacingWhileAnimating() const { 253 return GetBubbleOuterPadding(true) + LocationBarView::GetItemPadding() + 254 GetBubbleOuterPadding(false); 255} 256 257void ContentSettingImageView::OnClick() { 258 if (slide_animator_.is_animating()) { 259 if (!pause_animation_) { 260 pause_animation_ = true; 261 pause_animation_state_ = slide_animator_.GetCurrentValue(); 262 } 263 slide_animator_.Reset(); 264 } 265 266 content::WebContents* web_contents = parent_->GetWebContents(); 267 if (web_contents && !bubble_widget_) { 268 CreateBubble(web_contents); 269 } 270} 271 272void ContentSettingImageView::CreateBubble(content::WebContents* web_contents) { 273 bubble_widget_ = 274 parent_->delegate()->CreateViewsBubble(new ContentSettingBubbleContents( 275 ContentSettingBubbleModel::CreateContentSettingBubbleModel( 276 parent_->delegate()->GetContentSettingBubbleModelDelegate(), 277 web_contents, parent_->profile(), 278 content_setting_image_model_->get_content_settings_type()), 279 this, views::BubbleBorder::TOP_RIGHT)); 280 bubble_widget_->AddObserver(this); 281 bubble_widget_->Show(); 282} 283 284