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