OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 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. |
| 4 |
| 5 #include "chrome/browser/renderer_context_menu/open_with_menu_factory.h" |
| 6 |
| 7 #include <memory> |
| 8 #include <unordered_map> |
| 9 #include <utility> |
| 10 #include <vector> |
| 11 |
| 12 #include "ash/link_handler_model.h" |
| 13 #include "ash/link_handler_model_factory.h" |
| 14 #include "ash/shell.h" |
| 15 #include "base/strings/string_util.h" |
| 16 #include "base/strings/utf_string_conversions.h" |
| 17 #include "chrome/app/chrome_command_ids.h" |
| 18 #include "chrome/grit/generated_resources.h" |
| 19 #include "components/renderer_context_menu/render_view_context_menu_observer.h" |
| 20 #include "components/renderer_context_menu/render_view_context_menu_proxy.h" |
| 21 #include "content/public/common/context_menu_params.h" |
| 22 #include "ui/base/l10n/l10n_util.h" |
| 23 #include "ui/base/models/simple_menu_model.h" |
| 24 |
| 25 namespace { |
| 26 |
| 27 using HandlerMap = std::unordered_map<int, ash::LinkHandlerInfo>; |
| 28 |
| 29 // An observer class which populates the "Open with <app>" menu items either |
| 30 // synchronously or asynchronously. |
| 31 class OpenWithMenuObserver : public RenderViewContextMenuObserver, |
| 32 public ash::LinkHandlerModel::Observer { |
| 33 public: |
| 34 class SubMenuDelegate : public ui::SimpleMenuModel::Delegate { |
| 35 public: |
| 36 explicit SubMenuDelegate(OpenWithMenuObserver* parent) : parent_(parent) {} |
| 37 ~SubMenuDelegate() override {} |
| 38 |
| 39 bool IsCommandIdChecked(int command_id) const override { return false; } |
| 40 bool IsCommandIdEnabled(int command_id) const override { return true; } |
| 41 |
| 42 bool GetAcceleratorForCommandId(int command_id, |
| 43 ui::Accelerator* accelerator) override { |
| 44 return false; |
| 45 } |
| 46 |
| 47 void ExecuteCommand(int command_id, int event_flags) override { |
| 48 parent_->ExecuteCommand(command_id); |
| 49 } |
| 50 |
| 51 private: |
| 52 OpenWithMenuObserver* const parent_; |
| 53 |
| 54 DISALLOW_COPY_AND_ASSIGN(SubMenuDelegate); |
| 55 }; |
| 56 |
| 57 explicit OpenWithMenuObserver(RenderViewContextMenuProxy* proxy) |
| 58 : proxy_(proxy), |
| 59 submenu_delegate_(this), |
| 60 more_apps_label_( |
| 61 l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_MORE_APPS)) {} |
| 62 |
| 63 ~OpenWithMenuObserver() override {} |
| 64 |
| 65 // RenderViewContextMenuObserver overrides: |
| 66 void InitMenu(const content::ContextMenuParams& params) override { |
| 67 if (!ash::Shell::HasInstance()) |
| 68 return; |
| 69 ash::LinkHandlerModelFactory* factory = |
| 70 ash::Shell::GetInstance()->link_handler_model_factory(); |
| 71 if (!factory) |
| 72 return; |
| 73 |
| 74 link_url_ = params.link_url; |
| 75 menu_model_ = factory->CreateModel(link_url_); |
| 76 if (!menu_model_) |
| 77 return; |
| 78 |
| 79 // Add placeholder items. |
| 80 std::unique_ptr<ui::SimpleMenuModel> submenu( |
| 81 new ui::SimpleMenuModel(&submenu_delegate_)); |
| 82 for (int i = 0; i < kNumSubMenuCommands; ++i) { |
| 83 const int command_id = |
| 84 IDC_CONTENT_CONTEXT_OPEN_WITH1 + kNumMainMenuCommands + i; |
| 85 submenu->AddItem(command_id, base::EmptyString16()); |
| 86 } |
| 87 submenu_ = std::move(submenu); |
| 88 |
| 89 int command_id; |
| 90 for (int i = 0; i < kNumMainMenuCommands - 1; ++i) { |
| 91 command_id = IDC_CONTENT_CONTEXT_OPEN_WITH1 + i; |
| 92 proxy_->AddMenuItem(command_id, base::EmptyString16()); |
| 93 } |
| 94 proxy_->AddSubMenu(++command_id, more_apps_label_, submenu_.get()); |
| 95 |
| 96 menu_model_->AddObserver(this); |
| 97 } |
| 98 |
| 99 bool IsCommandIdSupported(int command_id) override { |
| 100 return command_id >= IDC_CONTENT_CONTEXT_OPEN_WITH1 && |
| 101 command_id <= IDC_CONTENT_CONTEXT_OPEN_WITH_LAST; |
| 102 } |
| 103 |
| 104 bool IsCommandIdChecked(int command_id) override { return false; } |
| 105 bool IsCommandIdEnabled(int command_id) override { return true; } |
| 106 |
| 107 void ExecuteCommand(int command_id) override { |
| 108 // Note: SubmenuDelegate also calls this method with a command_id for the |
| 109 // submenu. |
| 110 const auto it = handlers_.find(command_id); |
| 111 if (it == handlers_.end()) |
| 112 return; |
| 113 menu_model_->OpenLinkWithHandler(link_url_, it->second.id); |
| 114 } |
| 115 |
| 116 void OnMenuCancel() override {} |
| 117 |
| 118 // ash::OpenWithItems::Delegate overrides: |
| 119 void ModelChanged( |
| 120 const std::vector<ash::LinkHandlerInfo>& handlers) override { |
| 121 auto result = BuildHandlersMap(handlers); |
| 122 handlers_ = std::move(result.first); |
| 123 const int submenu_parent_id = result.second; |
| 124 for (int command_id = IDC_CONTENT_CONTEXT_OPEN_WITH1; |
| 125 command_id <= IDC_CONTENT_CONTEXT_OPEN_WITH_LAST; ++command_id) { |
| 126 const auto it = handlers_.find(command_id); |
| 127 if (command_id == submenu_parent_id) { |
| 128 // Show the submenu parent. |
| 129 proxy_->UpdateMenuItem(command_id, true, false, more_apps_label_); |
| 130 } else if (it == handlers_.end()) { |
| 131 // Hide the menu or submenu parent. |
| 132 proxy_->UpdateMenuItem(command_id, false, true, base::EmptyString16()); |
| 133 } else { |
| 134 // Update the menu with the new model. |
| 135 const base::string16 label = |
| 136 l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_OPEN_WITH_APP, |
| 137 base::UTF8ToUTF16(it->second.name)); |
| 138 proxy_->UpdateMenuItem(command_id, true, false, label); |
| 139 } |
| 140 } |
| 141 } |
| 142 |
| 143 static std::pair<HandlerMap, int> BuildHandlersMapForTesting( |
| 144 const std::vector<ash::LinkHandlerInfo>& handlers) { |
| 145 return BuildHandlersMap(handlers); |
| 146 } |
| 147 |
| 148 private: |
| 149 // Converts |handlers| into HandlerMap which is a map from a command ID to a |
| 150 // LinkHandlerInfo and returns the map. Also returns a command id for the |
| 151 // parent of the submenu. When the submenu is not needed, the function |
| 152 // returns |kInvalidCommandId|. |
| 153 static std::pair<HandlerMap, int> BuildHandlersMap( |
| 154 const std::vector<ash::LinkHandlerInfo>& handlers) { |
| 155 const int kInvalidCommandId = -1; |
| 156 const int submenu_id_start = |
| 157 IDC_CONTENT_CONTEXT_OPEN_WITH1 + kNumMainMenuCommands; |
| 158 |
| 159 HandlerMap handler_map; |
| 160 int submenu_parent_command_id = kInvalidCommandId; |
| 161 |
| 162 const int num_apps = handlers.size(); |
| 163 size_t handlers_index = 0; |
| 164 // We use the last item in the main menu (IDC_CONTENT_CONTEXT_OPEN_WITH1 + |
| 165 // kNumMainMenuCommands- 1) as a parent of a submenu, and others as regular |
| 166 // menu items. |
| 167 if (num_apps < kNumMainMenuCommands) { |
| 168 // All apps can be shown with the regular main menu items. |
| 169 for (int i = 0; i < num_apps; ++i) { |
| 170 handler_map[IDC_CONTENT_CONTEXT_OPEN_WITH1 + i] = |
| 171 handlers[handlers_index++]; |
| 172 } |
| 173 } else { |
| 174 // Otherwise, use the submenu too. In this case, disable the last item of |
| 175 // the regular main menu (hence '-2'). |
| 176 for (int i = 0; i < kNumMainMenuCommands - 2; ++i) { |
| 177 handler_map[IDC_CONTENT_CONTEXT_OPEN_WITH1 + i] = |
| 178 handlers[handlers_index++]; |
| 179 } |
| 180 submenu_parent_command_id = |
| 181 IDC_CONTENT_CONTEXT_OPEN_WITH1 + kNumMainMenuCommands - 1; |
| 182 const int sub_items = |
| 183 std::min(num_apps - (kNumMainMenuCommands - 2), kNumSubMenuCommands); |
| 184 for (int i = 0; i < sub_items; ++i) { |
| 185 handler_map[submenu_id_start + i] = handlers[handlers_index++]; |
| 186 } |
| 187 } |
| 188 |
| 189 return std::make_pair(std::move(handler_map), submenu_parent_command_id); |
| 190 } |
| 191 |
| 192 static const int kNumMainMenuCommands; |
| 193 static const int kNumSubMenuCommands; |
| 194 |
| 195 RenderViewContextMenuProxy* const proxy_; |
| 196 SubMenuDelegate submenu_delegate_; |
| 197 const base::string16 more_apps_label_; |
| 198 GURL link_url_; |
| 199 |
| 200 // A menu model received from Ash side. |
| 201 std::unique_ptr<ash::LinkHandlerModel> menu_model_; |
| 202 HandlerMap handlers_; |
| 203 // A submenu passed to Chrome side. |
| 204 std::unique_ptr<ui::MenuModel> submenu_; |
| 205 |
| 206 DISALLOW_COPY_AND_ASSIGN(OpenWithMenuObserver); |
| 207 }; |
| 208 |
| 209 const int OpenWithMenuObserver::kNumMainMenuCommands = 4; |
| 210 const int OpenWithMenuObserver::kNumSubMenuCommands = 10; |
| 211 |
| 212 } // namespace |
| 213 |
| 214 std::pair<HandlerMap, int> BuildHandlersMapForTesting( |
| 215 const std::vector<ash::LinkHandlerInfo>& handlers) { |
| 216 return OpenWithMenuObserver::BuildHandlersMapForTesting(handlers); |
| 217 } |
| 218 |
| 219 RenderViewContextMenuObserver* OpenWithMenuFactory::CreateMenu( |
| 220 RenderViewContextMenuProxy* proxy) { |
| 221 return new OpenWithMenuObserver(proxy); |
| 222 } |
OLD | NEW |