1 // Copyright (c) 2011 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/command_line.h" 8 #include "base/utf_string_conversions.h" 9 #include "chrome/browser/content_setting_bubble_model.h" 10 #include "chrome/browser/content_setting_image_model.h" 11 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" 12 #include "chrome/browser/ui/views/content_setting_bubble_contents.h" 13 #include "chrome/browser/ui/views/location_bar/location_bar_view.h" 14 #include "chrome/common/chrome_switches.h" 15 #include "content/browser/tab_contents/tab_contents.h" 16 #include "third_party/skia/include/core/SkShader.h" 17 #include "ui/base/l10n/l10n_util.h" 18 #include "ui/base/resource/resource_bundle.h" 19 #include "ui/gfx/canvas.h" 20 #include "ui/gfx/canvas_skia.h" 21 #include "ui/gfx/skia_util.h" 22 #include "views/border.h" 23 24 namespace { 25 // Animation parameters. 26 const int kOpenTimeMs = 150; 27 const int kFullOpenedTimeMs = 3200; 28 const int kMoveTimeMs = kFullOpenedTimeMs + 2 * kOpenTimeMs; 29 const int kFrameRateHz = 60; 30 // Colors for the animated box. 31 const SkColor kTopBoxColor = SkColorSetRGB(0xff, 0xf8, 0xd4); 32 const SkColor kBottomBoxColor = SkColorSetRGB(0xff, 0xe6, 0xaf); 33 const SkColor kBorderColor = SkColorSetRGB(0xe9, 0xb9, 0x66); 34 // Corner radius of the animated box. 35 const SkScalar kBoxCornerRadius = 2; 36 // Margins for animated box. 37 const int kTextMarginPixels = 4; 38 const int kIconLeftMargin = 4; 39 40 41 // The fraction of the animation we'll spend animating the string into view, and 42 // then again animating it closed - total animation (slide out, show, then 43 // slide in) is 1.0. 44 const double kAnimatingFraction = kOpenTimeMs * 1.0 / kMoveTimeMs; 45 } 46 47 ContentSettingImageView::ContentSettingImageView( 48 ContentSettingsType content_type, 49 LocationBarView* parent, 50 Profile* profile) 51 : ui::LinearAnimation(kMoveTimeMs, kFrameRateHz, NULL), 52 content_setting_image_model_( 53 ContentSettingImageModel::CreateContentSettingImageModel( 54 content_type)), 55 parent_(parent), 56 profile_(profile), 57 bubble_(NULL), 58 animation_in_progress_(false), 59 text_size_(0), 60 visible_text_size_(0) { 61 SetHorizontalAlignment(ImageView::LEADING); 62 } 63 64 ContentSettingImageView::~ContentSettingImageView() { 65 if (bubble_) 66 bubble_->Close(); 67 } 68 69 void ContentSettingImageView::UpdateFromTabContents(TabContents* tab_contents) { 70 content_setting_image_model_->UpdateFromTabContents(tab_contents); 71 if (!content_setting_image_model_->is_visible()) { 72 SetVisible(false); 73 return; 74 } 75 SetImage(ResourceBundle::GetSharedInstance().GetBitmapNamed( 76 content_setting_image_model_->get_icon())); 77 SetTooltipText(UTF8ToWide(content_setting_image_model_->get_tooltip())); 78 SetVisible(true); 79 80 TabSpecificContentSettings* content_settings = tab_contents ? 81 tab_contents->GetTabSpecificContentSettings() : NULL; 82 if (!content_settings || content_settings->IsBlockageIndicated( 83 content_setting_image_model_->get_content_settings_type())) 84 return; 85 86 // The content blockage was not yet indicated to the user. Start indication 87 // animation and clear "not yet shown" flag. 88 content_settings->SetBlockageHasBeenIndicated( 89 content_setting_image_model_->get_content_settings_type()); 90 91 int animated_string_id = 92 content_setting_image_model_->explanatory_string_id(); 93 // Check if the animation is enabled and if the string for animation is 94 // available. 95 if (CommandLine::ForCurrentProcess()->HasSwitch( 96 switches::kDisableBlockContentAnimation) || !animated_string_id) 97 return; 98 99 // Do not start animation if already in progress. 100 if (!animation_in_progress_) { 101 animation_in_progress_ = true; 102 // Initialize animated string. It will be cleared when animation is 103 // completed. 104 animated_text_ = l10n_util::GetStringUTF16(animated_string_id); 105 text_size_ = ResourceBundle::GetSharedInstance().GetFont( 106 ResourceBundle::MediumFont).GetStringWidth(animated_text_); 107 text_size_ += 2 * kTextMarginPixels + kIconLeftMargin; 108 if (border()) 109 border()->GetInsets(&saved_insets_); 110 Start(); 111 } 112 } 113 114 gfx::Size ContentSettingImageView::GetPreferredSize() { 115 gfx::Size preferred_size(views::ImageView::GetPreferredSize()); 116 // When view is animated visible_text_size_ > 0, it is 0 otherwise. 117 preferred_size.set_width(preferred_size.width() + visible_text_size_); 118 return preferred_size; 119 } 120 121 bool ContentSettingImageView::OnMousePressed(const views::MouseEvent& event) { 122 // We want to show the bubble on mouse release; that is the standard behavior 123 // for buttons. 124 return true; 125 } 126 127 void ContentSettingImageView::OnMouseReleased(const views::MouseEvent& event) { 128 if (!HitTest(event.location())) 129 return; 130 131 TabContents* tab_contents = parent_->GetTabContentsWrapper()->tab_contents(); 132 if (!tab_contents) 133 return; 134 135 // Prerender does not have a bubble. 136 ContentSettingsType content_settings_type = 137 content_setting_image_model_->get_content_settings_type(); 138 if (content_settings_type == CONTENT_SETTINGS_TYPE_PRERENDER) 139 return; 140 141 gfx::Rect screen_bounds(GetImageBounds()); 142 gfx::Point origin(screen_bounds.origin()); 143 views::View::ConvertPointToScreen(this, &origin); 144 screen_bounds.set_origin(origin); 145 ContentSettingBubbleContents* bubble_contents = 146 new ContentSettingBubbleContents( 147 ContentSettingBubbleModel::CreateContentSettingBubbleModel( 148 tab_contents, profile_, content_settings_type), 149 profile_, tab_contents); 150 bubble_ = Bubble::Show(GetWidget(), screen_bounds, BubbleBorder::TOP_RIGHT, 151 bubble_contents, this); 152 bubble_contents->set_bubble(bubble_); 153 } 154 155 void ContentSettingImageView::VisibilityChanged(View* starting_from, 156 bool is_visible) { 157 if (!is_visible && bubble_) 158 bubble_->Close(); 159 } 160 161 void ContentSettingImageView::OnPaint(gfx::Canvas* canvas) { 162 gfx::Insets current_insets; 163 if (border()) 164 border()->GetInsets(¤t_insets); 165 // During the animation we draw a border, an icon and the text. The text area 166 // is changing in size during the animation, giving the appearance of the text 167 // sliding out and then back in. When the text completely slid out the yellow 168 // border is no longer painted around the icon. |visible_text_size_| is 0 when 169 // animation is stopped. 170 int necessary_left_margin = std::min(kIconLeftMargin, visible_text_size_); 171 if (necessary_left_margin != current_insets.left() - saved_insets_.left()) { 172 // In the non-animated state borders' left() is 0, in the animated state it 173 // is the kIconLeftMargin, so we need to animate border reduction when it 174 // starts to disappear. 175 views::Border* empty_border = views::Border::CreateEmptyBorder( 176 saved_insets_.top(), 177 saved_insets_.left() + necessary_left_margin, 178 saved_insets_.bottom(), 179 saved_insets_.right()); 180 set_border(empty_border); 181 } 182 // Paint an icon with possibly non-empty left border. 183 views::ImageView::OnPaint(canvas); 184 if (animation_in_progress_) { 185 // Paint text to the right of the icon. 186 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 187 canvas->DrawStringInt(animated_text_, 188 rb.GetFont(ResourceBundle::MediumFont), SK_ColorBLACK, 189 GetImageBounds().right() + kTextMarginPixels, y(), 190 width() - GetImageBounds().width(), height(), 191 gfx::Canvas::TEXT_ALIGN_LEFT | gfx::Canvas::TEXT_VALIGN_MIDDLE); 192 } 193 } 194 195 void ContentSettingImageView::OnPaintBackground(gfx::Canvas* canvas) { 196 if (!animation_in_progress_) { 197 views::ImageView::OnPaintBackground(canvas); 198 return; 199 } 200 // Paint yellow gradient background if in animation mode. 201 const int kEdgeThickness = 1; 202 SkPaint paint; 203 paint.setShader(gfx::CreateGradientShader(kEdgeThickness, 204 height() - (2 * kEdgeThickness), 205 kTopBoxColor, kBottomBoxColor)); 206 SkSafeUnref(paint.getShader()); 207 SkRect color_rect; 208 color_rect.iset(0, 0, width() - 1, height() - 1); 209 canvas->AsCanvasSkia()->drawRoundRect(color_rect, kBoxCornerRadius, 210 kBoxCornerRadius, paint); 211 SkPaint outer_paint; 212 outer_paint.setStyle(SkPaint::kStroke_Style); 213 outer_paint.setColor(kBorderColor); 214 color_rect.inset(SkIntToScalar(kEdgeThickness), 215 SkIntToScalar(kEdgeThickness)); 216 canvas->AsCanvasSkia()->drawRoundRect(color_rect, kBoxCornerRadius, 217 kBoxCornerRadius, outer_paint); 218 } 219 220 void ContentSettingImageView::BubbleClosing(Bubble* bubble, 221 bool closed_by_escape) { 222 bubble_ = NULL; 223 } 224 225 bool ContentSettingImageView::CloseOnEscape() { 226 return true; 227 } 228 229 bool ContentSettingImageView::FadeInOnShow() { 230 return false; 231 } 232 233 void ContentSettingImageView::AnimateToState(double state) { 234 if (state >= 1.0) { 235 // Animaton is over, clear the variables. 236 animation_in_progress_ = false; 237 visible_text_size_ = 0; 238 } else if (state < kAnimatingFraction) { 239 visible_text_size_ = static_cast<int>(text_size_ * state / 240 kAnimatingFraction); 241 } else if (state > (1.0 - kAnimatingFraction)) { 242 visible_text_size_ = static_cast<int>(text_size_ * (1.0 - state) / 243 kAnimatingFraction); 244 } else { 245 visible_text_size_ = text_size_; 246 } 247 parent_->Layout(); 248 parent_->SchedulePaint(); 249 } 250 251