Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / views / download / download_item_view.cc
blobffc4865271f3b81de04f4466f635ea2360c346bd
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.
5 #include "chrome/browser/ui/views/download/download_item_view.h"
7 #include <algorithm>
8 #include <vector>
10 #include "base/bind.h"
11 #include "base/callback.h"
12 #include "base/files/file_path.h"
13 #include "base/i18n/break_iterator.h"
14 #include "base/i18n/rtl.h"
15 #include "base/metrics/histogram.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/strings/sys_string_conversions.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "chrome/browser/browser_process.h"
21 #include "chrome/browser/download/chrome_download_manager_delegate.h"
22 #include "chrome/browser/download/download_item_model.h"
23 #include "chrome/browser/download/download_stats.h"
24 #include "chrome/browser/download/drag_download_item.h"
25 #include "chrome/browser/safe_browsing/download_feedback_service.h"
26 #include "chrome/browser/safe_browsing/download_protection_service.h"
27 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
28 #include "chrome/browser/themes/theme_properties.h"
29 #include "chrome/browser/ui/views/download/download_shelf_context_menu_view.h"
30 #include "chrome/browser/ui/views/download/download_shelf_view.h"
31 #include "content/public/browser/download_danger_type.h"
32 #include "grit/generated_resources.h"
33 #include "grit/theme_resources.h"
34 #include "third_party/icu/source/common/unicode/uchar.h"
35 #include "ui/base/accessibility/accessible_view_state.h"
36 #include "ui/base/l10n/l10n_util.h"
37 #include "ui/base/resource/resource_bundle.h"
38 #include "ui/base/theme_provider.h"
39 #include "ui/events/event.h"
40 #include "ui/gfx/animation/slide_animation.h"
41 #include "ui/gfx/canvas.h"
42 #include "ui/gfx/color_utils.h"
43 #include "ui/gfx/image/image.h"
44 #include "ui/gfx/text_elider.h"
45 #include "ui/views/controls/button/label_button.h"
46 #include "ui/views/controls/label.h"
47 #include "ui/views/mouse_constants.h"
48 #include "ui/views/widget/root_view.h"
49 #include "ui/views/widget/widget.h"
51 // TODO(paulg): These may need to be adjusted when download progress
52 // animation is added, and also possibly to take into account
53 // different screen resolutions.
54 static const int kTextWidth = 140; // Pixels
55 static const int kDangerousTextWidth = 200; // Pixels
56 static const int kVerticalPadding = 3; // Pixels
57 static const int kVerticalTextPadding = 2; // Pixels
58 static const int kTooltipMaxWidth = 800; // Pixels
60 // We add some padding before the left image so that the progress animation icon
61 // hides the corners of the left image.
62 static const int kLeftPadding = 0; // Pixels.
64 // The space between the Save and Discard buttons when prompting for a dangerous
65 // download.
66 static const int kButtonPadding = 5; // Pixels.
68 // The space on the left and right side of the dangerous download label.
69 static const int kLabelPadding = 4; // Pixels.
71 static const SkColor kFileNameDisabledColor = SkColorSetRGB(171, 192, 212);
73 // How long the 'download complete' animation should last for.
74 static const int kCompleteAnimationDurationMs = 2500;
76 // How long the 'download interrupted' animation should last for.
77 static const int kInterruptedAnimationDurationMs = 2500;
79 // How long we keep the item disabled after the user clicked it to open the
80 // downloaded item.
81 static const int kDisabledOnOpenDuration = 3000;
83 // Darken light-on-dark download status text by 20% before drawing, thus
84 // creating a "muted" version of title text for both dark-on-light and
85 // light-on-dark themes.
86 static const double kDownloadItemLuminanceMod = 0.8;
88 using content::DownloadItem;
90 DownloadItemView::DownloadItemView(DownloadItem* download_item,
91 DownloadShelfView* parent)
92 : warning_icon_(NULL),
93 shelf_(parent),
94 status_text_(l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_STARTING)),
95 body_state_(NORMAL),
96 drop_down_state_(NORMAL),
97 mode_(NORMAL_MODE),
98 progress_angle_(DownloadShelf::kStartAngleDegrees),
99 drop_down_pressed_(false),
100 dragging_(false),
101 starting_drag_(false),
102 model_(download_item),
103 save_button_(NULL),
104 discard_button_(NULL),
105 dangerous_download_label_(NULL),
106 dangerous_download_label_sized_(false),
107 disabled_while_opening_(false),
108 creation_time_(base::Time::Now()),
109 time_download_warning_shown_(base::Time()),
110 weak_ptr_factory_(this) {
111 DCHECK(download());
112 download()->AddObserver(this);
113 set_context_menu_controller(this);
115 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
117 BodyImageSet normal_body_image_set = {
118 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP),
119 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE),
120 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM),
121 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP),
122 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE),
123 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM),
124 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP),
125 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE),
126 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM)
128 normal_body_image_set_ = normal_body_image_set;
130 DropDownImageSet normal_drop_down_image_set = {
131 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_TOP),
132 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE),
133 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM)
135 normal_drop_down_image_set_ = normal_drop_down_image_set;
137 BodyImageSet hot_body_image_set = {
138 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP_H),
139 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE_H),
140 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM_H),
141 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP_H),
142 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE_H),
143 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM_H),
144 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_H),
145 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_H),
146 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_H)
148 hot_body_image_set_ = hot_body_image_set;
150 DropDownImageSet hot_drop_down_image_set = {
151 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_TOP_H),
152 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE_H),
153 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM_H)
155 hot_drop_down_image_set_ = hot_drop_down_image_set;
157 BodyImageSet pushed_body_image_set = {
158 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP_P),
159 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE_P),
160 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM_P),
161 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP_P),
162 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE_P),
163 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM_P),
164 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_P),
165 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_P),
166 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_P)
168 pushed_body_image_set_ = pushed_body_image_set;
170 DropDownImageSet pushed_drop_down_image_set = {
171 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_TOP_P),
172 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_MIDDLE_P),
173 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_MENU_BOTTOM_P)
175 pushed_drop_down_image_set_ = pushed_drop_down_image_set;
177 BodyImageSet dangerous_mode_body_image_set = {
178 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_TOP),
179 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_MIDDLE),
180 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_LEFT_BOTTOM),
181 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_TOP),
182 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_MIDDLE),
183 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_CENTER_BOTTOM),
184 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_TOP_NO_DD),
185 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_MIDDLE_NO_DD),
186 rb.GetImageSkiaNamed(IDR_DOWNLOAD_BUTTON_RIGHT_BOTTOM_NO_DD)
188 dangerous_mode_body_image_set_ = dangerous_mode_body_image_set;
190 malicious_mode_body_image_set_ = normal_body_image_set;
192 LoadIcon();
194 font_list_ = rb.GetFontList(ui::ResourceBundle::BaseFont);
195 box_height_ = std::max<int>(2 * kVerticalPadding + font_list_.GetHeight() +
196 kVerticalTextPadding + font_list_.GetHeight(),
197 2 * kVerticalPadding +
198 normal_body_image_set_.top_left->height() +
199 normal_body_image_set_.bottom_left->height());
201 if (DownloadShelf::kSmallProgressIconSize > box_height_)
202 box_y_ = (DownloadShelf::kSmallProgressIconSize - box_height_) / 2;
203 else
204 box_y_ = 0;
206 body_hover_animation_.reset(new gfx::SlideAnimation(this));
207 drop_hover_animation_.reset(new gfx::SlideAnimation(this));
209 SetAccessibilityFocusable(true);
211 OnDownloadUpdated(download());
212 UpdateDropDownButtonPosition();
215 DownloadItemView::~DownloadItemView() {
216 StopDownloadProgress();
217 download()->RemoveObserver(this);
220 // Progress animation handlers.
222 void DownloadItemView::UpdateDownloadProgress() {
223 progress_angle_ =
224 (progress_angle_ + DownloadShelf::kUnknownIncrementDegrees) %
225 DownloadShelf::kMaxDegrees;
226 SchedulePaint();
229 void DownloadItemView::StartDownloadProgress() {
230 if (progress_timer_.IsRunning())
231 return;
232 progress_timer_.Start(FROM_HERE,
233 base::TimeDelta::FromMilliseconds(DownloadShelf::kProgressRateMs), this,
234 &DownloadItemView::UpdateDownloadProgress);
237 void DownloadItemView::StopDownloadProgress() {
238 progress_timer_.Stop();
241 void DownloadItemView::OnExtractIconComplete(gfx::Image* icon_bitmap) {
242 if (icon_bitmap)
243 shelf_->SchedulePaint();
246 // DownloadObserver interface.
248 // Update the progress graphic on the icon and our text status label
249 // to reflect our current bytes downloaded, time remaining.
250 void DownloadItemView::OnDownloadUpdated(DownloadItem* download_item) {
251 DCHECK_EQ(download(), download_item);
253 if (IsShowingWarningDialog() && !model_.IsDangerous()) {
254 // We have been approved.
255 ClearWarningDialog();
256 } else if (!IsShowingWarningDialog() && model_.IsDangerous()) {
257 ShowWarningDialog();
258 // Force the shelf to layout again as our size has changed.
259 shelf_->Layout();
260 SchedulePaint();
261 } else {
262 base::string16 status_text = model_.GetStatusText();
263 switch (download()->GetState()) {
264 case DownloadItem::IN_PROGRESS:
265 download()->IsPaused() ?
266 StopDownloadProgress() : StartDownloadProgress();
267 LoadIconIfItemPathChanged();
268 break;
269 case DownloadItem::INTERRUPTED:
270 StopDownloadProgress();
271 complete_animation_.reset(new gfx::SlideAnimation(this));
272 complete_animation_->SetSlideDuration(kInterruptedAnimationDurationMs);
273 complete_animation_->SetTweenType(gfx::Tween::LINEAR);
274 complete_animation_->Show();
275 SchedulePaint();
276 LoadIcon();
277 break;
278 case DownloadItem::COMPLETE:
279 if (model_.ShouldRemoveFromShelfWhenComplete()) {
280 shelf_->RemoveDownloadView(this); // This will delete us!
281 return;
283 StopDownloadProgress();
284 complete_animation_.reset(new gfx::SlideAnimation(this));
285 complete_animation_->SetSlideDuration(kCompleteAnimationDurationMs);
286 complete_animation_->SetTweenType(gfx::Tween::LINEAR);
287 complete_animation_->Show();
288 SchedulePaint();
289 LoadIcon();
290 break;
291 case DownloadItem::CANCELLED:
292 StopDownloadProgress();
293 if (complete_animation_)
294 complete_animation_->Stop();
295 LoadIcon();
296 break;
297 default:
298 NOTREACHED();
300 status_text_ = status_text;
303 base::string16 new_tip = model_.GetTooltipText(font_list_, kTooltipMaxWidth);
304 if (new_tip != tooltip_text_) {
305 tooltip_text_ = new_tip;
306 TooltipTextChanged();
309 UpdateAccessibleName();
311 // We use the parent's (DownloadShelfView's) SchedulePaint, since there
312 // are spaces between each DownloadItemView that the parent is responsible
313 // for painting.
314 shelf_->SchedulePaint();
317 void DownloadItemView::OnDownloadDestroyed(DownloadItem* download) {
318 shelf_->RemoveDownloadView(this); // This will delete us!
321 void DownloadItemView::OnDownloadOpened(DownloadItem* download) {
322 disabled_while_opening_ = true;
323 SetEnabled(false);
324 base::MessageLoop::current()->PostDelayedTask(
325 FROM_HERE,
326 base::Bind(&DownloadItemView::Reenable, weak_ptr_factory_.GetWeakPtr()),
327 base::TimeDelta::FromMilliseconds(kDisabledOnOpenDuration));
329 // Notify our parent.
330 shelf_->OpenedDownload(this);
333 // View overrides
335 // In dangerous mode we have to layout our buttons.
336 void DownloadItemView::Layout() {
337 if (IsShowingWarningDialog()) {
338 BodyImageSet* body_image_set =
339 (mode_ == DANGEROUS_MODE) ? &dangerous_mode_body_image_set_ :
340 &malicious_mode_body_image_set_;
341 int x = kLeftPadding + body_image_set->top_left->width() +
342 warning_icon_->width() + kLabelPadding;
343 int y = (height() - dangerous_download_label_->height()) / 2;
344 dangerous_download_label_->SetBounds(x, y,
345 dangerous_download_label_->width(),
346 dangerous_download_label_->height());
347 gfx::Size button_size = GetButtonSize();
348 x += dangerous_download_label_->width() + kLabelPadding;
349 y = (height() - button_size.height()) / 2;
350 if (save_button_) {
351 save_button_->SetBounds(x, y, button_size.width(), button_size.height());
352 x += button_size.width() + kButtonPadding;
354 discard_button_->SetBounds(x, y, button_size.width(), button_size.height());
355 UpdateColorsFromTheme();
359 gfx::Size DownloadItemView::GetPreferredSize() {
360 int width, height;
362 // First, we set the height to the height of two rows or text plus margins.
363 height = 2 * kVerticalPadding + 2 * font_list_.GetHeight() +
364 kVerticalTextPadding;
365 // Then we increase the size if the progress icon doesn't fit.
366 height = std::max<int>(height, DownloadShelf::kSmallProgressIconSize);
368 if (IsShowingWarningDialog()) {
369 BodyImageSet* body_image_set =
370 (mode_ == DANGEROUS_MODE) ? &dangerous_mode_body_image_set_ :
371 &malicious_mode_body_image_set_;
372 width = kLeftPadding + body_image_set->top_left->width();
373 width += warning_icon_->width() + kLabelPadding;
374 width += dangerous_download_label_->width() + kLabelPadding;
375 gfx::Size button_size = GetButtonSize();
376 // Make sure the button fits.
377 height = std::max<int>(height, 2 * kVerticalPadding + button_size.height());
378 // Then we make sure the warning icon fits.
379 height = std::max<int>(height, 2 * kVerticalPadding +
380 warning_icon_->height());
381 if (save_button_)
382 width += button_size.width() + kButtonPadding;
383 width += button_size.width();
384 width += body_image_set->top_right->width();
385 if (mode_ == MALICIOUS_MODE)
386 width += normal_drop_down_image_set_.top->width();
387 } else {
388 width = kLeftPadding + normal_body_image_set_.top_left->width();
389 width += DownloadShelf::kSmallProgressIconSize;
390 width += kTextWidth;
391 width += normal_body_image_set_.top_right->width();
392 width += normal_drop_down_image_set_.top->width();
394 return gfx::Size(width, height);
397 // Handle a mouse click and open the context menu if the mouse is
398 // over the drop-down region.
399 bool DownloadItemView::OnMousePressed(const ui::MouseEvent& event) {
400 HandlePressEvent(event, event.IsOnlyLeftMouseButton());
401 return true;
404 // Handle drag (file copy) operations.
405 bool DownloadItemView::OnMouseDragged(const ui::MouseEvent& event) {
406 // Mouse should not activate us in dangerous mode.
407 if (IsShowingWarningDialog())
408 return true;
410 if (!starting_drag_) {
411 starting_drag_ = true;
412 drag_start_point_ = event.location();
414 if (dragging_) {
415 if (download()->GetState() == DownloadItem::COMPLETE) {
416 IconManager* im = g_browser_process->icon_manager();
417 gfx::Image* icon = im->LookupIconFromFilepath(
418 download()->GetTargetFilePath(), IconLoader::SMALL);
419 if (icon) {
420 views::Widget* widget = GetWidget();
421 DragDownloadItem(
422 download(), icon, widget ? widget->GetNativeView() : NULL);
425 } else if (ExceededDragThreshold(event.location() - drag_start_point_)) {
426 dragging_ = true;
428 return true;
431 void DownloadItemView::OnMouseReleased(const ui::MouseEvent& event) {
432 HandleClickEvent(event, event.IsOnlyLeftMouseButton());
435 void DownloadItemView::OnMouseCaptureLost() {
436 // Mouse should not activate us in dangerous mode.
437 if (mode_ == DANGEROUS_MODE)
438 return;
440 if (dragging_) {
441 // Starting a drag results in a MouseCaptureLost.
442 dragging_ = false;
443 starting_drag_ = false;
445 SetState(NORMAL, NORMAL);
448 void DownloadItemView::OnMouseMoved(const ui::MouseEvent& event) {
449 // Mouse should not activate us in dangerous mode.
450 if (mode_ == DANGEROUS_MODE)
451 return;
453 bool on_body = !InDropDownButtonXCoordinateRange(event.x());
454 SetState(on_body ? HOT : NORMAL, on_body ? NORMAL : HOT);
457 void DownloadItemView::OnMouseExited(const ui::MouseEvent& event) {
458 // Mouse should not activate us in dangerous mode.
459 if (mode_ == DANGEROUS_MODE)
460 return;
462 SetState(NORMAL, drop_down_pressed_ ? PUSHED : NORMAL);
465 bool DownloadItemView::OnKeyPressed(const ui::KeyEvent& event) {
466 // Key press should not activate us in dangerous mode.
467 if (IsShowingWarningDialog())
468 return true;
470 if (event.key_code() == ui::VKEY_SPACE ||
471 event.key_code() == ui::VKEY_RETURN) {
472 OpenDownload();
473 return true;
475 return false;
478 bool DownloadItemView::GetTooltipText(const gfx::Point& p,
479 base::string16* tooltip) const {
480 if (IsShowingWarningDialog()) {
481 tooltip->clear();
482 return false;
485 tooltip->assign(tooltip_text_);
487 return true;
490 void DownloadItemView::GetAccessibleState(ui::AccessibleViewState* state) {
491 state->name = accessible_name_;
492 state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON;
493 if (model_.IsDangerous()) {
494 state->state = ui::AccessibilityTypes::STATE_UNAVAILABLE;
495 } else {
496 state->state = ui::AccessibilityTypes::STATE_HASPOPUP;
500 void DownloadItemView::OnThemeChanged() {
501 UpdateColorsFromTheme();
504 void DownloadItemView::OnGestureEvent(ui::GestureEvent* event) {
505 if (event->type() == ui::ET_GESTURE_TAP_DOWN) {
506 HandlePressEvent(*event, true);
507 event->SetHandled();
508 return;
511 if (event->type() == ui::ET_GESTURE_TAP) {
512 HandleClickEvent(*event, true);
513 event->SetHandled();
514 return;
517 SetState(NORMAL, NORMAL);
518 views::View::OnGestureEvent(event);
521 void DownloadItemView::ShowContextMenuForView(View* source,
522 const gfx::Point& point,
523 ui::MenuSourceType source_type) {
524 // |point| is in screen coordinates. So convert it to local coordinates first.
525 gfx::Point local_point = point;
526 ConvertPointFromScreen(this, &local_point);
527 ShowContextMenuImpl(local_point, source_type);
530 void DownloadItemView::ButtonPressed(views::Button* sender,
531 const ui::Event& event) {
532 base::TimeDelta warning_duration;
533 if (!time_download_warning_shown_.is_null())
534 warning_duration = base::Time::Now() - time_download_warning_shown_;
536 if (save_button_ && sender == save_button_) {
537 // The user has confirmed a dangerous download. We'd record how quickly the
538 // user did this to detect whether we're being clickjacked.
539 UMA_HISTOGRAM_LONG_TIMES("clickjacking.save_download", warning_duration);
540 // This will change the state and notify us.
541 download()->ValidateDangerousDownload();
542 return;
545 // WARNING: all end states after this point delete |this|.
546 DCHECK_EQ(discard_button_, sender);
547 if (model_.IsMalicious()) {
548 UMA_HISTOGRAM_LONG_TIMES("clickjacking.dismiss_download", warning_duration);
549 shelf_->RemoveDownloadView(this);
550 return;
552 if (model_.ShouldAllowDownloadFeedback() && BeginDownloadFeedback())
553 return;
554 UMA_HISTOGRAM_LONG_TIMES("clickjacking.discard_download", warning_duration);
555 download()->Remove();
558 void DownloadItemView::AnimationProgressed(const gfx::Animation* animation) {
559 // We don't care if what animation (body button/drop button/complete),
560 // is calling back, as they all have to go through the same paint call.
561 SchedulePaint();
564 void DownloadItemView::OnPaint(gfx::Canvas* canvas) {
565 OnPaintBackground(canvas);
566 if (HasFocus())
567 canvas->DrawFocusRect(GetLocalBounds());
570 // The DownloadItemView can be in three major modes (NORMAL_MODE, DANGEROUS_MODE
571 // and MALICIOUS_MODE).
573 // NORMAL_MODE: We are displaying an in-progress or completed download.
574 // .-------------------------------+-.
575 // | [icon] Filename |v|
576 // | [ ] Status | |
577 // `-------------------------------+-'
578 // | | \_ Drop down button. Invokes menu. Responds
579 // | | to mouse. (NORMAL, HOT or PUSHED).
580 // | \_ Icon is overlaid on top of in-progress animation.
581 // \_ Both the body and the drop down button respond to mouse hover and can be
582 // pushed (NORMAL, HOT or PUSHED).
584 // DANGEROUS_MODE: The file could be potentially dangerous.
585 // .-------------------------------------------------------.
586 // | [ ! ] [This type of file can ] [ Keep ] [ Discard ] |
587 // | [ ] [destroy your computer..] [ ] [ ] |
588 // `-------------------------------------------------------'
589 // | | | | \_ No drop down button.
590 // | | | \_ Buttons are views::LabelButtons.
591 // | | \_ Text is in a label (dangerous_download_label_)
592 // | \_ Warning icon. No progress animation.
593 // \_ Body is static. Doesn't respond to mouse hover or press. (NORMAL only)
595 // MALICIOUS_MODE: The file is known malware.
596 // .---------------------------------------------+-.
597 // | [ - ] [This file is malicious.] [ Discard ] |v|
598 // | [ ] [ ] [ ] | |-.
599 // `---------------------------------------------+-' |
600 // | | | | Drop down button. Responds to
601 // | | | | mouse.(NORMAL, HOT or PUSHED)
602 // | | | \_ Button is a views::LabelButton.
603 // | | \_ Text is in a label (dangerous_download_label_)
604 // | \_ Warning icon. No progress animation.
605 // \_ Body is static. Doesn't respond to mouse hover or press. (NORMAL only)
607 void DownloadItemView::OnPaintBackground(gfx::Canvas* canvas) {
608 BodyImageSet* body_image_set = NULL;
609 switch (mode_) {
610 case NORMAL_MODE:
611 if (body_state_ == PUSHED)
612 body_image_set = &pushed_body_image_set_;
613 else // NORMAL or HOT
614 body_image_set = &normal_body_image_set_;
615 break;
616 case DANGEROUS_MODE:
617 body_image_set = &dangerous_mode_body_image_set_;
618 break;
619 case MALICIOUS_MODE:
620 body_image_set = &malicious_mode_body_image_set_;
621 break;
622 default:
623 NOTREACHED();
626 DropDownImageSet* drop_down_image_set = NULL;
627 switch (mode_) {
628 case NORMAL_MODE:
629 case MALICIOUS_MODE:
630 if (drop_down_state_ == PUSHED)
631 drop_down_image_set = &pushed_drop_down_image_set_;
632 else // NORMAL or HOT
633 drop_down_image_set = &normal_drop_down_image_set_;
634 break;
635 case DANGEROUS_MODE:
636 // We don't use a drop down button for mode_ == DANGEROUS_MODE. So we let
637 // drop_down_image_set == NULL.
638 break;
639 default:
640 NOTREACHED();
643 int center_width = width() - kLeftPadding -
644 body_image_set->left->width() -
645 body_image_set->right->width() -
646 (drop_down_image_set ?
647 normal_drop_down_image_set_.center->width() :
650 // May be caused by animation.
651 if (center_width <= 0)
652 return;
654 // Draw status before button image to effectively lighten text. No status for
655 // warning dialogs.
656 if (!IsShowingWarningDialog()) {
657 if (!status_text_.empty()) {
658 int mirrored_x = GetMirroredXWithWidthInView(
659 DownloadShelf::kSmallProgressIconSize, kTextWidth);
660 // Add font_list_.height() to compensate for title, which is drawn later.
661 int y = box_y_ + kVerticalPadding + font_list_.GetHeight() +
662 kVerticalTextPadding;
663 SkColor file_name_color = GetThemeProvider()->GetColor(
664 ThemeProperties::COLOR_BOOKMARK_TEXT);
665 // If text is light-on-dark, lightening it alone will do nothing.
666 // Therefore we mute luminance a wee bit before drawing in this case.
667 if (color_utils::RelativeLuminance(file_name_color) > 0.5)
668 file_name_color = SkColorSetRGB(
669 static_cast<int>(kDownloadItemLuminanceMod *
670 SkColorGetR(file_name_color)),
671 static_cast<int>(kDownloadItemLuminanceMod *
672 SkColorGetG(file_name_color)),
673 static_cast<int>(kDownloadItemLuminanceMod *
674 SkColorGetB(file_name_color)));
675 canvas->DrawStringRect(status_text_, font_list_, file_name_color,
676 gfx::Rect(mirrored_x, y, kTextWidth,
677 font_list_.GetHeight()));
681 // Paint the background images.
682 int x = kLeftPadding;
683 canvas->Save();
684 if (base::i18n::IsRTL()) {
685 // Since we do not have the mirrored images for
686 // (hot_)body_image_set->top_left, (hot_)body_image_set->left,
687 // (hot_)body_image_set->bottom_left, and drop_down_image_set,
688 // for RTL UI, we flip the canvas to draw those images mirrored.
689 // Consequently, we do not need to mirror the x-axis of those images.
690 canvas->Translate(gfx::Vector2d(width(), 0));
691 canvas->Scale(-1, 1);
693 PaintImages(canvas,
694 body_image_set->top_left, body_image_set->left,
695 body_image_set->bottom_left,
696 x, box_y_, box_height_, body_image_set->top_left->width());
697 x += body_image_set->top_left->width();
698 PaintImages(canvas,
699 body_image_set->top, body_image_set->center,
700 body_image_set->bottom,
701 x, box_y_, box_height_, center_width);
702 x += center_width;
703 PaintImages(canvas,
704 body_image_set->top_right, body_image_set->right,
705 body_image_set->bottom_right,
706 x, box_y_, box_height_, body_image_set->top_right->width());
708 // Overlay our body hot state. Warning dialogs don't display body a hot state.
709 if (!IsShowingWarningDialog() &&
710 body_hover_animation_->GetCurrentValue() > 0) {
711 canvas->SaveLayerAlpha(
712 static_cast<int>(body_hover_animation_->GetCurrentValue() * 255));
714 int x = kLeftPadding;
715 PaintImages(canvas,
716 hot_body_image_set_.top_left, hot_body_image_set_.left,
717 hot_body_image_set_.bottom_left,
718 x, box_y_, box_height_, hot_body_image_set_.top_left->width());
719 x += body_image_set->top_left->width();
720 PaintImages(canvas,
721 hot_body_image_set_.top, hot_body_image_set_.center,
722 hot_body_image_set_.bottom,
723 x, box_y_, box_height_, center_width);
724 x += center_width;
725 PaintImages(canvas,
726 hot_body_image_set_.top_right, hot_body_image_set_.right,
727 hot_body_image_set_.bottom_right,
728 x, box_y_, box_height_,
729 hot_body_image_set_.top_right->width());
730 canvas->Restore();
733 x += body_image_set->top_right->width();
735 // Paint the drop-down.
736 if (drop_down_image_set) {
737 PaintImages(canvas,
738 drop_down_image_set->top, drop_down_image_set->center,
739 drop_down_image_set->bottom,
740 x, box_y_, box_height_, drop_down_image_set->top->width());
742 // Overlay our drop-down hot state.
743 if (drop_hover_animation_->GetCurrentValue() > 0) {
744 canvas->SaveLayerAlpha(
745 static_cast<int>(drop_hover_animation_->GetCurrentValue() * 255));
747 PaintImages(canvas,
748 drop_down_image_set->top, drop_down_image_set->center,
749 drop_down_image_set->bottom,
750 x, box_y_, box_height_, drop_down_image_set->top->width());
752 canvas->Restore();
756 // Restore the canvas to avoid file name etc. text are drawn flipped.
757 // Consequently, the x-axis of following canvas->DrawXXX() method should be
758 // mirrored so the text and images are down in the right positions.
759 canvas->Restore();
761 // Print the text, left aligned and always print the file extension.
762 // Last value of x was the end of the right image, just before the button.
763 // Note that in dangerous mode we use a label (as the text is multi-line).
764 if (!IsShowingWarningDialog()) {
765 base::string16 filename;
766 if (!disabled_while_opening_) {
767 filename = gfx::ElideFilename(download()->GetFileNameToReportUser(),
768 font_list_, kTextWidth);
769 } else {
770 // First, Calculate the download status opening string width.
771 base::string16 status_string =
772 l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_OPENING,
773 base::string16());
774 int status_string_width = font_list_.GetStringWidth(status_string);
775 // Then, elide the file name.
776 base::string16 filename_string =
777 gfx::ElideFilename(download()->GetFileNameToReportUser(), font_list_,
778 kTextWidth - status_string_width);
779 // Last, concat the whole string.
780 filename = l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_OPENING,
781 filename_string);
784 int mirrored_x = GetMirroredXWithWidthInView(
785 DownloadShelf::kSmallProgressIconSize, kTextWidth);
786 SkColor file_name_color = GetThemeProvider()->GetColor(
787 ThemeProperties::COLOR_BOOKMARK_TEXT);
788 int y =
789 box_y_ + (status_text_.empty() ?
790 ((box_height_ - font_list_.GetHeight()) / 2) : kVerticalPadding);
792 // Draw the file's name.
793 canvas->DrawStringRect(
794 filename, font_list_,
795 enabled() ? file_name_color : kFileNameDisabledColor,
796 gfx::Rect(mirrored_x, y, kTextWidth, font_list_.GetHeight()));
799 // Load the icon.
800 IconManager* im = g_browser_process->icon_manager();
801 gfx::Image* image = im->LookupIconFromFilepath(
802 download()->GetTargetFilePath(), IconLoader::SMALL);
803 const gfx::ImageSkia* icon = NULL;
804 if (IsShowingWarningDialog())
805 icon = warning_icon_;
806 else if (image)
807 icon = image->ToImageSkia();
809 // We count on the fact that the icon manager will cache the icons and if one
810 // is available, it will be cached here. We *don't* want to request the icon
811 // to be loaded here, since this will also get called if the icon can't be
812 // loaded, in which case LookupIcon will always be NULL. The loading will be
813 // triggered only when we think the status might change.
814 if (icon) {
815 if (!IsShowingWarningDialog()) {
816 DownloadItem::DownloadState state = download()->GetState();
817 if (state == DownloadItem::IN_PROGRESS) {
818 DownloadShelf::PaintDownloadProgress(canvas,
819 this,
822 progress_angle_,
823 model_.PercentComplete(),
824 DownloadShelf::SMALL);
825 } else if (complete_animation_.get() &&
826 complete_animation_->is_animating()) {
827 if (state == DownloadItem::INTERRUPTED) {
828 DownloadShelf::PaintDownloadInterrupted(
829 canvas,
830 this,
833 complete_animation_->GetCurrentValue(),
834 DownloadShelf::SMALL);
835 } else {
836 DCHECK_EQ(DownloadItem::COMPLETE, state);
837 DownloadShelf::PaintDownloadComplete(
838 canvas,
839 this,
842 complete_animation_->GetCurrentValue(),
843 DownloadShelf::SMALL);
848 // Draw the icon image.
849 int icon_x, icon_y;
851 if (IsShowingWarningDialog()) {
852 icon_x = kLeftPadding + body_image_set->top_left->width();
853 icon_y = (height() - icon->height()) / 2;
854 } else {
855 icon_x = DownloadShelf::kSmallProgressIconOffset;
856 icon_y = DownloadShelf::kSmallProgressIconOffset;
858 icon_x = GetMirroredXWithWidthInView(icon_x, icon->width());
859 if (enabled()) {
860 canvas->DrawImageInt(*icon, icon_x, icon_y);
861 } else {
862 // Use an alpha to make the image look disabled.
863 SkPaint paint;
864 paint.setAlpha(120);
865 canvas->DrawImageInt(*icon, icon_x, icon_y, paint);
870 void DownloadItemView::OnFocus() {
871 View::OnFocus();
872 // We render differently when focused.
873 SchedulePaint();
876 void DownloadItemView::OnBlur() {
877 View::OnBlur();
878 // We render differently when focused.
879 SchedulePaint();
882 void DownloadItemView::OpenDownload() {
883 DCHECK(!IsShowingWarningDialog());
884 // We're interested in how long it takes users to open downloads. If they
885 // open downloads super quickly, we should be concerned about clickjacking.
886 UMA_HISTOGRAM_LONG_TIMES("clickjacking.open_download",
887 base::Time::Now() - creation_time_);
888 download()->OpenDownload();
889 UpdateAccessibleName();
892 bool DownloadItemView::BeginDownloadFeedback() {
893 #if defined(FULL_SAFE_BROWSING)
894 SafeBrowsingService* sb_service = g_browser_process->safe_browsing_service();
895 if (!sb_service)
896 return false;
897 safe_browsing::DownloadProtectionService* download_protection_service =
898 sb_service->download_protection_service();
899 if (!download_protection_service)
900 return false;
901 base::TimeDelta warning_duration = base::TimeDelta();
902 if (!time_download_warning_shown_.is_null())
903 warning_duration = base::Time::Now() - time_download_warning_shown_;
904 UMA_HISTOGRAM_LONG_TIMES("clickjacking.report_and_discard_download",
905 warning_duration);
906 download_protection_service->feedback_service()->BeginFeedbackForDownload(
907 download());
908 // WARNING: we are deleted at this point. Don't access 'this'.
909 return true;
910 #else
911 NOTREACHED();
912 return false;
913 #endif
916 void DownloadItemView::LoadIcon() {
917 IconManager* im = g_browser_process->icon_manager();
918 last_download_item_path_ = download()->GetTargetFilePath();
919 im->LoadIcon(last_download_item_path_,
920 IconLoader::SMALL,
921 base::Bind(&DownloadItemView::OnExtractIconComplete,
922 base::Unretained(this)),
923 &cancelable_task_tracker_);
926 void DownloadItemView::LoadIconIfItemPathChanged() {
927 base::FilePath current_download_path = download()->GetTargetFilePath();
928 if (last_download_item_path_ == current_download_path)
929 return;
931 LoadIcon();
934 void DownloadItemView::UpdateColorsFromTheme() {
935 if (dangerous_download_label_ && GetThemeProvider()) {
936 dangerous_download_label_->SetEnabledColor(
937 GetThemeProvider()->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT));
941 void DownloadItemView::ShowContextMenuImpl(const gfx::Point& p,
942 ui::MenuSourceType source_type) {
943 gfx::Point point = p;
944 gfx::Size size;
946 // Similar hack as in MenuButton.
947 // We're about to show the menu from a mouse press. By showing from the
948 // mouse press event we block RootView in mouse dispatching. This also
949 // appears to cause RootView to get a mouse pressed BEFORE the mouse
950 // release is seen, which means RootView sends us another mouse press no
951 // matter where the user pressed. To force RootView to recalculate the
952 // mouse target during the mouse press we explicitly set the mouse handler
953 // to NULL.
954 static_cast<views::internal::RootView*>(GetWidget()->GetRootView())->
955 SetMouseHandler(NULL);
957 // If |is_mouse_gesture| is false, |p| is ignored. The menu is shown aligned
958 // to drop down arrow button.
959 if (source_type != ui::MENU_SOURCE_MOUSE &&
960 source_type != ui::MENU_SOURCE_TOUCH) {
961 drop_down_pressed_ = true;
962 SetState(NORMAL, PUSHED);
963 point.SetPoint(drop_down_x_left_, box_y_);
964 size.SetSize(drop_down_x_right_ - drop_down_x_left_, box_height_);
966 // Post a task to release the button. When we call the Run method on the menu
967 // below, it runs an inner message loop that might cause us to be deleted.
968 // Posting a task with a WeakPtr lets us safely handle the button release.
969 base::MessageLoop::current()->PostNonNestableTask(
970 FROM_HERE,
971 base::Bind(&DownloadItemView::ReleaseDropDown,
972 weak_ptr_factory_.GetWeakPtr()));
973 views::View::ConvertPointToScreen(this, &point);
975 if (!context_menu_.get()) {
976 context_menu_.reset(
977 new DownloadShelfContextMenuView(download(), shelf_->GetNavigator()));
979 context_menu_->Run(GetWidget()->GetTopLevelWidget(),
980 gfx::Rect(point, size), source_type);
981 // We could be deleted now.
984 void DownloadItemView::HandlePressEvent(const ui::LocatedEvent& event,
985 bool active_event) {
986 // The event should not activate us in dangerous mode.
987 if (mode_ == DANGEROUS_MODE)
988 return;
990 // Stop any completion animation.
991 if (complete_animation_.get() && complete_animation_->is_animating())
992 complete_animation_->End();
994 if (active_event) {
995 if (InDropDownButtonXCoordinateRange(event.x())) {
996 if (context_menu_.get()) {
997 // Ignore two close clicks. This typically happens when the user clicks
998 // the button to close the menu.
999 base::TimeDelta delta =
1000 base::TimeTicks::Now() - context_menu_->close_time();
1001 if (delta.InMilliseconds() < views::kMinimumMsBetweenButtonClicks)
1002 return;
1004 drop_down_pressed_ = true;
1005 SetState(NORMAL, PUSHED);
1006 // We are setting is_mouse_gesture to false when calling ShowContextMenu
1007 // so that the positioning of the context menu will be similar to a
1008 // keyboard invocation. I.e. we want the menu to always be positioned
1009 // next to the drop down button instead of the next to the pointer.
1010 ShowContextMenuImpl(event.location(), ui::MENU_SOURCE_KEYBOARD);
1011 // Once called, it is possible that *this was deleted (e.g.: due to
1012 // invoking the 'Discard' action.)
1013 } else if (!IsShowingWarningDialog()) {
1014 SetState(PUSHED, NORMAL);
1019 void DownloadItemView::HandleClickEvent(const ui::LocatedEvent& event,
1020 bool active_event) {
1021 // Mouse should not activate us in dangerous mode.
1022 if (mode_ == DANGEROUS_MODE)
1023 return;
1025 if (active_event &&
1026 !InDropDownButtonXCoordinateRange(event.x()) &&
1027 !IsShowingWarningDialog()) {
1028 OpenDownload();
1031 SetState(NORMAL, NORMAL);
1034 // Load an icon for the file type we're downloading, and animate any in progress
1035 // download state.
1036 void DownloadItemView::PaintImages(gfx::Canvas* canvas,
1037 const gfx::ImageSkia* top_image,
1038 const gfx::ImageSkia* center_image,
1039 const gfx::ImageSkia* bottom_image,
1040 int x, int y, int height, int width) {
1041 int middle_height = height - top_image->height() - bottom_image->height();
1042 // Draw the top.
1043 canvas->DrawImageInt(*top_image,
1044 0, 0, top_image->width(), top_image->height(),
1045 x, y, width, top_image->height(), false);
1046 y += top_image->height();
1047 // Draw the center.
1048 canvas->DrawImageInt(*center_image,
1049 0, 0, center_image->width(), center_image->height(),
1050 x, y, width, middle_height, false);
1051 y += middle_height;
1052 // Draw the bottom.
1053 canvas->DrawImageInt(*bottom_image,
1054 0, 0, bottom_image->width(), bottom_image->height(),
1055 x, y, width, bottom_image->height(), false);
1058 void DownloadItemView::SetState(State new_body_state, State new_drop_state) {
1059 // If we are showing a warning dialog, we don't change body state.
1060 if (IsShowingWarningDialog()) {
1061 new_body_state = NORMAL;
1063 // Current body_state_ should always be NORMAL for warning dialogs.
1064 DCHECK_EQ(NORMAL, body_state_);
1065 // We shouldn't be calling SetState if we are in DANGEROUS_MODE.
1066 DCHECK_NE(DANGEROUS_MODE, mode_);
1068 // Avoid extra SchedulePaint()s if the state is going to be the same.
1069 if (body_state_ == new_body_state && drop_down_state_ == new_drop_state)
1070 return;
1072 AnimateStateTransition(body_state_, new_body_state,
1073 body_hover_animation_.get());
1074 AnimateStateTransition(drop_down_state_, new_drop_state,
1075 drop_hover_animation_.get());
1076 body_state_ = new_body_state;
1077 drop_down_state_ = new_drop_state;
1078 SchedulePaint();
1081 void DownloadItemView::ClearWarningDialog() {
1082 DCHECK(download()->GetDangerType() ==
1083 content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED);
1084 DCHECK(mode_ == DANGEROUS_MODE || mode_ == MALICIOUS_MODE);
1086 mode_ = NORMAL_MODE;
1087 body_state_ = NORMAL;
1088 drop_down_state_ = NORMAL;
1090 // Remove the views used by the warning dialog.
1091 if (save_button_) {
1092 RemoveChildView(save_button_);
1093 delete save_button_;
1094 save_button_ = NULL;
1096 RemoveChildView(discard_button_);
1097 delete discard_button_;
1098 discard_button_ = NULL;
1099 RemoveChildView(dangerous_download_label_);
1100 delete dangerous_download_label_;
1101 dangerous_download_label_ = NULL;
1102 dangerous_download_label_sized_ = false;
1103 cached_button_size_.SetSize(0,0);
1105 // Set the accessible name back to the status and filename instead of the
1106 // download warning.
1107 UpdateAccessibleName();
1108 UpdateDropDownButtonPosition();
1110 // We need to load the icon now that the download has the real path.
1111 LoadIcon();
1113 // Force the shelf to layout again as our size has changed.
1114 shelf_->Layout();
1115 shelf_->SchedulePaint();
1117 TooltipTextChanged();
1120 void DownloadItemView::ShowWarningDialog() {
1121 DCHECK(mode_ != DANGEROUS_MODE && mode_ != MALICIOUS_MODE);
1122 time_download_warning_shown_ = base::Time::Now();
1123 content::DownloadDangerType danger_type = download()->GetDangerType();
1124 RecordDangerousDownloadWarningShown(danger_type);
1125 #if defined(FULL_SAFE_BROWSING)
1126 if (model_.ShouldAllowDownloadFeedback()) {
1127 safe_browsing::DownloadFeedbackService::RecordEligibleDownloadShown(
1128 danger_type);
1130 #endif
1131 mode_ = model_.MightBeMalicious() ? MALICIOUS_MODE : DANGEROUS_MODE;
1133 body_state_ = NORMAL;
1134 drop_down_state_ = NORMAL;
1135 if (mode_ == DANGEROUS_MODE) {
1136 save_button_ = new views::LabelButton(
1137 this, model_.GetWarningConfirmButtonText());
1138 save_button_->SetStyle(views::Button::STYLE_BUTTON);
1139 AddChildView(save_button_);
1141 int discard_button_message = model_.IsMalicious() ?
1142 IDS_DISMISS_DOWNLOAD : IDS_DISCARD_DOWNLOAD;
1143 if (!model_.IsMalicious() && model_.ShouldAllowDownloadFeedback())
1144 discard_button_message = IDS_REPORT_AND_DISCARD_DOWNLOAD;
1145 discard_button_ = new views::LabelButton(
1146 this, l10n_util::GetStringUTF16(discard_button_message));
1147 discard_button_->SetStyle(views::Button::STYLE_BUTTON);
1148 AddChildView(discard_button_);
1150 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1151 switch (danger_type) {
1152 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:
1153 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
1154 case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT:
1155 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST:
1156 case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED:
1157 warning_icon_ = rb.GetImageSkiaNamed(IDR_SAFEBROWSING_WARNING);
1158 break;
1160 case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
1161 case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT:
1162 case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED:
1163 case content::DOWNLOAD_DANGER_TYPE_MAX:
1164 NOTREACHED();
1165 // fallthrough
1167 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE:
1168 warning_icon_ = rb.GetImageSkiaNamed(IDR_WARNING);
1170 base::string16 dangerous_label =
1171 model_.GetWarningText(font_list_, kTextWidth);
1172 dangerous_download_label_ = new views::Label(dangerous_label);
1173 dangerous_download_label_->SetMultiLine(true);
1174 dangerous_download_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1175 dangerous_download_label_->SetAutoColorReadabilityEnabled(false);
1176 AddChildView(dangerous_download_label_);
1177 SizeLabelToMinWidth();
1178 UpdateDropDownButtonPosition();
1179 TooltipTextChanged();
1182 gfx::Size DownloadItemView::GetButtonSize() {
1183 DCHECK(discard_button_ && (mode_ == MALICIOUS_MODE || save_button_));
1184 gfx::Size size;
1186 // We cache the size when successfully retrieved, not for performance reasons
1187 // but because if this DownloadItemView is being animated while the tab is
1188 // not showing, the native buttons are not parented and their preferred size
1189 // is 0, messing-up the layout.
1190 if (cached_button_size_.width() != 0)
1191 return cached_button_size_;
1193 if (save_button_)
1194 size = save_button_->GetMinimumSize();
1195 gfx::Size discard_size = discard_button_->GetMinimumSize();
1197 size.SetSize(std::max(size.width(), discard_size.width()),
1198 std::max(size.height(), discard_size.height()));
1200 if (size.width() != 0)
1201 cached_button_size_ = size;
1203 return size;
1206 // This method computes the minimum width of the label for displaying its text
1207 // on 2 lines. It just breaks the string in 2 lines on the spaces and keeps the
1208 // configuration with minimum width.
1209 void DownloadItemView::SizeLabelToMinWidth() {
1210 if (dangerous_download_label_sized_)
1211 return;
1213 base::string16 label_text = dangerous_download_label_->text();
1214 TrimWhitespace(label_text, TRIM_ALL, &label_text);
1215 DCHECK_EQ(base::string16::npos, label_text.find('\n'));
1217 // Make the label big so that GetPreferredSize() is not constrained by the
1218 // current width.
1219 dangerous_download_label_->SetBounds(0, 0, 1000, 1000);
1221 // Use a const string from here. BreakIterator requies that text.data() not
1222 // change during its lifetime.
1223 const base::string16 original_text(label_text);
1224 // Using BREAK_WORD can work in most cases, but it can also break
1225 // lines where it should not. Using BREAK_LINE is safer although
1226 // slower for Chinese/Japanese. This is not perf-critical at all, though.
1227 base::i18n::BreakIterator iter(original_text,
1228 base::i18n::BreakIterator::BREAK_LINE);
1229 bool status = iter.Init();
1230 DCHECK(status);
1232 base::string16 prev_text = original_text;
1233 gfx::Size size = dangerous_download_label_->GetPreferredSize();
1234 int min_width = size.width();
1236 // Go through the string and try each line break (starting with no line break)
1237 // searching for the optimal line break position. Stop if we find one that
1238 // yields one that is less than kDangerousTextWidth wide. This is to prevent
1239 // a short string (e.g.: "This file is malicious") from being broken up
1240 // unnecessarily.
1241 while (iter.Advance() && min_width > kDangerousTextWidth) {
1242 size_t pos = iter.pos();
1243 if (pos >= original_text.length())
1244 break;
1245 base::string16 current_text = original_text;
1246 // This can be a low surrogate codepoint, but u_isUWhiteSpace will
1247 // return false and inserting a new line after a surrogate pair
1248 // is perfectly ok.
1249 base::char16 line_end_char = current_text[pos - 1];
1250 if (u_isUWhiteSpace(line_end_char))
1251 current_text.replace(pos - 1, 1, 1, base::char16('\n'));
1252 else
1253 current_text.insert(pos, 1, base::char16('\n'));
1254 dangerous_download_label_->SetText(current_text);
1255 size = dangerous_download_label_->GetPreferredSize();
1257 // If the width is growing again, it means we passed the optimal width spot.
1258 if (size.width() > min_width) {
1259 dangerous_download_label_->SetText(prev_text);
1260 break;
1261 } else {
1262 min_width = size.width();
1264 prev_text = current_text;
1267 dangerous_download_label_->SetBounds(0, 0, size.width(), size.height());
1268 dangerous_download_label_sized_ = true;
1271 void DownloadItemView::Reenable() {
1272 disabled_while_opening_ = false;
1273 SetEnabled(true); // Triggers a repaint.
1276 void DownloadItemView::ReleaseDropDown() {
1277 drop_down_pressed_ = false;
1278 SetState(NORMAL, NORMAL);
1281 bool DownloadItemView::InDropDownButtonXCoordinateRange(int x) {
1282 if (x > drop_down_x_left_ && x < drop_down_x_right_)
1283 return true;
1284 return false;
1287 void DownloadItemView::UpdateAccessibleName() {
1288 base::string16 new_name;
1289 if (IsShowingWarningDialog()) {
1290 new_name = dangerous_download_label_->text();
1291 } else {
1292 new_name = status_text_ + base::char16(' ') +
1293 download()->GetFileNameToReportUser().LossyDisplayName();
1296 // If the name has changed, notify assistive technology that the name
1297 // has changed so they can announce it immediately.
1298 if (new_name != accessible_name_) {
1299 accessible_name_ = new_name;
1300 NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_NAME_CHANGED, true);
1304 void DownloadItemView::UpdateDropDownButtonPosition() {
1305 gfx::Size size = GetPreferredSize();
1306 if (base::i18n::IsRTL()) {
1307 // Drop down button is glued to the left of the download shelf.
1308 drop_down_x_left_ = 0;
1309 drop_down_x_right_ = normal_drop_down_image_set_.top->width();
1310 } else {
1311 // Drop down button is glued to the right of the download shelf.
1312 drop_down_x_left_ =
1313 size.width() - normal_drop_down_image_set_.top->width();
1314 drop_down_x_right_ = size.width();
1318 void DownloadItemView::AnimateStateTransition(State from, State to,
1319 gfx::SlideAnimation* animation) {
1320 if (from == NORMAL && to == HOT) {
1321 animation->Show();
1322 } else if (from == HOT && to == NORMAL) {
1323 animation->Hide();
1324 } else if (from != to) {
1325 animation->Reset((to == HOT) ? 1.0 : 0.0);