base::Time multiplicative operator overloading
[chromium-blink-merge.git] / chrome / browser / extensions / context_menu_matcher.cc
blob81cb9712a037365e008541abf347eb4338ace606
1 // Copyright (c) 2012 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.
5 #include "chrome/browser/extensions/context_menu_matcher.h"
7 #include "base/strings/utf_string_conversions.h"
8 #include "chrome/app/chrome_command_ids.h"
9 #include "chrome/browser/extensions/extension_util.h"
10 #include "chrome/common/extensions/api/context_menus.h"
11 #include "content/public/browser/browser_context.h"
12 #include "content/public/common/context_menu_params.h"
13 #include "extensions/browser/extension_registry.h"
14 #include "ui/gfx/favicon_size.h"
15 #include "ui/gfx/image/image.h"
17 namespace extensions {
19 namespace {
21 // The range of command IDs reserved for extension's custom menus.
22 // TODO(oshima): These values will be injected by embedders.
23 int extensions_context_custom_first = IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST;
24 int extensions_context_custom_last = IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST;
26 } // namespace
28 // static
29 const size_t ContextMenuMatcher::kMaxExtensionItemTitleLength = 75;
31 // static
32 int ContextMenuMatcher::ConvertToExtensionsCustomCommandId(int id) {
33 return extensions_context_custom_first + id;
36 // static
37 bool ContextMenuMatcher::IsExtensionsCustomCommandId(int id) {
38 return id >= extensions_context_custom_first &&
39 id <= extensions_context_custom_last;
42 ContextMenuMatcher::ContextMenuMatcher(
43 content::BrowserContext* browser_context,
44 ui::SimpleMenuModel::Delegate* delegate,
45 ui::SimpleMenuModel* menu_model,
46 const base::Callback<bool(const MenuItem*)>& filter)
47 : browser_context_(browser_context),
48 menu_model_(menu_model),
49 delegate_(delegate),
50 filter_(filter) {
53 void ContextMenuMatcher::AppendExtensionItems(
54 const MenuItem::ExtensionKey& extension_key,
55 const base::string16& selection_text,
56 int* index,
57 bool is_action_menu) {
58 DCHECK_GE(*index, 0);
59 int max_index =
60 extensions_context_custom_last - extensions_context_custom_first;
61 if (*index >= max_index)
62 return;
64 const Extension* extension = NULL;
65 MenuItem::List items;
66 bool can_cross_incognito;
67 if (!GetRelevantExtensionTopLevelItems(
68 extension_key, &extension, &can_cross_incognito, &items))
69 return;
71 if (items.empty())
72 return;
74 // If this is the first extension-provided menu item, and there are other
75 // items in the menu, and the last item is not a separator add a separator.
76 if (*index == 0 && menu_model_->GetItemCount())
77 menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
79 // Extensions (other than platform apps) are only allowed one top-level slot
80 // (and it can't be a radio or checkbox item because we are going to put the
81 // extension icon next to it), unless the context menu is an an action menu.
82 // Action menus do not include the extension action, and they only include
83 // items from one extension, so they are not placed within a submenu.
84 // Otherwise, we automatically push them into a submenu if there is more than
85 // one top-level item.
86 if (extension->is_platform_app() || is_action_menu) {
87 RecursivelyAppendExtensionItems(items,
88 can_cross_incognito,
89 selection_text,
90 menu_model_,
91 index,
92 is_action_menu);
93 } else {
94 int menu_id = ConvertToExtensionsCustomCommandId(*index);
95 (*index)++;
96 base::string16 title;
97 MenuItem::List submenu_items;
99 if (items.size() > 1 || items[0]->type() != MenuItem::NORMAL) {
100 title = base::UTF8ToUTF16(extension->name());
101 submenu_items = items;
102 } else {
103 MenuItem* item = items[0];
104 extension_item_map_[menu_id] = item->id();
105 title = item->TitleWithReplacement(selection_text,
106 kMaxExtensionItemTitleLength);
107 submenu_items = GetRelevantExtensionItems(item->children(),
108 can_cross_incognito);
111 // Now add our item(s) to the menu_model_.
112 if (submenu_items.empty()) {
113 menu_model_->AddItem(menu_id, title);
114 } else {
115 ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(delegate_);
116 extension_menu_models_.push_back(submenu);
117 menu_model_->AddSubMenu(menu_id, title, submenu);
118 RecursivelyAppendExtensionItems(submenu_items,
119 can_cross_incognito,
120 selection_text,
121 submenu,
122 index,
123 false); // is_action_menu_top_level
125 if (!is_action_menu)
126 SetExtensionIcon(extension_key.extension_id);
130 void ContextMenuMatcher::Clear() {
131 extension_item_map_.clear();
132 extension_menu_models_.clear();
135 base::string16 ContextMenuMatcher::GetTopLevelContextMenuTitle(
136 const MenuItem::ExtensionKey& extension_key,
137 const base::string16& selection_text) {
138 const Extension* extension = NULL;
139 MenuItem::List items;
140 bool can_cross_incognito;
141 GetRelevantExtensionTopLevelItems(
142 extension_key, &extension, &can_cross_incognito, &items);
144 base::string16 title;
146 if (items.empty() ||
147 items.size() > 1 ||
148 items[0]->type() != MenuItem::NORMAL) {
149 title = base::UTF8ToUTF16(extension->name());
150 } else {
151 MenuItem* item = items[0];
152 title = item->TitleWithReplacement(
153 selection_text, kMaxExtensionItemTitleLength);
155 return title;
158 bool ContextMenuMatcher::IsCommandIdChecked(int command_id) const {
159 MenuItem* item = GetExtensionMenuItem(command_id);
160 if (!item)
161 return false;
162 return item->checked();
165 bool ContextMenuMatcher::IsCommandIdEnabled(int command_id) const {
166 MenuItem* item = GetExtensionMenuItem(command_id);
167 if (!item)
168 return true;
169 return item->enabled();
172 void ContextMenuMatcher::ExecuteCommand(int command_id,
173 content::WebContents* web_contents,
174 const content::ContextMenuParams& params) {
175 MenuItem* item = GetExtensionMenuItem(command_id);
176 if (!item)
177 return;
179 MenuManager* manager = MenuManager::Get(browser_context_);
180 manager->ExecuteCommand(browser_context_, web_contents, params, item->id());
183 bool ContextMenuMatcher::GetRelevantExtensionTopLevelItems(
184 const MenuItem::ExtensionKey& extension_key,
185 const Extension** extension,
186 bool* can_cross_incognito,
187 MenuItem::List* items) {
188 *extension = ExtensionRegistry::Get(
189 browser_context_)->enabled_extensions().GetByID(
190 extension_key.extension_id);
191 if (!*extension)
192 return false;
194 // Find matching items.
195 MenuManager* manager = MenuManager::Get(browser_context_);
196 const MenuItem::List* all_items = manager->MenuItems(extension_key);
197 if (!all_items || all_items->empty())
198 return false;
200 *can_cross_incognito = util::CanCrossIncognito(*extension, browser_context_);
201 *items = GetRelevantExtensionItems(*all_items, *can_cross_incognito);
203 return true;
206 MenuItem::List ContextMenuMatcher::GetRelevantExtensionItems(
207 const MenuItem::List& items,
208 bool can_cross_incognito) {
209 MenuItem::List result;
210 for (MenuItem::List::const_iterator i = items.begin();
211 i != items.end(); ++i) {
212 const MenuItem* item = *i;
214 if (!filter_.Run(item))
215 continue;
217 if (item->id().incognito == browser_context_->IsOffTheRecord() ||
218 can_cross_incognito)
219 result.push_back(*i);
221 return result;
224 void ContextMenuMatcher::RecursivelyAppendExtensionItems(
225 const MenuItem::List& items,
226 bool can_cross_incognito,
227 const base::string16& selection_text,
228 ui::SimpleMenuModel* menu_model,
229 int* index,
230 bool is_action_menu_top_level) {
231 MenuItem::Type last_type = MenuItem::NORMAL;
232 int radio_group_id = 1;
233 int num_items = 0;
235 for (MenuItem::List::const_iterator i = items.begin();
236 i != items.end(); ++i) {
237 MenuItem* item = *i;
239 // If last item was of type radio but the current one isn't, auto-insert
240 // a separator. The converse case is handled below.
241 if (last_type == MenuItem::RADIO &&
242 item->type() != MenuItem::RADIO) {
243 menu_model->AddSeparator(ui::NORMAL_SEPARATOR);
244 last_type = MenuItem::SEPARATOR;
247 int menu_id = ConvertToExtensionsCustomCommandId(*index);
248 ++(*index);
249 ++num_items;
250 // Action context menus have a limit for top level extension items to
251 // prevent control items from being pushed off the screen, since extension
252 // items will not be placed in a submenu.
253 const int top_level_limit = api::context_menus::ACTION_MENU_TOP_LEVEL_LIMIT;
254 if (menu_id >= extensions_context_custom_last ||
255 (is_action_menu_top_level && num_items >= top_level_limit))
256 return;
258 extension_item_map_[menu_id] = item->id();
259 base::string16 title = item->TitleWithReplacement(selection_text,
260 kMaxExtensionItemTitleLength);
261 if (item->type() == MenuItem::NORMAL) {
262 MenuItem::List children =
263 GetRelevantExtensionItems(item->children(), can_cross_incognito);
264 if (children.empty()) {
265 menu_model->AddItem(menu_id, title);
266 } else {
267 ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(delegate_);
268 extension_menu_models_.push_back(submenu);
269 menu_model->AddSubMenu(menu_id, title, submenu);
270 RecursivelyAppendExtensionItems(children,
271 can_cross_incognito,
272 selection_text,
273 submenu,
274 index,
275 false); // is_action_menu_top_level
277 } else if (item->type() == MenuItem::CHECKBOX) {
278 menu_model->AddCheckItem(menu_id, title);
279 } else if (item->type() == MenuItem::RADIO) {
280 if (i != items.begin() &&
281 last_type != MenuItem::RADIO) {
282 radio_group_id++;
284 // Auto-append a separator if needed.
285 menu_model->AddSeparator(ui::NORMAL_SEPARATOR);
288 menu_model->AddRadioItem(menu_id, title, radio_group_id);
289 } else if (item->type() == MenuItem::SEPARATOR) {
290 menu_model->AddSeparator(ui::NORMAL_SEPARATOR);
292 last_type = item->type();
296 MenuItem* ContextMenuMatcher::GetExtensionMenuItem(int id) const {
297 MenuManager* manager = MenuManager::Get(browser_context_);
298 std::map<int, MenuItem::Id>::const_iterator i =
299 extension_item_map_.find(id);
300 if (i != extension_item_map_.end()) {
301 MenuItem* item = manager->GetItemById(i->second);
302 if (item)
303 return item;
305 return NULL;
308 void ContextMenuMatcher::SetExtensionIcon(const std::string& extension_id) {
309 MenuManager* menu_manager = MenuManager::Get(browser_context_);
311 int index = menu_model_->GetItemCount() - 1;
312 DCHECK_GE(index, 0);
314 const SkBitmap& icon = menu_manager->GetIconForExtension(extension_id);
315 DCHECK(icon.width() == gfx::kFaviconSize);
316 DCHECK(icon.height() == gfx::kFaviconSize);
318 menu_model_->SetIcon(index, gfx::Image::CreateFrom1xBitmap(icon));
321 } // namespace extensions