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"
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"
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());
70 class DriveDefaultView
: public TrayItemMore
{
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());
81 virtual ~DriveDefaultView() {}
83 void Update(const DriveOperationStatusList
* list
) {
85 base::string16 label
= GetTrayLabel(*list
);
87 SetAccessibleName(label
);
91 DISALLOW_COPY_AND_ASSIGN(DriveDefaultView
);
94 class DriveDetailedView
: public TrayDetailsView
,
95 public ViewClickListener
{
97 DriveDetailedView(SystemTrayItem
* owner
,
98 const DriveOperationStatusList
* list
)
99 : TrayDetailsView(owner
),
101 in_progress_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
);
114 virtual ~DriveDetailedView() {
115 STLDeleteValues(&update_map_
);
118 void Update(const DriveOperationStatusList
* list
) {
119 AppendOperationList(list
);
121 AppendHeaderEntry(list
);
128 class OperationProgressBar
: public views::ProgressBar
{
130 OperationProgressBar() {}
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
{
144 RowView(DriveDetailedView
* parent
,
145 ash::DriveOperationStatus::OperationState state
,
147 const base::FilePath
& file_path
,
149 : HoverHighlightView(parent
),
152 label_container_(NULL
),
154 cancel_button_(NULL
),
155 operation_id_(operation_id
) {
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());
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() 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 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_
;
266 DISALLOW_COPY_AND_ASSIGN(RowView
);
269 void AppendHeaderEntry(const DriveOperationStatusList
* list
) {
272 CreateSpecialRow(IDS_ASH_STATUS_TRAY_DRIVE
, this);
275 gfx::ImageSkia
* GetImageForState(
276 ash::DriveOperationStatus::OperationState 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
:
283 case ash::DriveOperationStatus::OPERATION_FAILED
:
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
) {
296 CreateScrollableList();
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
,
313 RowView
* row_view
= new RowView(this,
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
327 std::set
<base::FilePath
> remove_set
;
328 for (std::map
<base::FilePath
, RowView
*>::iterator update_iter
=
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() {
355 HoverHighlightView
* container
= new HoverHighlightView(this);
356 container
->AddLabel(ui::ResourceBundle::GetSharedInstance().
357 GetLocalizedString(IDS_ASH_STATUS_TRAY_DRIVE_SETTINGS
),
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
);
385 TrayDrive::TrayDrive(SystemTray
* system_tray
) :
386 TrayImageItem(system_tray
, IDR_AURA_UBER_TRAY_DRIVE_LIGHT
),
389 Shell::GetInstance()->system_tray_notifier()->AddDriveObserver(this);
392 TrayDrive::~TrayDrive() {
393 Shell::GetInstance()->system_tray_notifier()->RemoveDriveObserver(this);
396 bool TrayDrive::GetInitialVisibility() {
400 views::View
* TrayDrive::CreateDefaultView(user::LoginStatus status
) {
403 if (status
!= user::LOGGED_IN_USER
&& status
!= user::LOGGED_IN_OWNER
)
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())
412 default_
= new tray::DriveDefaultView(this, list
.get());
416 views::View
* TrayDrive::CreateDetailedView(user::LoginStatus status
) {
419 if (status
!= user::LOGGED_IN_USER
&& status
!= user::LOGGED_IN_OWNER
)
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())
428 detailed_
= new tray::DriveDetailedView(this, list
.get());
432 void TrayDrive::DestroyDefaultView() {
436 void TrayDrive::DestroyDetailedView() {
440 void TrayDrive::UpdateAfterLoginStatusChange(user::LoginStatus status
) {
441 if (status
== user::LOGGED_IN_USER
|| status
== user::LOGGED_IN_OWNER
)
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
) {
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;
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
),
487 &TrayDrive::HideIfNoOperations
);
491 // If the list is non-empty, stop the hiding timer (if any).
494 tray_view()->SetVisible(true);
496 default_
->Update(list
.get());
498 detailed_
->Update(list
.get());
501 void TrayDrive::HideIfNoOperations() {
502 DriveOperationStatusList empty_list
;
504 tray_view()->SetVisible(false);
506 default_
->Update(&empty_list
);
508 detailed_
->Update(&empty_list
);
511 } // namespace internal