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