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/system_tray.h"
11 #include "ash/system/tray/system_tray_delegate.h"
12 #include "ash/system/tray/system_tray_notifier.h"
13 #include "ash/system/tray/tray_constants.h"
14 #include "ash/system/tray/tray_details_view.h"
15 #include "ash/system/tray/tray_item_more.h"
16 #include "ash/system/tray/tray_item_view.h"
17 #include "ash/system/tray/tray_views.h"
18 #include "base/logging.h"
19 #include "base/string_number_conversions.h"
20 #include "base/utf_string_conversions.h"
21 #include "base/stl_util.h"
22 #include "grit/ash_resources.h"
23 #include "grit/ash_strings.h"
24 #include "ui/base/l10n/l10n_util.h"
25 #include "ui/base/resource/resource_bundle.h"
26 #include "ui/gfx/font.h"
27 #include "ui/gfx/image/image.h"
28 #include "ui/views/controls/button/image_button.h"
29 #include "ui/views/controls/label.h"
30 #include "ui/views/controls/progress_bar.h"
31 #include "ui/views/layout/box_layout.h"
32 #include "ui/views/layout/grid_layout.h"
33 #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
= 8;
48 const int64 kHideDelayInMs
= 1000;
50 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 ash::DriveOperationStatusList
* GetCurrentOperationList() {
56 ash::SystemTrayDelegate
* delegate
=
57 ash::Shell::GetInstance()->system_tray_delegate();
58 ash::DriveOperationStatusList
* list
= new ash::DriveOperationStatusList();
59 delegate
->GetDriveOperationStatusList(list
);
67 class DriveDefaultView
: public TrayItemMore
{
69 DriveDefaultView(SystemTrayItem
* owner
,
70 const DriveOperationStatusList
* list
)
71 : TrayItemMore(owner
, true) {
72 ui::ResourceBundle
& bundle
= ui::ResourceBundle::GetSharedInstance();
74 SetImage(bundle
.GetImageNamed(IDR_AURA_UBER_TRAY_DRIVE
).ToImageSkia());
78 virtual ~DriveDefaultView() {}
80 void Update(const DriveOperationStatusList
* list
) {
82 string16 label
= GetTrayLabel(*list
);
84 SetAccessibleName(label
);
88 DISALLOW_COPY_AND_ASSIGN(DriveDefaultView
);
91 class DriveDetailedView
: public TrayDetailsView
,
92 public ViewClickListener
{
94 DriveDetailedView(SystemTrayItem
* owner
,
95 const DriveOperationStatusList
* list
)
96 : TrayDetailsView(owner
),
98 in_progress_img_(NULL
),
101 in_progress_img_
= ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
102 IDR_AURA_UBER_TRAY_DRIVE
);
103 done_img_
= ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
104 IDR_AURA_UBER_TRAY_DRIVE_DONE
);
105 failed_img_
= ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
106 IDR_AURA_UBER_TRAY_DRIVE_FAILED
);
111 virtual ~DriveDetailedView() {
112 STLDeleteValues(&update_map_
);
115 void Update(const DriveOperationStatusList
* list
) {
116 AppendOperationList(list
);
118 AppendHeaderEntry(list
);
125 class OperationProgressBar
: public views::ProgressBar
{
127 OperationProgressBar() {}
130 // Overridden from View:
131 virtual gfx::Size
GetPreferredSize() OVERRIDE
{
132 return gfx::Size(kProgressBarWidth
, kProgressBarHeight
);
135 DISALLOW_COPY_AND_ASSIGN(OperationProgressBar
);
138 class RowView
: public HoverHighlightView
,
139 public views::ButtonListener
{
141 RowView(DriveDetailedView
* parent
,
142 ash::DriveOperationStatus::OperationState state
,
144 const FilePath
& file_path
)
145 : HoverHighlightView(parent
),
148 label_container_(NULL
),
150 cancel_button_(NULL
),
151 file_path_(file_path
) {
153 status_img_
= new views::ImageView();
154 AddChildView(status_img_
);
156 label_container_
= new views::View();
157 label_container_
->SetLayoutManager(new views::BoxLayout(
158 views::BoxLayout::kVertical
, 0, 0, kVerticalPadding
));
159 #if defined(OS_POSIX)
160 string16 file_label
=
161 UTF8ToUTF16(file_path
.BaseName().value());
162 #elif defined(OS_WIN)
163 string16 file_label
=
164 WideToUTF16(file_path
.BaseName().value());
166 views::Label
* label
= new views::Label(file_label
);
167 label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
168 label_container_
->AddChildView(label
);
170 progress_bar_
= new OperationProgressBar();
171 label_container_
->AddChildView(progress_bar_
);
173 AddChildView(label_container_
);
175 cancel_button_
= new views::ImageButton(this);
176 cancel_button_
->SetImage(views::ImageButton::STATE_NORMAL
,
177 ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
178 IDR_AURA_UBER_TRAY_DRIVE_CANCEL
));
179 cancel_button_
->SetImage(views::ImageButton::STATE_HOVERED
,
180 ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
181 IDR_AURA_UBER_TRAY_DRIVE_CANCEL_HOVER
));
183 UpdateStatus(state
, progress
);
184 AddChildView(cancel_button_
);
187 void UpdateStatus(ash::DriveOperationStatus::OperationState state
,
189 status_img_
->SetImage(container_
->GetImageForState(state
));
190 progress_bar_
->SetValue(progress
);
191 cancel_button_
->SetVisible(
192 state
== ash::DriveOperationStatus::OPERATION_IN_PROGRESS
||
193 state
== ash::DriveOperationStatus::OPERATION_SUSPENDED
);
198 // views::View overrides.
199 virtual gfx::Size
GetPreferredSize() OVERRIDE
{
201 status_img_
->GetPreferredSize().width() +
202 label_container_
->GetPreferredSize().width() +
203 cancel_button_
->GetPreferredSize().width() +
204 2 * kSidePadding
+ 2 * kHorizontalPadding
,
205 std::max(status_img_
->GetPreferredSize().height(),
206 std::max(label_container_
->GetPreferredSize().height(),
207 cancel_button_
->GetPreferredSize().height())) +
208 kTopPadding
+ kBottomPadding
);
211 virtual void Layout() OVERRIDE
{
212 gfx::Rect
child_area(GetLocalBounds());
213 if (child_area
.IsEmpty())
216 int pos_x
= child_area
.x() + kSidePadding
;
217 int pos_y
= child_area
.y() + kTopPadding
;
219 gfx::Rect
bounds_status(
221 pos_y
+ (child_area
.height() - kTopPadding
-
223 status_img_
->GetPreferredSize().height())/2),
224 status_img_
->GetPreferredSize());
225 status_img_
->SetBoundsRect(
226 gfx::IntersectRects(bounds_status
, child_area
));
227 pos_x
+= status_img_
->bounds().width() + kHorizontalPadding
;
229 gfx::Rect
bounds_label(pos_x
,
231 child_area
.width() - 2 * kSidePadding
-
232 2 * kHorizontalPadding
-
233 status_img_
->GetPreferredSize().width() -
234 cancel_button_
->GetPreferredSize().width(),
235 label_container_
->GetPreferredSize().height());
236 label_container_
->SetBoundsRect(
237 gfx::IntersectRects(bounds_label
, child_area
));
238 pos_x
+= label_container_
->bounds().width() + kHorizontalPadding
;
240 gfx::Rect
bounds_button(
242 pos_y
+ (child_area
.height() - kTopPadding
-
244 cancel_button_
->GetPreferredSize().height())/2),
245 cancel_button_
->GetPreferredSize());
246 cancel_button_
->SetBoundsRect(
247 gfx::IntersectRects(bounds_button
, child_area
));
250 // views::ButtonListener overrides.
251 virtual void ButtonPressed(views::Button
* sender
,
252 const ui::Event
& event
) OVERRIDE
{
253 DCHECK(sender
== cancel_button_
);
254 container_
->OnCancelOperation(file_path_
);
257 DriveDetailedView
* container_
;
258 views::ImageView
* status_img_
;
259 views::View
* label_container_
;
260 views::ProgressBar
* progress_bar_
;
261 views::ImageButton
* cancel_button_
;
264 DISALLOW_COPY_AND_ASSIGN(RowView
);
267 void AppendHeaderEntry(const DriveOperationStatusList
* list
) {
270 CreateSpecialRow(IDS_ASH_STATUS_TRAY_DRIVE
, this);
273 gfx::ImageSkia
* GetImageForState(
274 ash::DriveOperationStatus::OperationState state
) {
276 case ash::DriveOperationStatus::OPERATION_NOT_STARTED
:
277 case ash::DriveOperationStatus::OPERATION_STARTED
:
278 case ash::DriveOperationStatus::OPERATION_IN_PROGRESS
:
279 case ash::DriveOperationStatus::OPERATION_SUSPENDED
:
280 return in_progress_img_
;
281 case ash::DriveOperationStatus::OPERATION_COMPLETED
:
283 case ash::DriveOperationStatus::OPERATION_FAILED
:
289 virtual void OnCancelOperation(const FilePath
& file_path
) {
290 SystemTrayDelegate
* delegate
= Shell::GetInstance()->system_tray_delegate();
291 delegate
->CancelDriveOperation(file_path
);
294 void AppendOperationList(const DriveOperationStatusList
* list
) {
296 CreateScrollableList();
299 std::set
<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
<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,
316 operation
.file_path
);
318 update_map_
[operation
.file_path
] = row_view
;
319 scroll_content()->AddChildView(row_view
);
320 item_list_changed
= true;
324 // Remove items from the list that haven't been added or modified with this
326 std::set
<FilePath
> remove_set
;
327 for (std::map
<FilePath
, RowView
*>::iterator update_iter
=
329 update_iter
!= update_map_
.end(); ++update_iter
) {
330 if (new_set
.find(update_iter
->first
) == new_set
.end()) {
331 remove_set
.insert(update_iter
->first
);
335 for (std::set
<FilePath
>::iterator removed_iter
= remove_set
.begin();
336 removed_iter
!= remove_set
.end(); ++removed_iter
) {
337 delete update_map_
[*removed_iter
];
338 update_map_
.erase(*removed_iter
);
339 item_list_changed
= true;
342 if (item_list_changed
)
343 scroller()->Layout();
345 // Close the details if there is really nothing to show there anymore.
346 if (new_set
.empty() && GetWidget())
347 GetWidget()->Close();
350 void AppendSettings() {
354 HoverHighlightView
* container
= new HoverHighlightView(this);
355 container
->set_fixed_height(kTrayPopupItemHeight
);
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 ClickedOn(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
<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() {
397 scoped_ptr
<DriveOperationStatusList
> list(GetCurrentOperationList());
398 return list
->size() > 0;
401 views::View
* TrayDrive::CreateDefaultView(user::LoginStatus status
) {
404 if (status
!= user::LOGGED_IN_USER
&& status
!= user::LOGGED_IN_OWNER
)
407 // If the list is empty AND the tray icon is invisible (= not in the margin
408 // duration of delayed item hiding), don't show the item.
409 scoped_ptr
<DriveOperationStatusList
> list(GetCurrentOperationList());
410 if (list
->empty() && !tray_view()->visible())
413 default_
= new tray::DriveDefaultView(this, list
.get());
417 views::View
* TrayDrive::CreateDetailedView(user::LoginStatus status
) {
420 if (status
!= user::LOGGED_IN_USER
&& status
!= user::LOGGED_IN_OWNER
)
423 // If the list is empty AND the tray icon is invisible (= not in the margin
424 // duration of delayed item hiding), don't show the item.
425 scoped_ptr
<DriveOperationStatusList
> list(GetCurrentOperationList());
426 if (list
->empty() && !tray_view()->visible())
429 detailed_
= new tray::DriveDetailedView(this, list
.get());
433 void TrayDrive::DestroyDefaultView() {
437 void TrayDrive::DestroyDetailedView() {
441 void TrayDrive::UpdateAfterLoginStatusChange(user::LoginStatus status
) {
442 if (status
== user::LOGGED_IN_USER
|| status
== user::LOGGED_IN_OWNER
)
445 tray_view()->SetVisible(false);
446 DestroyDefaultView();
447 DestroyDetailedView();
450 void TrayDrive::OnDriveRefresh(const DriveOperationStatusList
& list
) {
452 // If the list becomes empty, the tray item will be hidden after a certain
453 // amount of delay. This is to avoid flashes between sequentially executed
454 // Drive operations (see crbug/165679).
455 hide_timer_
.Start(FROM_HERE
,
456 base::TimeDelta::FromMilliseconds(kHideDelayInMs
),
458 &TrayDrive::HideIfNoOperations
);
462 // If the list is non-empty, stop the hiding timer (if any).
465 tray_view()->SetVisible(true);
467 default_
->Update(&list
);
469 detailed_
->Update(&list
);
472 void TrayDrive::HideIfNoOperations() {
473 DriveOperationStatusList empty_list
;
475 tray_view()->SetVisible(false);
477 default_
->Update(&empty_list
);
479 detailed_
->Update(&empty_list
);
482 } // namespace internal