blob: b24b9b7bddfdbfddf2e5557b70ff924f97cae7eb [file] [log] [blame]
[email protected]7f856be2008-10-29 23:38:061// Copyright (c) 2006-2008 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/bookmarks/bookmark_utils.h"
6
[email protected]cb362ccc2008-12-10 17:22:327#include "base/string_util.h"
[email protected]9333f182008-12-09 17:34:178#include "base/time.h"
[email protected]7f856be2008-10-29 23:38:069#include "chrome/browser/bookmarks/bookmark_drag_data.h"
10#include "chrome/browser/bookmarks/bookmark_model.h"
11#include "chrome/browser/browser.h"
12#include "chrome/browser/browser_list.h"
[email protected]9333f182008-12-09 17:34:1713#include "chrome/browser/history/query_parser.h"
[email protected]7f856be2008-10-29 23:38:0614#include "chrome/browser/page_navigator.h"
15#include "chrome/browser/tab_contents.h"
16#include "chrome/common/drag_drop_types.h"
17#include "chrome/common/l10n_util.h"
[email protected]fafc8a422008-11-07 17:53:0918#include "chrome/common/os_exchange_data.h"
[email protected]7f856be2008-10-29 23:38:0619#include "chrome/views/event.h"
[email protected]9333f182008-12-09 17:34:1720#include "chrome/views/tree_node_iterator.h"
[email protected]7f856be2008-10-29 23:38:0621
22#include "chromium_strings.h"
23#include "generated_resources.h"
24
25namespace {
26
[email protected]7f856be2008-10-29 23:38:0627// A PageNavigator implementation that creates a new Browser. This is used when
28// opening a url and there is no Browser open. The Browser is created the first
29// time the PageNavigator method is invoked.
30class NewBrowserPageNavigator : public PageNavigator {
31 public:
32 explicit NewBrowserPageNavigator(Profile* profile)
33 : profile_(profile),
34 browser_(NULL) {}
35
36 virtual ~NewBrowserPageNavigator() {
37 if (browser_)
[email protected]15952e462008-11-14 00:29:0538 browser_->window()->Show();
[email protected]7f856be2008-10-29 23:38:0639 }
40
41 Browser* browser() const { return browser_; }
42
43 virtual void OpenURL(const GURL& url,
44 const GURL& referrer,
45 WindowOpenDisposition disposition,
46 PageTransition::Type transition) {
47 if (!browser_) {
48 Profile* profile = (disposition == OFF_THE_RECORD) ?
49 profile_->GetOffTheRecordProfile() : profile_;
[email protected]15952e462008-11-14 00:29:0550 browser_ = Browser::Create(profile);
[email protected]7f856be2008-10-29 23:38:0651 // Always open the first tab in the foreground.
52 disposition = NEW_FOREGROUND_TAB;
53 }
54 browser_->OpenURLFromTab(NULL, url, referrer, NEW_FOREGROUND_TAB, transition);
55 }
56
57 private:
58 Profile* profile_;
59 Browser* browser_;
60
61 DISALLOW_COPY_AND_ASSIGN(NewBrowserPageNavigator);
62};
63
64void CloneDragDataImpl(BookmarkModel* model,
65 const BookmarkDragData::Element& element,
66 BookmarkNode* parent,
67 int index_to_add_at) {
68 if (element.is_url) {
69 model->AddURL(parent, index_to_add_at, element.title, element.url);
70 } else {
71 BookmarkNode* new_folder = model->AddGroup(parent, index_to_add_at,
72 element.title);
73 for (int i = 0; i < static_cast<int>(element.children.size()); ++i)
74 CloneDragDataImpl(model, element.children[i], new_folder, i);
75 }
76}
77
78// Returns the number of descendants of node that are of type url.
79int DescendantURLCount(BookmarkNode* node) {
80 int result = 0;
81 for (int i = 0; i < node->GetChildCount(); ++i) {
82 BookmarkNode* child = node->GetChild(i);
83 if (child->is_url())
84 result++;
85 else
86 result += DescendantURLCount(child);
87 }
88 return result;
89}
90
91// Implementation of OpenAll. Opens all nodes of type URL and recurses for
92// groups. |navigator| is the PageNavigator used to open URLs. After the first
93// url is opened |opened_url| is set to true and |navigator| is set to the
94// PageNavigator of the last active tab. This is done to handle a window
95// disposition of new window, in which case we want subsequent tabs to open in
96// that window.
97void OpenAllImpl(BookmarkNode* node,
98 WindowOpenDisposition initial_disposition,
99 PageNavigator** navigator,
100 bool* opened_url) {
101 if (node->is_url()) {
102 WindowOpenDisposition disposition;
103 if (*opened_url)
104 disposition = NEW_BACKGROUND_TAB;
105 else
106 disposition = initial_disposition;
107 (*navigator)->OpenURL(node->GetURL(), GURL(), disposition,
108 PageTransition::AUTO_BOOKMARK);
109 if (!*opened_url) {
110 *opened_url = true;
111 // We opened the first URL which may have opened a new window or clobbered
112 // the current page, reset the navigator just to be sure.
113 Browser* new_browser = BrowserList::GetLastActive();
114 if (new_browser) {
115 TabContents* current_tab = new_browser->GetSelectedTabContents();
116 DCHECK(new_browser && current_tab);
117 if (new_browser && current_tab)
118 *navigator = current_tab;
119 } // else, new_browser == NULL, which happens during testing.
120 }
121 } else {
122 // Group, recurse through children.
123 for (int i = 0; i < node->GetChildCount(); ++i) {
124 OpenAllImpl(node->GetChild(i), initial_disposition, navigator,
125 opened_url);
126 }
127 }
128}
129
130bool ShouldOpenAll(HWND parent, const std::vector<BookmarkNode*>& nodes) {
131 int descendant_count = 0;
132 for (size_t i = 0; i < nodes.size(); ++i)
133 descendant_count += DescendantURLCount(nodes[i]);
[email protected]f785ad12008-11-19 23:01:28134 if (descendant_count < bookmark_utils::num_urls_before_prompting)
[email protected]7f856be2008-10-29 23:38:06135 return true;
136
137 std::wstring message =
138 l10n_util::GetStringF(IDS_BOOKMARK_BAR_SHOULD_OPEN_ALL,
139 IntToWString(descendant_count));
140 return MessageBox(parent, message.c_str(),
141 l10n_util::GetString(IDS_PRODUCT_NAME).c_str(),
142 MB_YESNO | MB_ICONWARNING | MB_TOPMOST) == IDYES;
143}
144
[email protected]9333f182008-12-09 17:34:17145// Comparison function that compares based on date modified of the two nodes.
146bool MoreRecentlyModified(BookmarkNode* n1, BookmarkNode* n2) {
147 return n1->date_group_modified() > n2->date_group_modified();
148}
149
[email protected]cb362ccc2008-12-10 17:22:32150// Returns true if |text| contains each string in |words|. This is used when
151// searching for bookmarks.
152bool DoesBookmarkTextContainWords(const std::wstring& text,
153 const std::vector<std::wstring>& words) {
154 for (size_t i = 0; i < words.size(); ++i) {
155 if (text.find(words[i]) == std::wstring::npos)
156 return false;
157 }
158 return true;
159}
160
161// Returns true if |node|s title or url contains the strings in |words|.
162bool DoesBookmarkContainWords(BookmarkNode* node,
163 const std::vector<std::wstring>& words) {
164 return
165 DoesBookmarkTextContainWords(
166 l10n_util::ToLower(node->GetTitle()), words) ||
167 DoesBookmarkTextContainWords(UTF8ToWide(node->GetURL().spec()), words);
168}
169
[email protected]7f856be2008-10-29 23:38:06170} // namespace
171
172namespace bookmark_utils {
173
[email protected]f785ad12008-11-19 23:01:28174int num_urls_before_prompting = 15;
175
[email protected]f28cbb72008-11-04 19:29:08176int PreferredDropOperation(int source_operations, int operations) {
177 int common_ops = (source_operations & operations);
[email protected]7f856be2008-10-29 23:38:06178 if (!common_ops)
179 return 0;
180 if (DragDropTypes::DRAG_COPY & common_ops)
181 return DragDropTypes::DRAG_COPY;
182 if (DragDropTypes::DRAG_LINK & common_ops)
183 return DragDropTypes::DRAG_LINK;
184 if (DragDropTypes::DRAG_MOVE & common_ops)
185 return DragDropTypes::DRAG_MOVE;
186 return DragDropTypes::DRAG_NONE;
187}
188
189bool IsValidDropLocation(Profile* profile,
190 const BookmarkDragData& data,
191 BookmarkNode* drop_parent,
192 int index) {
193 if (!drop_parent->is_folder()) {
194 NOTREACHED();
195 return false;
196 }
197
198 if (!data.is_valid())
199 return false;
200
201 if (data.IsFromProfile(profile)) {
202 std::vector<BookmarkNode*> nodes = data.GetNodes(profile);
203 for (size_t i = 0; i < nodes.size(); ++i) {
204 // Don't allow the drop if the user is attempting to drop on one of the
205 // nodes being dragged.
206 BookmarkNode* node = nodes[i];
207 int node_index = (drop_parent == node->GetParent()) ?
208 drop_parent->IndexOfChild(nodes[i]) : -1;
209 if (node_index != -1 && (index == node_index || index == node_index + 1))
210 return false;
211
212 // drop_parent can't accept a child that is an ancestor.
213 if (drop_parent->HasAncestor(node))
214 return false;
215 }
216 return true;
217 }
218 // From the same profile, always accept.
219 return true;
220}
221
222void CloneDragData(BookmarkModel* model,
223 const std::vector<BookmarkDragData::Element>& elements,
224 BookmarkNode* parent,
225 int index_to_add_at) {
226 if (!parent->is_folder() || !model) {
227 NOTREACHED();
228 return;
229 }
230 for (size_t i = 0; i < elements.size(); ++i)
231 CloneDragDataImpl(model, elements[i], parent, index_to_add_at + i);
232}
233
234void OpenAll(HWND parent,
235 Profile* profile,
236 PageNavigator* navigator,
237 const std::vector<BookmarkNode*>& nodes,
238 WindowOpenDisposition initial_disposition) {
239 if (!ShouldOpenAll(parent, nodes))
240 return;
241
242 NewBrowserPageNavigator navigator_impl(profile);
243 if (!navigator) {
244 Browser* browser =
[email protected]299dabd2008-11-19 02:27:16245 BrowserList::FindBrowserWithType(profile, Browser::TYPE_NORMAL);
[email protected]b6f2b9132008-11-17 16:27:51246 if (!browser || !browser->GetSelectedTabContents()) {
[email protected]7f856be2008-10-29 23:38:06247 navigator = &navigator_impl;
[email protected]b6f2b9132008-11-17 16:27:51248 } else {
249 browser->window()->Activate();
[email protected]7f856be2008-10-29 23:38:06250 navigator = browser->GetSelectedTabContents();
[email protected]b6f2b9132008-11-17 16:27:51251 }
[email protected]7f856be2008-10-29 23:38:06252 }
253
254 bool opened_url = false;
255 for (size_t i = 0; i < nodes.size(); ++i)
256 OpenAllImpl(nodes[i], initial_disposition, &navigator, &opened_url);
257}
258
259void OpenAll(HWND parent,
260 Profile* profile,
261 PageNavigator* navigator,
262 BookmarkNode* node,
263 WindowOpenDisposition initial_disposition) {
264 std::vector<BookmarkNode*> nodes;
265 nodes.push_back(node);
266 OpenAll(parent, profile, navigator, nodes, initial_disposition);
267}
268
[email protected]fafc8a422008-11-07 17:53:09269void CopyToClipboard(BookmarkModel* model,
270 const std::vector<BookmarkNode*>& nodes,
271 bool remove_nodes) {
272 if (nodes.empty())
273 return;
274
275 OSExchangeData* data = new OSExchangeData();
276 BookmarkDragData(nodes).Write(NULL, data);
277 OleSetClipboard(data);
278 // OLE takes ownership of OSExchangeData.
279
280 if (remove_nodes) {
281 for (size_t i = 0; i < nodes.size(); ++i) {
282 model->Remove(nodes[i]->GetParent(),
283 nodes[i]->GetParent()->IndexOfChild(nodes[i]));
284 }
285 }
286}
287
288void PasteFromClipboard(BookmarkModel* model,
289 BookmarkNode* parent,
290 int index) {
291 if (!parent)
292 return;
293
294 IDataObject* data;
295 if (OleGetClipboard(&data) != S_OK)
296 return;
297
298 OSExchangeData data_wrapper(data);
299 BookmarkDragData bookmark_data;
300 if (!bookmark_data.Read(data_wrapper))
301 return;
302
303 if (index == -1)
304 index = parent->GetChildCount();
305 bookmark_utils::CloneDragData(model, bookmark_data.elements, parent, index);
306}
307
308bool CanPasteFromClipboard(BookmarkNode* node) {
309 if (!node)
310 return false;
311
312 IDataObject* data;
313 if (OleGetClipboard(&data) != S_OK)
314 return false;
315
316 OSExchangeData data_wrapper(data);
317 BookmarkDragData bookmark_data;
318 return bookmark_data.Read(data_wrapper);
319}
320
[email protected]9333f182008-12-09 17:34:17321std::vector<BookmarkNode*> GetMostRecentlyModifiedGroups(
322 BookmarkModel* model,
323 size_t max_count) {
324 std::vector<BookmarkNode*> nodes;
325 views::TreeNodeIterator<BookmarkNode> iterator(model->root_node());
326 while (iterator.has_next()) {
327 BookmarkNode* parent = iterator.Next();
328 if (parent->is_folder() && parent->date_group_modified() > base::Time()) {
329 if (max_count == 0) {
330 nodes.push_back(parent);
331 } else {
332 std::vector<BookmarkNode*>::iterator i =
333 std::upper_bound(nodes.begin(), nodes.end(), parent,
334 &MoreRecentlyModified);
335 if (nodes.size() < max_count || i != nodes.end()) {
336 nodes.insert(i, parent);
337 while (nodes.size() > max_count)
338 nodes.pop_back();
339 }
340 }
341 } // else case, the root node, which we don't care about or imported nodes
342 // (which have a time of 0).
343 }
344
345 if (nodes.size() < max_count) {
346 // Add the bookmark bar and other nodes if there is space.
347 if (find(nodes.begin(), nodes.end(), model->GetBookmarkBarNode()) ==
348 nodes.end()) {
349 nodes.push_back(model->GetBookmarkBarNode());
350 }
351
352 if (nodes.size() < max_count &&
353 find(nodes.begin(), nodes.end(), model->other_node()) == nodes.end()) {
354 nodes.push_back(model->other_node());
355 }
356 }
357 return nodes;
358}
359
360void GetMostRecentlyAddedEntries(BookmarkModel* model,
361 size_t count,
362 std::vector<BookmarkNode*>* nodes) {
363 views::TreeNodeIterator<BookmarkNode> iterator(model->root_node());
364 while (iterator.has_next()) {
365 BookmarkNode* node = iterator.Next();
366 if (node->is_url()) {
367 std::vector<BookmarkNode*>::iterator insert_position =
368 std::upper_bound(nodes->begin(), nodes->end(), node,
369 &MoreRecentlyAdded);
370 if (nodes->size() < count || insert_position != nodes->end()) {
371 nodes->insert(insert_position, node);
372 while (nodes->size() > count)
373 nodes->pop_back();
374 }
375 }
376 }
377}
378
379void GetBookmarksMatchingText(BookmarkModel* model,
380 const std::wstring& text,
381 size_t max_count,
382 std::vector<TitleMatch>* matches) {
383 QueryParser parser;
384 ScopedVector<QueryNode> query_nodes;
385 parser.ParseQuery(text, &query_nodes.get());
386 if (query_nodes.empty())
387 return;
388
389 views::TreeNodeIterator<BookmarkNode> iterator(model->root_node());
390 Snippet::MatchPositions match_position;
391 while (iterator.has_next()) {
392 BookmarkNode* node = iterator.Next();
[email protected]9333f182008-12-09 17:34:17393 if (node->is_url() &&
394 parser.DoesQueryMatch(node->GetTitle(), query_nodes.get(),
395 &match_position)) {
396 matches->push_back(TitleMatch());
397 matches->back().node = node;
398 matches->back().match_positions.swap(match_position);
399 if (matches->size() == max_count)
400 break;
401 }
402 }
403}
404
[email protected]9333f182008-12-09 17:34:17405bool MoreRecentlyAdded(BookmarkNode* n1, BookmarkNode* n2) {
406 return n1->date_added() > n2->date_added();
407}
408
[email protected]cb362ccc2008-12-10 17:22:32409void GetBookmarksContainingText(BookmarkModel* model,
410 const std::wstring& text,
411 size_t max_count,
412 std::vector<BookmarkNode*>* nodes) {
413 std::vector<std::wstring> words;
414 QueryParser parser;
415 parser.ExtractQueryWords(l10n_util::ToLower(text), &words);
416 if (words.empty())
417 return;
418
419 views::TreeNodeIterator<BookmarkNode> iterator(model->root_node());
420 while (iterator.has_next()) {
421 BookmarkNode* node = iterator.Next();
422 if (node->is_url() && DoesBookmarkContainWords(node, words)) {
423 nodes->push_back(node);
424 if (nodes->size() == max_count)
425 return;
426 }
427 }
428}
429
430bool DoesBookmarkContainText(BookmarkNode* node, const std::wstring& text) {
431 std::vector<std::wstring> words;
432 QueryParser parser;
433 parser.ExtractQueryWords(l10n_util::ToLower(text), &words);
434 if (words.empty())
435 return false;
436
437 return (node->is_url() && DoesBookmarkContainWords(node, words));
438}
439
[email protected]7f856be2008-10-29 23:38:06440} // namespace bookmark_utils