Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / views / download / download_shelf_view.cc
blob4b6552bbf1dd69c7756a1f8d22b24adf3153365d
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"
7 #include <algorithm>
8 #include <vector>
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;
59 // Border color.
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;
77 namespace {
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();
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);
92 } // namespace
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);
108 DownloadShelfView::~DownloadShelfView() {
109 parent_->RemoveChildView(this);
112 void DownloadShelfView::AddDownloadView(DownloadItemView* view) {
113 mouse_watcher_.Stop();
115 DCHECK(view);
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() {
134 Close(AUTOMATIC);
137 void DownloadShelfView::RemoveDownloadView(View* view) {
138 DCHECK(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);
144 delete view;
145 if (download_views_.empty())
146 Close(AUTOMATIC);
147 else if (CanAutoClose())
148 mouse_watcher_.Start();
149 Layout();
150 SchedulePaint();
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) {
163 if (CanAutoClose())
164 mouse_watcher_.Start();
167 content::PageNavigator* DownloadShelfView::GetNavigator() {
168 return browser_;
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()));
186 return prefsize;
189 void DownloadShelfView::AnimationProgressed(const gfx::Animation *animation) {
190 if (animation == new_item_animation_.get()) {
191 Layout();
192 SchedulePaint();
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
199 // top-resizing.
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())
208 Closed();
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
218 // something to see.
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();
224 int max_download_x =
225 std::max<int>(0, width() - kRightPadding - close_button_size.width() -
226 kCloseAndLinkPadding - show_all_size.width() -
227 kDownloadsTitlePadding - image_size.width() -
228 kDownloadPadding);
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);
253 return;
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();
261 int x = next_x;
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());
277 } else {
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())
321 return true;
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
329 // and the padding.
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)
334 return false;
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) {
365 Close(USER_ACTION);
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() {
377 SetVisible(true);
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)
385 ++num_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 {
394 return browser_;
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;
419 SetVisible(false);
422 bool DownloadShelfView::CanAutoClose() {
423 for (size_t i = 0; i < download_views_.size(); ++i) {
424 if (!download_views_[i]->download()->GetOpened())
425 return false;
427 return true;