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