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