- add sources.
[platform/framework/web/crosswalk.git] / src / ash / system / drive / tray_drive.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 "ash/system/drive/tray_drive.h"
6
7 #include <vector>
8
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"
36
37 namespace ash {
38
39 namespace internal {
40
41 namespace {
42
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;
51
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())));
55 }
56
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();
64 }
65
66 }
67
68 namespace tray {
69
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();
76
77     SetImage(bundle.GetImageNamed(IDR_AURA_UBER_TRAY_DRIVE).ToImageSkia());
78     Update(list);
79   }
80
81   virtual ~DriveDefaultView() {}
82
83   void Update(const DriveOperationStatusList* list) {
84     DCHECK(list);
85     base::string16 label = GetTrayLabel(*list);
86     SetLabel(label);
87     SetAccessibleName(label);
88   }
89
90  private:
91   DISALLOW_COPY_AND_ASSIGN(DriveDefaultView);
92 };
93
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);
110
111     Update(list);
112   }
113
114   virtual ~DriveDetailedView() {
115     STLDeleteValues(&update_map_);
116   }
117
118   void Update(const DriveOperationStatusList* list) {
119     AppendOperationList(list);
120     AppendSettings();
121     AppendHeaderEntry(list);
122
123     SchedulePaint();
124   }
125
126  private:
127
128   class OperationProgressBar : public views::ProgressBar {
129    public:
130     OperationProgressBar() {}
131    private:
132
133     // Overridden from View:
134     virtual gfx::Size GetPreferredSize() OVERRIDE {
135       return gfx::Size(kProgressBarWidth, kProgressBarHeight);
136     }
137
138     DISALLOW_COPY_AND_ASSIGN(OperationProgressBar);
139   };
140
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_);
159
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_);
174
175       AddChildView(label_container_);
176
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));
184
185       UpdateStatus(state, progress);
186       AddChildView(cancel_button_);
187     }
188
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);
196     }
197
198    private:
199
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);
211     }
212
213     virtual void Layout() OVERRIDE {
214       gfx::Rect child_area(GetLocalBounds());
215       if (child_area.IsEmpty())
216         return;
217
218       int pos_x = child_area.x() + kSidePadding;
219       int pos_y = child_area.y() + kTopPadding;
220
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;
230
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;
241
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));
250     }
251
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_);
257     }
258
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_;
265
266     DISALLOW_COPY_AND_ASSIGN(RowView);
267   };
268
269   void AppendHeaderEntry(const DriveOperationStatusList* list) {
270     if (footer())
271       return;
272     CreateSpecialRow(IDS_ASH_STATUS_TRAY_DRIVE, this);
273   }
274
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_;
285     }
286     return failed_img_;
287   }
288
289   void OnCancelOperation(int32 operation_id) {
290     SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
291     delegate->CancelDriveOperation(operation_id);
292   }
293
294   void AppendOperationList(const DriveOperationStatusList* list) {
295     if (!scroller())
296       CreateScrollableList();
297
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;
304
305       new_set.insert(operation.file_path);
306       std::map<base::FilePath, RowView*>::iterator existing_item =
307           update_map_.find(operation.file_path);
308
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);
318
319         update_map_[operation.file_path] = row_view;
320         scroll_content()->AddChildView(row_view);
321         item_list_changed = true;
322       }
323     }
324
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);
333       }
334     }
335
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;
341     }
342
343     if (item_list_changed)
344       scroller()->Layout();
345
346     // Close the details if there is really nothing to show there anymore.
347     if (new_set.empty() && GetWidget())
348       GetWidget()->Close();
349   }
350
351   void AppendSettings() {
352     if (settings_)
353       return;
354
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;
361   }
362
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();
370     }
371   }
372
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_;
379
380   DISALLOW_COPY_AND_ASSIGN(DriveDetailedView);
381 };
382
383 }  // namespace tray
384
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);
390 }
391
392 TrayDrive::~TrayDrive() {
393   Shell::GetInstance()->system_tray_notifier()->RemoveDriveObserver(this);
394 }
395
396 bool TrayDrive::GetInitialVisibility() {
397   return false;
398 }
399
400 views::View* TrayDrive::CreateDefaultView(user::LoginStatus status) {
401   DCHECK(!default_);
402
403   if (status != user::LOGGED_IN_USER && status != user::LOGGED_IN_OWNER)
404     return NULL;
405
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;
411
412   default_ = new tray::DriveDefaultView(this, list.get());
413   return default_;
414 }
415
416 views::View* TrayDrive::CreateDetailedView(user::LoginStatus status) {
417   DCHECK(!detailed_);
418
419   if (status != user::LOGGED_IN_USER && status != user::LOGGED_IN_OWNER)
420     return NULL;
421
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;
427
428   detailed_ = new tray::DriveDetailedView(this, list.get());
429   return detailed_;
430 }
431
432 void TrayDrive::DestroyDefaultView() {
433   default_ = NULL;
434 }
435
436 void TrayDrive::DestroyDetailedView() {
437   detailed_ = NULL;
438 }
439
440 void TrayDrive::UpdateAfterLoginStatusChange(user::LoginStatus status) {
441   if (status == user::LOGGED_IN_USER || status == user::LOGGED_IN_OWNER)
442     return;
443
444   tray_view()->SetVisible(false);
445   DestroyDefaultView();
446   DestroyDetailedView();
447 }
448
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.
453   //
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;
465     }
466   }
467   if (is_new_item)
468     list->push_back(status);
469
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;
477     }
478   }
479
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;
489   }
490
491   // If the list is non-empty, stop the hiding timer (if any).
492   hide_timer_.Stop();
493
494   tray_view()->SetVisible(true);
495   if (default_)
496     default_->Update(list.get());
497   if (detailed_)
498     detailed_->Update(list.get());
499 }
500
501 void TrayDrive::HideIfNoOperations() {
502   DriveOperationStatusList empty_list;
503
504   tray_view()->SetVisible(false);
505   if (default_)
506     default_->Update(&empty_list);
507   if (detailed_)
508     detailed_->Update(&empty_list);
509 }
510
511 }  // namespace internal
512 }  // namespace ash