Chromium Code Reviews| Index: chrome/browser/renderer_context_menu/open_with_menu_factory_ash.cc |
| diff --git a/chrome/browser/renderer_context_menu/open_with_menu_factory_ash.cc b/chrome/browser/renderer_context_menu/open_with_menu_factory_ash.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..d55e7231c1c839efd5eab5ca5dc6754352fb41e3 |
| --- /dev/null |
| +++ b/chrome/browser/renderer_context_menu/open_with_menu_factory_ash.cc |
| @@ -0,0 +1,220 @@ |
| +// Copyright 2016 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 "chrome/browser/renderer_context_menu/open_with_menu_factory.h" |
| + |
| +#include <memory> |
| +#include <unordered_map> |
| +#include <utility> |
| +#include <vector> |
| + |
| +#include "ash/renderer_context_menu/link_handler_model.h" |
| +#include "ash/renderer_context_menu/open_with_menu_controller.h" |
| +#include "ash/shell.h" |
| +#include "base/strings/string_util.h" |
| +#include "base/strings/utf_string_conversions.h" |
| +#include "chrome/app/chrome_command_ids.h" |
| +#include "chrome/grit/generated_resources.h" |
| +#include "components/renderer_context_menu/render_view_context_menu_observer.h" |
| +#include "components/renderer_context_menu/render_view_context_menu_proxy.h" |
| +#include "content/public/common/context_menu_params.h" |
| +#include "ui/base/l10n/l10n_util.h" |
| +#include "ui/base/models/simple_menu_model.h" |
| + |
| +namespace { |
| + |
| +using HandlerMap = std::unordered_map<int, ash::LinkHandlerInfo>; |
| + |
| +// An observer class which populates the "Open with <app>" menu items either |
| +// synchronously or asynchronously. |
| +class OpenWithMenuObserver : public RenderViewContextMenuObserver, |
| + public ash::LinkHandlerModel::Observer { |
| + public: |
| + class SubMenuDelegate : public ui::SimpleMenuModel::Delegate { |
| + public: |
| + explicit SubMenuDelegate(OpenWithMenuObserver* parent) : parent_(parent) {} |
| + ~SubMenuDelegate() override {} |
| + |
| + bool IsCommandIdChecked(int command_id) const override { return false; } |
| + bool IsCommandIdEnabled(int command_id) const override { return true; } |
| + |
| + bool GetAcceleratorForCommandId(int command_id, |
| + ui::Accelerator* accelerator) override { |
| + return false; |
| + } |
| + |
| + void ExecuteCommand(int command_id, int event_flags) override { |
| + parent_->ExecuteCommand(command_id); |
| + } |
| + |
| + private: |
| + OpenWithMenuObserver* const parent_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(SubMenuDelegate); |
| + }; |
| + |
| + explicit OpenWithMenuObserver(RenderViewContextMenuProxy* proxy) |
| + : proxy_(proxy), submenu_delegate_(this) {} |
| + |
| + ~OpenWithMenuObserver() override {} |
| + |
| + // RenderViewContextMenuObserver overrides: |
| + void InitMenu(const content::ContextMenuParams& params) override { |
| + if (!ash::Shell::HasInstance()) |
| + return; |
| + ash::OpenWithMenuController* controller = |
| + ash::Shell::GetInstance()->open_with_menu_controller(); |
| + if (!controller) |
| + return; |
| + |
| + link_url_ = params.link_url; |
| + menu_model_ = controller->CreateModel(link_url_); |
| + if (!menu_model_) |
| + return; |
| + |
| + // Add placeholder items. |
| + ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(&submenu_delegate_); |
|
hidehiko
2016/04/20 04:40:18
I'd prefer
std::unique_ptr here, too. Instead, std
Yusuke Sato
2016/04/22 05:27:48
Done.
|
| + for (int i = 0; i < kNumSubMenuCommands; ++i) { |
| + const int command_id = |
| + IDC_CONTENT_CONTEXT_OPEN_WITH1 + kNumMainMenuCommands + i; |
| + submenu->AddItem(command_id, base::EmptyString16()); |
| + } |
| + submenu_.reset(submenu); |
| + |
| + for (int i = 0; i < kNumMainMenuCommands; ++i) { |
| + const int command_id = IDC_CONTENT_CONTEXT_OPEN_WITH1 + i; |
| + if (i == kNumMainMenuCommands - 1) { |
|
hidehiko
2016/04/20 04:40:18
How about iterating between [i, kNumMainMenuComman
Yusuke Sato
2016/04/22 05:27:48
Done.
|
| + base::string16 label = |
| + l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_MORE_APPS); |
| + proxy_->AddSubMenu(command_id, label, submenu_.get()); |
| + } else { |
| + proxy_->AddMenuItem(command_id, base::EmptyString16()); |
| + } |
| + } |
| + |
| + menu_model_->AddObserver(this); |
| + } |
| + |
| + bool IsCommandIdSupported(int command_id) override { |
| + return command_id >= IDC_CONTENT_CONTEXT_OPEN_WITH1 && |
| + command_id <= IDC_CONTENT_CONTEXT_OPEN_WITH_LAST; |
|
hidehiko
2016/04/20 04:40:18
indent?
Yusuke Sato
2016/04/22 05:27:48
This is by git cl format actually..
|
| + } |
| + |
| + bool IsCommandIdChecked(int command_id) override { return false; } |
| + bool IsCommandIdEnabled(int command_id) override { return true; } |
| + |
| + void ExecuteCommand(int command_id) override { |
| + // Note: SubmenuDelegate also calls this method with a command_id for the |
| + // submenu. |
| + const auto it = handlers_.find(command_id); |
| + if (it == handlers_.end()) |
| + return; |
| + menu_model_->OpenLinkWithHandler(link_url_, it->second.id); |
| + } |
| + |
| + void OnMenuCancel() override {} |
| + |
| + // ash::OpenWithItems::Delegate overrides: |
| + void ModelChanged( |
|
hidehiko
2016/04/20 18:10:18
As we discussed offline, if this is called twice o
Yusuke Sato
2016/04/22 05:27:48
Done.
|
| + const std::vector<ash::LinkHandlerInfo>& handlers) override { |
| + auto result = BuildHandlersMap(handlers); |
| + handlers_ = std::move(result.first); |
| + const int submenu_parent_id = result.second; |
| + for (int command_id = IDC_CONTENT_CONTEXT_OPEN_WITH1; |
| + command_id <= IDC_CONTENT_CONTEXT_OPEN_WITH_LAST; ++command_id) { |
| + const auto it = handlers_.find(command_id); |
| + if (command_id == submenu_parent_id) { |
| + // Keep the submenu parent. |
| + } else if (it == handlers_.end()) { |
| + // Hide the menu. |
| + proxy_->UpdateMenuItem(command_id, false, true, base::EmptyString16()); |
| + } else { |
| + // Update the menu with the new model. |
| + const base::string16 label = |
| + l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_OPEN_WITH_APP, |
| + base::UTF8ToUTF16(it->second.name)); |
| + proxy_->UpdateMenuItem(command_id, true, false, label); |
| + } |
| + } |
| + } |
| + |
| + static std::pair<HandlerMap, int> BuildHandlersMapForTesting( |
| + const std::vector<ash::LinkHandlerInfo>& handlers) { |
| + return BuildHandlersMap(handlers); |
| + } |
| + |
| + private: |
| + // Converts |handlers| into HandlerMap which is a map from a command ID to a |
| + // LinkHandlerInfo and returns the map. Also returns a command id for the |
| + // parent of the submenu. When the submenu is not needed, the function |
| + // returns |kInvalidCommandId|. |
| + static std::pair<HandlerMap, int> BuildHandlersMap( |
| + const std::vector<ash::LinkHandlerInfo>& handlers) { |
| + const int kInvalidCommandId = -1; |
| + const int submenu_id_start = |
| + IDC_CONTENT_CONTEXT_OPEN_WITH1 + kNumMainMenuCommands; |
| + |
| + HandlerMap handler_map; |
| + int submenu_parent_command_id = kInvalidCommandId; |
| + |
| + const int num_apps = handlers.size(); |
| + size_t handlers_index = 0; |
| + // We use the last item in the main menu (IDC_CONTENT_CONTEXT_OPEN_WITH1 + |
| + // kNumMainMenuCommands- 1) as a parent of a submenu, and others as regular |
| + // menu items. |
| + if (num_apps < kNumMainMenuCommands) { |
| + // All apps can be shown with the regular main menu items. |
| + for (int i = 0; i < num_apps; ++i) { |
| + handler_map[IDC_CONTENT_CONTEXT_OPEN_WITH1 + i] = |
| + handlers[handlers_index++]; |
| + } |
| + } else { |
| + // Otherwise, use the submenu too. In this case, disable the last item of |
| + // the regular main menu (hence '-2'). |
| + for (int i = 0; i < kNumMainMenuCommands - 2; ++i) { |
| + handler_map[IDC_CONTENT_CONTEXT_OPEN_WITH1 + i] = |
| + handlers[handlers_index++]; |
| + } |
| + submenu_parent_command_id = |
| + IDC_CONTENT_CONTEXT_OPEN_WITH1 + kNumMainMenuCommands - 1; |
| + const int sub_items = |
| + std::min(num_apps - (kNumMainMenuCommands - 2), kNumSubMenuCommands); |
| + for (int i = 0; i < sub_items; ++i) { |
| + handler_map[submenu_id_start + i] = handlers[handlers_index++]; |
| + } |
| + } |
| + |
| + return std::make_pair(std::move(handler_map), submenu_parent_command_id); |
| + } |
| + |
| + static const int kNumMainMenuCommands; |
| + static const int kNumSubMenuCommands; |
| + |
| + RenderViewContextMenuProxy* const proxy_; |
| + SubMenuDelegate submenu_delegate_; |
| + GURL link_url_; |
| + |
| + // A menu model received from Ash side. |
| + std::unique_ptr<ash::LinkHandlerModel> menu_model_; |
| + HandlerMap handlers_; |
| + // A submenu passed to Chrome side. |
| + std::unique_ptr<ui::MenuModel> submenu_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(OpenWithMenuObserver); |
| +}; |
| + |
| +const int OpenWithMenuObserver::kNumMainMenuCommands = 4; |
| +const int OpenWithMenuObserver::kNumSubMenuCommands = 10; |
| + |
| +} // namespace |
| + |
| +std::pair<HandlerMap, int> BuildHandlersMapForTesting( |
| + const std::vector<ash::LinkHandlerInfo>& handlers) { |
| + return OpenWithMenuObserver::BuildHandlersMapForTesting(handlers); |
| +} |
| + |
| +RenderViewContextMenuObserver* OpenWithMenuFactory::CreateMenu( |
| + RenderViewContextMenuProxy* proxy) { |
| + return new OpenWithMenuObserver(proxy); |
| +} |