Windows should animate when they are about to get docked at screen edges.
[chromium-blink-merge.git] / ash / system / drive / tray_drive.cc
blob028aa29bb58f4dcba1e1af5eaca5af7c4f731036
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 "ash/system/drive/tray_drive.h"
7 #include <vector>
9 #include "ash/shell.h"
10 #include "ash/system/tray/fixed_sized_scroll_view.h"
11 #include "ash/system/tray/hover_highlight_view.h"
12 #include "ash/system/tray/system_tray.h"
13 #include "ash/system/tray/system_tray_delegate.h"
14 #include "ash/system/tray/system_tray_notifier.h"
15 #include "ash/system/tray/tray_constants.h"
16 #include "ash/system/tray/tray_details_view.h"
17 #include "ash/system/tray/tray_item_more.h"
18 #include "ash/system/tray/tray_item_view.h"
19 #include "base/logging.h"
20 #include "base/stl_util.h"
21 #include "base/strings/string_number_conversions.h"
22 #include "base/strings/utf_string_conversions.h"
23 #include "grit/ash_resources.h"
24 #include "grit/ash_strings.h"
25 #include "ui/base/l10n/l10n_util.h"
26 #include "ui/base/resource/resource_bundle.h"
27 #include "ui/gfx/font.h"
28 #include "ui/gfx/image/image.h"
29 #include "ui/views/controls/button/image_button.h"
30 #include "ui/views/controls/image_view.h"
31 #include "ui/views/controls/label.h"
32 #include "ui/views/controls/progress_bar.h"
33 #include "ui/views/layout/box_layout.h"
34 #include "ui/views/layout/grid_layout.h"
35 #include "ui/views/widget/widget.h"
37 namespace ash {
39 namespace internal {
41 namespace {
43 const int kSidePadding = 8;
44 const int kHorizontalPadding = 6;
45 const int kVerticalPadding = 6;
46 const int kTopPadding = 6;
47 const int kBottomPadding = 10;
48 const int kProgressBarWidth = 100;
49 const int kProgressBarHeight = 11;
50 const int64 kHideDelayInMs = 1000;
52 base::string16 GetTrayLabel(const ash::DriveOperationStatusList& list) {
53 return l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_DRIVE_SYNCING,
54 base::IntToString16(static_cast<int>(list.size())));
57 scoped_ptr<ash::DriveOperationStatusList> GetCurrentOperationList() {
58 ash::SystemTrayDelegate* delegate =
59 ash::Shell::GetInstance()->system_tray_delegate();
60 scoped_ptr<ash::DriveOperationStatusList> list(
61 new ash::DriveOperationStatusList);
62 delegate->GetDriveOperationStatusList(list.get());
63 return list.Pass();
68 namespace tray {
70 class DriveDefaultView : public TrayItemMore {
71 public:
72 DriveDefaultView(SystemTrayItem* owner,
73 const DriveOperationStatusList* list)
74 : TrayItemMore(owner, true) {
75 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
77 SetImage(bundle.GetImageNamed(IDR_AURA_UBER_TRAY_DRIVE).ToImageSkia());
78 Update(list);
81 virtual ~DriveDefaultView() {}
83 void Update(const DriveOperationStatusList* list) {
84 DCHECK(list);
85 base::string16 label = GetTrayLabel(*list);
86 SetLabel(label);
87 SetAccessibleName(label);
90 private:
91 DISALLOW_COPY_AND_ASSIGN(DriveDefaultView);
94 class DriveDetailedView : public TrayDetailsView,
95 public ViewClickListener {
96 public:
97 DriveDetailedView(SystemTrayItem* owner,
98 const DriveOperationStatusList* list)
99 : TrayDetailsView(owner),
100 settings_(NULL),
101 in_progress_img_(NULL),
102 done_img_(NULL),
103 failed_img_(NULL) {
104 in_progress_img_ = ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
105 IDR_AURA_UBER_TRAY_DRIVE);
106 done_img_ = ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
107 IDR_AURA_UBER_TRAY_DRIVE_DONE);
108 failed_img_ = ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
109 IDR_AURA_UBER_TRAY_DRIVE_FAILED);
111 Update(list);
114 virtual ~DriveDetailedView() {
115 STLDeleteValues(&update_map_);
118 void Update(const DriveOperationStatusList* list) {
119 AppendOperationList(list);
120 AppendSettings();
121 AppendHeaderEntry(list);
123 SchedulePaint();
126 private:
128 class OperationProgressBar : public views::ProgressBar {
129 public:
130 OperationProgressBar() {}
131 private:
133 // Overridden from View:
134 virtual gfx::Size GetPreferredSize() OVERRIDE {
135 return gfx::Size(kProgressBarWidth, kProgressBarHeight);
138 DISALLOW_COPY_AND_ASSIGN(OperationProgressBar);
141 class RowView : public HoverHighlightView,
142 public views::ButtonListener {
143 public:
144 RowView(DriveDetailedView* parent,
145 ash::DriveOperationStatus::OperationState state,
146 double progress,
147 const base::FilePath& file_path,
148 int32 operation_id)
149 : HoverHighlightView(parent),
150 container_(parent),
151 status_img_(NULL),
152 label_container_(NULL),
153 progress_bar_(NULL),
154 cancel_button_(NULL),
155 operation_id_(operation_id) {
156 // Status image.
157 status_img_ = new views::ImageView();
158 AddChildView(status_img_);
160 label_container_ = new views::View();
161 label_container_->SetLayoutManager(new views::BoxLayout(
162 views::BoxLayout::kVertical, 0, 0, kVerticalPadding));
163 #if defined(OS_POSIX)
164 base::string16 file_label = UTF8ToUTF16(file_path.BaseName().value());
165 #elif defined(OS_WIN)
166 base::string16 file_label = WideToUTF16(file_path.BaseName().value());
167 #endif
168 views::Label* label = new views::Label(file_label);
169 label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
170 label_container_->AddChildView(label);
171 // Add progress bar.
172 progress_bar_ = new OperationProgressBar();
173 label_container_->AddChildView(progress_bar_);
175 AddChildView(label_container_);
177 cancel_button_ = new views::ImageButton(this);
178 cancel_button_->SetImage(views::ImageButton::STATE_NORMAL,
179 ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
180 IDR_AURA_UBER_TRAY_DRIVE_CANCEL));
181 cancel_button_->SetImage(views::ImageButton::STATE_HOVERED,
182 ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
183 IDR_AURA_UBER_TRAY_DRIVE_CANCEL_HOVER));
185 UpdateStatus(state, progress);
186 AddChildView(cancel_button_);
189 void UpdateStatus(ash::DriveOperationStatus::OperationState state,
190 double progress) {
191 status_img_->SetImage(container_->GetImageForState(state));
192 progress_bar_->SetValue(progress);
193 cancel_button_->SetVisible(
194 state == ash::DriveOperationStatus::OPERATION_NOT_STARTED ||
195 state == ash::DriveOperationStatus::OPERATION_IN_PROGRESS);
198 private:
200 // views::View overrides.
201 virtual gfx::Size GetPreferredSize() OVERRIDE {
202 return gfx::Size(
203 status_img_->GetPreferredSize().width() +
204 label_container_->GetPreferredSize().width() +
205 cancel_button_->GetPreferredSize().width() +
206 2 * kSidePadding + 2 * kHorizontalPadding,
207 std::max(status_img_->GetPreferredSize().height(),
208 std::max(label_container_->GetPreferredSize().height(),
209 cancel_button_->GetPreferredSize().height())) +
210 kTopPadding + kBottomPadding);
213 virtual void Layout() OVERRIDE {
214 gfx::Rect child_area(GetLocalBounds());
215 if (child_area.IsEmpty())
216 return;
218 int pos_x = child_area.x() + kSidePadding;
219 int pos_y = child_area.y() + kTopPadding;
221 gfx::Rect bounds_status(
222 gfx::Point(pos_x,
223 pos_y + (child_area.height() - kTopPadding -
224 kBottomPadding -
225 status_img_->GetPreferredSize().height())/2),
226 status_img_->GetPreferredSize());
227 status_img_->SetBoundsRect(
228 gfx::IntersectRects(bounds_status, child_area));
229 pos_x += status_img_->bounds().width() + kHorizontalPadding;
231 gfx::Rect bounds_label(pos_x,
232 pos_y,
233 child_area.width() - 2 * kSidePadding -
234 2 * kHorizontalPadding -
235 status_img_->GetPreferredSize().width() -
236 cancel_button_->GetPreferredSize().width(),
237 label_container_->GetPreferredSize().height());
238 label_container_->SetBoundsRect(
239 gfx::IntersectRects(bounds_label, child_area));
240 pos_x += label_container_->bounds().width() + kHorizontalPadding;
242 gfx::Rect bounds_button(
243 gfx::Point(pos_x,
244 pos_y + (child_area.height() - kTopPadding -
245 kBottomPadding -
246 cancel_button_->GetPreferredSize().height())/2),
247 cancel_button_->GetPreferredSize());
248 cancel_button_->SetBoundsRect(
249 gfx::IntersectRects(bounds_button, child_area));
252 // views::ButtonListener overrides.
253 virtual void ButtonPressed(views::Button* sender,
254 const ui::Event& event) OVERRIDE {
255 DCHECK(sender == cancel_button_);
256 container_->OnCancelOperation(operation_id_);
259 DriveDetailedView* container_;
260 views::ImageView* status_img_;
261 views::View* label_container_;
262 views::ProgressBar* progress_bar_;
263 views::ImageButton* cancel_button_;
264 int32 operation_id_;
266 DISALLOW_COPY_AND_ASSIGN(RowView);
269 void AppendHeaderEntry(const DriveOperationStatusList* list) {
270 if (footer())
271 return;
272 CreateSpecialRow(IDS_ASH_STATUS_TRAY_DRIVE, this);
275 gfx::ImageSkia* GetImageForState(
276 ash::DriveOperationStatus::OperationState state) {
277 switch (state) {
278 case ash::DriveOperationStatus::OPERATION_NOT_STARTED:
279 case ash::DriveOperationStatus::OPERATION_IN_PROGRESS:
280 return in_progress_img_;
281 case ash::DriveOperationStatus::OPERATION_COMPLETED:
282 return done_img_;
283 case ash::DriveOperationStatus::OPERATION_FAILED:
284 return failed_img_;
286 return failed_img_;
289 void OnCancelOperation(int32 operation_id) {
290 SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
291 delegate->CancelDriveOperation(operation_id);
294 void AppendOperationList(const DriveOperationStatusList* list) {
295 if (!scroller())
296 CreateScrollableList();
298 // Apply the update.
299 std::set<base::FilePath> new_set;
300 bool item_list_changed = false;
301 for (DriveOperationStatusList::const_iterator it = list->begin();
302 it != list->end(); ++it) {
303 const DriveOperationStatus& operation = *it;
305 new_set.insert(operation.file_path);
306 std::map<base::FilePath, RowView*>::iterator existing_item =
307 update_map_.find(operation.file_path);
309 if (existing_item != update_map_.end()) {
310 existing_item->second->UpdateStatus(operation.state,
311 operation.progress);
312 } else {
313 RowView* row_view = new RowView(this,
314 operation.state,
315 operation.progress,
316 operation.file_path,
317 operation.id);
319 update_map_[operation.file_path] = row_view;
320 scroll_content()->AddChildView(row_view);
321 item_list_changed = true;
325 // Remove items from the list that haven't been added or modified with this
326 // update batch.
327 std::set<base::FilePath> remove_set;
328 for (std::map<base::FilePath, RowView*>::iterator update_iter =
329 update_map_.begin();
330 update_iter != update_map_.end(); ++update_iter) {
331 if (new_set.find(update_iter->first) == new_set.end()) {
332 remove_set.insert(update_iter->first);
336 for (std::set<base::FilePath>::iterator removed_iter = remove_set.begin();
337 removed_iter != remove_set.end(); ++removed_iter) {
338 delete update_map_[*removed_iter];
339 update_map_.erase(*removed_iter);
340 item_list_changed = true;
343 if (item_list_changed)
344 scroller()->Layout();
346 // Close the details if there is really nothing to show there anymore.
347 if (new_set.empty() && GetWidget())
348 GetWidget()->Close();
351 void AppendSettings() {
352 if (settings_)
353 return;
355 HoverHighlightView* container = new HoverHighlightView(this);
356 container->AddLabel(ui::ResourceBundle::GetSharedInstance().
357 GetLocalizedString(IDS_ASH_STATUS_TRAY_DRIVE_SETTINGS),
358 gfx::Font::NORMAL);
359 AddChildView(container);
360 settings_ = container;
363 // Overridden from ViewClickListener.
364 virtual void OnViewClicked(views::View* sender) OVERRIDE {
365 SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
366 if (sender == footer()->content()) {
367 owner()->system_tray()->ShowDefaultView(BUBBLE_USE_EXISTING);
368 } else if (sender == settings_) {
369 delegate->ShowDriveSettings();
373 // Maps operation entries to their file paths.
374 std::map<base::FilePath, RowView*> update_map_;
375 views::View* settings_;
376 gfx::ImageSkia* in_progress_img_;
377 gfx::ImageSkia* done_img_;
378 gfx::ImageSkia* failed_img_;
380 DISALLOW_COPY_AND_ASSIGN(DriveDetailedView);
383 } // namespace tray
385 TrayDrive::TrayDrive(SystemTray* system_tray) :
386 TrayImageItem(system_tray, IDR_AURA_UBER_TRAY_DRIVE_LIGHT),
387 default_(NULL),
388 detailed_(NULL) {
389 Shell::GetInstance()->system_tray_notifier()->AddDriveObserver(this);
392 TrayDrive::~TrayDrive() {
393 Shell::GetInstance()->system_tray_notifier()->RemoveDriveObserver(this);
396 bool TrayDrive::GetInitialVisibility() {
397 return false;
400 views::View* TrayDrive::CreateDefaultView(user::LoginStatus status) {
401 DCHECK(!default_);
403 if (status != user::LOGGED_IN_USER && status != user::LOGGED_IN_OWNER)
404 return NULL;
406 // If the list is empty AND the tray icon is invisible (= not in the margin
407 // duration of delayed item hiding), don't show the item.
408 scoped_ptr<DriveOperationStatusList> list(GetCurrentOperationList());
409 if (list->empty() && !tray_view()->visible())
410 return NULL;
412 default_ = new tray::DriveDefaultView(this, list.get());
413 return default_;
416 views::View* TrayDrive::CreateDetailedView(user::LoginStatus status) {
417 DCHECK(!detailed_);
419 if (status != user::LOGGED_IN_USER && status != user::LOGGED_IN_OWNER)
420 return NULL;
422 // If the list is empty AND the tray icon is invisible (= not in the margin
423 // duration of delayed item hiding), don't show the item.
424 scoped_ptr<DriveOperationStatusList> list(GetCurrentOperationList());
425 if (list->empty() && !tray_view()->visible())
426 return NULL;
428 detailed_ = new tray::DriveDetailedView(this, list.get());
429 return detailed_;
432 void TrayDrive::DestroyDefaultView() {
433 default_ = NULL;
436 void TrayDrive::DestroyDetailedView() {
437 detailed_ = NULL;
440 void TrayDrive::UpdateAfterLoginStatusChange(user::LoginStatus status) {
441 if (status == user::LOGGED_IN_USER || status == user::LOGGED_IN_OWNER)
442 return;
444 tray_view()->SetVisible(false);
445 DestroyDefaultView();
446 DestroyDetailedView();
449 void TrayDrive::OnDriveJobUpdated(const DriveOperationStatus& status) {
450 // The Drive job list manager changed its notification interface *not* to send
451 // the whole list of operations each time, to clarify which operation is
452 // updated and to reduce redundancy.
454 // TrayDrive should be able to benefit from the change, but for now, to
455 // incrementally migrate to the new way with minimum diffs, we still get the
456 // list of operations each time the event is fired.
457 // TODO(kinaba) http://crbug.com/128079 clean it up.
458 scoped_ptr<DriveOperationStatusList> list(GetCurrentOperationList());
459 bool is_new_item = true;
460 for (size_t i = 0; i < list->size(); ++i) {
461 if ((*list)[i].id == status.id) {
462 (*list)[i] = status;
463 is_new_item = false;
464 break;
467 if (is_new_item)
468 list->push_back(status);
470 // Check if all the operations are in the finished state.
471 bool all_jobs_finished = true;
472 for (size_t i = 0; i < list->size(); ++i) {
473 if ((*list)[i].state != DriveOperationStatus::OPERATION_COMPLETED &&
474 (*list)[i].state != DriveOperationStatus::OPERATION_FAILED) {
475 all_jobs_finished = false;
476 break;
480 if (all_jobs_finished) {
481 // If all the jobs ended, the tray item will be hidden after a certain
482 // amount of delay. This is to avoid flashes between sequentially executed
483 // Drive operations (see crbug/165679).
484 hide_timer_.Start(FROM_HERE,
485 base::TimeDelta::FromMilliseconds(kHideDelayInMs),
486 this,
487 &TrayDrive::HideIfNoOperations);
488 return;
491 // If the list is non-empty, stop the hiding timer (if any).
492 hide_timer_.Stop();
494 tray_view()->SetVisible(true);
495 if (default_)
496 default_->Update(list.get());
497 if (detailed_)
498 detailed_->Update(list.get());
501 void TrayDrive::HideIfNoOperations() {
502 DriveOperationStatusList empty_list;
504 tray_view()->SetVisible(false);
505 if (default_)
506 default_->Update(&empty_list);
507 if (detailed_)
508 detailed_->Update(&empty_list);
511 } // namespace internal
512 } // namespace ash