download_item_view.cc revision 90dce4d38c5ff5333bea97d859d4e484e27edf0c
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/string_util.h" 17#include "base/stringprintf.h" 18#include "base/strings/sys_string_conversions.h" 19#include "base/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 if (download()->IsPartialDownload()) 528 download()->Cancel(true); 529 download()->Delete(DownloadItem::DELETE_DUE_TO_USER_DISCARD); 530 // WARNING: we are deleted at this point. Don't access 'this'. 531 } else if (save_button_ && sender == save_button_) { 532 // The user has confirmed a dangerous download. We'd record how quickly the 533 // user did this to detect whether we're being clickjacked. 534 UMA_HISTOGRAM_LONG_TIMES("clickjacking.save_download", 535 base::Time::Now() - creation_time_); 536 // This will change the state and notify us. 537 download()->DangerousDownloadValidated(); 538 } 539} 540 541void DownloadItemView::AnimationProgressed(const ui::Animation* animation) { 542 // We don't care if what animation (body button/drop button/complete), 543 // is calling back, as they all have to go through the same paint call. 544 SchedulePaint(); 545} 546 547// The DownloadItemView can be in three major modes (NORMAL_MODE, DANGEROUS_MODE 548// and MALICIOUS_MODE). 549// 550// NORMAL_MODE: We are displaying an in-progress or completed download. 551// .-------------------------------+-. 552// | [icon] Filename |v| 553// | [ ] Status | | 554// `-------------------------------+-' 555// | | \_ Drop down button. Invokes menu. Responds 556// | | to mouse. (NORMAL, HOT or PUSHED). 557// | \_ Icon is overlaid on top of in-progress animation. 558// \_ Both the body and the drop down button respond to mouse hover and can be 559// pushed (NORMAL, HOT or PUSHED). 560// 561// DANGEROUS_MODE: The file could be potentially dangerous. 562// .-------------------------------------------------------. 563// | [ ! ] [This type of file can ] [ Keep ] [ Discard ] | 564// | [ ] [destroy your computer..] [ ] [ ] | 565// `-------------------------------------------------------' 566// | | | | \_ No drop down button. 567// | | | \_ Buttons are views::LabelButtons. 568// | | \_ Text is in a label (dangerous_download_label_) 569// | \_ Warning icon. No progress animation. 570// \_ Body is static. Doesn't respond to mouse hover or press. (NORMAL only) 571// 572// MALICIOUS_MODE: The file is known malware. 573// .---------------------------------------------+-. 574// | [ - ] [This file is malicious.] [ Discard ] |v| 575// | [ ] [ ] [ ] | |-. 576// `---------------------------------------------+-' | 577// | | | | Drop down button. Responds to 578// | | | | mouse.(NORMAL, HOT or PUSHED) 579// | | | \_ Button is a views::LabelButton. 580// | | \_ Text is in a label (dangerous_download_label_) 581// | \_ Warning icon. No progress animation. 582// \_ Body is static. Doesn't respond to mouse hover or press. (NORMAL only) 583// 584void DownloadItemView::OnPaint(gfx::Canvas* canvas) { 585 BodyImageSet* body_image_set = NULL; 586 switch (mode_) { 587 case NORMAL_MODE: 588 if (body_state_ == PUSHED) 589 body_image_set = &pushed_body_image_set_; 590 else // NORMAL or HOT 591 body_image_set = &normal_body_image_set_; 592 break; 593 case DANGEROUS_MODE: 594 body_image_set = &dangerous_mode_body_image_set_; 595 break; 596 case MALICIOUS_MODE: 597 body_image_set = &malicious_mode_body_image_set_; 598 break; 599 default: 600 NOTREACHED(); 601 } 602 603 DropDownImageSet* drop_down_image_set = NULL; 604 switch (mode_) { 605 case NORMAL_MODE: 606 case MALICIOUS_MODE: 607 if (drop_down_state_ == PUSHED) 608 drop_down_image_set = &pushed_drop_down_image_set_; 609 else // NORMAL or HOT 610 drop_down_image_set = &normal_drop_down_image_set_; 611 break; 612 case DANGEROUS_MODE: 613 // We don't use a drop down button for mode_ == DANGEROUS_MODE. So we let 614 // drop_down_image_set == NULL. 615 break; 616 default: 617 NOTREACHED(); 618 } 619 620 int center_width = width() - kLeftPadding - 621 body_image_set->left->width() - 622 body_image_set->right->width() - 623 (drop_down_image_set ? 624 normal_drop_down_image_set_.center->width() : 625 0); 626 627 // May be caused by animation. 628 if (center_width <= 0) 629 return; 630 631 // Draw status before button image to effectively lighten text. No status for 632 // warning dialogs. 633 if (!IsShowingWarningDialog()) { 634 if (!status_text_.empty()) { 635 int mirrored_x = GetMirroredXWithWidthInView( 636 download_util::kSmallProgressIconSize, kTextWidth); 637 // Add font_.height() to compensate for title, which is drawn later. 638 int y = box_y_ + kVerticalPadding + font_.GetHeight() + 639 kVerticalTextPadding; 640 SkColor file_name_color = GetThemeProvider()->GetColor( 641 ThemeProperties::COLOR_BOOKMARK_TEXT); 642 // If text is light-on-dark, lightening it alone will do nothing. 643 // Therefore we mute luminance a wee bit before drawing in this case. 644 if (color_utils::RelativeLuminance(file_name_color) > 0.5) 645 file_name_color = SkColorSetRGB( 646 static_cast<int>(kDownloadItemLuminanceMod * 647 SkColorGetR(file_name_color)), 648 static_cast<int>(kDownloadItemLuminanceMod * 649 SkColorGetG(file_name_color)), 650 static_cast<int>(kDownloadItemLuminanceMod * 651 SkColorGetB(file_name_color))); 652 canvas->DrawStringInt(status_text_, font_, 653 file_name_color, mirrored_x, y, kTextWidth, 654 font_.GetHeight()); 655 } 656 } 657 658 // Paint the background images. 659 int x = kLeftPadding; 660 canvas->Save(); 661 if (base::i18n::IsRTL()) { 662 // Since we do not have the mirrored images for 663 // (hot_)body_image_set->top_left, (hot_)body_image_set->left, 664 // (hot_)body_image_set->bottom_left, and drop_down_image_set, 665 // for RTL UI, we flip the canvas to draw those images mirrored. 666 // Consequently, we do not need to mirror the x-axis of those images. 667 canvas->Translate(gfx::Vector2d(width(), 0)); 668 canvas->Scale(-1, 1); 669 } 670 PaintImages(canvas, 671 body_image_set->top_left, body_image_set->left, 672 body_image_set->bottom_left, 673 x, box_y_, box_height_, body_image_set->top_left->width()); 674 x += body_image_set->top_left->width(); 675 PaintImages(canvas, 676 body_image_set->top, body_image_set->center, 677 body_image_set->bottom, 678 x, box_y_, box_height_, center_width); 679 x += center_width; 680 PaintImages(canvas, 681 body_image_set->top_right, body_image_set->right, 682 body_image_set->bottom_right, 683 x, box_y_, box_height_, body_image_set->top_right->width()); 684 685 // Overlay our body hot state. Warning dialogs don't display body a hot state. 686 if (!IsShowingWarningDialog() && 687 body_hover_animation_->GetCurrentValue() > 0) { 688 canvas->SaveLayerAlpha( 689 static_cast<int>(body_hover_animation_->GetCurrentValue() * 255)); 690 691 int x = kLeftPadding; 692 PaintImages(canvas, 693 hot_body_image_set_.top_left, hot_body_image_set_.left, 694 hot_body_image_set_.bottom_left, 695 x, box_y_, box_height_, hot_body_image_set_.top_left->width()); 696 x += body_image_set->top_left->width(); 697 PaintImages(canvas, 698 hot_body_image_set_.top, hot_body_image_set_.center, 699 hot_body_image_set_.bottom, 700 x, box_y_, box_height_, center_width); 701 x += center_width; 702 PaintImages(canvas, 703 hot_body_image_set_.top_right, hot_body_image_set_.right, 704 hot_body_image_set_.bottom_right, 705 x, box_y_, box_height_, 706 hot_body_image_set_.top_right->width()); 707 canvas->Restore(); 708 } 709 710 x += body_image_set->top_right->width(); 711 712 // Paint the drop-down. 713 if (drop_down_image_set) { 714 PaintImages(canvas, 715 drop_down_image_set->top, drop_down_image_set->center, 716 drop_down_image_set->bottom, 717 x, box_y_, box_height_, drop_down_image_set->top->width()); 718 719 // Overlay our drop-down hot state. 720 if (drop_hover_animation_->GetCurrentValue() > 0) { 721 canvas->SaveLayerAlpha( 722 static_cast<int>(drop_hover_animation_->GetCurrentValue() * 255)); 723 724 PaintImages(canvas, 725 drop_down_image_set->top, drop_down_image_set->center, 726 drop_down_image_set->bottom, 727 x, box_y_, box_height_, drop_down_image_set->top->width()); 728 729 canvas->Restore(); 730 } 731 } 732 733 // Restore the canvas to avoid file name etc. text are drawn flipped. 734 // Consequently, the x-axis of following canvas->DrawXXX() method should be 735 // mirrored so the text and images are down in the right positions. 736 canvas->Restore(); 737 738 // Print the text, left aligned and always print the file extension. 739 // Last value of x was the end of the right image, just before the button. 740 // Note that in dangerous mode we use a label (as the text is multi-line). 741 if (!IsShowingWarningDialog()) { 742 string16 filename; 743 if (!disabled_while_opening_) { 744 filename = ui::ElideFilename(download()->GetFileNameToReportUser(), 745 font_, kTextWidth); 746 } else { 747 // First, Calculate the download status opening string width. 748 string16 status_string = 749 l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_OPENING, string16()); 750 int status_string_width = font_.GetStringWidth(status_string); 751 // Then, elide the file name. 752 string16 filename_string = 753 ui::ElideFilename(download()->GetFileNameToReportUser(), font_, 754 kTextWidth - status_string_width); 755 // Last, concat the whole string. 756 filename = l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_OPENING, 757 filename_string); 758 } 759 760 int mirrored_x = GetMirroredXWithWidthInView( 761 download_util::kSmallProgressIconSize, kTextWidth); 762 SkColor file_name_color = GetThemeProvider()->GetColor( 763 ThemeProperties::COLOR_BOOKMARK_TEXT); 764 int y = 765 box_y_ + (status_text_.empty() ? 766 ((box_height_ - font_.GetHeight()) / 2) : kVerticalPadding); 767 768 // Draw the file's name. 769 canvas->DrawStringInt(filename, font_, 770 enabled() ? file_name_color 771 : kFileNameDisabledColor, 772 mirrored_x, y, kTextWidth, font_.GetHeight()); 773 } 774 775 // Load the icon. 776 IconManager* im = g_browser_process->icon_manager(); 777 gfx::Image* image = im->LookupIconFromFilepath( 778 download()->GetTargetFilePath(), IconLoader::SMALL); 779 const gfx::ImageSkia* icon = NULL; 780 if (IsShowingWarningDialog()) 781 icon = warning_icon_; 782 else if (image) 783 icon = image->ToImageSkia(); 784 785 // We count on the fact that the icon manager will cache the icons and if one 786 // is available, it will be cached here. We *don't* want to request the icon 787 // to be loaded here, since this will also get called if the icon can't be 788 // loaded, in which case LookupIcon will always be NULL. The loading will be 789 // triggered only when we think the status might change. 790 if (icon) { 791 if (!IsShowingWarningDialog()) { 792 if (download()->IsInProgress()) { 793 download_util::PaintDownloadProgress(canvas, this, 0, 0, 794 progress_angle_, 795 model_.PercentComplete(), 796 download_util::SMALL); 797 } else if (download()->IsComplete() && 798 complete_animation_.get() && 799 complete_animation_->is_animating()) { 800 if (download()->IsInterrupted()) { 801 download_util::PaintDownloadInterrupted(canvas, this, 0, 0, 802 complete_animation_->GetCurrentValue(), 803 download_util::SMALL); 804 } else { 805 download_util::PaintDownloadComplete(canvas, this, 0, 0, 806 complete_animation_->GetCurrentValue(), 807 download_util::SMALL); 808 } 809 } 810 } 811 812 // Draw the icon image. 813 int icon_x, icon_y; 814 815 if (IsShowingWarningDialog()) { 816 icon_x = kLeftPadding + body_image_set->top_left->width(); 817 icon_y = (height() - icon->height()) / 2; 818 } else { 819 icon_x = download_util::kSmallProgressIconOffset; 820 icon_y = download_util::kSmallProgressIconOffset; 821 } 822 icon_x = GetMirroredXWithWidthInView(icon_x, icon->width()); 823 if (enabled()) { 824 canvas->DrawImageInt(*icon, icon_x, icon_y); 825 } else { 826 // Use an alpha to make the image look disabled. 827 SkPaint paint; 828 paint.setAlpha(120); 829 canvas->DrawImageInt(*icon, icon_x, icon_y, paint); 830 } 831 } 832} 833 834void DownloadItemView::OpenDownload() { 835 DCHECK(!IsShowingWarningDialog()); 836 // We're interested in how long it takes users to open downloads. If they 837 // open downloads super quickly, we should be concerned about clickjacking. 838 UMA_HISTOGRAM_LONG_TIMES("clickjacking.open_download", 839 base::Time::Now() - creation_time_); 840 download()->OpenDownload(); 841 UpdateAccessibleName(); 842} 843 844void DownloadItemView::LoadIcon() { 845 IconManager* im = g_browser_process->icon_manager(); 846 last_download_item_path_ = download()->GetTargetFilePath(); 847 im->LoadIcon(last_download_item_path_, 848 IconLoader::SMALL, 849 base::Bind(&DownloadItemView::OnExtractIconComplete, 850 base::Unretained(this)), 851 &cancelable_task_tracker_); 852} 853 854void DownloadItemView::LoadIconIfItemPathChanged() { 855 base::FilePath current_download_path = download()->GetTargetFilePath(); 856 if (last_download_item_path_ == current_download_path) 857 return; 858 859 LoadIcon(); 860} 861 862void DownloadItemView::UpdateColorsFromTheme() { 863 if (dangerous_download_label_ && GetThemeProvider()) { 864 dangerous_download_label_->SetEnabledColor( 865 GetThemeProvider()->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT)); 866 } 867} 868 869void DownloadItemView::ShowContextMenuImpl(const gfx::Point& p, 870 bool is_mouse_gesture) { 871 gfx::Point point = p; 872 gfx::Size size; 873 874 // Similar hack as in MenuButton. 875 // We're about to show the menu from a mouse press. By showing from the 876 // mouse press event we block RootView in mouse dispatching. This also 877 // appears to cause RootView to get a mouse pressed BEFORE the mouse 878 // release is seen, which means RootView sends us another mouse press no 879 // matter where the user pressed. To force RootView to recalculate the 880 // mouse target during the mouse press we explicitly set the mouse handler 881 // to NULL. 882 static_cast<views::internal::RootView*>(GetWidget()->GetRootView())-> 883 SetMouseHandler(NULL); 884 885 // If |is_mouse_gesture| is false, |p| is ignored. The menu is shown aligned 886 // to drop down arrow button. 887 if (!is_mouse_gesture) { 888 drop_down_pressed_ = true; 889 SetState(NORMAL, PUSHED); 890 point.SetPoint(drop_down_x_left_, box_y_); 891 size.SetSize(drop_down_x_right_ - drop_down_x_left_, box_height_); 892 } 893 // Post a task to release the button. When we call the Run method on the menu 894 // below, it runs an inner message loop that might cause us to be deleted. 895 // Posting a task with a WeakPtr lets us safely handle the button release. 896 base::MessageLoop::current()->PostNonNestableTask( 897 FROM_HERE, 898 base::Bind(&DownloadItemView::ReleaseDropDown, 899 weak_ptr_factory_.GetWeakPtr())); 900 views::View::ConvertPointToScreen(this, &point); 901 902 if (!context_menu_.get()) { 903 context_menu_.reset( 904 new DownloadShelfContextMenuView(download(), shelf_->GetNavigator())); 905 } 906 context_menu_->Run(GetWidget()->GetTopLevelWidget(), 907 gfx::Rect(point, size)); 908 // We could be deleted now. 909} 910 911void DownloadItemView::HandlePressEvent(const ui::LocatedEvent& event, 912 bool active_event) { 913 // The event should not activate us in dangerous mode. 914 if (mode_ == DANGEROUS_MODE) 915 return; 916 917 // Stop any completion animation. 918 if (complete_animation_.get() && complete_animation_->is_animating()) 919 complete_animation_->End(); 920 921 if (active_event) { 922 if (InDropDownButtonXCoordinateRange(event.x())) { 923 drop_down_pressed_ = true; 924 SetState(NORMAL, PUSHED); 925 // We are setting is_mouse_gesture to false when calling ShowContextMenu 926 // so that the positioning of the context menu will be similar to a 927 // keyboard invocation. I.e. we want the menu to always be positioned 928 // next to the drop down button instead of the next to the pointer. 929 ShowContextMenuImpl(event.location(), false); 930 // Once called, it is possible that *this was deleted (e.g.: due to 931 // invoking the 'Discard' action.) 932 } else if (!IsShowingWarningDialog()) { 933 SetState(PUSHED, NORMAL); 934 } 935 } 936} 937 938void DownloadItemView::HandleClickEvent(const ui::LocatedEvent& event, 939 bool active_event) { 940 // Mouse should not activate us in dangerous mode. 941 if (mode_ == DANGEROUS_MODE) 942 return; 943 944 if (active_event && 945 !InDropDownButtonXCoordinateRange(event.x()) && 946 !IsShowingWarningDialog()) { 947 OpenDownload(); 948 } 949 950 SetState(NORMAL, NORMAL); 951} 952 953// Load an icon for the file type we're downloading, and animate any in progress 954// download state. 955void DownloadItemView::PaintImages(gfx::Canvas* canvas, 956 const gfx::ImageSkia* top_image, 957 const gfx::ImageSkia* center_image, 958 const gfx::ImageSkia* bottom_image, 959 int x, int y, int height, int width) { 960 int middle_height = height - top_image->height() - bottom_image->height(); 961 // Draw the top. 962 canvas->DrawImageInt(*top_image, 963 0, 0, top_image->width(), top_image->height(), 964 x, y, width, top_image->height(), false); 965 y += top_image->height(); 966 // Draw the center. 967 canvas->DrawImageInt(*center_image, 968 0, 0, center_image->width(), center_image->height(), 969 x, y, width, middle_height, false); 970 y += middle_height; 971 // Draw the bottom. 972 canvas->DrawImageInt(*bottom_image, 973 0, 0, bottom_image->width(), bottom_image->height(), 974 x, y, width, bottom_image->height(), false); 975} 976 977void DownloadItemView::SetState(State new_body_state, State new_drop_state) { 978 // If we are showing a warning dialog, we don't change body state. 979 if (IsShowingWarningDialog()) { 980 new_body_state = NORMAL; 981 982 // Current body_state_ should always be NORMAL for warning dialogs. 983 DCHECK_EQ(NORMAL, body_state_); 984 // We shouldn't be calling SetState if we are in DANGEROUS_MODE. 985 DCHECK_NE(DANGEROUS_MODE, mode_); 986 } 987 // Avoid extra SchedulePaint()s if the state is going to be the same. 988 if (body_state_ == new_body_state && drop_down_state_ == new_drop_state) 989 return; 990 991 AnimateStateTransition(body_state_, new_body_state, 992 body_hover_animation_.get()); 993 AnimateStateTransition(drop_down_state_, new_drop_state, 994 drop_hover_animation_.get()); 995 body_state_ = new_body_state; 996 drop_down_state_ = new_drop_state; 997 SchedulePaint(); 998} 999 1000void DownloadItemView::ClearWarningDialog() { 1001 DCHECK(download()->GetDangerType() == 1002 content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED); 1003 DCHECK(mode_ == DANGEROUS_MODE || mode_ == MALICIOUS_MODE); 1004 1005 mode_ = NORMAL_MODE; 1006 body_state_ = NORMAL; 1007 drop_down_state_ = NORMAL; 1008 1009 // Remove the views used by the warning dialog. 1010 if (save_button_) { 1011 RemoveChildView(save_button_); 1012 delete save_button_; 1013 save_button_ = NULL; 1014 } 1015 RemoveChildView(discard_button_); 1016 delete discard_button_; 1017 discard_button_ = NULL; 1018 RemoveChildView(dangerous_download_label_); 1019 delete dangerous_download_label_; 1020 dangerous_download_label_ = NULL; 1021 dangerous_download_label_sized_ = false; 1022 cached_button_size_.SetSize(0,0); 1023 1024 // Set the accessible name back to the status and filename instead of the 1025 // download warning. 1026 UpdateAccessibleName(); 1027 UpdateDropDownButtonPosition(); 1028 1029 // We need to load the icon now that the download has the real path. 1030 LoadIcon(); 1031 1032 // Force the shelf to layout again as our size has changed. 1033 shelf_->Layout(); 1034 shelf_->SchedulePaint(); 1035 1036 TooltipTextChanged(); 1037} 1038 1039void DownloadItemView::ShowWarningDialog() { 1040 DCHECK(mode_ != DANGEROUS_MODE && mode_ != MALICIOUS_MODE); 1041 mode_ = ((model_.IsMalicious()) ? MALICIOUS_MODE : DANGEROUS_MODE); 1042 1043 body_state_ = NORMAL; 1044 drop_down_state_ = NORMAL; 1045 if (mode_ == DANGEROUS_MODE) { 1046 save_button_ = new views::LabelButton( 1047 this, model_.GetWarningConfirmButtonText()); 1048 save_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 1049 AddChildView(save_button_); 1050 } 1051 discard_button_ = new views::LabelButton( 1052 this, l10n_util::GetStringUTF16(IDS_DISCARD_DOWNLOAD)); 1053 discard_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 1054 AddChildView(discard_button_); 1055 1056 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 1057 // The dangerous download label text and icon are different under 1058 // different cases. 1059 if (mode_ == MALICIOUS_MODE) { 1060 warning_icon_ = rb.GetImageSkiaNamed(IDR_SAFEBROWSING_WARNING); 1061 } else { 1062 // The download file has dangerous file type (e.g.: an executable). 1063 warning_icon_ = rb.GetImageSkiaNamed(IDR_WARNING); 1064 } 1065 string16 dangerous_label = model_.GetWarningText(font_, kTextWidth); 1066 dangerous_download_label_ = new views::Label(dangerous_label); 1067 dangerous_download_label_->SetMultiLine(true); 1068 dangerous_download_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 1069 dangerous_download_label_->SetAutoColorReadabilityEnabled(false); 1070 AddChildView(dangerous_download_label_); 1071 SizeLabelToMinWidth(); 1072 UpdateDropDownButtonPosition(); 1073 TooltipTextChanged(); 1074} 1075 1076gfx::Size DownloadItemView::GetButtonSize() { 1077 DCHECK(discard_button_ && (mode_ == MALICIOUS_MODE || save_button_)); 1078 gfx::Size size; 1079 1080 // We cache the size when successfully retrieved, not for performance reasons 1081 // but because if this DownloadItemView is being animated while the tab is 1082 // not showing, the native buttons are not parented and their preferred size 1083 // is 0, messing-up the layout. 1084 if (cached_button_size_.width() != 0) 1085 return cached_button_size_; 1086 1087 if (save_button_) 1088 size = save_button_->GetMinimumSize(); 1089 gfx::Size discard_size = discard_button_->GetMinimumSize(); 1090 1091 size.SetSize(std::max(size.width(), discard_size.width()), 1092 std::max(size.height(), discard_size.height())); 1093 1094 if (size.width() != 0) 1095 cached_button_size_ = size; 1096 1097 return size; 1098} 1099 1100// This method computes the minimum width of the label for displaying its text 1101// on 2 lines. It just breaks the string in 2 lines on the spaces and keeps the 1102// configuration with minimum width. 1103void DownloadItemView::SizeLabelToMinWidth() { 1104 if (dangerous_download_label_sized_) 1105 return; 1106 1107 string16 label_text = dangerous_download_label_->text(); 1108 TrimWhitespace(label_text, TRIM_ALL, &label_text); 1109 DCHECK_EQ(string16::npos, label_text.find('\n')); 1110 1111 // Make the label big so that GetPreferredSize() is not constrained by the 1112 // current width. 1113 dangerous_download_label_->SetBounds(0, 0, 1000, 1000); 1114 1115 // Use a const string from here. BreakIterator requies that text.data() not 1116 // change during its lifetime. 1117 const string16 original_text(label_text); 1118 // Using BREAK_WORD can work in most cases, but it can also break 1119 // lines where it should not. Using BREAK_LINE is safer although 1120 // slower for Chinese/Japanese. This is not perf-critical at all, though. 1121 base::i18n::BreakIterator iter(original_text, 1122 base::i18n::BreakIterator::BREAK_LINE); 1123 bool status = iter.Init(); 1124 DCHECK(status); 1125 1126 string16 prev_text = original_text; 1127 gfx::Size size = dangerous_download_label_->GetPreferredSize(); 1128 int min_width = size.width(); 1129 1130 // Go through the string and try each line break (starting with no line break) 1131 // searching for the optimal line break position. Stop if we find one that 1132 // yields one that is less than kDangerousTextWidth wide. This is to prevent 1133 // a short string (e.g.: "This file is malicious") from being broken up 1134 // unnecessarily. 1135 while (iter.Advance() && min_width > kDangerousTextWidth) { 1136 size_t pos = iter.pos(); 1137 if (pos >= original_text.length()) 1138 break; 1139 string16 current_text = original_text; 1140 // This can be a low surrogate codepoint, but u_isUWhiteSpace will 1141 // return false and inserting a new line after a surrogate pair 1142 // is perfectly ok. 1143 char16 line_end_char = current_text[pos - 1]; 1144 if (u_isUWhiteSpace(line_end_char)) 1145 current_text.replace(pos - 1, 1, 1, char16('\n')); 1146 else 1147 current_text.insert(pos, 1, char16('\n')); 1148 dangerous_download_label_->SetText(current_text); 1149 size = dangerous_download_label_->GetPreferredSize(); 1150 1151 // If the width is growing again, it means we passed the optimal width spot. 1152 if (size.width() > min_width) { 1153 dangerous_download_label_->SetText(prev_text); 1154 break; 1155 } else { 1156 min_width = size.width(); 1157 } 1158 prev_text = current_text; 1159 } 1160 1161 dangerous_download_label_->SetBounds(0, 0, size.width(), size.height()); 1162 dangerous_download_label_sized_ = true; 1163} 1164 1165void DownloadItemView::Reenable() { 1166 disabled_while_opening_ = false; 1167 SetEnabled(true); // Triggers a repaint. 1168} 1169 1170void DownloadItemView::ReleaseDropDown() { 1171 drop_down_pressed_ = false; 1172 SetState(NORMAL, NORMAL); 1173} 1174 1175bool DownloadItemView::InDropDownButtonXCoordinateRange(int x) { 1176 if (x > drop_down_x_left_ && x < drop_down_x_right_) 1177 return true; 1178 return false; 1179} 1180 1181void DownloadItemView::UpdateAccessibleName() { 1182 string16 new_name; 1183 if (IsShowingWarningDialog()) { 1184 new_name = dangerous_download_label_->text(); 1185 } else { 1186 new_name = status_text_ + char16(' ') + 1187 download()->GetFileNameToReportUser().LossyDisplayName(); 1188 } 1189 1190 // If the name has changed, notify assistive technology that the name 1191 // has changed so they can announce it immediately. 1192 if (new_name != accessible_name_) { 1193 accessible_name_ = new_name; 1194 NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_NAME_CHANGED, true); 1195 } 1196} 1197 1198void DownloadItemView::UpdateDropDownButtonPosition() { 1199 gfx::Size size = GetPreferredSize(); 1200 if (base::i18n::IsRTL()) { 1201 // Drop down button is glued to the left of the download shelf. 1202 drop_down_x_left_ = 0; 1203 drop_down_x_right_ = normal_drop_down_image_set_.top->width(); 1204 } else { 1205 // Drop down button is glued to the right of the download shelf. 1206 drop_down_x_left_ = 1207 size.width() - normal_drop_down_image_set_.top->width(); 1208 drop_down_x_right_ = size.width(); 1209 } 1210} 1211 1212void DownloadItemView::AnimateStateTransition(State from, State to, 1213 ui::SlideAnimation* animation) { 1214 if (from == NORMAL && to == HOT) { 1215 animation->Show(); 1216 } else if (from == HOT && to == NORMAL) { 1217 animation->Hide(); 1218 } else if (from != to) { 1219 animation->Reset((to == HOT) ? 1.0 : 0.0); 1220 } 1221} 1222