123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780 |
- // Copyright 2020 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/holding_space/holding_space_view_delegate.h"
- #include "ash/public/cpp/holding_space/holding_space_client.h"
- #include "ash/public/cpp/holding_space/holding_space_constants.h"
- #include "ash/public/cpp/holding_space/holding_space_controller.h"
- #include "ash/public/cpp/holding_space/holding_space_item.h"
- #include "ash/public/cpp/holding_space/holding_space_metrics.h"
- #include "ash/public/cpp/holding_space/holding_space_model.h"
- #include "ash/public/cpp/holding_space/holding_space_progress.h"
- #include "ash/public/cpp/holding_space/holding_space_util.h"
- #include "ash/resources/vector_icons/vector_icons.h"
- #include "ash/strings/grit/ash_strings.h"
- #include "ash/system/holding_space/holding_space_drag_util.h"
- #include "ash/system/holding_space/holding_space_item_view.h"
- #include "ash/system/holding_space/holding_space_tray.h"
- #include "ash/system/holding_space/holding_space_tray_bubble.h"
- #include "base/bind.h"
- #include "base/callback_helpers.h"
- #include "base/containers/contains.h"
- #include "base/containers/cxx20_erase_vector.h"
- #include "net/base/mime_util.h"
- #include "ui/accessibility/ax_action_data.h"
- #include "ui/accessibility/ax_enums.mojom.h"
- #include "ui/base/dragdrop/drag_drop_types.h"
- #include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
- #include "ui/base/dragdrop/os_exchange_data_provider.h"
- #include "ui/base/l10n/l10n_util.h"
- #include "ui/base/models/simple_menu_model.h"
- #include "ui/color/color_id.h"
- #include "ui/views/controls/menu/menu_runner.h"
- #include "ui/views/vector_icons.h"
- #include "ui/views/view.h"
- namespace ash {
- namespace {
- // It is expected that all holding space views share the same delegate in order
- // to support multiple selections which requires a shared state. We cache the
- // singleton `instance` in order to enforce this requirement.
- HoldingSpaceViewDelegate* instance = nullptr;
- // Helpers ---------------------------------------------------------------------
- // Returns the holding space items associated with the specified `views`.
- std::vector<const HoldingSpaceItem*> GetItems(
- const std::vector<const HoldingSpaceItemView*>& views) {
- std::vector<const HoldingSpaceItem*> items;
- for (const HoldingSpaceItemView* view : views)
- items.push_back(view->item());
- return items;
- }
- // Returns the subset of `views` in the range of `start` and `end` (inclusive).
- std::vector<HoldingSpaceItemView*> GetViewsInRange(
- const std::vector<HoldingSpaceItemView*>& views,
- HoldingSpaceItemView* start,
- HoldingSpaceItemView* end) {
- if (!start || !end)
- return {};
- bool found_start = false;
- bool found_end = false;
- std::vector<HoldingSpaceItemView*> range;
- for (HoldingSpaceItemView* view : views) {
- if (view == start)
- found_start = true;
- if (view == end)
- found_end = true;
- if (found_start || found_end)
- range.push_back(view);
- if (found_start && found_end)
- break;
- }
- DCHECK(found_start);
- DCHECK(found_end);
- return range;
- }
- // Attempts to open the holding space items associated with the given `views`.
- void OpenItems(const std::vector<const HoldingSpaceItemView*>& views) {
- DCHECK_GE(views.size(), 1u);
- HoldingSpaceController::Get()->client()->OpenItems(GetItems(views),
- base::DoNothing());
- }
- } // namespace
- // HoldingSpaceViewDelegate::ScopedSelectionRestore ----------------------------
- HoldingSpaceViewDelegate::ScopedSelectionRestore::ScopedSelectionRestore(
- HoldingSpaceViewDelegate* delegate)
- : delegate_(delegate) {
- // Save selection.
- for (const HoldingSpaceItemView* view : delegate_->GetSelection())
- selected_item_ids_.push_back(view->item_id());
- // Save `selected_range_start_`.
- if (delegate_->selected_range_start_)
- selected_range_start_item_id_ = delegate_->selected_range_start_->item_id();
- // Save `selected_range_end_`.
- if (delegate_->selected_range_end_)
- selected_range_end_item_id_ = delegate_->selected_range_end_->item_id();
- }
- HoldingSpaceViewDelegate::ScopedSelectionRestore::~ScopedSelectionRestore() {
- // Restore selection.
- delegate_->SetSelection(selected_item_ids_);
- if (!selected_range_start_item_id_ || !selected_range_end_item_id_)
- return;
- HoldingSpaceItemView* selected_range_start = nullptr;
- HoldingSpaceItemView* selected_range_end = nullptr;
- for (HoldingSpaceItemView* view :
- delegate_->bubble_->GetHoldingSpaceItemViews()) {
- // Cache `selected_range_start`.
- if (selected_range_start_item_id_ == view->item_id())
- selected_range_start = view;
- // Cache `selected_range_end`.
- if (selected_range_end_item_id_ == view->item_id())
- selected_range_end = view;
- // Restore `selected_range_start_` and `selected_range_end_` iff both are
- // still in existence during the restoration process.
- if (selected_range_start && selected_range_end) {
- delegate_->selected_range_start_ = selected_range_start;
- delegate_->selected_range_end_ = selected_range_end;
- break;
- }
- }
- }
- // HoldingSpaceViewDelegate ----------------------------------------------------
- HoldingSpaceViewDelegate::HoldingSpaceViewDelegate(
- HoldingSpaceTrayBubble* bubble)
- : bubble_(bubble) {
- DCHECK_EQ(nullptr, instance);
- instance = this;
- // Multi-select is the only selection UI in tablet mode. Outside of tablet
- // mode, selection UI is based on the `selection_size_`.
- selection_ui_ = TabletMode::Get()->InTabletMode()
- ? SelectionUi::kMultiSelect
- : SelectionUi::kSingleSelect;
- tablet_mode_observer_.Observe(TabletMode::Get());
- }
- HoldingSpaceViewDelegate::~HoldingSpaceViewDelegate() {
- DCHECK_EQ(instance, this);
- instance = nullptr;
- }
- void HoldingSpaceViewDelegate::OnHoldingSpaceItemViewCreated(
- HoldingSpaceItemView* view) {
- if (view->selected()) {
- ++selection_size_;
- UpdateSelectionUi();
- }
- }
- void HoldingSpaceViewDelegate::OnHoldingSpaceItemViewDestroying(
- HoldingSpaceItemView* view) {
- // If either endpoint of the selected range is destroyed, clear the cache so
- // that the next range-based selection attempt will start from scratch.
- if (selected_range_start_ == view || selected_range_end_ == view) {
- selected_range_start_ = nullptr;
- selected_range_end_ = nullptr;
- }
- if (view->selected()) {
- --selection_size_;
- UpdateSelectionUi();
- }
- }
- bool HoldingSpaceViewDelegate::OnHoldingSpaceItemViewAccessibleAction(
- HoldingSpaceItemView* view,
- const ui::AXActionData& action_data) {
- // When performing the default accessible action (e.g. Search + Space), open
- // the selected holding space items. If `view` is not part of the current
- // selection it will become the entire selection.
- if (action_data.action == ax::mojom::Action::kDoDefault) {
- if (!view->selected())
- SetSelection(view);
- OpenItems(GetSelection());
- return true;
- }
- // When showing the context menu via accessible action (e.g. Search + M),
- // ensure that `view` is part of the current selection. If it is not part of
- // the current selection it will become the entire selection.
- if (action_data.action == ax::mojom::Action::kShowContextMenu) {
- if (!view->selected())
- SetSelection(view);
- // Return false so that the views framework will show the context menu.
- return false;
- }
- return false;
- }
- bool HoldingSpaceViewDelegate::OnHoldingSpaceItemViewGestureEvent(
- HoldingSpaceItemView* view,
- const ui::GestureEvent& event) {
- // The user may alternate between using mouse and touch inputs. Treat gesture
- // events as mouse events when tracking range-based selection so that if
- // the user switches back to using mouse input, selection state will be
- // determined based on this most recent interaction with `view`.
- selected_range_start_ = view;
- selected_range_end_ = view;
- // When a long press or two finger tap gesture occurs we are going to show the
- // context menu. Ensure that the pressed `view` is part of the selection.
- if (event.type() == ui::ET_GESTURE_LONG_PRESS ||
- event.type() == ui::ET_GESTURE_TWO_FINGER_TAP) {
- view->SetSelected(true);
- return false;
- }
- // If a scroll begin gesture is received while the context menu is showing,
- // that means the user is trying to initiate a drag. Close the context menu
- // and start the item drag.
- if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN && context_menu_runner_ &&
- context_menu_runner_->IsRunning()) {
- context_menu_runner_.reset();
- view->StartDrag(event, ui::mojom::DragEventSource::kTouch);
- return false;
- }
- if (event.type() != ui::ET_GESTURE_TAP)
- return false;
- // When a tap gesture occurs and *no* views are currently selected, select and
- // open the tapped `view`. Note that the tap `event` should *not* propagate
- // further. Failure to halt propagation would result in the gesture reaching
- // the child bubble which clears selection state.
- if (GetSelection().empty()) {
- SetSelection(view);
- OpenItems(GetSelection());
- return true;
- }
- // When a tap gesture occurs and a selection *does* exist, the selected state
- // of the tapped `view` is toggled. Note that the tap event should *not*
- // propagate further. Failure to halt propagation would result in the gesture
- // reaching the child bubble which clears selection state.
- view->SetSelected(!view->selected());
- return true;
- }
- bool HoldingSpaceViewDelegate::OnHoldingSpaceItemViewKeyPressed(
- HoldingSpaceItemView* view,
- const ui::KeyEvent& event) {
- // The ENTER key should open all selected holding space items. If `view` isn't
- // already part of the selection, it will become the entire selection.
- if (event.key_code() == ui::KeyboardCode::VKEY_RETURN) {
- if (!view->selected())
- SetSelection(view);
- OpenItems(GetSelection());
- return true;
- }
- return false;
- }
- bool HoldingSpaceViewDelegate::OnHoldingSpaceItemViewMousePressed(
- HoldingSpaceItemView* view,
- const ui::MouseEvent& event) {
- // Since we are starting a new mouse pressed/released sequence, we need to
- // clear any view that we had cached to ignore mouse released events for.
- ignore_mouse_released_ = nullptr;
- // If SHIFT is *not* pressed, set `view` as the starting point for range-based
- // selection so that the next time the user shift-clicks, selection state
- // will be updated in the range of `view` and the view being shift-clicked.
- // Note that `view` is also set as the starting point if previously unset.
- if (!event.IsShiftDown() || !selected_range_start_)
- selected_range_start_ = view;
- // When a `view` is pressed it becomes the new end for range-based selection.
- // Note that this is performed in a scoped closure runner in order to give
- // `SetSelectedRange()` a chance to run and clean up any previous range-based
- // selection.
- base::ScopedClosureRunner set_selected_range_end(base::BindOnce(
- [](HoldingSpaceItemView** selected_range_end,
- HoldingSpaceItemView* view) { *selected_range_end = view; },
- &selected_range_end_, view));
- // If the SHIFT key is down, the user is attempting a range-based selection.
- // Remove from the selection the previously selected range and instead add
- // the newly selected range to the selection. Note that the next mouse
- // released event on `view` is ignored if this is not a double click so that
- // `view` isn't accidentally unselected right after having selected it. If
- // this is a double click, the mouse released event will be handled to launch
- // the selected holding space items.
- if (event.IsShiftDown()) {
- if (!(event.flags() & ui::EF_IS_DOUBLE_CLICK))
- ignore_mouse_released_ = view;
- SetSelectedRange(selected_range_start_, /*end=*/view);
- return true;
- }
- // If the `view` is already selected, mouse press is a no-op. Actions taken on
- // selected views are performed on mouse released in order to give drag/drop
- // a chance to take effect (assuming that drag thresholds are met).
- if (view->selected())
- return true;
- // If the CTRL key is down, we need to add `view` to the current selection.
- // We're going to need to ignore the next mouse released event on `view` if
- // this is not a double click so that we don't unselect `view` accidentally
- // right after having selected it. If this is a double click, the mouse
- // released event will be handled to launch the selected holding space items.
- if (event.IsControlDown()) {
- if (!(event.flags() & ui::EF_IS_DOUBLE_CLICK))
- ignore_mouse_released_ = view;
- view->SetSelected(true);
- return true;
- }
- // In the absence of any modifiers, pressing an unselected `view` will cause
- // `view` to become the current selection. Previous selections are cleared.
- SetSelection(view);
- return true;
- }
- void HoldingSpaceViewDelegate::OnHoldingSpaceItemViewMouseReleased(
- HoldingSpaceItemView* view,
- const ui::MouseEvent& event) {
- // We should always clear `ignore_mouse_released_` since that property should
- // affect at most one press/release sequence.
- views::View* const old_ignore_mouse_released = ignore_mouse_released_;
- ignore_mouse_released_ = nullptr;
- // We might be ignoring mouse released events for `view` if it was just
- // selected on mouse pressed. In this case, no-op here.
- if (old_ignore_mouse_released == view)
- return;
- // If the right mouse button is released we're showing the context menu. In
- // this case, no-op here.
- if (event.IsRightMouseButton())
- return;
- // If this mouse released `event` is part of a double click, we should open
- // the items associated with the current selection. It is expected that the
- // `view` being clicked is already part of the selection.
- if (event.flags() & ui::EF_IS_DOUBLE_CLICK) {
- DCHECK(view->selected());
- OpenItems(GetSelection());
- return;
- }
- // If the CTRL key is down, mouse release should toggle the selected state of
- // `view`. It's possible that the current selection be empty after doing so.
- if (event.IsControlDown()) {
- view->SetSelected(!view->selected());
- return;
- }
- // This mouse released `event` is not part of a double click, nor were there
- // any modifiers which resulted in special handling. In this case, the `view`
- // under the mouse should become the only selected view.
- SetSelection(view);
- }
- void HoldingSpaceViewDelegate::OnHoldingSpaceItemViewPrimaryActionPressed(
- HoldingSpaceItemView* view) {
- if (!view->selected())
- ClearSelection();
- }
- void HoldingSpaceViewDelegate::OnHoldingSpaceItemViewSecondaryActionPressed(
- HoldingSpaceItemView* view) {
- if (!view->selected())
- ClearSelection();
- }
- void HoldingSpaceViewDelegate::OnHoldingSpaceItemViewSelectedChanged(
- HoldingSpaceItemView* view) {
- selection_size_ += view->selected() ? 1 : -1;
- UpdateSelectionUi();
- }
- bool HoldingSpaceViewDelegate::OnHoldingSpaceTrayBubbleKeyPressed(
- const ui::KeyEvent& event) {
- // The ENTER key should open all selected holding space items.
- if (event.key_code() == ui::KeyboardCode::VKEY_RETURN) {
- if (!GetSelection().empty()) {
- OpenItems(GetSelection());
- return true;
- }
- }
- return false;
- }
- void HoldingSpaceViewDelegate::OnHoldingSpaceTrayChildBubbleGestureEvent(
- const ui::GestureEvent& event) {
- if (event.type() == ui::ET_GESTURE_TAP)
- ClearSelection();
- }
- void HoldingSpaceViewDelegate::OnHoldingSpaceTrayChildBubbleMousePressed(
- const ui::MouseEvent& event) {
- ClearSelection();
- }
- base::RepeatingClosureList::Subscription
- HoldingSpaceViewDelegate::AddSelectionUiChangedCallback(
- base::RepeatingClosureList::CallbackType callback) {
- return selection_ui_changed_callbacks_.Add(std::move(callback));
- }
- void HoldingSpaceViewDelegate::UpdateTrayVisibility() {
- bubble_->tray()->UpdateVisibility();
- }
- void HoldingSpaceViewDelegate::ShowContextMenuForViewImpl(
- views::View* source,
- const gfx::Point& point,
- ui::MenuSourceType source_type) {
- // In touch mode, gesture events continue to be sent to holding space views
- // after showing the context menu so that it can be aborted if the user
- // initiates a drag sequence. This means both `ui::ET_GESTURE_LONG_TAP` and
- // `ui::ET_GESTURE_LONG_PRESS` may be received while showing the context menu
- // which would result in trying to show the context menu twice. This would not
- // be a fatal failure but would result in UI jank.
- if (context_menu_runner_ && context_menu_runner_->IsRunning())
- return;
- int run_types = views::MenuRunner::USE_ASH_SYS_UI_LAYOUT |
- views::MenuRunner::CONTEXT_MENU |
- views::MenuRunner::FIXED_ANCHOR;
- // In touch mode the context menu may be aborted if the user initiates a drag.
- // In order to determine if the gesture resulting in this context menu being
- // shown was actually the start of a drag sequence, holding space views will
- // have to receive events that would otherwise be consumed by the `MenuHost`.
- if (source_type == ui::MenuSourceType::MENU_SOURCE_TOUCH)
- run_types |= views::MenuRunner::SEND_GESTURE_EVENTS_TO_OWNER;
- context_menu_runner_ =
- std::make_unique<views::MenuRunner>(BuildMenuModel(), run_types);
- context_menu_runner_->RunMenuAt(
- source->GetWidget(), nullptr /*button_controller*/,
- source->GetBoundsInScreen(),
- views::MenuAnchorPosition::kBubbleBottomRight, source_type);
- }
- bool HoldingSpaceViewDelegate::CanStartDragForView(
- views::View* sender,
- const gfx::Point& press_pt,
- const gfx::Point& current_pt) {
- const gfx::Vector2d delta = current_pt - press_pt;
- return views::View::ExceededDragThreshold(delta);
- }
- int HoldingSpaceViewDelegate::GetDragOperationsForView(
- views::View* sender,
- const gfx::Point& press_pt) {
- return ui::DragDropTypes::DRAG_COPY;
- }
- void HoldingSpaceViewDelegate::WriteDragDataForView(views::View* sender,
- const gfx::Point& press_pt,
- ui::OSExchangeData* data) {
- std::vector<const HoldingSpaceItemView*> selection = GetSelection();
- DCHECK_GE(selection.size(), 1u);
- holding_space_metrics::RecordItemAction(
- GetItems(selection), holding_space_metrics::ItemAction::kDrag);
- // Drag image.
- gfx::ImageSkia drag_image;
- gfx::Vector2d drag_offset;
- holding_space_util::CreateDragImage(selection, &drag_image, &drag_offset);
- data->provider().SetDragImage(std::move(drag_image), drag_offset);
- // Payload.
- std::vector<ui::FileInfo> filenames;
- for (const HoldingSpaceItemView* view : selection) {
- const base::FilePath& file_path = view->item()->file_path();
- filenames.push_back(ui::FileInfo(file_path, file_path.BaseName()));
- }
- data->SetFilenames(filenames);
- }
- void HoldingSpaceViewDelegate::ExecuteCommand(int command, int event_flags) {
- const std::vector<const HoldingSpaceItem*> items(GetItems(GetSelection()));
- DCHECK_GE(items.size(), 1u);
- const auto command_id = static_cast<HoldingSpaceCommandId>(command);
- HoldingSpaceClient* const client = HoldingSpaceController::Get()->client();
- switch (command_id) {
- case HoldingSpaceCommandId::kCopyImageToClipboard:
- DCHECK_EQ(items.size(), 1u);
- client->CopyImageToClipboard(*items.front(), base::DoNothing());
- break;
- case HoldingSpaceCommandId::kPinItem:
- client->PinItems(items);
- break;
- case HoldingSpaceCommandId::kRemoveItem:
- HoldingSpaceController::Get()->model()->RemoveIf(base::BindRepeating(
- [](const std::vector<const HoldingSpaceItem*>& items,
- const HoldingSpaceItem* item) {
- const bool remove = base::Contains(items, item);
- if (remove) {
- holding_space_metrics::RecordItemAction(
- {item}, holding_space_metrics::ItemAction::kRemove);
- }
- return remove;
- },
- std::cref(items)));
- break;
- case HoldingSpaceCommandId::kShowInFolder:
- DCHECK_EQ(items.size(), 1u);
- client->ShowItemInFolder(*items.front(), base::DoNothing());
- break;
- case HoldingSpaceCommandId::kUnpinItem:
- client->UnpinItems(items);
- break;
- default:
- if (holding_space_util::IsInProgressCommand(command_id)) {
- for (const HoldingSpaceItem* item : items) {
- if (!holding_space_util::ExecuteInProgressCommand(item, command_id))
- NOTREACHED();
- }
- } else {
- NOTREACHED();
- }
- break;
- }
- }
- void HoldingSpaceViewDelegate::OnTabletModeStarted() {
- UpdateSelectionUi();
- }
- void HoldingSpaceViewDelegate::OnTabletModeEnded() {
- UpdateSelectionUi();
- }
- ui::SimpleMenuModel* HoldingSpaceViewDelegate::BuildMenuModel() {
- context_menu_model_ = std::make_unique<ui::SimpleMenuModel>(this);
- std::vector<const HoldingSpaceItemView*> selection = GetSelection();
- DCHECK_GE(selection.size(), 1u);
- // Whether any item in `selection` is complete.
- bool is_any_item_complete = false;
- // Whether all items in `selection` are removable.
- bool is_removable = true;
- // A value for `is_pinnable` will only be present if the `selection` contains
- // at least one holding space item which is *not* in-progress. In-progress
- // items are ignored with respect to pin-/unpin-ability.
- absl::optional<bool> is_pinnable;
- // A value for `in_progress_commands` will only be present if the `selection`
- // does *not* contain any items which are complete.
- absl::optional<std::vector<HoldingSpaceItem::InProgressCommand>>
- in_progress_commands;
- HoldingSpaceModel* const model = HoldingSpaceController::Get()->model();
- for (const HoldingSpaceItemView* view : selection) {
- const HoldingSpaceItem* item = view->item();
- // In-progress commands are only available if supported by the entire
- // `selection`. In-progress commands supported by only a subset of the
- // `selection` are removed.
- if (!item->progress().IsComplete() && !is_any_item_complete) {
- if (!in_progress_commands.has_value()) {
- in_progress_commands = item->in_progress_commands();
- } else {
- base::EraseIf(in_progress_commands.value(),
- [&](const HoldingSpaceItem::InProgressCommand&
- in_progress_command) {
- return !holding_space_util::SupportsInProgressCommand(
- item, in_progress_command.command_id);
- });
- }
- } else {
- in_progress_commands = absl::nullopt;
- is_any_item_complete = true;
- }
- // The "Remove" command should only be present if *all* of the selected
- // holding space items are removable.
- is_removable &= item->type() != HoldingSpaceItem::Type::kPinnedFile &&
- item->progress().IsComplete();
- // In-progress holding space items are ignored with respect to the pin-/
- // unpin-ability of the `selection`.
- if (!item->progress().IsComplete())
- continue;
- // The "Pin" command should be present if *any* selected holding space item
- // is unpinned. When executing this command, any holding space items that
- // are already pinned will be ignored.
- is_pinnable = is_pinnable.value_or(false) ||
- !model->ContainsItem(HoldingSpaceItem::Type::kPinnedFile,
- item->file_path());
- }
- struct MenuItemModel {
- const HoldingSpaceCommandId command_id;
- const int label_id;
- const gfx::VectorIcon& icon;
- };
- using MenuSectionModel = std::vector<MenuItemModel>;
- std::vector<MenuSectionModel> menu_sections(1);
- // In-progress commands.
- if (in_progress_commands.has_value()) {
- for (const HoldingSpaceItem::InProgressCommand& in_progress_command :
- in_progress_commands.value()) {
- menu_sections.back().emplace_back(
- MenuItemModel{.command_id = in_progress_command.command_id,
- .label_id = in_progress_command.label_id,
- .icon = *in_progress_command.icon});
- }
- }
- // The in-progress commands are separated from other commands.
- if (!menu_sections.back().empty())
- menu_sections.emplace_back();
- if (selection.size() == 1u) {
- // The "Show in folder" command should only be present if there is only one
- // holding space item selected.
- menu_sections.back().emplace_back(MenuItemModel{
- .command_id = HoldingSpaceCommandId::kShowInFolder,
- .label_id = IDS_ASH_HOLDING_SPACE_CONTEXT_MENU_SHOW_IN_FOLDER,
- .icon = kFolderIcon});
- std::string mime_type;
- const bool is_image =
- net::GetMimeTypeFromFile(selection.front()->item()->file_path(),
- &mime_type) &&
- net::MatchesMimeType(kMimeTypeImage, mime_type);
- if (is_image) {
- // The "Copy image" command should only be present if there is only one
- // holding space item selected and that item is backed by an image file.
- menu_sections.back().emplace_back(MenuItemModel{
- .command_id = HoldingSpaceCommandId::kCopyImageToClipboard,
- .label_id =
- IDS_ASH_HOLDING_SPACE_CONTEXT_MENU_COPY_IMAGE_TO_CLIPBOARD,
- .icon = kCopyIcon});
- }
- }
- if (is_pinnable.has_value()) {
- if (is_pinnable.value()) {
- menu_sections.back().emplace_back(
- MenuItemModel{.command_id = HoldingSpaceCommandId::kPinItem,
- .label_id = IDS_ASH_HOLDING_SPACE_CONTEXT_MENU_PIN,
- .icon = views::kPinIcon});
- } else {
- menu_sections.back().emplace_back(
- MenuItemModel{.command_id = HoldingSpaceCommandId::kUnpinItem,
- .label_id = IDS_ASH_HOLDING_SPACE_CONTEXT_MENU_UNPIN,
- .icon = views::kUnpinIcon});
- }
- }
- if (is_removable) {
- menu_sections.back().emplace_back(
- MenuItemModel{.command_id = HoldingSpaceCommandId::kRemoveItem,
- .label_id = IDS_ASH_HOLDING_SPACE_CONTEXT_MENU_REMOVE,
- .icon = kCancelCircleOutlineIcon});
- }
- // Add modeled `menu_sections` to the `context_menu_model_`.
- for (const MenuSectionModel& menu_section : menu_sections) {
- if (menu_section.empty())
- continue;
- // Each `menu_section` should be separated by a normal separator.
- if (context_menu_model_->GetItemCount()) {
- context_menu_model_->AddSeparator(
- ui::MenuSeparatorType::NORMAL_SEPARATOR);
- }
- // Each `menu_section` should contain their respective `menu_item`s.
- for (const MenuItemModel& menu_item : menu_section) {
- context_menu_model_->AddItemWithIcon(
- static_cast<int>(menu_item.command_id),
- l10n_util::GetStringUTF16(menu_item.label_id),
- ui::ImageModel::FromVectorIcon(menu_item.icon,
- ui::kColorAshSystemUIMenuIcon,
- kHoldingSpaceIconSize));
- }
- }
- return context_menu_model_.get();
- }
- std::vector<const HoldingSpaceItemView*>
- HoldingSpaceViewDelegate::GetSelection() {
- std::vector<const HoldingSpaceItemView*> selection;
- for (const HoldingSpaceItemView* view : bubble_->GetHoldingSpaceItemViews()) {
- if (view->selected())
- selection.push_back(view);
- }
- DCHECK_EQ(selection.size(), selection_size_);
- return selection;
- }
- void HoldingSpaceViewDelegate::ClearSelection() {
- SetSelection(std::vector<std::string>());
- }
- void HoldingSpaceViewDelegate::SetSelection(HoldingSpaceItemView* selection) {
- SetSelection({selection->item_id()});
- }
- void HoldingSpaceViewDelegate::SetSelection(
- const std::vector<std::string>& item_ids) {
- std::vector<HoldingSpaceItemView*> selection;
- for (HoldingSpaceItemView* view : bubble_->GetHoldingSpaceItemViews()) {
- view->SetSelected(base::Contains(item_ids, view->item_id()));
- if (view->selected())
- selection.push_back(view);
- }
- if (selection.size() == 1u) {
- selected_range_start_ = selection.front();
- selected_range_end_ = selection.front();
- } else {
- selected_range_start_ = nullptr;
- selected_range_end_ = nullptr;
- }
- }
- void HoldingSpaceViewDelegate::SetSelectedRange(HoldingSpaceItemView* start,
- HoldingSpaceItemView* end) {
- const std::vector<HoldingSpaceItemView*> views =
- bubble_->GetHoldingSpaceItemViews();
- for (HoldingSpaceItemView* view :
- GetViewsInRange(views, selected_range_start_, selected_range_end_)) {
- view->SetSelected(false);
- }
- selected_range_start_ = start;
- selected_range_end_ = end;
- for (HoldingSpaceItemView* view :
- GetViewsInRange(views, selected_range_start_, selected_range_end_)) {
- view->SetSelected(true);
- }
- }
- void HoldingSpaceViewDelegate::UpdateSelectionUi() {
- const SelectionUi selection_ui =
- TabletMode::Get()->InTabletMode() || selection_size_ > 1u
- ? SelectionUi::kMultiSelect
- : SelectionUi::kSingleSelect;
- if (selection_ui_ == selection_ui)
- return;
- selection_ui_ = selection_ui;
- selection_ui_changed_callbacks_.Notify();
- }
- } // namespace ash
|