• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_shelf_view.h"
6 
7 #include <algorithm>
8 #include <vector>
9 
10 #include "base/logging.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/download/download_item_model.h"
13 #include "chrome/browser/download/download_stats.h"
14 #include "chrome/browser/themes/theme_properties.h"
15 #include "chrome/browser/ui/browser.h"
16 #include "chrome/browser/ui/chrome_pages.h"
17 #include "chrome/browser/ui/view_ids.h"
18 #include "chrome/browser/ui/views/download/download_item_view.h"
19 #include "chrome/browser/ui/views/frame/browser_view.h"
20 #include "content/public/browser/download_item.h"
21 #include "content/public/browser/download_manager.h"
22 #include "content/public/browser/page_navigator.h"
23 #include "grit/generated_resources.h"
24 #include "grit/theme_resources.h"
25 #include "grit/ui_resources.h"
26 #include "ui/base/l10n/l10n_util.h"
27 #include "ui/base/resource/resource_bundle.h"
28 #include "ui/base/theme_provider.h"
29 #include "ui/gfx/animation/slide_animation.h"
30 #include "ui/gfx/canvas.h"
31 #include "ui/views/background.h"
32 #include "ui/views/controls/button/image_button.h"
33 #include "ui/views/controls/image_view.h"
34 #include "ui/views/controls/link.h"
35 #include "ui/views/mouse_watcher_view_host.h"
36 
37 // Max number of download views we'll contain. Any time a view is added and
38 // we already have this many download views, one is removed.
39 static const size_t kMaxDownloadViews = 15;
40 
41 // Padding from left edge and first download view.
42 static const int kLeftPadding = 2;
43 
44 // Padding from right edge and close button/show downloads link.
45 static const int kRightPadding = 10;
46 
47 // Padding between the show all link and close button.
48 static const int kCloseAndLinkPadding = 14;
49 
50 // Padding between the download views.
51 static const int kDownloadPadding = 10;
52 
53 // Padding between the top/bottom and the content.
54 static const int kTopBottomPadding = 2;
55 
56 // Padding between the icon and 'show all downloads' link
57 static const int kDownloadsTitlePadding = 4;
58 
59 // Border color.
60 static const SkColor kBorderColor = SkColorSetRGB(214, 214, 214);
61 
62 // New download item animation speed in milliseconds.
63 static const int kNewItemAnimationDurationMs = 800;
64 
65 // Shelf show/hide speed.
66 static const int kShelfAnimationDurationMs = 120;
67 
68 // Amount of time to delay if the mouse leaves the shelf by way of entering
69 // another window. This is much larger than the normal delay as openning a
70 // download is most likely going to trigger a new window to appear over the
71 // button. Delay the time so that the user has a chance to quickly close the
72 // other app and return to chrome with the download shelf still open.
73 static const int kNotifyOnExitTimeMS = 5000;
74 
75 using content::DownloadItem;
76 
77 namespace {
78 
79 // Sets size->width() to view's preferred width + size->width().s
80 // Sets size->height() to the max of the view's preferred height and
81 // size->height();
AdjustSize(views::View * view,gfx::Size * size)82 void AdjustSize(views::View* view, gfx::Size* size) {
83   gfx::Size view_preferred = view->GetPreferredSize();
84   size->Enlarge(view_preferred.width(), 0);
85   size->set_height(std::max(view_preferred.height(), size->height()));
86 }
87 
CenterPosition(int size,int target_size)88 int CenterPosition(int size, int target_size) {
89   return std::max((target_size - size) / 2, kTopBottomPadding);
90 }
91 
92 }  // namespace
93 
DownloadShelfView(Browser * browser,BrowserView * parent)94 DownloadShelfView::DownloadShelfView(Browser* browser, BrowserView* parent)
95     : browser_(browser),
96       arrow_image_(NULL),
97       show_all_view_(NULL),
98       close_button_(NULL),
99       parent_(parent),
100       mouse_watcher_(new views::MouseWatcherViewHost(this, gfx::Insets()),
101                      this) {
102   mouse_watcher_.set_notify_on_exit_time(
103       base::TimeDelta::FromMilliseconds(kNotifyOnExitTimeMS));
104   set_id(VIEW_ID_DOWNLOAD_SHELF);
105   parent->AddChildView(this);
106 }
107 
~DownloadShelfView()108 DownloadShelfView::~DownloadShelfView() {
109   parent_->RemoveChildView(this);
110 }
111 
AddDownloadView(DownloadItemView * view)112 void DownloadShelfView::AddDownloadView(DownloadItemView* view) {
113   mouse_watcher_.Stop();
114 
115   DCHECK(view);
116   download_views_.push_back(view);
117 
118   // Insert the new view as the first child, so the logical child order matches
119   // the visual order.  This ensures that tabbing through downloads happens in
120   // the order users would expect.
121   AddChildViewAt(view, 0);
122   if (download_views_.size() > kMaxDownloadViews)
123     RemoveDownloadView(*download_views_.begin());
124 
125   new_item_animation_->Reset();
126   new_item_animation_->Show();
127 }
128 
DoAddDownload(DownloadItem * download)129 void DownloadShelfView::DoAddDownload(DownloadItem* download) {
130   DownloadItemView* view = new DownloadItemView(download, this);
131   AddDownloadView(view);
132 }
133 
MouseMovedOutOfHost()134 void DownloadShelfView::MouseMovedOutOfHost() {
135   Close(AUTOMATIC);
136 }
137 
RemoveDownloadView(View * view)138 void DownloadShelfView::RemoveDownloadView(View* view) {
139   DCHECK(view);
140   std::vector<DownloadItemView*>::iterator i =
141       find(download_views_.begin(), download_views_.end(), view);
142   DCHECK(i != download_views_.end());
143   download_views_.erase(i);
144   RemoveChildView(view);
145   delete view;
146   if (download_views_.empty())
147     Close(AUTOMATIC);
148   else if (CanAutoClose())
149     mouse_watcher_.Start();
150   Layout();
151   SchedulePaint();
152 }
153 
GetDefaultFocusableChild()154 views::View* DownloadShelfView::GetDefaultFocusableChild() {
155   return download_views_.empty() ?
156       static_cast<View*>(show_all_view_) : download_views_.back();
157 }
158 
OnPaintBorder(gfx::Canvas * canvas)159 void DownloadShelfView::OnPaintBorder(gfx::Canvas* canvas) {
160   canvas->FillRect(gfx::Rect(0, 0, width(), 1), kBorderColor);
161 }
162 
OpenedDownload(DownloadItemView * view)163 void DownloadShelfView::OpenedDownload(DownloadItemView* view) {
164   if (CanAutoClose())
165     mouse_watcher_.Start();
166 }
167 
GetNavigator()168 content::PageNavigator* DownloadShelfView::GetNavigator() {
169   return browser_;
170 }
171 
GetPreferredSize() const172 gfx::Size DownloadShelfView::GetPreferredSize() const {
173   gfx::Size prefsize(kRightPadding + kLeftPadding + kCloseAndLinkPadding, 0);
174   AdjustSize(close_button_, &prefsize);
175   AdjustSize(show_all_view_, &prefsize);
176   // Add one download view to the preferred size.
177   if (!download_views_.empty()) {
178     AdjustSize(*download_views_.begin(), &prefsize);
179     prefsize.Enlarge(kDownloadPadding, 0);
180   }
181   prefsize.Enlarge(0, kTopBottomPadding + kTopBottomPadding);
182   if (shelf_animation_->is_animating()) {
183     prefsize.set_height(static_cast<int>(
184         static_cast<double>(prefsize.height()) *
185                             shelf_animation_->GetCurrentValue()));
186   }
187   return prefsize;
188 }
189 
AnimationProgressed(const gfx::Animation * animation)190 void DownloadShelfView::AnimationProgressed(const gfx::Animation *animation) {
191   if (animation == new_item_animation_.get()) {
192     Layout();
193     SchedulePaint();
194   } else if (animation == shelf_animation_.get()) {
195     // Force a re-layout of the parent, which will call back into
196     // GetPreferredSize, where we will do our animation. In the case where the
197     // animation is hiding, we do a full resize - the fast resizing would
198     // otherwise leave blank white areas where the shelf was and where the
199     // user's eye is. Thankfully bottom-resizing is a lot faster than
200     // top-resizing.
201     parent_->ToolbarSizeChanged(shelf_animation_->IsShowing());
202   }
203 }
204 
AnimationEnded(const gfx::Animation * animation)205 void DownloadShelfView::AnimationEnded(const gfx::Animation *animation) {
206   if (animation == shelf_animation_.get()) {
207     parent_->SetDownloadShelfVisible(shelf_animation_->IsShowing());
208     if (!shelf_animation_->IsShowing())
209       Closed();
210   }
211 }
212 
Layout()213 void DownloadShelfView::Layout() {
214   // Let our base class layout our child views
215   views::View::Layout();
216 
217   // If there is not enough room to show the first download item, show the
218   // "Show all downloads" link to the left to make it more visible that there is
219   // something to see.
220   bool show_link_only = !CanFitFirstDownloadItem();
221 
222   gfx::Size image_size = arrow_image_->GetPreferredSize();
223   gfx::Size close_button_size = close_button_->GetPreferredSize();
224   gfx::Size show_all_size = show_all_view_->GetPreferredSize();
225   int max_download_x =
226       std::max<int>(0, width() - kRightPadding - close_button_size.width() -
227                        kCloseAndLinkPadding - show_all_size.width() -
228                        kDownloadsTitlePadding - image_size.width() -
229                        kDownloadPadding);
230   int next_x = show_link_only ? kLeftPadding :
231                                 max_download_x + kDownloadPadding;
232   // Align vertically with show_all_view_.
233   arrow_image_->SetBounds(next_x,
234                           CenterPosition(image_size.height(), height()),
235                           image_size.width(), image_size.height());
236   next_x += image_size.width() + kDownloadsTitlePadding;
237   show_all_view_->SetBounds(next_x,
238                             CenterPosition(show_all_size.height(), height()),
239                             show_all_size.width(),
240                             show_all_size.height());
241   next_x += show_all_size.width() + kCloseAndLinkPadding;
242   // If the window is maximized, we want to expand the hitbox of the close
243   // button to the right and bottom to make it easier to click.
244   bool is_maximized = browser_->window()->IsMaximized();
245   int y = CenterPosition(close_button_size.height(), height());
246   close_button_->SetBounds(next_x, y,
247       is_maximized ? width() - next_x : close_button_size.width(),
248       is_maximized ? height() - y : close_button_size.height());
249   if (show_link_only) {
250     // Let's hide all the items.
251     std::vector<DownloadItemView*>::reverse_iterator ri;
252     for (ri = download_views_.rbegin(); ri != download_views_.rend(); ++ri)
253       (*ri)->SetVisible(false);
254     return;
255   }
256 
257   next_x = kLeftPadding;
258   std::vector<DownloadItemView*>::reverse_iterator ri;
259   for (ri = download_views_.rbegin(); ri != download_views_.rend(); ++ri) {
260     gfx::Size view_size = (*ri)->GetPreferredSize();
261 
262     int x = next_x;
263 
264     // Figure out width of item.
265     int item_width = view_size.width();
266     if (new_item_animation_->is_animating() && ri == download_views_.rbegin()) {
267        item_width = static_cast<int>(static_cast<double>(view_size.width()) *
268                      new_item_animation_->GetCurrentValue());
269     }
270 
271     next_x += item_width;
272 
273     // Make sure our item can be contained within the shelf.
274     if (next_x < max_download_x) {
275       (*ri)->SetVisible(true);
276       (*ri)->SetBounds(x, CenterPosition(view_size.height(), height()),
277                        item_width, view_size.height());
278     } else {
279       (*ri)->SetVisible(false);
280     }
281   }
282 }
283 
ViewHierarchyChanged(const ViewHierarchyChangedDetails & details)284 void DownloadShelfView::ViewHierarchyChanged(
285     const ViewHierarchyChangedDetails& details) {
286   View::ViewHierarchyChanged(details);
287 
288   if (details.is_add && (details.child == this)) {
289     ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
290     arrow_image_ = new views::ImageView();
291     arrow_image_->SetImage(rb.GetImageSkiaNamed(IDR_DOWNLOADS_FAVICON));
292     AddChildView(arrow_image_);
293 
294     show_all_view_ = new views::Link(
295         l10n_util::GetStringUTF16(IDS_SHOW_ALL_DOWNLOADS));
296     show_all_view_->set_listener(this);
297     AddChildView(show_all_view_);
298 
299     close_button_ = new views::ImageButton(this);
300     close_button_->SetImage(views::CustomButton::STATE_NORMAL,
301                             rb.GetImageSkiaNamed(IDR_CLOSE_1));
302     close_button_->SetImage(views::CustomButton::STATE_HOVERED,
303                             rb.GetImageSkiaNamed(IDR_CLOSE_1_H));
304     close_button_->SetImage(views::CustomButton::STATE_PRESSED,
305                             rb.GetImageSkiaNamed(IDR_CLOSE_1_P));
306     close_button_->SetAccessibleName(
307         l10n_util::GetStringUTF16(IDS_ACCNAME_CLOSE));
308     AddChildView(close_button_);
309 
310     UpdateColorsFromTheme();
311 
312     new_item_animation_.reset(new gfx::SlideAnimation(this));
313     new_item_animation_->SetSlideDuration(kNewItemAnimationDurationMs);
314 
315     shelf_animation_.reset(new gfx::SlideAnimation(this));
316     shelf_animation_->SetSlideDuration(kShelfAnimationDurationMs);
317   }
318 }
319 
CanFitFirstDownloadItem()320 bool DownloadShelfView::CanFitFirstDownloadItem() {
321   if (download_views_.empty())
322     return true;
323 
324   gfx::Size image_size = arrow_image_->GetPreferredSize();
325   gfx::Size close_button_size = close_button_->GetPreferredSize();
326   gfx::Size show_all_size = show_all_view_->GetPreferredSize();
327 
328   // Let's compute the width available for download items, which is the width
329   // of the shelf minus the "Show all downloads" link, arrow and close button
330   // and the padding.
331   int available_width = width() - kRightPadding - close_button_size.width() -
332       kCloseAndLinkPadding - show_all_size.width() - kDownloadsTitlePadding -
333       image_size.width() - kDownloadPadding - kLeftPadding;
334   if (available_width <= 0)
335     return false;
336 
337   gfx::Size item_size = (*download_views_.rbegin())->GetPreferredSize();
338   return item_size.width() < available_width;
339 }
340 
UpdateColorsFromTheme()341 void DownloadShelfView::UpdateColorsFromTheme() {
342   if (show_all_view_ && close_button_ && GetThemeProvider()) {
343     ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
344     set_background(views::Background::CreateSolidBackground(
345         GetThemeProvider()->GetColor(ThemeProperties::COLOR_TOOLBAR)));
346     show_all_view_->SetBackgroundColor(background()->get_color());
347     show_all_view_->SetEnabledColor(
348         GetThemeProvider()->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT));
349     close_button_->SetBackground(
350         GetThemeProvider()->GetColor(ThemeProperties::COLOR_TAB_TEXT),
351         rb.GetImageSkiaNamed(IDR_CLOSE_1),
352         rb.GetImageSkiaNamed(IDR_CLOSE_1_MASK));
353   }
354 }
355 
OnThemeChanged()356 void DownloadShelfView::OnThemeChanged() {
357   UpdateColorsFromTheme();
358 }
359 
LinkClicked(views::Link * source,int event_flags)360 void DownloadShelfView::LinkClicked(views::Link* source, int event_flags) {
361   chrome::ShowDownloads(browser_);
362 }
363 
ButtonPressed(views::Button * button,const ui::Event & event)364 void DownloadShelfView::ButtonPressed(
365     views::Button* button, const ui::Event& event) {
366   Close(USER_ACTION);
367 }
368 
IsShowing() const369 bool DownloadShelfView::IsShowing() const {
370   return shelf_animation_->IsShowing();
371 }
372 
IsClosing() const373 bool DownloadShelfView::IsClosing() const {
374   return shelf_animation_->IsClosing();
375 }
376 
DoShow()377 void DownloadShelfView::DoShow() {
378   shelf_animation_->Show();
379 }
380 
DoClose(CloseReason reason)381 void DownloadShelfView::DoClose(CloseReason reason) {
382   int num_in_progress = 0;
383   for (size_t i = 0; i < download_views_.size(); ++i) {
384     if (download_views_[i]->download()->GetState() == DownloadItem::IN_PROGRESS)
385       ++num_in_progress;
386   }
387   RecordDownloadShelfClose(
388       download_views_.size(), num_in_progress, reason == AUTOMATIC);
389   parent_->SetDownloadShelfVisible(false);
390   shelf_animation_->Hide();
391 }
392 
browser() const393 Browser* DownloadShelfView::browser() const {
394   return browser_;
395 }
396 
Closed()397 void DownloadShelfView::Closed() {
398   // Don't remove completed downloads if the shelf is just being auto-hidden
399   // rather than explicitly closed by the user.
400   if (is_hidden())
401     return;
402   // When the close animation is complete, remove all completed downloads.
403   size_t i = 0;
404   while (i < download_views_.size()) {
405     DownloadItem* download = download_views_[i]->download();
406     DownloadItem::DownloadState state = download->GetState();
407     bool is_transfer_done = state == DownloadItem::COMPLETE ||
408                             state == DownloadItem::CANCELLED ||
409                             state == DownloadItem::INTERRUPTED;
410     if (is_transfer_done && !download->IsDangerous()) {
411       RemoveDownloadView(download_views_[i]);
412     } else {
413       // Treat the item as opened when we close. This way if we get shown again
414       // the user need not open this item for the shelf to auto-close.
415       download->SetOpened(true);
416       ++i;
417     }
418   }
419 }
420 
CanAutoClose()421 bool DownloadShelfView::CanAutoClose() {
422   for (size_t i = 0; i < download_views_.size(); ++i) {
423     if (!download_views_[i]->download()->GetOpened())
424       return false;
425   }
426   return true;
427 }
428