| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/views/location_bar/content_setting_image_view.h" |
| |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/content_settings/tab_specific_content_settings.h" |
| #include "chrome/browser/ui/content_settings/content_setting_bubble_model.h" |
| #include "chrome/browser/ui/content_settings/content_setting_image_model.h" |
| #include "chrome/browser/ui/views/content_setting_bubble_contents.h" |
| #include "chrome/browser/ui/views/location_bar/location_bar_view.h" |
| #include "grit/theme_resources.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/gfx/color_utils.h" |
| #include "ui/views/controls/image_view.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/widget/widget.h" |
| |
| |
| namespace { |
| const int kBackgroundImages[] = IMAGE_GRID(IDR_OMNIBOX_CONTENT_SETTING_BUBBLE); |
| const int kStayOpenTimeMS = 3200; // Time spent with animation fully open. |
| } |
| |
| |
| // static |
| const int ContentSettingImageView::kOpenTimeMS = 150; |
| const int ContentSettingImageView::kAnimationDurationMS = |
| (kOpenTimeMS * 2) + kStayOpenTimeMS; |
| |
| ContentSettingImageView::ContentSettingImageView( |
| ContentSettingsType content_type, |
| LocationBarView* parent, |
| const gfx::FontList& font_list, |
| int font_y_offset, |
| SkColor text_color, |
| SkColor parent_background_color) |
| : parent_(parent), |
| content_setting_image_model_( |
| ContentSettingImageModel::CreateContentSettingImageModel( |
| content_type)), |
| background_painter_( |
| views::Painter::CreateImageGridPainter(kBackgroundImages)), |
| icon_(new views::ImageView), |
| text_label_(new views::Label(string16(), font_list)), |
| slide_animator_(this), |
| pause_animation_(false), |
| pause_animation_state_(0.0), |
| bubble_widget_(NULL) { |
| icon_->SetHorizontalAlignment(views::ImageView::LEADING); |
| AddChildView(icon_); |
| |
| text_label_->SetVisible(false); |
| text_label_->set_border( |
| views::Border::CreateEmptyBorder(font_y_offset, 0, 0, 0)); |
| text_label_->SetEnabledColor(text_color); |
| // Calculate the actual background color for the label. The background images |
| // are painted atop |parent_background_color|. We grab the color of the |
| // middle pixel of the middle image of the background, which we treat as the |
| // representative color of the entire background (reasonable, given the |
| // current appearance of these images). Then we alpha-blend it over the |
| // parent background color to determine the actual color the label text will |
| // sit atop. |
| const SkBitmap& bitmap( |
| ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( |
| kBackgroundImages[4])->GetRepresentation( |
| ui::SCALE_FACTOR_100P).sk_bitmap()); |
| SkAutoLockPixels pixel_lock(bitmap); |
| SkColor background_image_color = |
| bitmap.getColor(bitmap.width() / 2, bitmap.height() / 2); |
| // Tricky bit: We alpha blend an opaque version of |background_image_color| |
| // against |parent_background_color| using the original image grid color's |
| // alpha. This is because AlphaBlend(a, b, 255) always returns |a| unchanged |
| // even if |a| is a color with non-255 alpha. |
| text_label_->SetBackgroundColor( |
| color_utils::AlphaBlend(SkColorSetA(background_image_color, 255), |
| parent_background_color, |
| SkColorGetA(background_image_color))); |
| text_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| text_label_->SetElideBehavior(views::Label::NO_ELIDE); |
| AddChildView(text_label_); |
| |
| LocationBarView::InitTouchableLocationBarChildView(this); |
| |
| slide_animator_.SetSlideDuration(kAnimationDurationMS); |
| slide_animator_.SetTweenType(gfx::Tween::LINEAR); |
| } |
| |
| ContentSettingImageView::~ContentSettingImageView() { |
| if (bubble_widget_) |
| bubble_widget_->RemoveObserver(this); |
| } |
| |
| void ContentSettingImageView::UpdatePreLayout( |
| content::WebContents* web_contents) { |
| // Note: We explicitly want to call this even if |web_contents| is NULL, so we |
| // get hidden properly while the user is editing the omnibox. |
| content_setting_image_model_->UpdateFromWebContents(web_contents); |
| |
| if (!content_setting_image_model_->is_visible()) { |
| SetVisible(false); |
| return; |
| } |
| |
| icon_->SetImage(ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( |
| content_setting_image_model_->get_icon())); |
| icon_->SetTooltipText( |
| UTF8ToUTF16(content_setting_image_model_->get_tooltip())); |
| SetVisible(true); |
| |
| // If the content blockage should be indicated to the user, start the |
| // animation and record that we indicated the blockage. |
| TabSpecificContentSettings* content_settings = web_contents ? |
| TabSpecificContentSettings::FromWebContents(web_contents) : NULL; |
| if (!content_settings || content_settings->IsBlockageIndicated( |
| content_setting_image_model_->get_content_settings_type())) |
| return; |
| |
| // We just ignore this blockage if we're already showing some other string to |
| // the user. If this becomes a problem, we could design some sort of queueing |
| // mechanism to show one after the other, but it doesn't seem important now. |
| int string_id = content_setting_image_model_->explanatory_string_id(); |
| if (string_id && !background_showing()) { |
| text_label_->SetText(l10n_util::GetStringUTF16(string_id)); |
| text_label_->SetVisible(true); |
| slide_animator_.Show(); |
| } |
| // Since indicating blockage may include showing a bubble, which must be done |
| // in UpdatePostLayout() in order for the bubble to have the right anchor |
| // coordinates, we delay calling SetBlockageHasBeeenIndicated() until that |
| // function completes. |
| } |
| |
| void ContentSettingImageView::UpdatePostLayout( |
| content::WebContents* web_contents) { |
| if (!content_setting_image_model_->is_visible()) |
| return; |
| |
| TabSpecificContentSettings* content_settings = web_contents ? |
| TabSpecificContentSettings::FromWebContents(web_contents) : NULL; |
| if (!content_settings) |
| return; |
| |
| if (!content_settings->IsBlockageIndicated( |
| content_setting_image_model_->get_content_settings_type())) { |
| if (content_setting_image_model_->ShouldShowBubbleOnBlockage()) |
| CreateBubble(web_contents); |
| content_settings->SetBlockageHasBeenIndicated( |
| content_setting_image_model_->get_content_settings_type()); |
| } |
| } |
| |
| // static |
| int ContentSettingImageView::GetBubbleOuterPadding(bool by_icon) { |
| return LocationBarView::GetItemPadding() - LocationBarView::kBubblePadding + |
| (by_icon ? 0 : LocationBarView::kIconInternalPadding); |
| } |
| |
| void ContentSettingImageView::AnimationEnded(const gfx::Animation* animation) { |
| slide_animator_.Reset(); |
| if (!pause_animation_) { |
| text_label_->SetVisible(false); |
| parent_->Layout(); |
| parent_->SchedulePaint(); |
| } |
| } |
| |
| void ContentSettingImageView::AnimationProgressed( |
| const gfx::Animation* animation) { |
| if (!pause_animation_) { |
| parent_->Layout(); |
| parent_->SchedulePaint(); |
| } |
| } |
| |
| void ContentSettingImageView::AnimationCanceled( |
| const gfx::Animation* animation) { |
| AnimationEnded(animation); |
| } |
| |
| gfx::Size ContentSettingImageView::GetPreferredSize() { |
| // Height will be ignored by the LocationBarView. |
| gfx::Size size(icon_->GetPreferredSize()); |
| if (background_showing()) { |
| double state = slide_animator_.GetCurrentValue(); |
| // The fraction of the animation we'll spend animating the string into view, |
| // which is also the fraction we'll spend animating it closed; total |
| // animation (slide out, show, then slide in) is 1.0. |
| const double kOpenFraction = |
| static_cast<double>(kOpenTimeMS) / kAnimationDurationMS; |
| double size_fraction = 1.0; |
| if (state < kOpenFraction) |
| size_fraction = state / kOpenFraction; |
| if (state > (1.0 - kOpenFraction)) |
| size_fraction = (1.0 - state) / kOpenFraction; |
| size.Enlarge( |
| size_fraction * (text_label_->GetPreferredSize().width() + |
| GetTotalSpacingWhileAnimating()), 0); |
| size.SetToMax(background_painter_->GetMinimumSize()); |
| } |
| return size; |
| } |
| |
| void ContentSettingImageView::Layout() { |
| const int icon_width = icon_->GetPreferredSize().width(); |
| icon_->SetBounds( |
| std::min((width() - icon_width) / 2, GetBubbleOuterPadding(true)), 0, |
| icon_width, height()); |
| text_label_->SetBounds( |
| icon_->bounds().right() + LocationBarView::GetItemPadding(), 0, |
| std::max(width() - GetTotalSpacingWhileAnimating() - icon_width, 0), |
| text_label_->GetPreferredSize().height()); |
| } |
| |
| bool ContentSettingImageView::OnMousePressed(const ui::MouseEvent& event) { |
| // We want to show the bubble on mouse release; that is the standard behavior |
| // for buttons. |
| return true; |
| } |
| |
| void ContentSettingImageView::OnMouseReleased(const ui::MouseEvent& event) { |
| if (HitTestPoint(event.location())) |
| OnClick(); |
| } |
| |
| void ContentSettingImageView::OnGestureEvent(ui::GestureEvent* event) { |
| if (event->type() == ui::ET_GESTURE_TAP) |
| OnClick(); |
| if ((event->type() == ui::ET_GESTURE_TAP) || |
| (event->type() == ui::ET_GESTURE_TAP_DOWN)) |
| event->SetHandled(); |
| } |
| |
| void ContentSettingImageView::OnPaintBackground(gfx::Canvas* canvas) { |
| if (background_showing()) |
| background_painter_->Paint(canvas, size()); |
| } |
| |
| void ContentSettingImageView::OnWidgetDestroying(views::Widget* widget) { |
| DCHECK_EQ(bubble_widget_, widget); |
| bubble_widget_->RemoveObserver(this); |
| bubble_widget_ = NULL; |
| |
| if (pause_animation_) { |
| slide_animator_.Reset(pause_animation_state_); |
| pause_animation_ = false; |
| slide_animator_.Show(); |
| } |
| } |
| |
| int ContentSettingImageView::GetTotalSpacingWhileAnimating() const { |
| return GetBubbleOuterPadding(true) + LocationBarView::GetItemPadding() + |
| GetBubbleOuterPadding(false); |
| } |
| |
| void ContentSettingImageView::OnClick() { |
| if (slide_animator_.is_animating()) { |
| if (!pause_animation_) { |
| pause_animation_ = true; |
| pause_animation_state_ = slide_animator_.GetCurrentValue(); |
| } |
| slide_animator_.Reset(); |
| } |
| |
| content::WebContents* web_contents = parent_->GetWebContents(); |
| if (web_contents && !bubble_widget_) { |
| CreateBubble(web_contents); |
| } |
| } |
| |
| void ContentSettingImageView::CreateBubble(content::WebContents* web_contents) { |
| bubble_widget_ = |
| parent_->delegate()->CreateViewsBubble(new ContentSettingBubbleContents( |
| ContentSettingBubbleModel::CreateContentSettingBubbleModel( |
| parent_->delegate()->GetContentSettingBubbleModelDelegate(), |
| web_contents, parent_->profile(), |
| content_setting_image_model_->get_content_settings_type()), |
| web_contents, this, views::BubbleBorder::TOP_RIGHT)); |
| bubble_widget_->AddObserver(this); |
| bubble_widget_->Show(); |
| } |
| |