summaryrefslogtreecommitdiff
path: root/chromium/ash/system/drive/tray_drive.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/ash/system/drive/tray_drive.cc')
-rw-r--r--chromium/ash/system/drive/tray_drive.cc517
1 files changed, 517 insertions, 0 deletions
diff --git a/chromium/ash/system/drive/tray_drive.cc b/chromium/ash/system/drive/tray_drive.cc
new file mode 100644
index 00000000000..b246d52a7bf
--- /dev/null
+++ b/chromium/ash/system/drive/tray_drive.cc
@@ -0,0 +1,517 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/system/drive/tray_drive.h"
+
+#include <vector>
+
+#include "ash/metrics/user_metrics_recorder.h"
+#include "ash/shell.h"
+#include "ash/system/tray/fixed_sized_scroll_view.h"
+#include "ash/system/tray/hover_highlight_view.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/tray/system_tray_notifier.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray/tray_details_view.h"
+#include "ash/system/tray/tray_item_more.h"
+#include "ash/system/tray/tray_item_view.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/font.h"
+#include "ui/gfx/image/image.h"
+#include "ui/views/controls/button/image_button.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/controls/progress_bar.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/layout/grid_layout.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+
+namespace internal {
+
+namespace {
+
+const int kSidePadding = 8;
+const int kHorizontalPadding = 6;
+const int kVerticalPadding = 6;
+const int kTopPadding = 6;
+const int kBottomPadding = 10;
+const int kProgressBarWidth = 100;
+const int kProgressBarHeight = 11;
+const int64 kHideDelayInMs = 1000;
+
+base::string16 GetTrayLabel(const ash::DriveOperationStatusList& list) {
+ return l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_DRIVE_SYNCING,
+ base::IntToString16(static_cast<int>(list.size())));
+}
+
+scoped_ptr<ash::DriveOperationStatusList> GetCurrentOperationList() {
+ ash::SystemTrayDelegate* delegate =
+ ash::Shell::GetInstance()->system_tray_delegate();
+ scoped_ptr<ash::DriveOperationStatusList> list(
+ new ash::DriveOperationStatusList);
+ delegate->GetDriveOperationStatusList(list.get());
+ return list.Pass();
+}
+
+}
+
+namespace tray {
+
+class DriveDefaultView : public TrayItemMore {
+ public:
+ DriveDefaultView(SystemTrayItem* owner,
+ const DriveOperationStatusList* list)
+ : TrayItemMore(owner, true) {
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+
+ SetImage(bundle.GetImageNamed(IDR_AURA_UBER_TRAY_DRIVE).ToImageSkia());
+ Update(list);
+ }
+
+ virtual ~DriveDefaultView() {}
+
+ void Update(const DriveOperationStatusList* list) {
+ DCHECK(list);
+ base::string16 label = GetTrayLabel(*list);
+ SetLabel(label);
+ SetAccessibleName(label);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DriveDefaultView);
+};
+
+class DriveDetailedView : public TrayDetailsView,
+ public ViewClickListener {
+ public:
+ DriveDetailedView(SystemTrayItem* owner,
+ const DriveOperationStatusList* list)
+ : TrayDetailsView(owner),
+ settings_(NULL),
+ in_progress_img_(NULL),
+ done_img_(NULL),
+ failed_img_(NULL) {
+ in_progress_img_ = ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+ IDR_AURA_UBER_TRAY_DRIVE);
+ done_img_ = ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+ IDR_AURA_UBER_TRAY_DRIVE_DONE);
+ failed_img_ = ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+ IDR_AURA_UBER_TRAY_DRIVE_FAILED);
+
+ Update(list);
+ }
+
+ virtual ~DriveDetailedView() {
+ STLDeleteValues(&update_map_);
+ }
+
+ void Update(const DriveOperationStatusList* list) {
+ AppendOperationList(list);
+ AppendSettings();
+ AppendHeaderEntry(list);
+
+ SchedulePaint();
+ }
+
+ private:
+
+ class OperationProgressBar : public views::ProgressBar {
+ public:
+ OperationProgressBar() {}
+ private:
+
+ // Overridden from View:
+ virtual gfx::Size GetPreferredSize() OVERRIDE {
+ return gfx::Size(kProgressBarWidth, kProgressBarHeight);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(OperationProgressBar);
+ };
+
+ class RowView : public HoverHighlightView,
+ public views::ButtonListener {
+ public:
+ RowView(DriveDetailedView* parent,
+ ash::DriveOperationStatus::OperationState state,
+ double progress,
+ const base::FilePath& file_path,
+ int32 operation_id)
+ : HoverHighlightView(parent),
+ container_(parent),
+ status_img_(NULL),
+ label_container_(NULL),
+ progress_bar_(NULL),
+ cancel_button_(NULL),
+ operation_id_(operation_id) {
+ // Status image.
+ status_img_ = new views::ImageView();
+ AddChildView(status_img_);
+
+ label_container_ = new views::View();
+ label_container_->SetLayoutManager(new views::BoxLayout(
+ views::BoxLayout::kVertical, 0, 0, kVerticalPadding));
+#if defined(OS_POSIX)
+ base::string16 file_label = UTF8ToUTF16(file_path.BaseName().value());
+#elif defined(OS_WIN)
+ base::string16 file_label = WideToUTF16(file_path.BaseName().value());
+#endif
+ views::Label* label = new views::Label(file_label);
+ label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ label_container_->AddChildView(label);
+ // Add progress bar.
+ progress_bar_ = new OperationProgressBar();
+ label_container_->AddChildView(progress_bar_);
+
+ AddChildView(label_container_);
+
+ cancel_button_ = new views::ImageButton(this);
+ cancel_button_->SetImage(views::ImageButton::STATE_NORMAL,
+ ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+ IDR_AURA_UBER_TRAY_DRIVE_CANCEL));
+ cancel_button_->SetImage(views::ImageButton::STATE_HOVERED,
+ ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+ IDR_AURA_UBER_TRAY_DRIVE_CANCEL_HOVER));
+
+ UpdateStatus(state, progress);
+ AddChildView(cancel_button_);
+ }
+
+ void UpdateStatus(ash::DriveOperationStatus::OperationState state,
+ double progress) {
+ status_img_->SetImage(container_->GetImageForState(state));
+ progress_bar_->SetValue(progress);
+ cancel_button_->SetVisible(
+ state == ash::DriveOperationStatus::OPERATION_NOT_STARTED ||
+ state == ash::DriveOperationStatus::OPERATION_IN_PROGRESS);
+ }
+
+ private:
+
+ // views::View overrides.
+ virtual gfx::Size GetPreferredSize() OVERRIDE {
+ return gfx::Size(
+ status_img_->GetPreferredSize().width() +
+ label_container_->GetPreferredSize().width() +
+ cancel_button_->GetPreferredSize().width() +
+ 2 * kSidePadding + 2 * kHorizontalPadding,
+ std::max(status_img_->GetPreferredSize().height(),
+ std::max(label_container_->GetPreferredSize().height(),
+ cancel_button_->GetPreferredSize().height())) +
+ kTopPadding + kBottomPadding);
+ }
+
+ virtual void Layout() OVERRIDE {
+ gfx::Rect child_area(GetLocalBounds());
+ if (child_area.IsEmpty())
+ return;
+
+ int pos_x = child_area.x() + kSidePadding;
+ int pos_y = child_area.y() + kTopPadding;
+
+ gfx::Rect bounds_status(
+ gfx::Point(pos_x,
+ pos_y + (child_area.height() - kTopPadding -
+ kBottomPadding -
+ status_img_->GetPreferredSize().height())/2),
+ status_img_->GetPreferredSize());
+ status_img_->SetBoundsRect(
+ gfx::IntersectRects(bounds_status, child_area));
+ pos_x += status_img_->bounds().width() + kHorizontalPadding;
+
+ gfx::Rect bounds_label(pos_x,
+ pos_y,
+ child_area.width() - 2 * kSidePadding -
+ 2 * kHorizontalPadding -
+ status_img_->GetPreferredSize().width() -
+ cancel_button_->GetPreferredSize().width(),
+ label_container_->GetPreferredSize().height());
+ label_container_->SetBoundsRect(
+ gfx::IntersectRects(bounds_label, child_area));
+ pos_x += label_container_->bounds().width() + kHorizontalPadding;
+
+ gfx::Rect bounds_button(
+ gfx::Point(pos_x,
+ pos_y + (child_area.height() - kTopPadding -
+ kBottomPadding -
+ cancel_button_->GetPreferredSize().height())/2),
+ cancel_button_->GetPreferredSize());
+ cancel_button_->SetBoundsRect(
+ gfx::IntersectRects(bounds_button, child_area));
+ }
+
+ // views::ButtonListener overrides.
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) OVERRIDE {
+ DCHECK(sender == cancel_button_);
+ Shell::GetInstance()->metrics()->RecordUserMetricsAction(
+ ash::UMA_STATUS_AREA_DRIVE_CANCEL_OPERATION);
+ container_->OnCancelOperation(operation_id_);
+ }
+
+ DriveDetailedView* container_;
+ views::ImageView* status_img_;
+ views::View* label_container_;
+ views::ProgressBar* progress_bar_;
+ views::ImageButton* cancel_button_;
+ int32 operation_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(RowView);
+ };
+
+ void AppendHeaderEntry(const DriveOperationStatusList* list) {
+ if (footer())
+ return;
+ CreateSpecialRow(IDS_ASH_STATUS_TRAY_DRIVE, this);
+ }
+
+ gfx::ImageSkia* GetImageForState(
+ ash::DriveOperationStatus::OperationState state) {
+ switch (state) {
+ case ash::DriveOperationStatus::OPERATION_NOT_STARTED:
+ case ash::DriveOperationStatus::OPERATION_IN_PROGRESS:
+ return in_progress_img_;
+ case ash::DriveOperationStatus::OPERATION_COMPLETED:
+ return done_img_;
+ case ash::DriveOperationStatus::OPERATION_FAILED:
+ return failed_img_;
+ }
+ return failed_img_;
+ }
+
+ void OnCancelOperation(int32 operation_id) {
+ SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
+ delegate->CancelDriveOperation(operation_id);
+ }
+
+ void AppendOperationList(const DriveOperationStatusList* list) {
+ if (!scroller())
+ CreateScrollableList();
+
+ // Apply the update.
+ std::set<base::FilePath> new_set;
+ bool item_list_changed = false;
+ for (DriveOperationStatusList::const_iterator it = list->begin();
+ it != list->end(); ++it) {
+ const DriveOperationStatus& operation = *it;
+
+ new_set.insert(operation.file_path);
+ std::map<base::FilePath, RowView*>::iterator existing_item =
+ update_map_.find(operation.file_path);
+
+ if (existing_item != update_map_.end()) {
+ existing_item->second->UpdateStatus(operation.state,
+ operation.progress);
+ } else {
+ RowView* row_view = new RowView(this,
+ operation.state,
+ operation.progress,
+ operation.file_path,
+ operation.id);
+
+ update_map_[operation.file_path] = row_view;
+ scroll_content()->AddChildView(row_view);
+ item_list_changed = true;
+ }
+ }
+
+ // Remove items from the list that haven't been added or modified with this
+ // update batch.
+ std::set<base::FilePath> remove_set;
+ for (std::map<base::FilePath, RowView*>::iterator update_iter =
+ update_map_.begin();
+ update_iter != update_map_.end(); ++update_iter) {
+ if (new_set.find(update_iter->first) == new_set.end()) {
+ remove_set.insert(update_iter->first);
+ }
+ }
+
+ for (std::set<base::FilePath>::iterator removed_iter = remove_set.begin();
+ removed_iter != remove_set.end(); ++removed_iter) {
+ delete update_map_[*removed_iter];
+ update_map_.erase(*removed_iter);
+ item_list_changed = true;
+ }
+
+ if (item_list_changed)
+ scroller()->Layout();
+
+ // Close the details if there is really nothing to show there anymore.
+ if (new_set.empty() && GetWidget())
+ GetWidget()->Close();
+ }
+
+ void AppendSettings() {
+ if (settings_)
+ return;
+
+ HoverHighlightView* container = new HoverHighlightView(this);
+ container->AddLabel(ui::ResourceBundle::GetSharedInstance().
+ GetLocalizedString(IDS_ASH_STATUS_TRAY_DRIVE_SETTINGS),
+ gfx::Font::NORMAL);
+ AddChildView(container);
+ settings_ = container;
+ }
+
+ // Overridden from ViewClickListener.
+ virtual void OnViewClicked(views::View* sender) OVERRIDE {
+ SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
+ if (sender == footer()->content()) {
+ TransitionToDefaultView();
+ } else if (sender == settings_) {
+ delegate->ShowDriveSettings();
+ }
+ }
+
+ // Maps operation entries to their file paths.
+ std::map<base::FilePath, RowView*> update_map_;
+ views::View* settings_;
+ gfx::ImageSkia* in_progress_img_;
+ gfx::ImageSkia* done_img_;
+ gfx::ImageSkia* failed_img_;
+
+ DISALLOW_COPY_AND_ASSIGN(DriveDetailedView);
+};
+
+} // namespace tray
+
+TrayDrive::TrayDrive(SystemTray* system_tray) :
+ TrayImageItem(system_tray, IDR_AURA_UBER_TRAY_DRIVE_LIGHT),
+ default_(NULL),
+ detailed_(NULL) {
+ Shell::GetInstance()->system_tray_notifier()->AddDriveObserver(this);
+}
+
+TrayDrive::~TrayDrive() {
+ Shell::GetInstance()->system_tray_notifier()->RemoveDriveObserver(this);
+}
+
+bool TrayDrive::GetInitialVisibility() {
+ return false;
+}
+
+views::View* TrayDrive::CreateDefaultView(user::LoginStatus status) {
+ DCHECK(!default_);
+
+ if (status != user::LOGGED_IN_USER && status != user::LOGGED_IN_OWNER)
+ return NULL;
+
+ // If the list is empty AND the tray icon is invisible (= not in the margin
+ // duration of delayed item hiding), don't show the item.
+ scoped_ptr<DriveOperationStatusList> list(GetCurrentOperationList());
+ if (list->empty() && !tray_view()->visible())
+ return NULL;
+
+ default_ = new tray::DriveDefaultView(this, list.get());
+ return default_;
+}
+
+views::View* TrayDrive::CreateDetailedView(user::LoginStatus status) {
+ DCHECK(!detailed_);
+
+ if (status != user::LOGGED_IN_USER && status != user::LOGGED_IN_OWNER)
+ return NULL;
+
+ // If the list is empty AND the tray icon is invisible (= not in the margin
+ // duration of delayed item hiding), don't show the item.
+ scoped_ptr<DriveOperationStatusList> list(GetCurrentOperationList());
+ if (list->empty() && !tray_view()->visible())
+ return NULL;
+
+ Shell::GetInstance()->metrics()->RecordUserMetricsAction(
+ ash::UMA_STATUS_AREA_DETAILED_DRIVE_VIEW);
+ detailed_ = new tray::DriveDetailedView(this, list.get());
+ return detailed_;
+}
+
+void TrayDrive::DestroyDefaultView() {
+ default_ = NULL;
+}
+
+void TrayDrive::DestroyDetailedView() {
+ detailed_ = NULL;
+}
+
+void TrayDrive::UpdateAfterLoginStatusChange(user::LoginStatus status) {
+ if (status == user::LOGGED_IN_USER || status == user::LOGGED_IN_OWNER)
+ return;
+
+ tray_view()->SetVisible(false);
+ DestroyDefaultView();
+ DestroyDetailedView();
+}
+
+void TrayDrive::OnDriveJobUpdated(const DriveOperationStatus& status) {
+ // The Drive job list manager changed its notification interface *not* to send
+ // the whole list of operations each time, to clarify which operation is
+ // updated and to reduce redundancy.
+ //
+ // TrayDrive should be able to benefit from the change, but for now, to
+ // incrementally migrate to the new way with minimum diffs, we still get the
+ // list of operations each time the event is fired.
+ // TODO(kinaba) http://crbug.com/128079 clean it up.
+ scoped_ptr<DriveOperationStatusList> list(GetCurrentOperationList());
+ bool is_new_item = true;
+ for (size_t i = 0; i < list->size(); ++i) {
+ if ((*list)[i].id == status.id) {
+ (*list)[i] = status;
+ is_new_item = false;
+ break;
+ }
+ }
+ if (is_new_item)
+ list->push_back(status);
+
+ // Check if all the operations are in the finished state.
+ bool all_jobs_finished = true;
+ for (size_t i = 0; i < list->size(); ++i) {
+ if ((*list)[i].state != DriveOperationStatus::OPERATION_COMPLETED &&
+ (*list)[i].state != DriveOperationStatus::OPERATION_FAILED) {
+ all_jobs_finished = false;
+ break;
+ }
+ }
+
+ if (all_jobs_finished) {
+ // If all the jobs ended, the tray item will be hidden after a certain
+ // amount of delay. This is to avoid flashes between sequentially executed
+ // Drive operations (see crbug/165679).
+ hide_timer_.Start(FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kHideDelayInMs),
+ this,
+ &TrayDrive::HideIfNoOperations);
+ return;
+ }
+
+ // If the list is non-empty, stop the hiding timer (if any).
+ hide_timer_.Stop();
+
+ tray_view()->SetVisible(true);
+ if (default_)
+ default_->Update(list.get());
+ if (detailed_)
+ detailed_->Update(list.get());
+}
+
+void TrayDrive::HideIfNoOperations() {
+ DriveOperationStatusList empty_list;
+
+ tray_view()->SetVisible(false);
+ if (default_)
+ default_->Update(&empty_list);
+ if (detailed_)
+ detailed_->Update(&empty_list);
+}
+
+} // namespace internal
+} // namespace ash