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