diff options
Diffstat (limited to 'chromium/ash/system/drive/tray_drive.cc')
-rw-r--r-- | chromium/ash/system/drive/tray_drive.cc | 517 |
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 |