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.
5 #include "chrome/browser/ui/views/download/download_shelf_view.h"
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 "chrome/grit/generated_resources.h"
21 #include "content/public/browser/download_item.h"
22 #include "content/public/browser/download_manager.h"
23 #include "content/public/browser/page_navigator.h"
24 #include "grit/theme_resources.h"
25 #include "ui/base/l10n/l10n_util.h"
26 #include "ui/base/resource/resource_bundle.h"
27 #include "ui/base/theme_provider.h"
28 #include "ui/gfx/animation/slide_animation.h"
29 #include "ui/gfx/canvas.h"
30 #include "ui/resources/grit/ui_resources.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"
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;
41 // Padding from left edge and first download view.
42 static const int kLeftPadding
= 2;
44 // Padding from right edge and close button/show downloads link.
45 static const int kRightPadding
= 10;
47 // Padding between the show all link and close button.
48 static const int kCloseAndLinkPadding
= 14;
50 // Padding between the download views.
51 static const int kDownloadPadding
= 10;
53 // Padding between the top/bottom and the content.
54 static const int kTopBottomPadding
= 2;
56 // Padding between the icon and 'show all downloads' link
57 static const int kDownloadsTitlePadding
= 4;
60 static const SkColor kBorderColor
= SkColorSetRGB(214, 214, 214);
62 // New download item animation speed in milliseconds.
63 static const int kNewItemAnimationDurationMs
= 800;
65 // Shelf show/hide speed.
66 static const int kShelfAnimationDurationMs
= 120;
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;
75 using content::DownloadItem
;
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
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()));
88 int CenterPosition(int size
, int target_size
) {
89 return std::max((target_size
- size
) / 2, kTopBottomPadding
);
94 DownloadShelfView::DownloadShelfView(Browser
* browser
, BrowserView
* parent
)
100 mouse_watcher_(new views::MouseWatcherViewHost(this, gfx::Insets()),
102 mouse_watcher_
.set_notify_on_exit_time(
103 base::TimeDelta::FromMilliseconds(kNotifyOnExitTimeMS
));
104 set_id(VIEW_ID_DOWNLOAD_SHELF
);
105 parent
->AddChildView(this);
108 DownloadShelfView::~DownloadShelfView() {
109 parent_
->RemoveChildView(this);
112 void DownloadShelfView::AddDownloadView(DownloadItemView
* view
) {
113 mouse_watcher_
.Stop();
116 download_views_
.push_back(view
);
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());
125 new_item_animation_
->Reset();
126 new_item_animation_
->Show();
129 void DownloadShelfView::DoAddDownload(DownloadItem
* download
) {
130 AddDownloadView(new DownloadItemView(download
, this));
133 void DownloadShelfView::MouseMovedOutOfHost() {
137 void DownloadShelfView::RemoveDownloadView(View
* view
) {
139 std::vector
<DownloadItemView
*>::iterator i
=
140 find(download_views_
.begin(), download_views_
.end(), view
);
141 DCHECK(i
!= download_views_
.end());
142 download_views_
.erase(i
);
143 RemoveChildView(view
);
145 if (download_views_
.empty())
147 else if (CanAutoClose())
148 mouse_watcher_
.Start();
153 views::View
* DownloadShelfView::GetDefaultFocusableChild() {
154 return download_views_
.empty() ?
155 static_cast<View
*>(show_all_view_
) : download_views_
.back();
158 void DownloadShelfView::OnPaintBorder(gfx::Canvas
* canvas
) {
159 canvas
->FillRect(gfx::Rect(0, 0, width(), 1), kBorderColor
);
162 void DownloadShelfView::OpenedDownload(DownloadItemView
* view
) {
164 mouse_watcher_
.Start();
167 content::PageNavigator
* DownloadShelfView::GetNavigator() {
171 gfx::Size
DownloadShelfView::GetPreferredSize() const {
172 gfx::Size
prefsize(kRightPadding
+ kLeftPadding
+ kCloseAndLinkPadding
, 0);
173 AdjustSize(close_button_
, &prefsize
);
174 AdjustSize(show_all_view_
, &prefsize
);
175 // Add one download view to the preferred size.
176 if (!download_views_
.empty()) {
177 AdjustSize(*download_views_
.begin(), &prefsize
);
178 prefsize
.Enlarge(kDownloadPadding
, 0);
180 prefsize
.Enlarge(0, kTopBottomPadding
+ kTopBottomPadding
);
181 if (shelf_animation_
->is_animating()) {
182 prefsize
.set_height(static_cast<int>(
183 static_cast<double>(prefsize
.height()) *
184 shelf_animation_
->GetCurrentValue()));
189 void DownloadShelfView::AnimationProgressed(const gfx::Animation
*animation
) {
190 if (animation
== new_item_animation_
.get()) {
193 } else if (animation
== shelf_animation_
.get()) {
194 // Force a re-layout of the parent, which will call back into
195 // GetPreferredSize, where we will do our animation. In the case where the
196 // animation is hiding, we do a full resize - the fast resizing would
197 // otherwise leave blank white areas where the shelf was and where the
198 // user's eye is. Thankfully bottom-resizing is a lot faster than
200 parent_
->ToolbarSizeChanged(shelf_animation_
->IsShowing());
204 void DownloadShelfView::AnimationEnded(const gfx::Animation
*animation
) {
205 if (animation
== shelf_animation_
.get()) {
206 parent_
->SetDownloadShelfVisible(shelf_animation_
->IsShowing());
207 if (!shelf_animation_
->IsShowing())
212 void DownloadShelfView::Layout() {
213 // Let our base class layout our child views
214 views::View::Layout();
216 // If there is not enough room to show the first download item, show the
217 // "Show all downloads" link to the left to make it more visible that there is
219 bool show_link_only
= !CanFitFirstDownloadItem();
221 gfx::Size image_size
= arrow_image_
->GetPreferredSize();
222 gfx::Size close_button_size
= close_button_
->GetPreferredSize();
223 gfx::Size show_all_size
= show_all_view_
->GetPreferredSize();
225 std::max
<int>(0, width() - kRightPadding
- close_button_size
.width() -
226 kCloseAndLinkPadding
- show_all_size
.width() -
227 kDownloadsTitlePadding
- image_size
.width() -
229 int next_x
= show_link_only
? kLeftPadding
:
230 max_download_x
+ kDownloadPadding
;
231 // Align vertically with show_all_view_.
232 arrow_image_
->SetBounds(next_x
,
233 CenterPosition(image_size
.height(), height()),
234 image_size
.width(), image_size
.height());
235 next_x
+= image_size
.width() + kDownloadsTitlePadding
;
236 show_all_view_
->SetBounds(next_x
,
237 CenterPosition(show_all_size
.height(), height()),
238 show_all_size
.width(),
239 show_all_size
.height());
240 next_x
+= show_all_size
.width() + kCloseAndLinkPadding
;
241 // If the window is maximized, we want to expand the hitbox of the close
242 // button to the right and bottom to make it easier to click.
243 bool is_maximized
= browser_
->window()->IsMaximized();
244 int y
= CenterPosition(close_button_size
.height(), height());
245 close_button_
->SetBounds(next_x
, y
,
246 is_maximized
? width() - next_x
: close_button_size
.width(),
247 is_maximized
? height() - y
: close_button_size
.height());
248 if (show_link_only
) {
249 // Let's hide all the items.
250 std::vector
<DownloadItemView
*>::reverse_iterator ri
;
251 for (ri
= download_views_
.rbegin(); ri
!= download_views_
.rend(); ++ri
)
252 (*ri
)->SetVisible(false);
256 next_x
= kLeftPadding
;
257 std::vector
<DownloadItemView
*>::reverse_iterator ri
;
258 for (ri
= download_views_
.rbegin(); ri
!= download_views_
.rend(); ++ri
) {
259 gfx::Size view_size
= (*ri
)->GetPreferredSize();
263 // Figure out width of item.
264 int item_width
= view_size
.width();
265 if (new_item_animation_
->is_animating() && ri
== download_views_
.rbegin()) {
266 item_width
= static_cast<int>(static_cast<double>(view_size
.width()) *
267 new_item_animation_
->GetCurrentValue());
270 next_x
+= item_width
;
272 // Make sure our item can be contained within the shelf.
273 if (next_x
< max_download_x
) {
274 (*ri
)->SetVisible(true);
275 (*ri
)->SetBounds(x
, CenterPosition(view_size
.height(), height()),
276 item_width
, view_size
.height());
278 (*ri
)->SetVisible(false);
283 void DownloadShelfView::ViewHierarchyChanged(
284 const ViewHierarchyChangedDetails
& details
) {
285 View::ViewHierarchyChanged(details
);
287 if (details
.is_add
&& (details
.child
== this)) {
288 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
289 arrow_image_
= new views::ImageView();
290 arrow_image_
->SetImage(rb
.GetImageSkiaNamed(IDR_DOWNLOADS_FAVICON
));
291 AddChildView(arrow_image_
);
293 show_all_view_
= new views::Link(
294 l10n_util::GetStringUTF16(IDS_SHOW_ALL_DOWNLOADS
));
295 show_all_view_
->set_listener(this);
296 AddChildView(show_all_view_
);
298 close_button_
= new views::ImageButton(this);
299 close_button_
->SetImage(views::CustomButton::STATE_NORMAL
,
300 rb
.GetImageSkiaNamed(IDR_CLOSE_1
));
301 close_button_
->SetImage(views::CustomButton::STATE_HOVERED
,
302 rb
.GetImageSkiaNamed(IDR_CLOSE_1_H
));
303 close_button_
->SetImage(views::CustomButton::STATE_PRESSED
,
304 rb
.GetImageSkiaNamed(IDR_CLOSE_1_P
));
305 close_button_
->SetAccessibleName(
306 l10n_util::GetStringUTF16(IDS_ACCNAME_CLOSE
));
307 AddChildView(close_button_
);
309 UpdateColorsFromTheme();
311 new_item_animation_
.reset(new gfx::SlideAnimation(this));
312 new_item_animation_
->SetSlideDuration(kNewItemAnimationDurationMs
);
314 shelf_animation_
.reset(new gfx::SlideAnimation(this));
315 shelf_animation_
->SetSlideDuration(kShelfAnimationDurationMs
);
319 bool DownloadShelfView::CanFitFirstDownloadItem() {
320 if (download_views_
.empty())
323 gfx::Size image_size
= arrow_image_
->GetPreferredSize();
324 gfx::Size close_button_size
= close_button_
->GetPreferredSize();
325 gfx::Size show_all_size
= show_all_view_
->GetPreferredSize();
327 // Let's compute the width available for download items, which is the width
328 // of the shelf minus the "Show all downloads" link, arrow and close button
330 int available_width
= width() - kRightPadding
- close_button_size
.width() -
331 kCloseAndLinkPadding
- show_all_size
.width() - kDownloadsTitlePadding
-
332 image_size
.width() - kDownloadPadding
- kLeftPadding
;
333 if (available_width
<= 0)
336 gfx::Size item_size
= (*download_views_
.rbegin())->GetPreferredSize();
337 return item_size
.width() < available_width
;
340 void DownloadShelfView::UpdateColorsFromTheme() {
341 if (show_all_view_
&& close_button_
&& GetThemeProvider()) {
342 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
343 set_background(views::Background::CreateSolidBackground(
344 GetThemeProvider()->GetColor(ThemeProperties::COLOR_TOOLBAR
)));
345 show_all_view_
->SetBackgroundColor(background()->get_color());
346 show_all_view_
->SetEnabledColor(
347 GetThemeProvider()->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT
));
348 close_button_
->SetBackground(
349 GetThemeProvider()->GetColor(ThemeProperties::COLOR_TAB_TEXT
),
350 rb
.GetImageSkiaNamed(IDR_CLOSE_1
),
351 rb
.GetImageSkiaNamed(IDR_CLOSE_1_MASK
));
355 void DownloadShelfView::OnThemeChanged() {
356 UpdateColorsFromTheme();
359 void DownloadShelfView::LinkClicked(views::Link
* source
, int event_flags
) {
360 chrome::ShowDownloads(browser_
);
363 void DownloadShelfView::ButtonPressed(
364 views::Button
* button
, const ui::Event
& event
) {
368 bool DownloadShelfView::IsShowing() const {
369 return visible() && shelf_animation_
->IsShowing();
372 bool DownloadShelfView::IsClosing() const {
373 return shelf_animation_
->IsClosing();
376 void DownloadShelfView::DoShow() {
378 shelf_animation_
->Show();
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
)
387 RecordDownloadShelfClose(
388 download_views_
.size(), num_in_progress
, reason
== AUTOMATIC
);
389 parent_
->SetDownloadShelfVisible(false);
390 shelf_animation_
->Hide();
393 Browser
* DownloadShelfView::browser() const {
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.
402 // When the close animation is complete, remove all completed downloads.
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
]);
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);
422 bool DownloadShelfView::CanAutoClose() {
423 for (size_t i
= 0; i
< download_views_
.size(); ++i
) {
424 if (!download_views_
[i
]->download()->GetOpened())