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