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/renderer_context_menu/link_handler_model.h" | |
13 #include "ash/renderer_context_menu/open_with_menu_controller.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), submenu_delegate_(this) {} | |
59 | |
60 ~OpenWithMenuObserver() override {} | |
61 | |
62 // RenderViewContextMenuObserver overrides: | |
63 void InitMenu(const content::ContextMenuParams& params) override { | |
64 if (!ash::Shell::HasInstance()) | |
65 return; | |
66 ash::OpenWithMenuController* controller = | |
67 ash::Shell::GetInstance()->open_with_menu_controller(); | |
68 if (!controller) | |
69 return; | |
70 | |
71 link_url_ = params.link_url; | |
72 menu_model_ = controller->CreateModel(link_url_); | |
73 if (!menu_model_) | |
74 return; | |
75 | |
76 // Add placeholder items. | |
77 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.
| |
78 for (int i = 0; i < kNumSubMenuCommands; ++i) { | |
79 const int command_id = | |
80 IDC_CONTENT_CONTEXT_OPEN_WITH1 + kNumMainMenuCommands + i; | |
81 submenu->AddItem(command_id, base::EmptyString16()); | |
82 } | |
83 submenu_.reset(submenu); | |
84 | |
85 for (int i = 0; i < kNumMainMenuCommands; ++i) { | |
86 const int command_id = IDC_CONTENT_CONTEXT_OPEN_WITH1 + i; | |
87 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.
| |
88 base::string16 label = | |
89 l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_MORE_APPS); | |
90 proxy_->AddSubMenu(command_id, label, submenu_.get()); | |
91 } else { | |
92 proxy_->AddMenuItem(command_id, base::EmptyString16()); | |
93 } | |
94 } | |
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; | |
hidehiko
2016/04/20 04:40:18
indent?
Yusuke Sato
2016/04/22 05:27:48
This is by git cl format actually..
| |
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( | |
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.
| |
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 // Keep the submenu parent. | |
129 } else if (it == handlers_.end()) { | |
130 // Hide the menu. | |
131 proxy_->UpdateMenuItem(command_id, false, true, base::EmptyString16()); | |
132 } else { | |
133 // Update the menu with the new model. | |
134 const base::string16 label = | |
135 l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_OPEN_WITH_APP, | |
136 base::UTF8ToUTF16(it->second.name)); | |
137 proxy_->UpdateMenuItem(command_id, true, false, label); | |
138 } | |
139 } | |
140 } | |
141 | |
142 static std::pair<HandlerMap, int> BuildHandlersMapForTesting( | |
143 const std::vector<ash::LinkHandlerInfo>& handlers) { | |
144 return BuildHandlersMap(handlers); | |
145 } | |
146 | |
147 private: | |
148 // Converts |handlers| into HandlerMap which is a map from a command ID to a | |
149 // LinkHandlerInfo and returns the map. Also returns a command id for the | |
150 // parent of the submenu. When the submenu is not needed, the function | |
151 // returns |kInvalidCommandId|. | |
152 static std::pair<HandlerMap, int> BuildHandlersMap( | |
153 const std::vector<ash::LinkHandlerInfo>& handlers) { | |
154 const int kInvalidCommandId = -1; | |
155 const int submenu_id_start = | |
156 IDC_CONTENT_CONTEXT_OPEN_WITH1 + kNumMainMenuCommands; | |
157 | |
158 HandlerMap handler_map; | |
159 int submenu_parent_command_id = kInvalidCommandId; | |
160 | |
161 const int num_apps = handlers.size(); | |
162 size_t handlers_index = 0; | |
163 // We use the last item in the main menu (IDC_CONTENT_CONTEXT_OPEN_WITH1 + | |
164 // kNumMainMenuCommands- 1) as a parent of a submenu, and others as regular | |
165 // menu items. | |
166 if (num_apps < kNumMainMenuCommands) { | |
167 // All apps can be shown with the regular main menu items. | |
168 for (int i = 0; i < num_apps; ++i) { | |
169 handler_map[IDC_CONTENT_CONTEXT_OPEN_WITH1 + i] = | |
170 handlers[handlers_index++]; | |
171 } | |
172 } else { | |
173 // Otherwise, use the submenu too. In this case, disable the last item of | |
174 // the regular main menu (hence '-2'). | |
175 for (int i = 0; i < kNumMainMenuCommands - 2; ++i) { | |
176 handler_map[IDC_CONTENT_CONTEXT_OPEN_WITH1 + i] = | |
177 handlers[handlers_index++]; | |
178 } | |
179 submenu_parent_command_id = | |
180 IDC_CONTENT_CONTEXT_OPEN_WITH1 + kNumMainMenuCommands - 1; | |
181 const int sub_items = | |
182 std::min(num_apps - (kNumMainMenuCommands - 2), kNumSubMenuCommands); | |
183 for (int i = 0; i < sub_items; ++i) { | |
184 handler_map[submenu_id_start + i] = handlers[handlers_index++]; | |
185 } | |
186 } | |
187 | |
188 return std::make_pair(std::move(handler_map), submenu_parent_command_id); | |
189 } | |
190 | |
191 static const int kNumMainMenuCommands; | |
192 static const int kNumSubMenuCommands; | |
193 | |
194 RenderViewContextMenuProxy* const proxy_; | |
195 SubMenuDelegate submenu_delegate_; | |
196 GURL link_url_; | |
197 | |
198 // A menu model received from Ash side. | |
199 std::unique_ptr<ash::LinkHandlerModel> menu_model_; | |
200 HandlerMap handlers_; | |
201 // A submenu passed to Chrome side. | |
202 std::unique_ptr<ui::MenuModel> submenu_; | |
203 | |
204 DISALLOW_COPY_AND_ASSIGN(OpenWithMenuObserver); | |
205 }; | |
206 | |
207 const int OpenWithMenuObserver::kNumMainMenuCommands = 4; | |
208 const int OpenWithMenuObserver::kNumSubMenuCommands = 10; | |
209 | |
210 } // namespace | |
211 | |
212 std::pair<HandlerMap, int> BuildHandlersMapForTesting( | |
213 const std::vector<ash::LinkHandlerInfo>& handlers) { | |
214 return OpenWithMenuObserver::BuildHandlersMapForTesting(handlers); | |
215 } | |
216 | |
217 RenderViewContextMenuObserver* OpenWithMenuFactory::CreateMenu( | |
218 RenderViewContextMenuProxy* proxy) { | |
219 return new OpenWithMenuObserver(proxy); | |
220 } | |
OLD | NEW |