- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / views / download / download_shelf_view.cc
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();
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
88 int CenterPosition(int size, int target_size) {
89   return std::max((target_size - size) / 2, kTopBottomPadding);
90 }
91
92 }  // namespace
93
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
108 DownloadShelfView::~DownloadShelfView() {
109   parent_->RemoveChildView(this);
110 }
111
112 void DownloadShelfView::AddDownloadView(DownloadItemView* view) {
113   mouse_watcher_.Stop();
114
115   DCHECK(view);
116   download_views_.push_back(view);
117   AddChildView(view);
118   if (download_views_.size() > kMaxDownloadViews)
119     RemoveDownloadView(*download_views_.begin());
120
121   new_item_animation_->Reset();
122   new_item_animation_->Show();
123 }
124
125 void DownloadShelfView::DoAddDownload(DownloadItem* download) {
126   DownloadItemView* view = new DownloadItemView(download, this);
127   AddDownloadView(view);
128 }
129
130 void DownloadShelfView::MouseMovedOutOfHost() {
131   Close(AUTOMATIC);
132 }
133
134 void DownloadShelfView::OnWillChangeFocus(views::View* focused_before,
135                                           views::View* focused_now) {
136   SchedulePaintForDownloadItem(focused_before);
137   SchedulePaintForDownloadItem(focused_now);
138 }
139
140 void DownloadShelfView::OnDidChangeFocus(views::View* focused_before,
141                                          views::View* focused_now) {
142   AccessiblePaneView::OnDidChangeFocus(focused_before, focused_now);
143 }
144
145 void DownloadShelfView::RemoveDownloadView(View* view) {
146   DCHECK(view);
147   std::vector<DownloadItemView*>::iterator i =
148       find(download_views_.begin(), download_views_.end(), view);
149   DCHECK(i != download_views_.end());
150   download_views_.erase(i);
151   RemoveChildView(view);
152   delete view;
153   if (download_views_.empty())
154     Close(AUTOMATIC);
155   else if (CanAutoClose())
156     mouse_watcher_.Start();
157   Layout();
158   SchedulePaint();
159 }
160
161 views::View* DownloadShelfView::GetDefaultFocusableChild() {
162   return download_views_.empty() ?
163       static_cast<View*>(show_all_view_) : download_views_[0];
164 }
165
166 void DownloadShelfView::OnPaint(gfx::Canvas* canvas) {
167   OnPaintBackground(canvas);
168   OnPaintBorder(canvas);
169
170   // Draw the focus rect here, since it's outside the bounds of the item.
171   for (size_t i = 0; i < download_views_.size(); ++i) {
172     if (download_views_[i]->HasFocus()) {
173       gfx::Rect r = GetFocusRectBounds(download_views_[i]);
174       r.Inset(0, 0, 0, 1);
175       canvas->DrawFocusRect(r);
176       break;
177     }
178   }
179 }
180
181 void DownloadShelfView::OnPaintBorder(gfx::Canvas* canvas) {
182   canvas->FillRect(gfx::Rect(0, 0, width(), 1), kBorderColor);
183 }
184
185 void DownloadShelfView::OpenedDownload(DownloadItemView* view) {
186   if (CanAutoClose())
187     mouse_watcher_.Start();
188 }
189
190 content::PageNavigator* DownloadShelfView::GetNavigator() {
191   return browser_;
192 }
193
194 gfx::Size DownloadShelfView::GetPreferredSize() {
195   gfx::Size prefsize(kRightPadding + kLeftPadding + kCloseAndLinkPadding, 0);
196   AdjustSize(close_button_, &prefsize);
197   AdjustSize(show_all_view_, &prefsize);
198   // Add one download view to the preferred size.
199   if (!download_views_.empty()) {
200     AdjustSize(*download_views_.begin(), &prefsize);
201     prefsize.Enlarge(kDownloadPadding, 0);
202   }
203   prefsize.Enlarge(0, kTopBottomPadding + kTopBottomPadding);
204   if (shelf_animation_->is_animating()) {
205     prefsize.set_height(static_cast<int>(
206         static_cast<double>(prefsize.height()) *
207                             shelf_animation_->GetCurrentValue()));
208   }
209   return prefsize;
210 }
211
212 void DownloadShelfView::AnimationProgressed(const gfx::Animation *animation) {
213   if (animation == new_item_animation_.get()) {
214     Layout();
215     SchedulePaint();
216   } else if (animation == shelf_animation_.get()) {
217     // Force a re-layout of the parent, which will call back into
218     // GetPreferredSize, where we will do our animation. In the case where the
219     // animation is hiding, we do a full resize - the fast resizing would
220     // otherwise leave blank white areas where the shelf was and where the
221     // user's eye is. Thankfully bottom-resizing is a lot faster than
222     // top-resizing.
223     parent_->ToolbarSizeChanged(shelf_animation_->IsShowing());
224   }
225 }
226
227 void DownloadShelfView::AnimationEnded(const gfx::Animation *animation) {
228   if (animation == shelf_animation_.get()) {
229     parent_->SetDownloadShelfVisible(shelf_animation_->IsShowing());
230     if (!shelf_animation_->IsShowing())
231       Closed();
232   }
233 }
234
235 void DownloadShelfView::Layout() {
236   // Let our base class layout our child views
237   views::View::Layout();
238
239   // If there is not enough room to show the first download item, show the
240   // "Show all downloads" link to the left to make it more visible that there is
241   // something to see.
242   bool show_link_only = !CanFitFirstDownloadItem();
243
244   gfx::Size image_size = arrow_image_->GetPreferredSize();
245   gfx::Size close_button_size = close_button_->GetPreferredSize();
246   gfx::Size show_all_size = show_all_view_->GetPreferredSize();
247   int max_download_x =
248       std::max<int>(0, width() - kRightPadding - close_button_size.width() -
249                        kCloseAndLinkPadding - show_all_size.width() -
250                        kDownloadsTitlePadding - image_size.width() -
251                        kDownloadPadding);
252   int next_x = show_link_only ? kLeftPadding :
253                                 max_download_x + kDownloadPadding;
254   // Align vertically with show_all_view_.
255   arrow_image_->SetBounds(next_x,
256                           CenterPosition(image_size.height(), height()),
257                           image_size.width(), image_size.height());
258   next_x += image_size.width() + kDownloadsTitlePadding;
259   show_all_view_->SetBounds(next_x,
260                             CenterPosition(show_all_size.height(), height()),
261                             show_all_size.width(),
262                             show_all_size.height());
263   next_x += show_all_size.width() + kCloseAndLinkPadding;
264   // If the window is maximized, we want to expand the hitbox of the close
265   // button to the right and bottom to make it easier to click.
266   bool is_maximized = browser_->window()->IsMaximized();
267   int y = CenterPosition(close_button_size.height(), height());
268   close_button_->SetBounds(next_x, y,
269       is_maximized ? width() - next_x : close_button_size.width(),
270       is_maximized ? height() - y : close_button_size.height());
271   if (show_link_only) {
272     // Let's hide all the items.
273     std::vector<DownloadItemView*>::reverse_iterator ri;
274     for (ri = download_views_.rbegin(); ri != download_views_.rend(); ++ri)
275       (*ri)->SetVisible(false);
276     return;
277   }
278
279   next_x = kLeftPadding;
280   std::vector<DownloadItemView*>::reverse_iterator ri;
281   for (ri = download_views_.rbegin(); ri != download_views_.rend(); ++ri) {
282     gfx::Size view_size = (*ri)->GetPreferredSize();
283
284     int x = next_x;
285
286     // Figure out width of item.
287     int item_width = view_size.width();
288     if (new_item_animation_->is_animating() && ri == download_views_.rbegin()) {
289        item_width = static_cast<int>(static_cast<double>(view_size.width()) *
290                      new_item_animation_->GetCurrentValue());
291     }
292
293     next_x += item_width;
294
295     // Make sure our item can be contained within the shelf.
296     if (next_x < max_download_x) {
297       (*ri)->SetVisible(true);
298       (*ri)->SetBounds(x, CenterPosition(view_size.height(), height()),
299                        item_width, view_size.height());
300     } else {
301       (*ri)->SetVisible(false);
302     }
303   }
304 }
305
306 void DownloadShelfView::ViewHierarchyChanged(
307     const ViewHierarchyChangedDetails& details) {
308   View::ViewHierarchyChanged(details);
309
310   if (details.is_add && (details.child == this)) {
311     ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
312     arrow_image_ = new views::ImageView();
313     arrow_image_->SetImage(rb.GetImageSkiaNamed(IDR_DOWNLOADS_FAVICON));
314     AddChildView(arrow_image_);
315
316     show_all_view_ = new views::Link(
317         l10n_util::GetStringUTF16(IDS_SHOW_ALL_DOWNLOADS));
318     show_all_view_->set_listener(this);
319     AddChildView(show_all_view_);
320
321     close_button_ = new views::ImageButton(this);
322     close_button_->SetImage(views::CustomButton::STATE_NORMAL,
323                             rb.GetImageSkiaNamed(IDR_CLOSE_1));
324     close_button_->SetImage(views::CustomButton::STATE_HOVERED,
325                             rb.GetImageSkiaNamed(IDR_CLOSE_1_H));
326     close_button_->SetImage(views::CustomButton::STATE_PRESSED,
327                             rb.GetImageSkiaNamed(IDR_CLOSE_1_P));
328     close_button_->SetAccessibleName(
329         l10n_util::GetStringUTF16(IDS_ACCNAME_CLOSE));
330     AddChildView(close_button_);
331
332     UpdateColorsFromTheme();
333
334     new_item_animation_.reset(new gfx::SlideAnimation(this));
335     new_item_animation_->SetSlideDuration(kNewItemAnimationDurationMs);
336
337     shelf_animation_.reset(new gfx::SlideAnimation(this));
338     shelf_animation_->SetSlideDuration(kShelfAnimationDurationMs);
339   }
340 }
341
342 bool DownloadShelfView::CanFitFirstDownloadItem() {
343   if (download_views_.empty())
344     return true;
345
346   gfx::Size image_size = arrow_image_->GetPreferredSize();
347   gfx::Size close_button_size = close_button_->GetPreferredSize();
348   gfx::Size show_all_size = show_all_view_->GetPreferredSize();
349
350   // Let's compute the width available for download items, which is the width
351   // of the shelf minus the "Show all downloads" link, arrow and close button
352   // and the padding.
353   int available_width = width() - kRightPadding - close_button_size.width() -
354       kCloseAndLinkPadding - show_all_size.width() - kDownloadsTitlePadding -
355       image_size.width() - kDownloadPadding - kLeftPadding;
356   if (available_width <= 0)
357     return false;
358
359   gfx::Size item_size = (*download_views_.rbegin())->GetPreferredSize();
360   return item_size.width() < available_width;
361 }
362
363 void DownloadShelfView::UpdateColorsFromTheme() {
364   if (show_all_view_ && close_button_ && GetThemeProvider()) {
365     ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
366     set_background(views::Background::CreateSolidBackground(
367         GetThemeProvider()->GetColor(ThemeProperties::COLOR_TOOLBAR)));
368     show_all_view_->SetBackgroundColor(background()->get_color());
369     show_all_view_->SetEnabledColor(
370         GetThemeProvider()->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT));
371     close_button_->SetBackground(
372         GetThemeProvider()->GetColor(ThemeProperties::COLOR_TAB_TEXT),
373         rb.GetImageSkiaNamed(IDR_CLOSE_1),
374         rb.GetImageSkiaNamed(IDR_CLOSE_1_MASK));
375   }
376 }
377
378 void DownloadShelfView::OnThemeChanged() {
379   UpdateColorsFromTheme();
380 }
381
382 void DownloadShelfView::LinkClicked(views::Link* source, int event_flags) {
383   chrome::ShowDownloads(browser_);
384 }
385
386 void DownloadShelfView::ButtonPressed(
387     views::Button* button, const ui::Event& event) {
388   Close(USER_ACTION);
389 }
390
391 bool DownloadShelfView::IsShowing() const {
392   return shelf_animation_->IsShowing();
393 }
394
395 bool DownloadShelfView::IsClosing() const {
396   return shelf_animation_->IsClosing();
397 }
398
399 void DownloadShelfView::DoShow() {
400   shelf_animation_->Show();
401 }
402
403 void DownloadShelfView::DoClose(CloseReason reason) {
404   int num_in_progress = 0;
405   for (size_t i = 0; i < download_views_.size(); ++i) {
406     if (download_views_[i]->download()->GetState() == DownloadItem::IN_PROGRESS)
407       ++num_in_progress;
408   }
409   RecordDownloadShelfClose(
410       download_views_.size(), num_in_progress, reason == AUTOMATIC);
411   parent_->SetDownloadShelfVisible(false);
412   shelf_animation_->Hide();
413 }
414
415 Browser* DownloadShelfView::browser() const {
416   return browser_;
417 }
418
419 void DownloadShelfView::Closed() {
420   // Don't remove completed downloads if the shelf is just being auto-hidden
421   // rather than explicitly closed by the user.
422   if (is_hidden())
423     return;
424   // When the close animation is complete, remove all completed downloads.
425   size_t i = 0;
426   while (i < download_views_.size()) {
427     DownloadItem* download = download_views_[i]->download();
428     DownloadItem::DownloadState state = download->GetState();
429     bool is_transfer_done = state == DownloadItem::COMPLETE ||
430                             state == DownloadItem::CANCELLED ||
431                             state == DownloadItem::INTERRUPTED;
432     if (is_transfer_done && !download->IsDangerous()) {
433       RemoveDownloadView(download_views_[i]);
434     } else {
435       // Treat the item as opened when we close. This way if we get shown again
436       // the user need not open this item for the shelf to auto-close.
437       download->SetOpened(true);
438       ++i;
439     }
440   }
441 }
442
443 bool DownloadShelfView::CanAutoClose() {
444   for (size_t i = 0; i < download_views_.size(); ++i) {
445     if (!download_views_[i]->download()->GetOpened())
446       return false;
447   }
448   return true;
449 }
450
451 void DownloadShelfView::SchedulePaintForDownloadItem(views::View* view) {
452   // Make sure it's not NULL.  (Focus sometimes changes to or from NULL.)
453   if (!view)
454     return;
455
456   // Make sure it's one of our DownloadItemViews.
457   bool found = false;
458   for (size_t i = 0; i < download_views_.size(); ++i) {
459     if (download_views_[i] == view)
460       found = true;
461   }
462   if (!found)
463     return;
464
465   // Invalidate it
466   gfx::Rect invalid_rect =
467       GetFocusRectBounds(static_cast<DownloadItemView*>(view));
468   SchedulePaintInRect(invalid_rect);
469 }
470
471 gfx::Rect DownloadShelfView::GetFocusRectBounds(
472     const DownloadItemView* download_item_view) {
473   gfx::Rect bounds = download_item_view->bounds();
474
475 #if defined(TOOLKIT_VIEWS)
476   bounds.set_height(bounds.height() - 1);
477   bounds.Offset(0, 3);
478 #endif
479
480   return bounds;
481 }