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"
9 #include "ash/metrics/user_metrics_recorder.h"
10 #include "ash/shell.h"
11 #include "ash/system/tray/fixed_sized_scroll_view.h"
12 #include "ash/system/tray/hover_highlight_view.h"
13 #include "ash/system/tray/system_tray.h"
14 #include "ash/system/tray/system_tray_delegate.h"
15 #include "ash/system/tray/system_tray_notifier.h"
16 #include "ash/system/tray/tray_constants.h"
17 #include "ash/system/tray/tray_details_view.h"
18 #include "ash/system/tray/tray_item_more.h"
19 #include "ash/system/tray/tray_item_view.h"
20 #include "base/logging.h"
21 #include "base/stl_util.h"
22 #include "base/strings/string_number_conversions.h"
23 #include "base/strings/utf_string_conversions.h"
24 #include "grit/ash_resources.h"
25 #include "grit/ash_strings.h"
26 #include "ui/base/l10n/l10n_util.h"
27 #include "ui/base/resource/resource_bundle.h"
28 #include "ui/gfx/font.h"
29 #include "ui/gfx/image/image.h"
30 #include "ui/views/controls/button/image_button.h"
31 #include "ui/views/controls/image_view.h"
32 #include "ui/views/controls/label.h"
33 #include "ui/views/controls/progress_bar.h"
34 #include "ui/views/layout/box_layout.h"
35 #include "ui/views/layout/grid_layout.h"
36 #include "ui/views/widget/widget.h"
41 const int kSidePadding
= 8;
42 const int kHorizontalPadding
= 6;
43 const int kVerticalPadding
= 6;
44 const int kTopPadding
= 6;
45 const int kBottomPadding
= 10;
46 const int kProgressBarWidth
= 100;
47 const int kProgressBarHeight
= 11;
48 const int64 kHideDelayInMs
= 1000;
50 base::string16
GetTrayLabel(const ash::DriveOperationStatusList
& list
) {
51 return l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_DRIVE_SYNCING
,
52 base::IntToString16(static_cast<int>(list
.size())));
55 scoped_ptr
<ash::DriveOperationStatusList
> GetCurrentOperationList() {
56 ash::SystemTrayDelegate
* delegate
=
57 ash::Shell::GetInstance()->system_tray_delegate();
58 scoped_ptr
<ash::DriveOperationStatusList
> list(
59 new ash::DriveOperationStatusList
);
60 delegate
->GetDriveOperationStatusList(list
.get());
68 class DriveDefaultView
: public TrayItemMore
{
70 DriveDefaultView(SystemTrayItem
* owner
,
71 const DriveOperationStatusList
* list
)
72 : TrayItemMore(owner
, true) {
73 ui::ResourceBundle
& bundle
= ui::ResourceBundle::GetSharedInstance();
75 SetImage(bundle
.GetImageNamed(IDR_AURA_UBER_TRAY_DRIVE
).ToImageSkia());
79 virtual ~DriveDefaultView() {}
81 void Update(const DriveOperationStatusList
* list
) {
83 base::string16 label
= GetTrayLabel(*list
);
85 SetAccessibleName(label
);
89 DISALLOW_COPY_AND_ASSIGN(DriveDefaultView
);
92 class DriveDetailedView
: public TrayDetailsView
,
93 public ViewClickListener
{
95 DriveDetailedView(SystemTrayItem
* owner
,
96 const DriveOperationStatusList
* list
)
97 : TrayDetailsView(owner
),
99 in_progress_img_(NULL
),
102 in_progress_img_
= ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
103 IDR_AURA_UBER_TRAY_DRIVE
);
104 done_img_
= ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
105 IDR_AURA_UBER_TRAY_DRIVE_DONE
);
106 failed_img_
= ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
107 IDR_AURA_UBER_TRAY_DRIVE_FAILED
);
112 virtual ~DriveDetailedView() {
113 STLDeleteValues(&update_map_
);
116 void Update(const DriveOperationStatusList
* list
) {
117 AppendOperationList(list
);
119 AppendHeaderEntry(list
);
126 class OperationProgressBar
: public views::ProgressBar
{
128 OperationProgressBar() {}
131 // Overridden from View:
132 virtual gfx::Size
GetPreferredSize() const OVERRIDE
{
133 return gfx::Size(kProgressBarWidth
, kProgressBarHeight
);
136 DISALLOW_COPY_AND_ASSIGN(OperationProgressBar
);
139 class RowView
: public HoverHighlightView
,
140 public views::ButtonListener
{
142 RowView(DriveDetailedView
* parent
,
143 ash::DriveOperationStatus::OperationState state
,
145 const base::FilePath
& file_path
,
147 : HoverHighlightView(parent
),
150 label_container_(NULL
),
152 cancel_button_(NULL
),
153 operation_id_(operation_id
) {
155 status_img_
= new views::ImageView();
156 AddChildView(status_img_
);
158 label_container_
= new views::View();
159 label_container_
->SetLayoutManager(new views::BoxLayout(
160 views::BoxLayout::kVertical
, 0, 0, kVerticalPadding
));
161 #if defined(OS_POSIX)
162 base::string16 file_label
=
163 base::UTF8ToUTF16(file_path
.BaseName().value());
164 #elif defined(OS_WIN)
165 base::string16 file_label
=
166 base::WideToUTF16(file_path
.BaseName().value());
168 views::Label
* label
= new views::Label(file_label
);
169 label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
170 label_container_
->AddChildView(label
);
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
,
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
);
200 // views::View overrides.
201 virtual gfx::Size
GetPreferredSize() const OVERRIDE
{
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())
218 int pos_x
= child_area
.x() + kSidePadding
;
219 int pos_y
= child_area
.y() + kTopPadding
;
221 gfx::Rect
bounds_status(
223 pos_y
+ (child_area
.height() - kTopPadding
-
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
,
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(
244 pos_y
+ (child_area
.height() - kTopPadding
-
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 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
257 ash::UMA_STATUS_AREA_DRIVE_CANCEL_OPERATION
);
258 container_
->OnCancelOperation(operation_id_
);
261 DriveDetailedView
* container_
;
262 views::ImageView
* status_img_
;
263 views::View
* label_container_
;
264 views::ProgressBar
* progress_bar_
;
265 views::ImageButton
* cancel_button_
;
268 DISALLOW_COPY_AND_ASSIGN(RowView
);
271 void AppendHeaderEntry(const DriveOperationStatusList
* list
) {
274 CreateSpecialRow(IDS_ASH_STATUS_TRAY_DRIVE
, this);
277 gfx::ImageSkia
* GetImageForState(
278 ash::DriveOperationStatus::OperationState state
) {
280 case ash::DriveOperationStatus::OPERATION_NOT_STARTED
:
281 case ash::DriveOperationStatus::OPERATION_IN_PROGRESS
:
282 return in_progress_img_
;
283 case ash::DriveOperationStatus::OPERATION_COMPLETED
:
285 case ash::DriveOperationStatus::OPERATION_FAILED
:
291 void OnCancelOperation(int32 operation_id
) {
292 SystemTrayDelegate
* delegate
= Shell::GetInstance()->system_tray_delegate();
293 delegate
->CancelDriveOperation(operation_id
);
296 void AppendOperationList(const DriveOperationStatusList
* list
) {
298 CreateScrollableList();
301 std::set
<base::FilePath
> new_set
;
302 bool item_list_changed
= false;
303 for (DriveOperationStatusList::const_iterator it
= list
->begin();
304 it
!= list
->end(); ++it
) {
305 const DriveOperationStatus
& operation
= *it
;
307 new_set
.insert(operation
.file_path
);
308 std::map
<base::FilePath
, RowView
*>::iterator existing_item
=
309 update_map_
.find(operation
.file_path
);
311 if (existing_item
!= update_map_
.end()) {
312 existing_item
->second
->UpdateStatus(operation
.state
,
315 RowView
* row_view
= new RowView(this,
321 update_map_
[operation
.file_path
] = row_view
;
322 scroll_content()->AddChildView(row_view
);
323 item_list_changed
= true;
327 // Remove items from the list that haven't been added or modified with this
329 std::set
<base::FilePath
> remove_set
;
330 for (std::map
<base::FilePath
, RowView
*>::iterator update_iter
=
332 update_iter
!= update_map_
.end(); ++update_iter
) {
333 if (new_set
.find(update_iter
->first
) == new_set
.end()) {
334 remove_set
.insert(update_iter
->first
);
338 for (std::set
<base::FilePath
>::iterator removed_iter
= remove_set
.begin();
339 removed_iter
!= remove_set
.end(); ++removed_iter
) {
340 delete update_map_
[*removed_iter
];
341 update_map_
.erase(*removed_iter
);
342 item_list_changed
= true;
345 if (item_list_changed
)
346 scroller()->Layout();
348 // Close the details if there is really nothing to show there anymore.
349 if (new_set
.empty() && GetWidget())
350 GetWidget()->Close();
353 void AppendSettings() {
357 HoverHighlightView
* container
= new HoverHighlightView(this);
359 ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
360 IDS_ASH_STATUS_TRAY_DRIVE_SETTINGS
),
363 AddChildView(container
);
364 settings_
= container
;
367 // Overridden from ViewClickListener.
368 virtual void OnViewClicked(views::View
* sender
) OVERRIDE
{
369 SystemTrayDelegate
* delegate
= Shell::GetInstance()->system_tray_delegate();
370 if (sender
== footer()->content()) {
371 TransitionToDefaultView();
372 } else if (sender
== settings_
) {
373 delegate
->ShowDriveSettings();
377 // Maps operation entries to their file paths.
378 std::map
<base::FilePath
, RowView
*> update_map_
;
379 views::View
* settings_
;
380 gfx::ImageSkia
* in_progress_img_
;
381 gfx::ImageSkia
* done_img_
;
382 gfx::ImageSkia
* failed_img_
;
384 DISALLOW_COPY_AND_ASSIGN(DriveDetailedView
);
389 TrayDrive::TrayDrive(SystemTray
* system_tray
) :
390 TrayImageItem(system_tray
, IDR_AURA_UBER_TRAY_DRIVE_LIGHT
),
393 Shell::GetInstance()->system_tray_notifier()->AddDriveObserver(this);
396 TrayDrive::~TrayDrive() {
397 Shell::GetInstance()->system_tray_notifier()->RemoveDriveObserver(this);
400 bool TrayDrive::GetInitialVisibility() {
404 views::View
* TrayDrive::CreateDefaultView(user::LoginStatus status
) {
407 if (status
!= user::LOGGED_IN_USER
&& status
!= user::LOGGED_IN_OWNER
)
410 // If the list is empty AND the tray icon is invisible (= not in the margin
411 // duration of delayed item hiding), don't show the item.
412 scoped_ptr
<DriveOperationStatusList
> list(GetCurrentOperationList());
413 if (list
->empty() && !tray_view()->visible())
416 default_
= new tray::DriveDefaultView(this, list
.get());
420 views::View
* TrayDrive::CreateDetailedView(user::LoginStatus status
) {
423 if (status
!= user::LOGGED_IN_USER
&& status
!= user::LOGGED_IN_OWNER
)
426 // If the list is empty AND the tray icon is invisible (= not in the margin
427 // duration of delayed item hiding), don't show the item.
428 scoped_ptr
<DriveOperationStatusList
> list(GetCurrentOperationList());
429 if (list
->empty() && !tray_view()->visible())
432 Shell::GetInstance()->metrics()->RecordUserMetricsAction(
433 ash::UMA_STATUS_AREA_DETAILED_DRIVE_VIEW
);
434 detailed_
= new tray::DriveDetailedView(this, list
.get());
438 void TrayDrive::DestroyDefaultView() {
442 void TrayDrive::DestroyDetailedView() {
446 void TrayDrive::UpdateAfterLoginStatusChange(user::LoginStatus status
) {
447 if (status
== user::LOGGED_IN_USER
|| status
== user::LOGGED_IN_OWNER
)
450 tray_view()->SetVisible(false);
451 DestroyDefaultView();
452 DestroyDetailedView();
455 void TrayDrive::OnDriveJobUpdated(const DriveOperationStatus
& status
) {
456 // The Drive job list manager changed its notification interface *not* to send
457 // the whole list of operations each time, to clarify which operation is
458 // updated and to reduce redundancy.
460 // TrayDrive should be able to benefit from the change, but for now, to
461 // incrementally migrate to the new way with minimum diffs, we still get the
462 // list of operations each time the event is fired.
463 // TODO(kinaba) http://crbug.com/128079 clean it up.
464 scoped_ptr
<DriveOperationStatusList
> list(GetCurrentOperationList());
465 bool is_new_item
= true;
466 for (size_t i
= 0; i
< list
->size(); ++i
) {
467 if ((*list
)[i
].id
== status
.id
) {
474 list
->push_back(status
);
476 // Check if all the operations are in the finished state.
477 bool all_jobs_finished
= true;
478 for (size_t i
= 0; i
< list
->size(); ++i
) {
479 if ((*list
)[i
].state
!= DriveOperationStatus::OPERATION_COMPLETED
&&
480 (*list
)[i
].state
!= DriveOperationStatus::OPERATION_FAILED
) {
481 all_jobs_finished
= false;
486 if (all_jobs_finished
) {
487 // If all the jobs ended, the tray item will be hidden after a certain
488 // amount of delay. This is to avoid flashes between sequentially executed
489 // Drive operations (see crbug/165679).
490 hide_timer_
.Start(FROM_HERE
,
491 base::TimeDelta::FromMilliseconds(kHideDelayInMs
),
493 &TrayDrive::HideIfNoOperations
);
497 // If the list is non-empty, stop the hiding timer (if any).
500 tray_view()->SetVisible(true);
502 default_
->Update(list
.get());
504 detailed_
->Update(list
.get());
507 void TrayDrive::HideIfNoOperations() {
508 DriveOperationStatusList empty_list
;
510 tray_view()->SetVisible(false);
512 default_
->Update(&empty_list
);
514 detailed_
->Update(&empty_list
);