Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(281)

Side by Side Diff: chrome/browser/cocoa/bookmark_editor_controller.mm

Issue 357005: Implement Bookmark All Tabs... using the BookmarkEditorController and determi... (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 11 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #import "chrome/browser/cocoa/bookmark_editor_controller.h"
5 #include "app/l10n_util.h" 6 #include "app/l10n_util.h"
6 #include "base/logging.h"
7 #include "base/mac_util.h"
8 #include "base/sys_string_conversions.h" 7 #include "base/sys_string_conversions.h"
9 #include "chrome/browser/bookmarks/bookmark_editor.h"
10 #include "chrome/browser/bookmarks/bookmark_model.h" 8 #include "chrome/browser/bookmarks/bookmark_model.h"
11 #include "chrome/browser/profile.h"
12 #import "chrome/browser/cocoa/bookmark_tree_browser_cell.h"
13 #import "chrome/browser/cocoa/bookmark_editor_controller.h"
14 #include "grit/generated_resources.h"
15 9
16 @interface BookmarkEditorController (Private) 10 @interface BookmarkEditorController (Private)
17 11
18 // Grab the url from the text field and convert. 12 // Grab the url from the text field and convert.
19 - (GURL)GURLFromUrlField; 13 - (GURL)GURLFromUrlField;
20 14
21 // Given a cell in the folder browser, make that cell editable so that the
22 // bookmark folder name can be modified by the user.
23 - (void)editFolderNameInCell:(BookmarkTreeBrowserCell*)cell;
24
25 // The action called by the bookmark folder name cell being edited by
26 // the user when editing has been completed (such as by pressing <return>).
27 - (void)cellEditingCompleted:(id)sender;
28
29 // Update the folder name from the current edit in the given cell
30 // and return the focus to the folder tree browser.
31 - (void)saveFolderNameForCell:(BookmarkTreeBrowserCell*)cell;
32
33 // A custom action handler called by the bookmark folder browser when the
34 // user has double-clicked on a folder name.
35 - (void)browserDoubleClicked:(id)sender;
36
37 // Determine and returns the rightmost selected/highlighted element (node)
38 // in the bookmark tree view if the tree view is showing, otherwise returns
39 // the original parentNode_. If the tree view is showing but nothing is
40 // selected then return the root node. This assumes that leaf nodes (pure
41 // bookmarks) are not being presented.
42 - (const BookmarkNode*)selectedNode;
43
44 // Select/highlight the given node within the browser tree view. If the
45 // node is nil then select the bookmark bar.
46 - (void)selectNodeInBrowser:(const BookmarkNode*)node;
47
48 @end 15 @end
49 16
50 // static; implemented for each platform. 17 @implementation BookmarkEditorController
51 void BookmarkEditor::Show(gfx::NativeWindow parent_hwnd, 18
52 Profile* profile, 19 @synthesize displayURL = displayURL_;
53 const BookmarkNode* parent, 20
54 const EditDetails& details, 21 + (NSSet*)keyPathsForValuesAffectingOkEnabled {
55 Configuration configuration, 22 return [NSSet setWithObject:@"displayURL"];
56 Handler* handler) {
57 if (details.type == EditDetails::NEW_FOLDER) {
58 // TODO(sky): implement this.
59 NOTIMPLEMENTED();
60 return;
61 }
62 BookmarkEditorController* controller = [[BookmarkEditorController alloc]
63 initWithParentWindow:parent_hwnd
64 profile:profile
65 parent:parent
66 node:details.existing_node
67 configuration:configuration
68 handler:handler];
69 [controller runAsModalSheet];
70 } 23 }
71 24
72 #pragma mark Bookmark TreeNode Helpers
73
74 namespace {
75
76 // Find the index'th folder child of a parent, ignoring bookmarks (leafs).
77 const BookmarkNode* GetFolderChildForParent(const BookmarkNode* parent_node,
78 NSInteger folder_index) {
79 const BookmarkNode* child_node = nil;
80 int i = 0;
81 int child_count = parent_node->GetChildCount();
82 do {
83 child_node = parent_node->GetChild(i);
84 if (child_node->type() != BookmarkNode::URL)
85 --folder_index;
86 ++i;
87 } while (folder_index >= 0 && i < child_count);
88 return child_node;
89 }
90
91 // Determine the index of a child within its parent ignoring
92 // bookmarks (leafs).
93 int IndexOfFolderChild(const BookmarkNode* child_node) {
94 const BookmarkNode* node_parent = child_node->GetParent();
95 int child_index = node_parent->IndexOfChild(child_node);
96 for (int i = child_index - 1; i >= 0; --i) {
97 const BookmarkNode* sibling = node_parent->GetChild(i);
98 if (sibling->type() == BookmarkNode::URL)
99 --child_index;
100 }
101 return child_index;
102 }
103
104 } // namespace
105
106 @implementation BookmarkEditorController
107
108 - (id)initWithParentWindow:(NSWindow*)parentWindow 25 - (id)initWithParentWindow:(NSWindow*)parentWindow
109 profile:(Profile*)profile 26 profile:(Profile*)profile
110 parent:(const BookmarkNode*)parent 27 parent:(const BookmarkNode*)parent
111 node:(const BookmarkNode*)node 28 node:(const BookmarkNode*)node
112 configuration:(BookmarkEditor::Configuration)configuration 29 configuration:(BookmarkEditor::Configuration)configuration
113 handler:(BookmarkEditor::Handler*)handler { 30 handler:(BookmarkEditor::Handler*)handler {
114 NSString* nibpath = [mac_util::MainAppBundle() 31 if ((self = [super initWithParentWindow:parentWindow
115 pathForResource:@"BookmarkEditor" 32 nibName:@"BookmarkEditor"
116 ofType:@"nib"]; 33 profile:profile
117 if ((self = [super initWithWindowNibPath:nibpath owner:self])) { 34 parent:parent
118 parentWindow_ = parentWindow; 35 configuration:configuration
119 profile_ = profile; 36 handler:handler])) {
120 parentNode_ = parent;
121 // "Add Page..." has no "node" so this may be NULL. 37 // "Add Page..." has no "node" so this may be NULL.
122 node_ = node; 38 node_ = node;
123 configuration_ = configuration;
124 handler_.reset(handler);
125 } 39 }
126 return self; 40 return self;
127 } 41 }
128 42
43 - (void)dealloc {
44 [displayURL_ release];
45 [super dealloc];
46 }
47
129 - (void)awakeFromNib { 48 - (void)awakeFromNib {
130 // Set text fields to match our bookmark. If the node is NULL we 49 // Set text fields to match our bookmark. If the node is NULL we
131 // arrived here from an "Add Page..." item in a context menu. 50 // arrived here from an "Add Page..." item in a context menu.
132 if (node_) { 51 if (node_) {
133 initialName_.reset([base::SysWideToNSString(node_->GetTitle()) retain]); 52 [self setInitialName:base::SysWideToNSString(node_->GetTitle())];
134 std::string url_string = node_->GetURL().possibly_invalid_spec(); 53 std::string url_string = node_->GetURL().possibly_invalid_spec();
135 initialUrl_.reset([[NSString stringWithUTF8String:url_string.c_str()] 54 initialUrl_.reset([[NSString stringWithUTF8String:url_string.c_str()]
136 retain]); 55 retain]);
137 } else { 56 } else {
138 initialName_.reset([@"" retain]);
139 initialUrl_.reset([@"" retain]); 57 initialUrl_.reset([@"" retain]);
140 } 58 }
141 [nameField_ setStringValue:initialName_]; 59 [self setDisplayURL:initialUrl_];
142 [urlField_ setStringValue:initialUrl_]; 60 [super awakeFromNib];
143
144 // Get a ping when the URL or name text fields change;
145 // trigger an initial ping to set things up.
146 [nameField_ setDelegate:self];
147 [urlField_ setDelegate:self];
148 [self controlTextDidChange:nil];
149
150 if (configuration_ != BookmarkEditor::SHOW_TREE) {
151 // Remember the NSBrowser's height; we will shrink our frame by that
152 // much.
153 NSRect frame = [[self window] frame];
154 CGFloat browserHeight = [folderBrowser_ frame].size.height;
155 frame.size.height -= browserHeight;
156 frame.origin.y += browserHeight;
157 // Remove the NSBrowser and "new folder" button.
158 [folderBrowser_ removeFromSuperview];
159 [newFolderButton_ removeFromSuperview];
160 // Finally, commit the size change.
161 [[self window] setFrame:frame display:YES];
162 }
163
164 [folderBrowser_ setCellClass:[BookmarkTreeBrowserCell class]];
165 [folderBrowser_ setDoubleAction:@selector(browserDoubleClicked:)];
166 }
167
168 - (void)windowDidLoad {
169 if (configuration_ == BookmarkEditor::SHOW_TREE) {
170 // Find and select the |parent| bookmark node in the folder tree browser.
171 [self selectNodeInBrowser:parentNode_];
172 }
173 }
174
175 - (void)windowWillClose:(NSNotification *)notification {
176 // If a folder name cell is being edited then force it to end editing
177 // so that any changes are recorded.
178 [[self window] makeFirstResponder:nil];
179
180 // This is probably unnecessary but it feels cleaner since the
181 // delegate of a text field can be automatically registered for
182 // notifications.
183 [nameField_ setDelegate:nil];
184 [urlField_ setDelegate:nil];
185 [self autorelease];
186 }
187
188 /* TODO(jrg):
189 // Implementing this informal protocol allows us to open the sheet
190 // somewhere other than at the top of the window. NOTE: this means
191 // that I, the controller, am also the window's delegate.
192 - (NSRect)window:(NSWindow*)window willPositionSheet:(NSWindow*)sheet
193 usingRect:(NSRect)rect {
194 // adjust rect.origin.y to be the bottom of the toolbar
195 return rect;
196 }
197 */
198
199 // TODO(jrg): consider NSModalSession.
200 - (void)runAsModalSheet {
201 [NSApp beginSheet:[self window]
202 modalForWindow:parentWindow_
203 modalDelegate:self
204 didEndSelector:@selector(didEndSheet:returnCode:contextInfo:)
205 contextInfo:nil];
206 }
207
208 - (void)selectNodeInBrowser:(const BookmarkNode*)node {
209 DCHECK(configuration_ == BookmarkEditor::SHOW_TREE);
210 std::deque<NSInteger> rowsToSelect;
211 const BookmarkNode* nodeParent = nil;
212 if (node) {
213 nodeParent = node->GetParent();
214 // There should always be a parent node.
215 DCHECK(nodeParent);
216 while (nodeParent) {
217 int nodeRow = IndexOfFolderChild(node);
218 rowsToSelect.push_front(nodeRow);
219 node = nodeParent;
220 nodeParent = nodeParent->GetParent();
221 }
222 } else {
223 BookmarkModel* model = profile_->GetBookmarkModel();
224 nodeParent = model->GetBookmarkBarNode();
225 rowsToSelect.push_front(0);
226 }
227 for (std::deque<NSInteger>::size_type column = 0;
228 column < rowsToSelect.size();
229 ++column) {
230 [folderBrowser_ selectRow:rowsToSelect[column] inColumn:column];
231 }
232 [self controlTextDidChange:nil];
233 }
234
235 - (const BookmarkNode*)selectedNode {
236 BookmarkModel* model = profile_->GetBookmarkModel();
237 const BookmarkNode* selectedNode = NULL;
238 // Determine a new parent node only if the browser is showing.
239 if (configuration_ == BookmarkEditor::SHOW_TREE) {
240 selectedNode = model->root_node();
241 NSInteger column = 0;
242 NSInteger selectedRow = [folderBrowser_ selectedRowInColumn:column];
243 while (selectedRow >= 0) {
244 selectedNode = GetFolderChildForParent(selectedNode,
245 selectedRow);
246 ++column;
247 selectedRow = [folderBrowser_ selectedRowInColumn:column];
248 }
249 } else {
250 // If the tree is not showing then we use the original parent.
251 selectedNode = parentNode_;
252 }
253 return selectedNode;
254 }
255
256 #pragma mark New Folder Handler & Folder Cell Editing
257
258 - (void)editFolderNameInCell:(BookmarkTreeBrowserCell*)cell {
259 DCHECK([cell isKindOfClass:[BookmarkTreeBrowserCell class]]);
260 [cell setEditable:YES];
261 [cell setTarget:self];
262 [cell setAction:@selector(cellEditingCompleted:)];
263 [cell setSendsActionOnEndEditing:YES];
264 NSMatrix* matrix = [cell matrix];
265 // Set the delegate so that we get called when editing wants to complete.
266 [matrix setDelegate:self];
267 [matrix selectText:self];
268 }
269
270 - (void)cellEditingCompleted:(id)sender {
271 DCHECK([sender isKindOfClass:[NSMatrix class]]);
272 BookmarkTreeBrowserCell* cell = [sender selectedCell];
273 DCHECK([cell isKindOfClass:[BookmarkTreeBrowserCell class]]);
274 [self saveFolderNameForCell:cell];
275 }
276
277 - (void)saveFolderNameForCell:(BookmarkTreeBrowserCell*)cell {
278 DCHECK([cell isKindOfClass:[BookmarkTreeBrowserCell class]]);
279 // It's possible that the cell can get reused so clean things up
280 // to prevent inadvertant notifications.
281 [cell setTarget:nil];
282 [cell setAction:nil];
283 [cell setEditable:NO];
284 [cell setSendsActionOnEndEditing:NO];
285 // Force a responder change here to force the editing of the cell's text
286 // to complete otherwise the call to -[cell title] could return stale text.
287 [[folderBrowser_ window] makeFirstResponder:folderBrowser_];
288 const BookmarkNode* bookmarkNode = [cell bookmarkNode];
289 BookmarkModel* model = profile_->GetBookmarkModel();
290 NSString* newTitle = [cell title];
291 model->SetTitle(bookmarkNode, base::SysNSStringToWide(newTitle));
292 }
293
294 - (void)browserDoubleClicked:(id)sender {
295 BookmarkTreeBrowserCell* cell = [folderBrowser_ selectedCell];
296 DCHECK([cell isKindOfClass:[BookmarkTreeBrowserCell class]]);
297 [self editFolderNameInCell:cell];
298 }
299
300 - (IBAction)newFolder:(id)sender {
301 BookmarkModel* model = profile_->GetBookmarkModel();
302 const BookmarkNode* newParentNode = [self selectedNode];
303 int newIndex = newParentNode->GetChildCount();
304 std::wstring newFolderString =
305 l10n_util::GetString(IDS_BOOMARK_EDITOR_NEW_FOLDER_NAME);
306 const BookmarkNode* newFolder = model->AddGroup(newParentNode, newIndex,
307 newFolderString);
308 [self selectNodeInBrowser:newFolder];
309 BookmarkTreeBrowserCell* cell = [folderBrowser_ selectedCell];
310 [self editFolderNameInCell:cell];
311 } 61 }
312 62
313 #pragma mark Bookmark Editing 63 #pragma mark Bookmark Editing
314 64
315 // If possible, return a valid GURL from the URL text field. 65 // If possible, return a valid GURL from the URL text field.
316 - (GURL)GURLFromUrlField { 66 - (GURL)GURLFromUrlField {
317 NSString* url = [urlField_ stringValue]; 67 NSString* url = [self displayURL];
318 GURL newURL = GURL([url UTF8String]); 68 GURL newURL = GURL([url UTF8String]);
319 if (!newURL.is_valid()) { 69 if (!newURL.is_valid()) {
320 // Mimic observed friendliness from Windows 70 // Mimic observed friendliness from Windows
321 newURL = GURL([[NSString stringWithFormat:@"http://%@", url] UTF8String]); 71 newURL = GURL([[NSString stringWithFormat:@"http://%@", url] UTF8String]);
322 } 72 }
323 return newURL; 73 return newURL;
324 } 74 }
325 75
326 // Enable the OK button if there is a bookmark name and there is a valid URL. 76 // Enable the OK button if there is a valid URL.
327 // We set ourselves as the delegate of urlField_ so this gets called. 77 - (BOOL)okEnabled {
328 // (Yes, setting ourself as a delegate automatically registers us for 78 BOOL okEnabled = NO;
329 // the notification.) 79 if ([[self displayURL] length]) {
330 - (void)controlTextDidChange:(NSNotification*)aNotification { 80 GURL newURL = [self GURLFromUrlField];
331 GURL newURL = [self GURLFromUrlField]; 81 okEnabled = (newURL.is_valid()) ? YES : NO;
332 [okButton_ setEnabled:(newURL.is_valid()) ? YES : NO]; 82 }
83 return okEnabled;
333 } 84 }
334 85
335 // The ok: action is connected to the OK button in the Edit Bookmark window 86 // The the bookmark's URL is assumed to be valid (otherwise the OK button
336 // of the BookmarkEditor.xib. The the bookmark's name and URL are assumed 87 // should not be enabled). If the bookmark previously existed then it is
337 // to be valid (otherwise the OK button should not be enabled). If the 88 // removed from its old folder. The bookmark is then added to its new
338 // bookmark previously existed then it is removed from its old folder. 89 // folder. If the folder has not changed then the bookmark stays in its
339 // The bookmark is then added to its new folder. If the folder has not 90 // original position (index) otherwise it is added to the end of the new folder.
340 // changed then the bookmark stays in its original position (index) otherwise
341 // it is added to the end of the new folder.
342 - (IBAction)ok:(id)sender { 91 - (IBAction)ok:(id)sender {
343 NSString* name = [nameField_ stringValue]; 92 NSString* name = [[self displayName] stringByTrimmingCharactersInSet:
93 [NSCharacterSet newlineCharacterSet]];
344 std::wstring newTitle = base::SysNSStringToWide(name); 94 std::wstring newTitle = base::SysNSStringToWide(name);
95 const BookmarkNode* newParentNode = [self selectedNode];
96 BookmarkModel* model = [self bookmarkModel];
97 int newIndex = newParentNode->GetChildCount();
345 GURL newURL = [self GURLFromUrlField]; 98 GURL newURL = [self GURLFromUrlField];
346 if (!newURL.is_valid()) { 99 if (!newURL.is_valid()) {
347 // Shouldn't be reached -- OK button disabled if not valid! 100 // Shouldn't be reached -- OK button disabled if not valid!
348 NOTREACHED(); 101 NOTREACHED();
349 return; 102 return;
350 } 103 }
351 104
352 // Determine where the new/replacement bookmark is to go. 105 // Determine where the new/replacement bookmark is to go.
353 const BookmarkNode* newParentNode = [self selectedNode]; 106 const BookmarkNode* parentNode = [self parentNode];
354 BookmarkModel* model = profile_->GetBookmarkModel(); 107 if (node_ && parentNode) {
355 int newIndex = newParentNode->GetChildCount();
356 if (node_ && parentNode_) {
357 // Replace the old bookmark with the updated bookmark. 108 // Replace the old bookmark with the updated bookmark.
358 int oldIndex = parentNode_->IndexOfChild(node_); 109 int oldIndex = parentNode->IndexOfChild(node_);
359 if (oldIndex >= 0) 110 if (oldIndex >= 0)
360 model->Remove(parentNode_, oldIndex); 111 model->Remove(parentNode, oldIndex);
361 if (parentNode_ == newParentNode) 112 if (parentNode == newParentNode)
362 newIndex = oldIndex; 113 newIndex = oldIndex;
363 } 114 }
364 // Add bookmark as new node at the end of the newly selected folder. 115 // Add bookmark as new node at the end of the newly selected folder.
365 const BookmarkNode* node = model->AddURL(newParentNode, newIndex, 116 const BookmarkNode* node = model->AddURL(newParentNode, newIndex,
366 newTitle, newURL); 117 newTitle, newURL);
367 // Honor handler semantics: callback on node creation. 118 // Honor handler semantics: callback on node creation.
368 if (handler_.get()) 119 [self NotifyHandlerCreatedNode:node];
369 handler_->NodeCreated(node); 120 [super ok:sender];
370 [NSApp endSheet:[self window]];
371 }
372
373 - (IBAction)cancel:(id)sender {
374 [NSApp endSheet:[self window]];
375 }
376
377 - (void)didEndSheet:(NSWindow*)sheet
378 returnCode:(int)returnCode
379 contextInfo:(void*)contextInfo {
380 [sheet close];
381 }
382
383 #pragma mark For Unit Test Use Only
384
385 - (NSString*)displayName {
386 return [nameField_ stringValue];
387 }
388
389 - (NSString*)displayURL {
390 return [urlField_ stringValue];
391 }
392
393 - (void)setDisplayName:(NSString*)name {
394 [nameField_ setStringValue:name];
395 [self controlTextDidChange:nil];
396 }
397
398 - (void)setDisplayURL:(NSString*)name {
399 [urlField_ setStringValue:name];
400 [self controlTextDidChange:nil];
401 }
402
403 - (BOOL)okButtonEnabled {
404 return [okButton_ isEnabled];
405 }
406
407 - (void)selectTestNodeInBrowser:(const BookmarkNode*)node {
408 [self selectNodeInBrowser:node];
409 }
410
411 #pragma mark NSBrowser delegate methods
412
413 // Given a column number, determine the parent bookmark folder node for the
414 // bookmarks being shown in that column. This is done by scanning forward
415 // from column zero and following the selected row for each column up
416 // to the parent of the desired column.
417 - (const BookmarkNode*)parentNodeForColumn:(NSInteger)column {
418 DCHECK(column >= 0);
419 BookmarkModel* model = profile_->GetBookmarkModel();
420 const BookmarkNode* node = model->root_node();
421 for (NSInteger i = 0; i < column; ++i) {
422 NSInteger selectedRowInColumn = [folderBrowser_ selectedRowInColumn:i];
423 node = GetFolderChildForParent(node, selectedRowInColumn);
424 }
425 return node;
426 }
427
428 // This implementation returns the number of folders contained in the parent
429 // folder node for this column.
430 // TOTO(mrossetti): Decide if bookmark (i.e. non-folder) nodes should be
431 // shown, perhaps in gray.
432 - (NSInteger)browser:(NSBrowser*)sender numberOfRowsInColumn:(NSInteger)col {
433 NSInteger rowCount = 0;
434 const BookmarkNode* parentNode = [self parentNodeForColumn:col];
435 if (parentNode) {
436 int childCount = parentNode->GetChildCount();
437 for (int i = 0; i < childCount; ++i) {
438 const BookmarkNode* childNode = parentNode->GetChild(i);
439 if (childNode->type() != BookmarkNode::URL)
440 ++rowCount;
441 }
442 }
443 return rowCount;
444 }
445
446 - (void)browser:(NSBrowser*)sender
447 willDisplayCell:(NSBrowserCell*)cell
448 atRow:(NSInteger)row
449 column:(NSInteger)column {
450 DCHECK(row >= 0); // Trust AppKit, but verify.
451 DCHECK(column >= 0);
452 DCHECK([cell isKindOfClass:[BookmarkTreeBrowserCell class]]);
453 const BookmarkNode* parentNode = [self parentNodeForColumn:column];
454 const BookmarkNode* childNode = GetFolderChildForParent(parentNode, row);
455 DCHECK(childNode);
456 BookmarkTreeBrowserCell* browserCell =
457 static_cast<BookmarkTreeBrowserCell*>(cell);
458 [browserCell setTitle:base::SysWideToNSString(childNode->GetTitle())];
459 [browserCell setBookmarkNode:childNode];
460 [browserCell setMatrix:[folderBrowser_ matrixInColumn:column]];
461 } 121 }
462 122
463 @end // BookmarkEditorController 123 @end // BookmarkEditorController
464 124
OLDNEW
« no previous file with comments | « chrome/browser/cocoa/bookmark_editor_controller.h ('k') | chrome/browser/cocoa/bookmark_editor_controller_unittest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698