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