OLD | NEW |
| (Empty) |
1 // Copyright (c) 2010 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 "build/build_config.h" | |
6 | |
7 #include "chrome/browser/back_forward_menu_model.h" | |
8 | |
9 #include "app/l10n_util.h" | |
10 #include "app/text_elider.h" | |
11 #include "app/resource_bundle.h" | |
12 #include "base/string_number_conversions.h" | |
13 #include "chrome/browser/metrics/user_metrics.h" | |
14 #include "chrome/browser/tab_contents/navigation_controller.h" | |
15 #include "chrome/browser/tab_contents/navigation_entry.h" | |
16 #include "chrome/browser/tab_contents/tab_contents.h" | |
17 #include "chrome/browser/ui/browser.h" | |
18 #include "chrome/common/url_constants.h" | |
19 #include "grit/generated_resources.h" | |
20 #include "grit/theme_resources.h" | |
21 #include "net/base/registry_controlled_domain.h" | |
22 | |
23 const int BackForwardMenuModel::kMaxHistoryItems = 12; | |
24 const int BackForwardMenuModel::kMaxChapterStops = 5; | |
25 static const int kMaxWidth = 700; | |
26 | |
27 BackForwardMenuModel::BackForwardMenuModel(Browser* browser, | |
28 ModelType model_type) | |
29 : browser_(browser), | |
30 test_tab_contents_(NULL), | |
31 model_type_(model_type) { | |
32 } | |
33 | |
34 bool BackForwardMenuModel::HasIcons() const { | |
35 return true; | |
36 } | |
37 | |
38 int BackForwardMenuModel::GetItemCount() const { | |
39 int items = GetHistoryItemCount(); | |
40 | |
41 if (items > 0) { | |
42 int chapter_stops = 0; | |
43 | |
44 // Next, we count ChapterStops, if any. | |
45 if (items == kMaxHistoryItems) | |
46 chapter_stops = GetChapterStopCount(items); | |
47 | |
48 if (chapter_stops) | |
49 items += chapter_stops + 1; // Chapter stops also need a separator. | |
50 | |
51 // If the menu is not empty, add two positions in the end | |
52 // for a separator and a "Show Full History" item. | |
53 items += 2; | |
54 } | |
55 | |
56 return items; | |
57 } | |
58 | |
59 menus::MenuModel::ItemType BackForwardMenuModel::GetTypeAt(int index) const { | |
60 return IsSeparator(index) ? TYPE_SEPARATOR : TYPE_COMMAND; | |
61 } | |
62 | |
63 int BackForwardMenuModel::GetCommandIdAt(int index) const { | |
64 return index; | |
65 } | |
66 | |
67 string16 BackForwardMenuModel::GetLabelAt(int index) const { | |
68 // Return label "Show Full History" for the last item of the menu. | |
69 if (index == GetItemCount() - 1) | |
70 return l10n_util::GetStringUTF16(IDS_SHOWFULLHISTORY_LINK); | |
71 | |
72 // Return an empty string for a separator. | |
73 if (IsSeparator(index)) | |
74 return string16(); | |
75 | |
76 // Return the entry title, escaping any '&' characters and eliding it if it's | |
77 // super long. | |
78 NavigationEntry* entry = GetNavigationEntry(index); | |
79 string16 menu_text(entry->GetTitleForDisplay( | |
80 &GetTabContents()->controller())); | |
81 menu_text = gfx::ElideText(menu_text, gfx::Font(), kMaxWidth, false); | |
82 | |
83 for (size_t i = menu_text.find('&'); i != string16::npos; | |
84 i = menu_text.find('&', i + 2)) { | |
85 menu_text.insert(i, 1, '&'); | |
86 } | |
87 return menu_text; | |
88 } | |
89 | |
90 bool BackForwardMenuModel::IsLabelDynamicAt(int index) const { | |
91 // This object is only used for a single showing of a menu. | |
92 return false; | |
93 } | |
94 | |
95 bool BackForwardMenuModel::GetAcceleratorAt( | |
96 int index, | |
97 menus::Accelerator* accelerator) const { | |
98 return false; | |
99 } | |
100 | |
101 bool BackForwardMenuModel::IsItemCheckedAt(int index) const { | |
102 return false; | |
103 } | |
104 | |
105 int BackForwardMenuModel::GetGroupIdAt(int index) const { | |
106 return false; | |
107 } | |
108 | |
109 bool BackForwardMenuModel::GetIconAt(int index, SkBitmap* icon) const { | |
110 if (!ItemHasIcon(index)) | |
111 return false; | |
112 | |
113 if (index == GetItemCount() - 1) { | |
114 *icon = *ResourceBundle::GetSharedInstance().GetBitmapNamed( | |
115 IDR_HISTORY_FAVICON); | |
116 } else { | |
117 NavigationEntry* entry = GetNavigationEntry(index); | |
118 *icon = entry->favicon().bitmap(); | |
119 } | |
120 | |
121 return true; | |
122 } | |
123 | |
124 menus::ButtonMenuItemModel* BackForwardMenuModel::GetButtonMenuItemAt( | |
125 int index) const { | |
126 return NULL; | |
127 } | |
128 | |
129 bool BackForwardMenuModel::IsEnabledAt(int index) const { | |
130 return index < GetItemCount() && !IsSeparator(index); | |
131 } | |
132 | |
133 menus::MenuModel* BackForwardMenuModel::GetSubmenuModelAt(int index) const { | |
134 return NULL; | |
135 } | |
136 | |
137 void BackForwardMenuModel::HighlightChangedTo(int index) { | |
138 } | |
139 | |
140 void BackForwardMenuModel::ActivatedAt(int index) { | |
141 ActivatedAtWithDisposition(index, CURRENT_TAB); | |
142 } | |
143 | |
144 void BackForwardMenuModel::ActivatedAtWithDisposition( | |
145 int index, int disposition) { | |
146 Profile* profile = browser_->profile(); | |
147 | |
148 DCHECK(!IsSeparator(index)); | |
149 | |
150 // Execute the command for the last item: "Show Full History". | |
151 if (index == GetItemCount() - 1) { | |
152 UserMetrics::RecordComputedAction(BuildActionName("ShowFullHistory", -1), | |
153 profile); | |
154 browser_->ShowSingletonTab(GURL(chrome::kChromeUIHistoryURL), false); | |
155 return; | |
156 } | |
157 | |
158 // Log whether it was a history or chapter click. | |
159 if (index < GetHistoryItemCount()) { | |
160 UserMetrics::RecordComputedAction( | |
161 BuildActionName("HistoryClick", index), profile); | |
162 } else { | |
163 UserMetrics::RecordComputedAction( | |
164 BuildActionName("ChapterClick", index - GetHistoryItemCount() - 1), | |
165 profile); | |
166 } | |
167 | |
168 int controller_index = MenuIndexToNavEntryIndex(index); | |
169 if (!browser_->NavigateToIndexWithDisposition( | |
170 controller_index, static_cast<WindowOpenDisposition>(disposition))) { | |
171 NOTREACHED(); | |
172 } | |
173 } | |
174 | |
175 void BackForwardMenuModel::MenuWillShow() { | |
176 UserMetrics::RecordComputedAction(BuildActionName("Popup", -1), | |
177 browser_->profile()); | |
178 } | |
179 | |
180 bool BackForwardMenuModel::IsSeparator(int index) const { | |
181 int history_items = GetHistoryItemCount(); | |
182 // If the index is past the number of history items + separator, | |
183 // we then consider if it is a chapter-stop entry. | |
184 if (index > history_items) { | |
185 // We either are in ChapterStop area, or at the end of the list (the "Show | |
186 // Full History" link). | |
187 int chapter_stops = GetChapterStopCount(history_items); | |
188 if (chapter_stops == 0) | |
189 return false; // We must have reached the "Show Full History" link. | |
190 // Otherwise, look to see if we have reached the separator for the | |
191 // chapter-stops. If not, this is a chapter stop. | |
192 return (index == history_items + 1 + chapter_stops); | |
193 } | |
194 | |
195 // Look to see if we have reached the separator for the history items. | |
196 return index == history_items; | |
197 } | |
198 | |
199 int BackForwardMenuModel::GetHistoryItemCount() const { | |
200 TabContents* contents = GetTabContents(); | |
201 int items = 0; | |
202 | |
203 if (model_type_ == FORWARD_MENU) { | |
204 // Only count items from n+1 to end (if n is current entry) | |
205 items = contents->controller().entry_count() - | |
206 contents->controller().GetCurrentEntryIndex() - 1; | |
207 } else { | |
208 items = contents->controller().GetCurrentEntryIndex(); | |
209 } | |
210 | |
211 if (items > kMaxHistoryItems) | |
212 items = kMaxHistoryItems; | |
213 else if (items < 0) | |
214 items = 0; | |
215 | |
216 return items; | |
217 } | |
218 | |
219 int BackForwardMenuModel::GetChapterStopCount(int history_items) const { | |
220 TabContents* contents = GetTabContents(); | |
221 | |
222 int chapter_stops = 0; | |
223 int current_entry = contents->controller().GetCurrentEntryIndex(); | |
224 | |
225 if (history_items == kMaxHistoryItems) { | |
226 int chapter_id = current_entry; | |
227 if (model_type_ == FORWARD_MENU) { | |
228 chapter_id += history_items; | |
229 } else { | |
230 chapter_id -= history_items; | |
231 } | |
232 | |
233 do { | |
234 chapter_id = GetIndexOfNextChapterStop(chapter_id, | |
235 model_type_ == FORWARD_MENU); | |
236 if (chapter_id != -1) | |
237 ++chapter_stops; | |
238 } while (chapter_id != -1 && chapter_stops < kMaxChapterStops); | |
239 } | |
240 | |
241 return chapter_stops; | |
242 } | |
243 | |
244 int BackForwardMenuModel::GetIndexOfNextChapterStop(int start_from, | |
245 bool forward) const { | |
246 TabContents* contents = GetTabContents(); | |
247 NavigationController& controller = contents->controller(); | |
248 | |
249 int max_count = controller.entry_count(); | |
250 if (start_from < 0 || start_from >= max_count) | |
251 return -1; // Out of bounds. | |
252 | |
253 if (forward) { | |
254 if (start_from < max_count - 1) { | |
255 // We want to advance over the current chapter stop, so we add one. | |
256 // We don't need to do this when direction is backwards. | |
257 start_from++; | |
258 } else { | |
259 return -1; | |
260 } | |
261 } | |
262 | |
263 NavigationEntry* start_entry = controller.GetEntryAtIndex(start_from); | |
264 const GURL& url = start_entry->url(); | |
265 | |
266 if (!forward) { | |
267 // When going backwards we return the first entry we find that has a | |
268 // different domain. | |
269 for (int i = start_from - 1; i >= 0; --i) { | |
270 if (!net::RegistryControlledDomainService::SameDomainOrHost(url, | |
271 controller.GetEntryAtIndex(i)->url())) | |
272 return i; | |
273 } | |
274 // We have reached the beginning without finding a chapter stop. | |
275 return -1; | |
276 } else { | |
277 // When going forwards we return the entry before the entry that has a | |
278 // different domain. | |
279 for (int i = start_from + 1; i < max_count; ++i) { | |
280 if (!net::RegistryControlledDomainService::SameDomainOrHost(url, | |
281 controller.GetEntryAtIndex(i)->url())) | |
282 return i - 1; | |
283 } | |
284 // Last entry is always considered a chapter stop. | |
285 return max_count - 1; | |
286 } | |
287 } | |
288 | |
289 int BackForwardMenuModel::FindChapterStop(int offset, | |
290 bool forward, | |
291 int skip) const { | |
292 if (offset < 0 || skip < 0) | |
293 return -1; | |
294 | |
295 if (!forward) | |
296 offset *= -1; | |
297 | |
298 TabContents* contents = GetTabContents(); | |
299 int entry = contents->controller().GetCurrentEntryIndex() + offset; | |
300 for (int i = 0; i < skip + 1; i++) | |
301 entry = GetIndexOfNextChapterStop(entry, forward); | |
302 | |
303 return entry; | |
304 } | |
305 | |
306 bool BackForwardMenuModel::ItemHasCommand(int index) const { | |
307 return index < GetItemCount() && !IsSeparator(index); | |
308 } | |
309 | |
310 bool BackForwardMenuModel::ItemHasIcon(int index) const { | |
311 return index < GetItemCount() && !IsSeparator(index); | |
312 } | |
313 | |
314 string16 BackForwardMenuModel::GetShowFullHistoryLabel() const { | |
315 return l10n_util::GetStringUTF16(IDS_SHOWFULLHISTORY_LINK); | |
316 } | |
317 | |
318 TabContents* BackForwardMenuModel::GetTabContents() const { | |
319 // We use the test tab contents if the unit test has specified it. | |
320 return test_tab_contents_ ? test_tab_contents_ : | |
321 browser_->GetSelectedTabContents(); | |
322 } | |
323 | |
324 int BackForwardMenuModel::MenuIndexToNavEntryIndex(int index) const { | |
325 TabContents* contents = GetTabContents(); | |
326 int history_items = GetHistoryItemCount(); | |
327 | |
328 DCHECK_GE(index, 0); | |
329 | |
330 // Convert anything above the History items separator. | |
331 if (index < history_items) { | |
332 if (model_type_ == FORWARD_MENU) { | |
333 index += contents->controller().GetCurrentEntryIndex() + 1; | |
334 } else { | |
335 // Back menu is reverse. | |
336 index = contents->controller().GetCurrentEntryIndex() - (index + 1); | |
337 } | |
338 return index; | |
339 } | |
340 if (index == history_items) | |
341 return -1; // Don't translate the separator for history items. | |
342 | |
343 if (index >= history_items + 1 + GetChapterStopCount(history_items)) | |
344 return -1; // This is beyond the last chapter stop so we abort. | |
345 | |
346 // This menu item is a chapter stop located between the two separators. | |
347 index = FindChapterStop(history_items, | |
348 model_type_ == FORWARD_MENU, | |
349 index - history_items - 1); | |
350 | |
351 return index; | |
352 } | |
353 | |
354 NavigationEntry* BackForwardMenuModel::GetNavigationEntry(int index) const { | |
355 int controller_index = MenuIndexToNavEntryIndex(index); | |
356 NavigationController& controller = GetTabContents()->controller(); | |
357 if (controller_index >= 0 && controller_index < controller.entry_count()) | |
358 return controller.GetEntryAtIndex(controller_index); | |
359 | |
360 NOTREACHED(); | |
361 return NULL; | |
362 } | |
363 | |
364 std::string BackForwardMenuModel::BuildActionName( | |
365 const std::string& action, int index) const { | |
366 DCHECK(!action.empty()); | |
367 DCHECK(index >= -1); | |
368 std::string metric_string; | |
369 if (model_type_ == FORWARD_MENU) | |
370 metric_string += "ForwardMenu_"; | |
371 else | |
372 metric_string += "BackMenu_"; | |
373 metric_string += action; | |
374 if (index != -1) { | |
375 // +1 is for historical reasons (indices used to start at 1). | |
376 metric_string += base::IntToString(index + 1); | |
377 } | |
378 return metric_string; | |
379 } | |
OLD | NEW |