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