[content shell] implement testRunner.overridePreference
[chromium-blink-merge.git] / ash / system / drive / tray_drive.cc
blob4c83ee00377fd1e4889a6f938db0dd2d620d75f6
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/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"
35 namespace ash {
37 namespace internal {
39 namespace {
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);
60 return list;
65 namespace tray {
67 class DriveDefaultView : public TrayItemMore {
68 public:
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());
75 Update(list);
78 virtual ~DriveDefaultView() {}
80 void Update(const DriveOperationStatusList* list) {
81 DCHECK(list);
82 string16 label = GetTrayLabel(*list);
83 SetLabel(label);
84 SetAccessibleName(label);
87 private:
88 DISALLOW_COPY_AND_ASSIGN(DriveDefaultView);
91 class DriveDetailedView : public TrayDetailsView,
92 public ViewClickListener {
93 public:
94 DriveDetailedView(SystemTrayItem* owner,
95 const DriveOperationStatusList* list)
96 : TrayDetailsView(owner),
97 settings_(NULL),
98 in_progress_img_(NULL),
99 done_img_(NULL),
100 failed_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);
108 Update(list);
111 virtual ~DriveDetailedView() {
112 STLDeleteValues(&update_map_);
115 void Update(const DriveOperationStatusList* list) {
116 AppendOperationList(list);
117 AppendSettings();
118 AppendHeaderEntry(list);
120 SchedulePaint();
123 private:
125 class OperationProgressBar : public views::ProgressBar {
126 public:
127 OperationProgressBar() {}
128 private:
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 {
140 public:
141 RowView(DriveDetailedView* parent,
142 ash::DriveOperationStatus::OperationState state,
143 double progress,
144 const FilePath& file_path)
145 : HoverHighlightView(parent),
146 container_(parent),
147 status_img_(NULL),
148 label_container_(NULL),
149 progress_bar_(NULL),
150 cancel_button_(NULL),
151 file_path_(file_path) {
152 // Status image.
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());
165 #endif
166 views::Label* label = new views::Label(file_label);
167 label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
168 label_container_->AddChildView(label);
169 // Add progress bar.
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,
188 double progress) {
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);
196 private:
198 // views::View overrides.
199 virtual gfx::Size GetPreferredSize() OVERRIDE {
200 return gfx::Size(
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())
214 return;
216 int pos_x = child_area.x() + kSidePadding;
217 int pos_y = child_area.y() + kTopPadding;
219 gfx::Rect bounds_status(
220 gfx::Point(pos_x,
221 pos_y + (child_area.height() - kTopPadding -
222 kBottomPadding -
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,
230 pos_y,
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(
241 gfx::Point(pos_x,
242 pos_y + (child_area.height() - kTopPadding -
243 kBottomPadding -
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_;
262 FilePath file_path_;
264 DISALLOW_COPY_AND_ASSIGN(RowView);
267 void AppendHeaderEntry(const DriveOperationStatusList* list) {
268 if (footer())
269 return;
270 CreateSpecialRow(IDS_ASH_STATUS_TRAY_DRIVE, this);
273 gfx::ImageSkia* GetImageForState(
274 ash::DriveOperationStatus::OperationState state) {
275 switch (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:
282 return done_img_;
283 case ash::DriveOperationStatus::OPERATION_FAILED:
284 return failed_img_;
286 return failed_img_;
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) {
295 if (!scroller())
296 CreateScrollableList();
298 // Apply the update.
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,
311 operation.progress);
312 } else {
313 RowView* row_view = new RowView(this,
314 operation.state,
315 operation.progress,
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
325 // update batch.
326 std::set<FilePath> remove_set;
327 for (std::map<FilePath, RowView*>::iterator update_iter =
328 update_map_.begin();
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() {
351 if (settings_)
352 return;
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),
358 gfx::Font::NORMAL);
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);
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 scoped_ptr<DriveOperationStatusList> list(GetCurrentOperationList());
398 return list->size() > 0;
401 views::View* TrayDrive::CreateDefaultView(user::LoginStatus status) {
402 DCHECK(!default_);
404 if (status != user::LOGGED_IN_USER && status != user::LOGGED_IN_OWNER)
405 return NULL;
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())
411 return NULL;
413 default_ = new tray::DriveDefaultView(this, list.get());
414 return default_;
417 views::View* TrayDrive::CreateDetailedView(user::LoginStatus status) {
418 DCHECK(!detailed_);
420 if (status != user::LOGGED_IN_USER && status != user::LOGGED_IN_OWNER)
421 return NULL;
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())
427 return NULL;
429 detailed_ = new tray::DriveDetailedView(this, list.get());
430 return detailed_;
433 void TrayDrive::DestroyDefaultView() {
434 default_ = NULL;
437 void TrayDrive::DestroyDetailedView() {
438 detailed_ = NULL;
441 void TrayDrive::UpdateAfterLoginStatusChange(user::LoginStatus status) {
442 if (status == user::LOGGED_IN_USER || status == user::LOGGED_IN_OWNER)
443 return;
445 tray_view()->SetVisible(false);
446 DestroyDefaultView();
447 DestroyDetailedView();
450 void TrayDrive::OnDriveRefresh(const DriveOperationStatusList& list) {
451 if (list.empty()) {
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),
457 this,
458 &TrayDrive::HideIfNoOperations);
459 return;
462 // If the list is non-empty, stop the hiding timer (if any).
463 hide_timer_.Stop();
465 tray_view()->SetVisible(true);
466 if (default_)
467 default_->Update(&list);
468 if (detailed_)
469 detailed_->Update(&list);
472 void TrayDrive::HideIfNoOperations() {
473 DriveOperationStatusList empty_list;
475 tray_view()->SetVisible(false);
476 if (default_)
477 default_->Update(&empty_list);
478 if (detailed_)
479 detailed_->Update(&empty_list);
482 } // namespace internal
483 } // namespace ash