Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / bookmarks / bookmark_editor_base_controller.mm
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.
4
5 #include <stack>
6
7 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_editor_base_controller.h"
8
9 #include "base/auto_reset.h"
10 #include "base/logging.h"
11 #include "base/mac/bundle_locations.h"
12 #include "base/mac/mac_util.h"
13 #include "base/strings/sys_string_conversions.h"
14 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
15 #include "chrome/browser/bookmarks/chrome_bookmark_client.h"
16 #include "chrome/browser/bookmarks/chrome_bookmark_client_factory.h"
17 #include "chrome/browser/profiles/profile.h"
18 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_all_tabs_controller.h"
19 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_editor_controller.h"
20 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_name_folder_controller.h"
21 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_tree_browser_cell.h"
22 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
23 #include "chrome/grit/generated_resources.h"
24 #include "components/bookmarks/browser/bookmark_model.h"
25 #include "ui/base/l10n/l10n_util.h"
26 #include "ui/base/l10n/l10n_util_mac.h"
27
28 using bookmarks::BookmarkExpandedStateTracker;
29
30 @interface BookmarkEditorBaseController ()
31
32 // Return the folder tree object for the given path.
33 - (BookmarkFolderInfo*)folderForIndexPath:(NSIndexPath*)path;
34
35 // (Re)build the folder tree from the BookmarkModel's current state.
36 - (void)buildFolderTree;
37
38 // Notifies the controller that the bookmark model has changed.
39 // |selection| specifies if the current selection should be
40 // maintained (usually YES).
41 - (void)modelChangedPreserveSelection:(BOOL)preserve;
42
43 // Notifies the controller that a node has been removed.
44 - (void)nodeRemoved:(const BookmarkNode*)node
45          fromParent:(const BookmarkNode*)parent;
46
47 // Given a folder node, collect an array containing BookmarkFolderInfos
48 // describing its subchildren which are also folders.
49 - (NSMutableArray*)addChildFoldersFromNode:(const BookmarkNode*)node;
50
51 // Scan the folder tree stemming from the given tree folder and create
52 // any newly added folders.  Pass down info for the folder which was
53 // selected before we began creating folders.
54 - (void)createNewFoldersForFolder:(BookmarkFolderInfo*)treeFolder
55                selectedFolderInfo:(BookmarkFolderInfo*)selectedFolderInfo;
56
57 // Scan the folder tree looking for the given bookmark node and return
58 // the selection path thereto.
59 - (NSIndexPath*)selectionPathForNode:(const BookmarkNode*)node;
60
61 // Implementation of getExpandedNodes. See description in header for details.
62 - (void)getExpandedNodes:(BookmarkExpandedStateTracker::Nodes*)nodes
63                   folder:(BookmarkFolderInfo*)info
64                     path:(std::vector<NSUInteger>*)path
65                     root:(id)root;
66 @end
67
68 // static; implemented for each platform.  Update this function for new
69 // classes derived from BookmarkEditorBaseController.
70 void BookmarkEditor::Show(gfx::NativeWindow parent_window,
71                           Profile* profile,
72                           const EditDetails& details,
73                           Configuration configuration) {
74   if (details.type == EditDetails::EXISTING_NODE &&
75       details.existing_node->is_folder()) {
76     BookmarkNameFolderController* controller =
77         [[BookmarkNameFolderController alloc]
78             initWithParentWindow:parent_window
79                          profile:profile
80                             node:details.existing_node];
81     [controller runAsModalSheet];
82     return;
83   }
84
85   if (details.type == EditDetails::NEW_FOLDER && details.urls.empty()) {
86     BookmarkNameFolderController* controller =
87         [[BookmarkNameFolderController alloc]
88              initWithParentWindow:parent_window
89                           profile:profile
90                            parent:details.parent_node
91                          newIndex:details.index];
92      [controller runAsModalSheet];
93      return;
94   }
95
96   BookmarkEditorBaseController* controller = nil;
97   if (details.type == EditDetails::NEW_FOLDER) {
98     controller = [[BookmarkAllTabsController alloc]
99                   initWithParentWindow:parent_window
100                                profile:profile
101                                 parent:details.parent_node
102                                    url:details.url
103                                  title:details.title
104                          configuration:configuration];
105   } else {
106     controller = [[BookmarkEditorController alloc]
107                   initWithParentWindow:parent_window
108                                profile:profile
109                                 parent:details.parent_node
110                                   node:details.existing_node
111                                    url:details.url
112                                  title:details.title
113                          configuration:configuration];
114   }
115   [controller runAsModalSheet];
116 }
117
118 // Adapter to tell BookmarkEditorBaseController when bookmarks change.
119 class BookmarkEditorBaseControllerBridge : public BookmarkModelObserver {
120  public:
121   BookmarkEditorBaseControllerBridge(BookmarkEditorBaseController* controller)
122       : controller_(controller),
123         importing_(false)
124   { }
125
126   virtual void BookmarkModelLoaded(BookmarkModel* model,
127                                    bool ids_reassigned) OVERRIDE {
128     [controller_ modelChangedPreserveSelection:YES];
129   }
130
131   virtual void BookmarkNodeMoved(BookmarkModel* model,
132                                  const BookmarkNode* old_parent,
133                                  int old_index,
134                                  const BookmarkNode* new_parent,
135                                  int new_index) OVERRIDE {
136     if (!importing_ && new_parent->GetChild(new_index)->is_folder())
137       [controller_ modelChangedPreserveSelection:YES];
138   }
139
140   virtual void BookmarkNodeAdded(BookmarkModel* model,
141                                  const BookmarkNode* parent,
142                                  int index) OVERRIDE {
143     if (!importing_ && parent->GetChild(index)->is_folder())
144       [controller_ modelChangedPreserveSelection:YES];
145   }
146
147   virtual void BookmarkNodeRemoved(
148       BookmarkModel* model,
149       const BookmarkNode* parent,
150       int old_index,
151       const BookmarkNode* node,
152       const std::set<GURL>& removed_urls) OVERRIDE {
153     [controller_ nodeRemoved:node fromParent:parent];
154     if (node->is_folder())
155       [controller_ modelChangedPreserveSelection:NO];
156   }
157
158   virtual void BookmarkAllUserNodesRemoved(
159       BookmarkModel* model,
160       const std::set<GURL>& removed_urls) OVERRIDE {
161     [controller_ modelChangedPreserveSelection:NO];
162   }
163
164   virtual void BookmarkNodeChanged(BookmarkModel* model,
165                                    const BookmarkNode* node) OVERRIDE {
166     if (!importing_ && node->is_folder())
167       [controller_ modelChangedPreserveSelection:YES];
168   }
169
170   virtual void BookmarkNodeChildrenReordered(
171       BookmarkModel* model,
172       const BookmarkNode* node) OVERRIDE {
173     if (!importing_)
174       [controller_ modelChangedPreserveSelection:YES];
175   }
176
177   virtual void BookmarkNodeFaviconChanged(BookmarkModel* model,
178                                           const BookmarkNode* node) OVERRIDE {
179     // I care nothing for these 'favicons': I only show folders.
180   }
181
182   virtual void ExtensiveBookmarkChangesBeginning(
183       BookmarkModel* model) OVERRIDE {
184     importing_ = true;
185   }
186
187   // Invoked after a batch import finishes.  This tells observers to update
188   // themselves if they were waiting for the update to finish.
189   virtual void ExtensiveBookmarkChangesEnded(BookmarkModel* model) OVERRIDE {
190     importing_ = false;
191     [controller_ modelChangedPreserveSelection:YES];
192   }
193
194  private:
195   BookmarkEditorBaseController* controller_;  // weak
196   bool importing_;
197 };
198
199
200 #pragma mark -
201
202 @implementation BookmarkEditorBaseController
203
204 @synthesize initialName = initialName_;
205 @synthesize displayName = displayName_;
206
207 - (id)initWithParentWindow:(NSWindow*)parentWindow
208                    nibName:(NSString*)nibName
209                    profile:(Profile*)profile
210                     parent:(const BookmarkNode*)parent
211                        url:(const GURL&)url
212                      title:(const base::string16&)title
213              configuration:(BookmarkEditor::Configuration)configuration {
214   NSString* nibpath = [base::mac::FrameworkBundle()
215                         pathForResource:nibName
216                                  ofType:@"nib"];
217   if ((self = [super initWithWindowNibPath:nibpath owner:self])) {
218     parentWindow_ = parentWindow;
219     profile_ = profile;
220     parentNode_ = parent;
221     url_ = url;
222     title_ = title;
223     configuration_ = configuration;
224     initialName_ = [@"" retain];
225     observer_.reset(new BookmarkEditorBaseControllerBridge(self));
226     [self bookmarkModel]->AddObserver(observer_.get());
227   }
228   return self;
229 }
230
231 - (void)dealloc {
232   [self bookmarkModel]->RemoveObserver(observer_.get());
233   [initialName_ release];
234   [displayName_ release];
235   [super dealloc];
236 }
237
238 - (void)awakeFromNib {
239   [self setDisplayName:[self initialName]];
240
241   if (configuration_ != BookmarkEditor::SHOW_TREE) {
242     // Remember the tree view's height; we will shrink our frame by that much.
243     NSRect frame = [[self window] frame];
244     CGFloat browserHeight = [folderTreeView_ frame].size.height;
245     frame.size.height -= browserHeight;
246     frame.origin.y += browserHeight;
247     // Remove the folder tree and "new folder" button.
248     [folderTreeView_ removeFromSuperview];
249     [newFolderButton_ removeFromSuperview];
250     // Finally, commit the size change.
251     [[self window] setFrame:frame display:YES];
252   }
253
254   // Build up a tree of the current folder configuration.
255   [self buildFolderTree];
256 }
257
258 - (void)windowDidLoad {
259   if (configuration_ == BookmarkEditor::SHOW_TREE) {
260     [self selectNodeInBrowser:parentNode_];
261   }
262 }
263
264 /* TODO(jrg):
265 // Implementing this informal protocol allows us to open the sheet
266 // somewhere other than at the top of the window.  NOTE: this means
267 // that I, the controller, am also the window's delegate.
268 - (NSRect)window:(NSWindow*)window willPositionSheet:(NSWindow*)sheet
269         usingRect:(NSRect)rect {
270   // adjust rect.origin.y to be the bottom of the toolbar
271   return rect;
272 }
273 */
274
275 // TODO(jrg): consider NSModalSession.
276 - (void)runAsModalSheet {
277   // Lock down floating bar when in full-screen mode.  Don't animate
278   // otherwise the pane will be misplaced.
279   [[BrowserWindowController browserWindowControllerForWindow:parentWindow_]
280    lockBarVisibilityForOwner:self withAnimation:NO delay:NO];
281   [NSApp beginSheet:[self window]
282      modalForWindow:parentWindow_
283       modalDelegate:self
284      didEndSelector:@selector(didEndSheet:returnCode:contextInfo:)
285         contextInfo:nil];
286 }
287
288 // This constant has to match the name of the method after it.
289 NSString* const kOkEnabledName = @"okEnabled";
290 - (BOOL)okEnabled {
291   return YES;
292 }
293
294 - (IBAction)ok:(id)sender {
295   NSWindow* window = [self window];
296   [window makeFirstResponder:window];
297   // At least one of these two functions should be provided by derived classes.
298   BOOL hasWillCommit = [self respondsToSelector:@selector(willCommit)];
299   BOOL hasDidCommit = [self respondsToSelector:@selector(didCommit)];
300   DCHECK(hasWillCommit || hasDidCommit);
301   BOOL shouldContinue = YES;
302   if (hasWillCommit) {
303     NSNumber* hasWillContinue = [self performSelector:@selector(willCommit)];
304     if (hasWillContinue && [hasWillContinue isKindOfClass:[NSNumber class]])
305       shouldContinue = [hasWillContinue boolValue];
306   }
307   if (shouldContinue)
308     [self createNewFolders];
309   if (hasDidCommit) {
310     NSNumber* hasDidContinue = [self performSelector:@selector(didCommit)];
311     if (hasDidContinue && [hasDidContinue isKindOfClass:[NSNumber class]])
312       shouldContinue = [hasDidContinue boolValue];
313   }
314   if (shouldContinue)
315     [NSApp endSheet:window];
316 }
317
318 - (IBAction)cancel:(id)sender {
319   [NSApp endSheet:[self window]];
320 }
321
322 - (void)didEndSheet:(NSWindow*)sheet
323          returnCode:(int)returnCode
324         contextInfo:(void*)contextInfo {
325   [sheet close];
326   [[BrowserWindowController browserWindowControllerForWindow:parentWindow_]
327    releaseBarVisibilityForOwner:self withAnimation:YES delay:NO];
328 }
329
330 - (void)windowWillClose:(NSNotification*)notification {
331   [self autorelease];
332 }
333
334 #pragma mark Folder Tree Management
335
336 - (BookmarkModel*)bookmarkModel {
337   return BookmarkModelFactory::GetForProfile(profile_);
338 }
339
340 - (Profile*)profile {
341   return profile_;
342 }
343
344 - (const BookmarkNode*)parentNode {
345   return parentNode_;
346 }
347
348 - (const GURL&)url {
349   return url_;
350 }
351
352 - (const base::string16&)title{
353   return title_;
354 }
355
356 - (BookmarkFolderInfo*)folderForIndexPath:(NSIndexPath*)indexPath {
357   NSUInteger pathCount = [indexPath length];
358   BookmarkFolderInfo* item = nil;
359   NSArray* treeNode = [self folderTreeArray];
360   for (NSUInteger i = 0; i < pathCount; ++i) {
361     item = [treeNode objectAtIndex:[indexPath indexAtPosition:i]];
362     treeNode = [item children];
363   }
364   return item;
365 }
366
367 - (NSIndexPath*)selectedIndexPath {
368   NSIndexPath* selectedIndexPath = nil;
369   NSArray* selections = [self tableSelectionPaths];
370   if ([selections count]) {
371     DCHECK([selections count] == 1);  // Should be exactly one selection.
372     selectedIndexPath = [selections objectAtIndex:0];
373   }
374   return selectedIndexPath;
375 }
376
377 - (BookmarkFolderInfo*)selectedFolder {
378   BookmarkFolderInfo* item = nil;
379   NSIndexPath* selectedIndexPath = [self selectedIndexPath];
380   if (selectedIndexPath) {
381     item = [self folderForIndexPath:selectedIndexPath];
382   }
383   return item;
384 }
385
386 - (const BookmarkNode*)selectedNode {
387   const BookmarkNode* selectedNode = NULL;
388   // Determine a new parent node only if the browser is showing.
389   if (configuration_ == BookmarkEditor::SHOW_TREE) {
390     BookmarkFolderInfo* folderInfo = [self selectedFolder];
391     if (folderInfo)
392       selectedNode = [folderInfo folderNode];
393   } else {
394     // If the tree is not showing then we use the original parent.
395     selectedNode = parentNode_;
396   }
397   return selectedNode;
398 }
399
400 - (void)expandNodes:(const BookmarkExpandedStateTracker::Nodes&)nodes {
401   id treeControllerRoot = [folderTreeController_ arrangedObjects];
402   for (BookmarkExpandedStateTracker::Nodes::const_iterator i = nodes.begin();
403        i != nodes.end(); ++i) {
404     NSIndexPath* path = [self selectionPathForNode:*i];
405     id folderNode = [treeControllerRoot descendantNodeAtIndexPath:path];
406     [folderTreeView_ expandItem:folderNode];
407   }
408 }
409
410 - (BookmarkExpandedStateTracker::Nodes)getExpandedNodes {
411   BookmarkExpandedStateTracker::Nodes nodes;
412   std::vector<NSUInteger> path;
413   NSArray* folderNodes = [self folderTreeArray];
414   NSUInteger i = 0;
415   for (BookmarkFolderInfo* folder in folderNodes) {
416     path.push_back(i);
417     [self getExpandedNodes:&nodes
418                     folder:folder
419                       path:&path
420                       root:[folderTreeController_ arrangedObjects]];
421     path.clear();
422     ++i;
423   }
424   return nodes;
425 }
426
427 - (void)getExpandedNodes:(BookmarkExpandedStateTracker::Nodes*)nodes
428                   folder:(BookmarkFolderInfo*)folder
429                     path:(std::vector<NSUInteger>*)path
430                     root:(id)root {
431   NSIndexPath* indexPath = [NSIndexPath indexPathWithIndexes:&(path->front())
432                                                       length:path->size()];
433   id node = [root descendantNodeAtIndexPath:indexPath];
434   if (![folderTreeView_ isItemExpanded:node])
435     return;
436   nodes->insert([folder folderNode]);
437   NSArray* children = [folder children];
438   NSUInteger i = 0;
439   for (BookmarkFolderInfo* childFolder in children) {
440     path->push_back(i);
441     [self getExpandedNodes:nodes folder:childFolder path:path root:root];
442     path->pop_back();
443     ++i;
444   }
445 }
446
447 - (NSArray*)folderTreeArray {
448   return folderTreeArray_.get();
449 }
450
451 - (NSArray*)tableSelectionPaths {
452   return tableSelectionPaths_.get();
453 }
454
455 - (void)setTableSelectionPath:(NSIndexPath*)tableSelectionPath {
456   [self setTableSelectionPaths:[NSArray arrayWithObject:tableSelectionPath]];
457 }
458
459 - (void)setTableSelectionPaths:(NSArray*)tableSelectionPaths {
460   tableSelectionPaths_.reset([tableSelectionPaths retain]);
461 }
462
463 - (void)selectNodeInBrowser:(const BookmarkNode*)node {
464   DCHECK(configuration_ == BookmarkEditor::SHOW_TREE);
465   NSIndexPath* selectionPath = [self selectionPathForNode:node];
466   [self willChangeValueForKey:kOkEnabledName];
467   [self setTableSelectionPath:selectionPath];
468   [self didChangeValueForKey:kOkEnabledName];
469 }
470
471 - (NSIndexPath*)selectionPathForNode:(const BookmarkNode*)desiredNode {
472   // Back up the parent chaing for desiredNode, building up a stack
473   // of ancestor nodes.  Then crawl down the folderTreeArray looking
474   // for each ancestor in order while building up the selectionPath.
475   std::stack<const BookmarkNode*> nodeStack;
476   BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
477   const BookmarkNode* rootNode = model->root_node();
478   const BookmarkNode* node = desiredNode;
479   while (node != rootNode) {
480     DCHECK(node);
481     nodeStack.push(node);
482     node = node->parent();
483   }
484   NSUInteger stackSize = nodeStack.size();
485
486   NSIndexPath* path = nil;
487   NSArray* folders = [self folderTreeArray];
488   while (!nodeStack.empty()) {
489     node = nodeStack.top();
490     nodeStack.pop();
491     // Find node in the current folders array.
492     NSUInteger i = 0;
493     for (BookmarkFolderInfo *folderInfo in folders) {
494       const BookmarkNode* testNode = [folderInfo folderNode];
495       if (testNode == node) {
496         path = path ? [path indexPathByAddingIndex:i] :
497         [NSIndexPath indexPathWithIndex:i];
498         folders = [folderInfo children];
499         break;
500       }
501       ++i;
502     }
503   }
504   DCHECK([path length] == stackSize);
505   return path;
506 }
507
508 - (NSMutableArray*)addChildFoldersFromNode:(const BookmarkNode*)node {
509   ChromeBookmarkClient* client =
510       ChromeBookmarkClientFactory::GetForProfile(profile_);
511   NSMutableArray* childFolders = nil;
512   int childCount = node->child_count();
513   for (int i = 0; i < childCount; ++i) {
514     const BookmarkNode* childNode = node->GetChild(i);
515     if (childNode->is_folder() && childNode->IsVisible() &&
516         client->CanBeEditedByUser(childNode)) {
517       NSString* childName = base::SysUTF16ToNSString(childNode->GetTitle());
518       NSMutableArray* children = [self addChildFoldersFromNode:childNode];
519       BookmarkFolderInfo* folderInfo =
520           [BookmarkFolderInfo bookmarkFolderInfoWithFolderName:childName
521                                                     folderNode:childNode
522                                                       children:children];
523       if (!childFolders)
524         childFolders = [NSMutableArray arrayWithObject:folderInfo];
525       else
526         [childFolders addObject:folderInfo];
527     }
528   }
529   return childFolders;
530 }
531
532 - (void)buildFolderTree {
533   // Build up a tree of the current folder configuration.
534   BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile_);
535   const BookmarkNode* rootNode = model->root_node();
536   NSMutableArray* baseArray = [self addChildFoldersFromNode:rootNode];
537   DCHECK(baseArray);
538   [self willChangeValueForKey:@"folderTreeArray"];
539   folderTreeArray_.reset([baseArray retain]);
540   [self didChangeValueForKey:@"folderTreeArray"];
541 }
542
543 - (void)modelChangedPreserveSelection:(BOOL)preserve {
544   if (creatingNewFolders_)
545     return;
546   const BookmarkNode* selectedNode = [self selectedNode];
547   [self buildFolderTree];
548   if (preserve &&
549       selectedNode &&
550       configuration_ == BookmarkEditor::SHOW_TREE)
551     [self selectNodeInBrowser:selectedNode];
552 }
553
554 - (void)nodeRemoved:(const BookmarkNode*)node
555          fromParent:(const BookmarkNode*)parent {
556   if (node->is_folder()) {
557     if (parentNode_ == node || parentNode_->HasAncestor(node)) {
558       parentNode_ = [self bookmarkModel]->bookmark_bar_node();
559       if (configuration_ != BookmarkEditor::SHOW_TREE) {
560         // The user can't select a different folder, so just close up shop.
561         [self cancel:self];
562         return;
563       }
564     }
565
566     if (configuration_ == BookmarkEditor::SHOW_TREE) {
567       // For safety's sake, in case deleted node was an ancestor of selection,
568       // go back to a known safe place.
569       [self selectNodeInBrowser:parentNode_];
570     }
571   }
572 }
573
574 #pragma mark New Folder Handler
575
576 - (void)createNewFoldersForFolder:(BookmarkFolderInfo*)folderInfo
577                selectedFolderInfo:(BookmarkFolderInfo*)selectedFolderInfo {
578   NSArray* subfolders = [folderInfo children];
579   const BookmarkNode* parentNode = [folderInfo folderNode];
580   DCHECK(parentNode);
581   NSUInteger i = 0;
582   for (BookmarkFolderInfo* subFolderInfo in subfolders) {
583     if ([subFolderInfo newFolder]) {
584       BookmarkModel* model = [self bookmarkModel];
585       const BookmarkNode* newFolder =
586           model->AddFolder(parentNode, i,
587               base::SysNSStringToUTF16([subFolderInfo folderName]));
588       // Update our dictionary with the actual folder node just created.
589       [subFolderInfo setFolderNode:newFolder];
590       [subFolderInfo setNewFolder:NO];
591     }
592     [self createNewFoldersForFolder:subFolderInfo
593                  selectedFolderInfo:selectedFolderInfo];
594     ++i;
595   }
596 }
597
598 - (IBAction)newFolder:(id)sender {
599   // Create a new folder off of the selected folder node.
600   BookmarkFolderInfo* parentInfo = [self selectedFolder];
601   if (parentInfo) {
602     NSIndexPath* selection = [self selectedIndexPath];
603     NSString* newFolderName =
604         l10n_util::GetNSStringWithFixup(IDS_BOOKMARK_EDITOR_NEW_FOLDER_NAME);
605     BookmarkFolderInfo* folderInfo =
606         [BookmarkFolderInfo bookmarkFolderInfoWithFolderName:newFolderName];
607     [self willChangeValueForKey:@"folderTreeArray"];
608     NSMutableArray* children = [parentInfo children];
609     if (children) {
610       [children addObject:folderInfo];
611     } else {
612       children = [NSMutableArray arrayWithObject:folderInfo];
613       [parentInfo setChildren:children];
614     }
615     [self didChangeValueForKey:@"folderTreeArray"];
616
617     // Expose the parent folder children.
618     [folderTreeView_ expandItem:parentInfo];
619
620     // Select the new folder node and put the folder name into edit mode.
621     selection = [selection indexPathByAddingIndex:[children count] - 1];
622     [self setTableSelectionPath:selection];
623     NSInteger row = [folderTreeView_ selectedRow];
624     DCHECK(row >= 0);
625
626     // Put the cell into single-line mode before putting it into edit mode.
627     // TODO(kushi.p): Remove this when the project hits a 10.6+ only state.
628     NSCell* folderCell = [folderTreeView_ preparedCellAtColumn:0 row:row];
629     if ([folderCell
630           respondsToSelector:@selector(setUsesSingleLineMode:)]) {
631       [folderCell setUsesSingleLineMode:YES];
632     }
633
634     [folderTreeView_ editColumn:0 row:row withEvent:nil select:YES];
635   }
636 }
637
638 - (void)createNewFolders {
639   base::AutoReset<BOOL> creatingNewFoldersSetter(&creatingNewFolders_, YES);
640   // Scan the tree looking for nodes marked 'newFolder' and create those nodes.
641   NSArray* folderTreeArray = [self folderTreeArray];
642   for (BookmarkFolderInfo *folderInfo in folderTreeArray) {
643     [self createNewFoldersForFolder:folderInfo
644                  selectedFolderInfo:[self selectedFolder]];
645   }
646 }
647
648 #pragma mark For Unit Test Use Only
649
650 - (BOOL)okButtonEnabled {
651   return [okButton_ isEnabled];
652 }
653
654 - (void)selectTestNodeInBrowser:(const BookmarkNode*)node {
655   [self selectNodeInBrowser:node];
656 }
657
658 @end  // BookmarkEditorBaseController
659
660 @implementation BookmarkFolderInfo
661
662 @synthesize folderName = folderName_;
663 @synthesize folderNode = folderNode_;
664 @synthesize children = children_;
665 @synthesize newFolder = newFolder_;
666
667 + (id)bookmarkFolderInfoWithFolderName:(NSString*)folderName
668                             folderNode:(const BookmarkNode*)folderNode
669                               children:(NSMutableArray*)children {
670   return [[[BookmarkFolderInfo alloc] initWithFolderName:folderName
671                                               folderNode:folderNode
672                                                 children:children
673                                                newFolder:NO]
674           autorelease];
675 }
676
677 + (id)bookmarkFolderInfoWithFolderName:(NSString*)folderName {
678   return [[[BookmarkFolderInfo alloc] initWithFolderName:folderName
679                                               folderNode:NULL
680                                                 children:nil
681                                                newFolder:YES]
682           autorelease];
683 }
684
685 - (id)initWithFolderName:(NSString*)folderName
686               folderNode:(const BookmarkNode*)folderNode
687                 children:(NSMutableArray*)children
688                newFolder:(BOOL)newFolder {
689   if ((self = [super init])) {
690     // A folderName is always required, and if newFolder is NO then there
691     // should be a folderNode.  Children is optional.
692     DCHECK(folderName && (newFolder || folderNode));
693     if (folderName && (newFolder || folderNode)) {
694       folderName_ = [folderName copy];
695       folderNode_ = folderNode;
696       children_ = [children retain];
697       newFolder_ = newFolder;
698     } else {
699       NOTREACHED();  // Invalid init.
700       [self release];
701       self = nil;
702     }
703   }
704   return self;
705 }
706
707 - (id)init {
708   NOTREACHED();  // Should never be called.
709   return [self initWithFolderName:nil folderNode:nil children:nil newFolder:NO];
710 }
711
712 - (void)dealloc {
713   [folderName_ release];
714   [children_ release];
715   [super dealloc];
716 }
717
718 // Implementing isEqual: allows the NSTreeController to preserve the selection
719 // and open/shut state of outline items when the data changes.
720 - (BOOL)isEqual:(id)other {
721   return [other isKindOfClass:[BookmarkFolderInfo class]] &&
722       folderNode_ == [(BookmarkFolderInfo*)other folderNode];
723 }
724
725 @end