summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/bookmarks/bookmark_model_unittest.cc47
-rw-r--r--chrome/browser/cocoa/bookmark_bar_controller.h19
-rw-r--r--chrome/browser/cocoa/bookmark_bar_controller.mm410
-rw-r--r--chrome/browser/cocoa/bookmark_bar_controller_unittest.mm235
-rw-r--r--chrome/browser/cocoa/bookmark_bar_folder_controller.h10
-rw-r--r--chrome/browser/cocoa/bookmark_bar_folder_controller.mm271
-rw-r--r--chrome/browser/cocoa/bookmark_bar_folder_controller_unittest.mm574
-rw-r--r--chrome/browser/cocoa/bookmark_bar_folder_view.mm5
-rw-r--r--chrome/browser/cocoa/bookmark_bar_folder_view_unittest.mm15
-rw-r--r--chrome/browser/cocoa/bookmark_bar_unittest_helper.h48
-rw-r--r--chrome/browser/cocoa/bookmark_bar_unittest_helper.mm67
-rw-r--r--chrome/browser/cocoa/bookmark_button.h30
-rw-r--r--chrome/browser/cocoa/bookmark_button.mm1
-rw-r--r--chrome/chrome_tests.gypi4
-rw-r--r--chrome/test/model_test_utils.cc82
-rw-r--r--chrome/test/model_test_utils.h40
16 files changed, 1638 insertions, 220 deletions
diff --git a/chrome/browser/bookmarks/bookmark_model_unittest.cc b/chrome/browser/bookmarks/bookmark_model_unittest.cc
index f1c9c93..9d85f1f 100644
--- a/chrome/browser/bookmarks/bookmark_model_unittest.cc
+++ b/chrome/browser/bookmarks/bookmark_model_unittest.cc
@@ -17,6 +17,7 @@
#include "chrome/common/chrome_paths.h"
#include "chrome/common/notification_registrar.h"
#include "chrome/common/notification_service.h"
+#include "chrome/test/model_test_utils.h"
#include "chrome/test/testing_profile.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -36,24 +37,6 @@ void SwapDateAdded(BookmarkNode* n1, BookmarkNode* n2) {
n2->set_date_added(tmp);
}
-// Helper function used to verify node arrangements.
-std::wstring ModelStringFromNode(const BookmarkNode* node) {
- // Since the children of the node are not available as a vector,
- // we'll just have to do it the hard way.
- int childCount = node->GetChildCount();
- std::wstring childString;
- for (int i = 0; i < childCount; ++i) {
- const BookmarkNode* child = node->GetChild(i);
- if (child->is_folder()) {
- childString += child->GetTitle() + L":[ " + ModelStringFromNode(child)
- + L"] ";
- } else {
- childString += child->GetTitle() + L" ";
- }
- }
- return childString;
-}
-
} // anonymous namespace
class BookmarkModelTest : public testing::Test, public BookmarkModelObserver {
@@ -326,61 +309,53 @@ TEST_F(BookmarkModelTest, Move) {
TEST_F(BookmarkModelTest, Copy) {
const BookmarkNode* root = model.GetBookmarkBarNode();
- model.AddURL(root, 0, L"a", GURL("http://a.com"));
- const BookmarkNode* node1 = model.AddGroup(root, 1, L"1");
- model.AddURL(node1, 0, L"b", GURL("http://b.com"));
- model.AddURL(node1, 1, L"c", GURL("http://c.com"));
- model.AddURL(root, 2, L"d", GURL("http://d.com"));
- const BookmarkNode* node2 = model.AddGroup(root, 3, L"2");
- model.AddURL(node2, 0, L"e", GURL("http://e.com"));
- model.AddURL(node2, 1, L"f", GURL("http://f.com"));
- model.AddURL(node2, 2, L"g", GURL("http://g.com"));
- model.AddURL(root, 4, L"h", GURL("http://d.com"));
+ static const std::wstring model_string(L"a 1:[ b c ] d 2:[ e f g ] h ");
+ model_test_utils::AddNodesFromModelString(model, root, model_string);
// Validate initial model.
- std::wstring actualModelString = ModelStringFromNode(root);
- EXPECT_EQ(L"a 1:[ b c ] d 2:[ e f g ] h ", ModelStringFromNode(root));
+ std::wstring actualModelString = model_test_utils::ModelStringFromNode(root);
+ EXPECT_EQ(model_string, actualModelString);
// Copy 'd' to be after '1:b': URL item from bar to folder.
const BookmarkNode* nodeToCopy = root->GetChild(2);
const BookmarkNode* destination = root->GetChild(1);
model.Copy(nodeToCopy, destination, 1);
- actualModelString = ModelStringFromNode(root);
+ actualModelString = model_test_utils::ModelStringFromNode(root);
EXPECT_EQ(L"a 1:[ b d c ] d 2:[ e f g ] h ", actualModelString);
// Copy '1:d' to be after 'a': URL item from folder to bar.
const BookmarkNode* group = root->GetChild(1);
nodeToCopy = group->GetChild(1);
model.Copy(nodeToCopy, root, 1);
- actualModelString = ModelStringFromNode(root);
+ actualModelString = model_test_utils::ModelStringFromNode(root);
EXPECT_EQ(L"a d 1:[ b d c ] d 2:[ e f g ] h ", actualModelString);
// Copy '1' to be after '2:e': Folder from bar to folder.
nodeToCopy = root->GetChild(2);
destination = root->GetChild(4);
model.Copy(nodeToCopy, destination, 1);
- actualModelString = ModelStringFromNode(root);
+ actualModelString = model_test_utils::ModelStringFromNode(root);
EXPECT_EQ(L"a d 1:[ b d c ] d 2:[ e 1:[ b d c ] f g ] h ", actualModelString);
// Copy '2:1' to be after '2:f': Folder within same folder.
group = root->GetChild(4);
nodeToCopy = group->GetChild(1);
model.Copy(nodeToCopy, group, 3);
- actualModelString = ModelStringFromNode(root);
+ actualModelString = model_test_utils::ModelStringFromNode(root);
EXPECT_EQ(L"a d 1:[ b d c ] d 2:[ e 1:[ b d c ] f 1:[ b d c ] g ] h ",
actualModelString);
// Copy first 'd' to be after 'h': URL item within the bar.
nodeToCopy = root->GetChild(1);
model.Copy(nodeToCopy, root, 6);
- actualModelString = ModelStringFromNode(root);
+ actualModelString = model_test_utils::ModelStringFromNode(root);
EXPECT_EQ(L"a d 1:[ b d c ] d 2:[ e 1:[ b d c ] f 1:[ b d c ] g ] h d ",
actualModelString);
// Copy '2' to be after 'a': Folder within the bar.
nodeToCopy = root->GetChild(4);
model.Copy(nodeToCopy, root, 1);
- actualModelString = ModelStringFromNode(root);
+ actualModelString = model_test_utils::ModelStringFromNode(root);
EXPECT_EQ(L"a 2:[ e 1:[ b d c ] f 1:[ b d c ] g ] d 1:[ b d c ] "
L"d 2:[ e 1:[ b d c ] f 1:[ b d c ] g ] h d ",
actualModelString);
diff --git a/chrome/browser/cocoa/bookmark_bar_controller.h b/chrome/browser/cocoa/bookmark_bar_controller.h
index d9183b4..29952b8 100644
--- a/chrome/browser/cocoa/bookmark_bar_controller.h
+++ b/chrome/browser/cocoa/bookmark_bar_controller.h
@@ -44,13 +44,17 @@ const CGFloat kDefaultBookmarkWidth = 150.0;
// TODO(jrg): http://crbug.com/36276 to get final sizes.
// Used as a min/max width for buttons on menus (not on the bar).
-const CGFloat kBookmarkMenuButtonMinimumWidth = kDefaultBookmarkWidth;
+const CGFloat kBookmarkMenuButtonMinimumWidth = 100.0;
const CGFloat kBookmarkMenuButtonMaximumWidth = 485.0;
const CGFloat kBookmarkVerticalPadding = 2.0;
const CGFloat kBookmarkHorizontalPadding = 1.0;
// (end magic numbers from Cole)
+// Make subfolder menus overlap their parent menu a bit to give a better
+// perception of a menuing system.
+const CGFloat kBookmarkMenuOverlap = 1.0;
+
// Delay before opening a subfolder (and closing the previous one)
// when hovering over a folder button.
const NSTimeInterval kHoverOpenDelay = 0.3;
@@ -208,11 +212,7 @@ willAnimateFromState:(bookmarks::VisualState)oldState
// Since we create everything before doing layout we can't be sure
// that all bookmark buttons we create will be visible. Thus,
// [buttons_ count] isn't a definitive check.
- int bookmarkBarDisplayedButtons_;
-
- // We only build the off-the-side menu on demand.
- // We flag a need to rebuild with this variable.
- BOOL needToRebuildOffTheSideMenu_;
+ int displayedButtonCount_;
}
@property(readonly, nonatomic) bookmarks::VisualState visualState;
@@ -264,9 +264,6 @@ willAnimateFromState:(bookmarks::VisualState)oldState
// Provide a favIcon for a bookmark node. May return nil.
- (NSImage*)favIconForNode:(const BookmarkNode*)node;
-// Provide a contextual menu for a bookmark node. May return nil.
-- (NSMenu*)contextMenuForNode:(const BookmarkNode*)node;
-
// Actions for manipulating bookmarks.
// Open a normal bookmark or folder from a button, ...
- (IBAction)openBookmark:(id)sender;
@@ -318,7 +315,7 @@ willAnimateFromState:(bookmarks::VisualState)oldState
- (NSCell*)cellForBookmarkNode:(const BookmarkNode*)node;
- (void)clearBookmarkBar;
- (BookmarkBarView*)buttonView;
-- (NSArray*)buttons;
+- (NSMutableArray*)buttons;
- (NSRect)frameForBookmarkButtonFromCell:(NSCell*)cell xOffset:(int*)xOffset;
- (void)checkForBookmarkButtonGrowth:(NSButton*)button;
- (void)frameDidChange;
@@ -336,6 +333,8 @@ willAnimateFromState:(bookmarks::VisualState)oldState
- (BookmarkButton*)buttonForDroppingOnAtPoint:(NSPoint)point;
- (BOOL)isEventAnExitEvent:(NSEvent*)event;
- (id)folderTarget;
+- (int)displayedButtonCount;
+
@end
// The (internal) |NSPasteboard| type string for bookmark button drags, used for
diff --git a/chrome/browser/cocoa/bookmark_bar_controller.mm b/chrome/browser/cocoa/bookmark_bar_controller.mm
index 1ebe272..809cba4 100644
--- a/chrome/browser/cocoa/bookmark_bar_controller.mm
+++ b/chrome/browser/cocoa/bookmark_bar_controller.mm
@@ -138,6 +138,11 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
// Modifies self->buttons_. Do not add more buttons than will fit on the view.
- (void)addNodesToButtonList:(const BookmarkNode*)node;
+// Create an autoreleased button appropriate for insertion into the bookmark
+// bar. Update |xOffset| with the offset appropriate for the subsequent button.
+- (BookmarkButton*)buttonForNode:(const BookmarkNode*)node
+ xOffset:(int*)xOffset;
+
// Puts stuff into the final visual state without animating, stopping a running
// animation if necessary.
- (void)finalizeVisualState;
@@ -161,6 +166,14 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
- (int)indexForDragOfButton:(BookmarkButton*)sourceButton
toPoint:(NSPoint)point;
+// Add or remove buttons to/from the bar until it is filled but not overflowed.
+- (void)redistributeButtonsOnBarAsNeeded;
+
+// Determine the nature of the bookmark bar contents based on the number of
+// buttons showing. If too many then show the off-the-side list, if none
+// then show the no items label.
+- (void)reconfigureBookmarkBar;
+
- (void)addNode:(const BookmarkNode*)child toMenu:(NSMenu*)menu;
- (void)addFolderNode:(const BookmarkNode*)node toMenu:(NSMenu*)menu;
- (void)tagEmptyMenu:(NSMenu*)menu;
@@ -368,11 +381,11 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
// Configure the off-the-side button (e.g. specify the node range,
// check if we should enable or disable it, etc).
- (void)configureOffTheSideButtonContentsAndVisibility {
- [[offTheSideButton_ cell] setStartingChildIndex:bookmarkBarDisplayedButtons_];
+ [[offTheSideButton_ cell] setStartingChildIndex:displayedButtonCount_];
[[offTheSideButton_ cell]
setBookmarkNode:bookmarkModel_->GetBookmarkBarNode()];
int bookmarkChildren = bookmarkModel_->GetBookmarkBarNode()->GetChildCount();
- if (bookmarkChildren > bookmarkBarDisplayedButtons_) {
+ if (bookmarkChildren > displayedButtonCount_) {
[offTheSideButton_ setHidden:NO];
} else {
[offTheSideButton_ setHidden:YES];
@@ -387,18 +400,8 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
- (void)frameDidChange {
if (!bookmarkModel_->IsLoaded())
return;
- CGFloat width = NSWidth([[self view] frame]);
- if (width > savedFrameWidth_) {
- savedFrameWidth_ = width;
- [self clearBookmarkBar];
- [self addNodesToButtonList:bookmarkModel_->GetBookmarkBarNode()];
- [self updateTheme:[[[self view] window] themeProvider]];
- [self addButtonsToView];
- }
- [self positionOffTheSideButton];
- [self addButtonsToView];
- [self configureOffTheSideButtonContentsAndVisibility];
- [self centerNoItemsLabel];
+ [self updateTheme:[[[self view] window] themeProvider]];
+ [self reconfigureBookmarkBar];
}
// Close all bookmark folders. "Folder" here is the fake menu for
@@ -449,6 +452,11 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
// Else open a new one if it makes sense to do so.
if ([sender bookmarkNode]->is_folder())
[folderTarget_ openBookmarkFolderFromButton:sender];
+#if 1
+ else
+ // We're over a non-folder bookmark so close any old folders.
+ [self closeAllBookmarkFolders];
+#endif
}
// BookmarkButtonDelegate protocol implementation.
@@ -528,6 +536,9 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
return folderTarget_.get();
}
+- (int)displayedButtonCount {
+ return displayedButtonCount_;
+}
// Keep the "no items" label centered in response to a frame size change.
- (void)centerNoItemsLabel {
@@ -742,11 +753,11 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
DCHECK(afterNode);
int index = afterNode->GetParent()->IndexOfChild(afterNode);
// Make sure we don't get confused by buttons which aren't visible.
- return std::min(index, bookmarkBarDisplayedButtons_);
+ return std::min(index, displayedButtonCount_);
}
// If nothing is to my right I am at the end!
- return bookmarkBarDisplayedButtons_;
+ return displayedButtonCount_;
}
- (BOOL)dragButton:(BookmarkButton*)sourceButton
@@ -776,11 +787,10 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
// Be sure we don't try and drop a folder into itself.
if (sourceNode != destParent) {
- if (copy) {
+ if (copy)
bookmarkModel_->Copy(sourceNode, destParent, destIndex);
- } else {
+ else
bookmarkModel_->Move(sourceNode, destParent, destIndex);
- }
}
[self closeAllBookmarkFolders]; // For a hover open, if needed.
@@ -862,6 +872,21 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)info {
NSPoint point = [info draggingLocation];
BookmarkButton* button = [self buttonForDroppingOnAtPoint:point];
+
+ // Don't allow drops that would result in cycles.
+ if (button) {
+ NSData* data = [[info draggingPasteboard]
+ dataForType:kBookmarkButtonDragType];
+ if (data && [info draggingSource]) {
+ BookmarkButton* sourceButton = nil;
+ [data getBytes:&sourceButton length:sizeof(sourceButton)];
+ const BookmarkNode* sourceNode = [sourceButton bookmarkNode];
+ const BookmarkNode* destNode = [button bookmarkNode];
+ if (destNode->HasAncestor(sourceNode))
+ button = nil;
+ }
+ }
+
if ([button isFolder]) {
if (hoverButton_ == button) {
return NSDragOperationMove; // already open or timed to open
@@ -916,7 +941,7 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
toPoint:(NSPoint)point {
CGFloat x = 0;
int destIndex = [self indexForDragOfButton:sourceButton toPoint:point];
- int numButtons = bookmarkBarDisplayedButtons_;
+ int numButtons = displayedButtonCount_;
// If it's a drop strictly between existing buttons ...
if (destIndex >= 0 && destIndex < numButtons) {
@@ -1121,7 +1146,7 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
- (IBAction)openOffTheSideFolderFromButton:(id)sender {
DCHECK([sender isKindOfClass:[BookmarkButton class]]);
DCHECK([[sender cell] isKindOfClass:[BookmarkButtonCell class]]);
- [[sender cell] setStartingChildIndex:bookmarkBarDisplayedButtons_];
+ [[sender cell] setStartingChildIndex:displayedButtonCount_];
[folderTarget_ openBookmarkFolderFromButton:sender];
}
@@ -1407,8 +1432,7 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
[buttons_ makeObjectsPerformSelector:@selector(removeFromSuperview)];
[buttons_ removeAllObjects];
[self clearMenuTagMap];
- needToRebuildOffTheSideMenu_ = YES;
- bookmarkBarDisplayedButtons_ = 0;
+ displayedButtonCount_ = 0;
// Make sure there are no stale pointers in the pasteboard. This
// can be important if a bookmark is deleted (via bookmark sync)
@@ -1428,7 +1452,8 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
// TODO(jrg): move much of the cell config into the BookmarkButtonCell class.
- (NSCell*)cellForBookmarkNode:(const BookmarkNode*)node {
NSImage* image = node ? [self favIconForNode:node] : nil;
- NSMenu* menu = [self contextMenuForNode:node];
+ NSMenu* menu = node && node->is_folder() ? buttonFolderContextMenu_ :
+ buttonContextMenu_;
BookmarkButtonCell* cell = [BookmarkButtonCell buttonCellForNode:node
contextMenu:menu
cellText:nil
@@ -1440,11 +1465,6 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
return cell;
}
-- (NSMenu*)contextMenuForNode:(const BookmarkNode*)node {
- return node && node->is_folder() ? buttonFolderContextMenu_ :
- buttonContextMenu_;
-}
-
// Return an appropriate width for the given bookmark button cell.
// The "+2" is needed because, sometimes, Cocoa is off by a tad.
// Example: for a bookmark named "Moma" or "SFGate", it is one pixel
@@ -1461,12 +1481,8 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
// for the next button.
- (NSRect)frameForBookmarkButtonFromCell:(NSCell*)cell
xOffset:(int*)xOffset {
+ DCHECK(xOffset);
NSRect bounds = [buttonView_ bounds];
- // TODO(erg,jrg): There used to be an if statement here, comparing the height
- // to 0. This essentially broke sizing because we're dealing with floats and
- // the height wasn't precisely zero. The previous author wrote that they were
- // doing this because of an animator, but we are not doing animations for beta
- // so we do not care.
bounds.size.height = bookmarks::kBookmarkButtonHeight;
NSRect frame = NSInsetRect(bounds,
@@ -1525,42 +1541,45 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
int xOffset = 0;
for (int i = 0; i < node->GetChildCount(); i++) {
const BookmarkNode* child = node->GetChild(i);
+ BookmarkButton* button = [self buttonForNode:child xOffset:&xOffset];
+ if (NSMinX([button frame]) >= maxViewX)
+ break;
+ [buttons_ addObject:button];
+ }
+}
- NSCell* cell = [self cellForBookmarkNode:child];
- NSRect frame = [self frameForBookmarkButtonFromCell:cell xOffset:&xOffset];
+- (BookmarkButton*)buttonForNode:(const BookmarkNode*)node
+ xOffset:(int*)xOffset {
+ NSCell* cell = [self cellForBookmarkNode:node];
+ NSRect frame = [self frameForBookmarkButtonFromCell:cell xOffset:xOffset];
- // Break early if the button is off the end of the parent view.
- if (NSMinX(frame) >= maxViewX)
- break;
+ scoped_nsobject<BookmarkButton>
+ button([[BookmarkButton alloc] initWithFrame:frame]);
+ DCHECK(button.get());
- scoped_nsobject<BookmarkButton>
- button([[BookmarkButton alloc] initWithFrame:frame]);
- DCHECK(button.get());
- [buttons_ addObject:button];
+ // [NSButton setCell:] warns to NOT use setCell: other than in the
+ // initializer of a control. However, we are using a basic
+ // NSButton whose initializer does not take an NSCell as an
+ // object. To honor the assumed semantics, we do nothing with
+ // NSButton between alloc/init and setCell:.
+ [button setCell:cell];
+ [button setDelegate:self];
- // [NSButton setCell:] warns to NOT use setCell: other than in the
- // initializer of a control. However, we are using a basic
- // NSButton whose initializer does not take an NSCell as an
- // object. To honor the assumed semantics, we do nothing with
- // NSButton between alloc/init and setCell:.
- [button setCell:cell];
- [button setDelegate:self];
-
- if (child->is_folder()) {
- [button setTarget:self];
- [button setAction:@selector(openBookmarkFolderFromButton:)];
- } else {
- // Make the button do something
- [button setTarget:self];
- [button setAction:@selector(openBookmark:)];
- // Add a tooltip.
- NSString* title = base::SysWideToNSString(child->GetTitle());
- std::string url_string = child->GetURL().possibly_invalid_spec();
- NSString* tooltip = [NSString stringWithFormat:@"%@\n%s", title,
- url_string.c_str()];
- [button setToolTip:tooltip];
- }
+ if (node->is_folder()) {
+ [button setTarget:self];
+ [button setAction:@selector(openBookmarkFolderFromButton:)];
+ } else {
+ // Make the button do something
+ [button setTarget:self];
+ [button setAction:@selector(openBookmark:)];
+ // Add a tooltip.
+ NSString* title = base::SysWideToNSString(node->GetTitle());
+ std::string url_string = node->GetURL().possibly_invalid_spec();
+ NSString* tooltip = [NSString stringWithFormat:@"%@\n%s", title,
+ url_string.c_str()];
+ [button setToolTip:tooltip];
}
+ return [[button.get() retain] autorelease];
}
// Add non-bookmark buttons to the view. This includes the chevron
@@ -1577,21 +1596,23 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
// Add bookmark buttons to the view only if they are completely
// visible and don't overlap the "other bookmarks". Remove buttons
-// which are clipped. Called when building the bookmark bar and when
-// the window resizes.
+// which are clipped. Called when building the bookmark bar the first time.
- (void)addButtonsToView {
- NSView* superview = nil;
- bookmarkBarDisplayedButtons_ = 0;
- for (NSButton* button in buttons_.get()) {
- superview = [button superview];
- if (NSMaxX([button frame]) <= NSMinX([offTheSideButton_ frame])) {
- if (!superview)
- [buttonView_ addSubview:button];
- bookmarkBarDisplayedButtons_++;
- } else {
- if (superview)
- [button removeFromSuperview];
- }
+ displayedButtonCount_ = 0;
+ NSMutableArray* buttons = [self buttons];
+ for (NSButton* button in buttons) {
+ if (NSMaxX([button frame]) > (NSMinX([offTheSideButton_ frame]) -
+ bookmarks::kBookmarkHorizontalPadding))
+ break;
+ [buttonView_ addSubview:button];
+ ++displayedButtonCount_;
+ }
+ NSUInteger removalCount =
+ [buttons count] - (NSUInteger)displayedButtonCount_;
+ if (removalCount > 0) {
+ NSRange removalRange =
+ NSMakeRange(displayedButtonCount_, removalCount);
+ [buttons removeObjectsInRange:removalRange];
}
}
@@ -1668,23 +1689,37 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
// http://crbug.com/38665
}
-// TODO(jrg): for now this is brute force.
- (void)nodeMoved:(BookmarkModel*)model
oldParent:(const BookmarkNode*)oldParent oldIndex:(int)oldIndex
newParent:(const BookmarkNode*)newParent newIndex:(int)newIndex {
- [self loaded:model];
+ const BookmarkNode* movedNode = newParent->GetChild(newIndex);
+ id<BookmarkButtonControllerProtocol> oldController =
+ [self controllerForNode:oldParent];
+ id<BookmarkButtonControllerProtocol> newController =
+ [self controllerForNode:newParent];
+ if (newController == oldController) {
+ [oldController moveButtonFromIndex:oldIndex toIndex:newIndex];
+ } else {
+ [oldController removeButton:oldIndex animate:NO];
+ [newController addButtonForNode:movedNode atIndex:newIndex];
+ }
}
-// TODO(jrg): for now this is brute force.
- (void)nodeAdded:(BookmarkModel*)model
- parent:(const BookmarkNode*)oldParent index:(int)index {
- [self loaded:model];
+ parent:(const BookmarkNode*)newParent index:(int)newIndex {
+ const BookmarkNode* newNode = newParent->GetChild(newIndex);
+ id<BookmarkButtonControllerProtocol> newController =
+ [self controllerForNode:newParent];
+ [newController addButtonForNode:newNode atIndex:newIndex];
}
-// TODO(jrg): for now this is brute force.
- (void)nodeRemoved:(BookmarkModel*)model
parent:(const BookmarkNode*)oldParent index:(int)index {
- [self loaded:model];
+ // Locate the parent node. The parent may not be showing, in which case
+ // we do nothing.
+ id<BookmarkButtonControllerProtocol> parentController =
+ [self controllerForNode:oldParent];
+ [parentController removeButton:index animate:YES];
}
// TODO(jrg): for now this is brute force.
@@ -1722,7 +1757,7 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
[self loaded:model];
}
-- (NSArray*)buttons {
+- (NSMutableArray*)buttons {
return buttons_.get();
}
@@ -1916,4 +1951,205 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
[[self folderTarget] fillPasteboard:pboard forDragOfButton:button];
}
+- (id<BookmarkButtonControllerProtocol>)controllerForNode:
+ (const BookmarkNode*)node {
+ // See if it's in the bar, then if it is in the hierarchy of visible
+ // folder menus.
+ if (bookmarkModel_->GetBookmarkBarNode() == node)
+ return self;
+ return [folderController_ controllerForNode:node];
+}
+
+- (void)reconfigureBookmarkBar {
+ [self redistributeButtonsOnBarAsNeeded];
+ [self positionOffTheSideButton];
+ [self configureOffTheSideButtonContentsAndVisibility];
+ [self centerNoItemsLabel];
+}
+
+- (void)redistributeButtonsOnBarAsNeeded {
+ const BookmarkNode* node = bookmarkModel_->GetBookmarkBarNode();
+ NSInteger barCount = node->GetChildCount();
+
+ // Determine the current maximum extent of the visible buttons.
+ CGFloat maxViewX = NSMaxX([[self view] bounds]);
+ NSButton* otherBookmarksButton = otherBookmarksButton_.get();
+ // If necessary, pull in the width to account for the Other Bookmarks button.
+ if (otherBookmarksButton_)
+ maxViewX = [otherBookmarksButton frame].origin.x -
+ bookmarks::kBookmarkHorizontalPadding;
+ // If we're already overflowing, then we need to account for the chevron.
+ if (barCount > displayedButtonCount_)
+ maxViewX = [offTheSideButton_ frame].origin.x -
+ bookmarks::kBookmarkHorizontalPadding;
+
+ // As a result of pasting or dragging, the bar may now have more buttons
+ // than will fit so remove any which overflow. They will be shown in
+ // the off-the-side folder.
+ while (displayedButtonCount_ > 0) {
+ BookmarkButton* button = [buttons_ lastObject];
+ if (NSMaxX([button frame]) < maxViewX)
+ break;
+ [buttons_ removeLastObject];
+ [button removeFromSuperview];
+ --displayedButtonCount_;
+ }
+
+ // As a result of cutting, deleting and dragging, the bar may now have room
+ // for more buttons.
+ int xOffset = displayedButtonCount_ > 0 ?
+ NSMaxX([[buttons_ lastObject] frame]) +
+ bookmarks::kBookmarkHorizontalPadding : 0;
+ for (int i = displayedButtonCount_; i < barCount; ++i) {
+ const BookmarkNode* child = node->GetChild(i);
+ BookmarkButton* button = [self buttonForNode:child xOffset:&xOffset];
+ // If we're testing against the last possible button then account
+ // for the chevron no longer needing to be shown.
+ if (i == barCount + 1)
+ maxViewX += NSWidth([offTheSideButton_ frame]) +
+ bookmarks::kBookmarkHorizontalPadding;
+ if (NSMaxX([button frame]) >= maxViewX)
+ break;
+ ++displayedButtonCount_;
+ [buttons_ addObject:button];
+ [buttonView_ addSubview:button];
+ }
+}
+
+#pragma mark BookmarkButtonControllerProtocol
+
+- (void)addButtonForNode:(const BookmarkNode*)node
+ atIndex:(NSInteger)buttonIndex {
+ int newOffset = 0;
+ if (buttonIndex == -1)
+ buttonIndex = [buttons_ count]; // New button goes at the end.
+ if (buttonIndex <= (NSInteger)[buttons_ count]) {
+ if (buttonIndex) {
+ BookmarkButton* targetButton = [buttons_ objectAtIndex:buttonIndex - 1];
+ NSRect targetFrame = [targetButton frame];
+ newOffset = targetFrame.origin.x + NSWidth(targetFrame) +
+ bookmarks::kBookmarkHorizontalPadding;
+ }
+ BookmarkButton* newButton = [self buttonForNode:node xOffset:&newOffset];
+ CGFloat xOffset =
+ NSWidth([newButton frame]) + bookmarks::kBookmarkHorizontalPadding;
+ NSUInteger buttonCount = [buttons_ count];
+ for (NSUInteger i = buttonIndex; i < buttonCount; ++i) {
+ BookmarkButton* button = [buttons_ objectAtIndex:i];
+ NSPoint buttonOrigin = [button frame].origin;
+ buttonOrigin.x += xOffset;
+ [button setFrameOrigin:buttonOrigin];
+ }
+ ++displayedButtonCount_;
+ [buttons_ insertObject:newButton atIndex:buttonIndex];
+ [buttonView_ addSubview:newButton];
+
+ // See if any buttons need to be pushed off to or brought in from the side.
+ [self reconfigureBookmarkBar];
+ } else {
+ // A button from somewhere else (not the bar) is being moved to the
+ // off-the-side so insure it gets redrawn if its showing.
+ [self reconfigureBookmarkBar];
+ [folderController_ reconfigureMenu];
+ }
+}
+
+// TODO(mrossetti): jrg wants this broken up into smaller functions.
+- (void)moveButtonFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex {
+ if (fromIndex != toIndex) {
+ NSInteger buttonCount = (NSInteger)[buttons_ count];
+ if (toIndex == -1)
+ toIndex = buttonCount;
+ // See if we have a simple move within the bar, which will be the case if
+ // both button indexes are in the visible space.
+ if (fromIndex < buttonCount && toIndex < buttonCount) {
+ BookmarkButton* movedButton = [buttons_ objectAtIndex:fromIndex];
+ NSRect movedFrame = [movedButton frame];
+ NSPoint toOrigin = movedFrame.origin;
+ CGFloat xOffset =
+ NSWidth(movedFrame) + bookmarks::kBookmarkHorizontalPadding;
+ // Hide the button to reduce flickering while drawing the window.
+ [movedButton setHidden:YES];
+ [buttons_ removeObjectAtIndex:fromIndex];
+ if (fromIndex < toIndex) {
+ // Move the button from left to right within the bar.
+ BookmarkButton* targetButton = [buttons_ objectAtIndex:toIndex - 1];
+ NSRect toFrame = [targetButton frame];
+ toOrigin.x = toFrame.origin.x - NSWidth(movedFrame) + NSWidth(toFrame);
+ for (NSInteger i = fromIndex; i < toIndex; ++i) {
+ BookmarkButton* button = [buttons_ objectAtIndex:i];
+ NSRect frame = [button frame];
+ frame.origin.x -= xOffset;
+ [button setFrameOrigin:frame.origin];
+ }
+ } else {
+ // Move the button from right to left within the bar.
+ BookmarkButton* targetButton = [buttons_ objectAtIndex:toIndex];
+ toOrigin = [targetButton frame].origin;
+ for (NSInteger i = fromIndex - 1; i >= toIndex; --i) {
+ BookmarkButton* button = [buttons_ objectAtIndex:i];
+ NSRect buttonFrame = [button frame];
+ buttonFrame.origin.x += xOffset;
+ [button setFrameOrigin:buttonFrame.origin];
+ }
+ }
+ [buttons_ insertObject:movedButton atIndex:toIndex];
+ [movedButton setFrameOrigin:toOrigin];
+ [movedButton setHidden:NO];
+ } else if (fromIndex < buttonCount) {
+ // A button is being removed from the bar and added to off-the-side.
+ // By now the node has already been inserted into the model so the
+ // button to be added is represented by |toIndex|. Things get
+ // complicated because the off-the-side is showing and must be redrawn
+ // while possibly re-laying out the bookmark bar.
+ [self removeButton:fromIndex animate:NO];
+ [self reconfigureBookmarkBar];
+ [folderController_ reconfigureMenu];
+ } else if (toIndex < buttonCount) {
+ // A button is being added to the bar and removed from off-the-side.
+ // By now the node has already been inserted into the model so the
+ // button to be added is represented by |toIndex|.
+ const BookmarkNode* node = bookmarkModel_->GetBookmarkBarNode();
+ const BookmarkNode* movedNode = node->GetChild(toIndex);
+ DCHECK(movedNode);
+ [self addButtonForNode:movedNode atIndex:toIndex];
+ [self reconfigureBookmarkBar];
+ } else {
+ // A button is being moved within the off-the-side.
+ fromIndex -= buttonCount;
+ toIndex -= buttonCount;
+ [folderController_ moveButtonFromIndex:fromIndex toIndex:toIndex];
+ }
+ }
+}
+
+- (void)removeButton:(NSInteger)buttonIndex animate:(BOOL)animate {
+ if (buttonIndex < (NSInteger)[buttons_ count]) {
+ BookmarkButton* oldButton = [buttons_ objectAtIndex:buttonIndex];
+ NSRect poofFrame = [oldButton bounds];
+ NSPoint poofPoint = NSMakePoint(NSMidX(poofFrame), NSMidY(poofFrame));
+ poofPoint = [oldButton convertPoint:poofPoint toView:nil];
+ poofPoint = [[oldButton window] convertBaseToScreen:poofPoint];
+ NSRect oldFrame = [oldButton frame];
+ [oldButton removeFromSuperview];
+ if (animate)
+ NSShowAnimationEffect(NSAnimationEffectDisappearingItemDefault, poofPoint,
+ NSZeroSize, nil, nil, nil);
+ CGFloat xOffset = NSWidth(oldFrame) + bookmarks::kBookmarkHorizontalPadding;
+ [buttons_ removeObjectAtIndex:buttonIndex];
+ NSUInteger buttonCount = [buttons_ count];
+ for (NSUInteger i = buttonIndex; i < buttonCount; ++i) {
+ BookmarkButton* button = [buttons_ objectAtIndex:i];
+ NSRect buttonFrame = [button frame];
+ buttonFrame.origin.x -= xOffset;
+ [button setFrame:buttonFrame];
+ // If this button is showing its menu then we need to move the menu, too.
+ if (button == [folderController_ parentButton])
+ [folderController_ offsetFolderMenuWindow:NSMakeSize(xOffset, 0.0)];
+ }
+ --displayedButtonCount_;
+ [self reconfigureBookmarkBar];
+ }
+}
+
@end
diff --git a/chrome/browser/cocoa/bookmark_bar_controller_unittest.mm b/chrome/browser/cocoa/bookmark_bar_controller_unittest.mm
index 8bf9a27..996e677 100644
--- a/chrome/browser/cocoa/bookmark_bar_controller_unittest.mm
+++ b/chrome/browser/cocoa/bookmark_bar_controller_unittest.mm
@@ -11,6 +11,7 @@
#import "chrome/browser/cocoa/bookmark_bar_constants.h"
#import "chrome/browser/cocoa/bookmark_bar_controller.h"
#import "chrome/browser/cocoa/bookmark_bar_folder_window.h"
+#import "chrome/browser/cocoa/bookmark_bar_unittest_helper.h"
#import "chrome/browser/cocoa/bookmark_bar_view.h"
#import "chrome/browser/cocoa/bookmark_button.h"
#import "chrome/browser/cocoa/bookmark_button_cell.h"
@@ -20,6 +21,7 @@
#include "chrome/browser/cocoa/test_event_utils.h"
#import "chrome/browser/cocoa/view_resizer_pong.h"
#include "chrome/common/pref_names.h"
+#include "chrome/test/model_test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
@@ -171,53 +173,75 @@ class FakeTheme : public ThemeProvider {
namespace {
-static const int kContentAreaHeight = 500;
-static const int kInfoBarViewHeight = 30;
-
-class BookmarkBarControllerTest : public CocoaTest {
+class BookmarkBarControllerTestBase : public CocoaTest {
public:
- BookmarkBarControllerTest() {
+ BrowserTestHelper helper_;
+ scoped_nsobject<NSView> parent_view_;
+ scoped_nsobject<ViewResizerPong> resizeDelegate_;
+ scoped_nsobject<BookmarkBarController> bar_;
+
+ BookmarkBarControllerTestBase() {
resizeDelegate_.reset([[ViewResizerPong alloc] init]);
NSRect parent_frame = NSMakeRect(0, 0, 800, 50);
parent_view_.reset([[NSView alloc] initWithFrame:parent_frame]);
[parent_view_ setHidden:YES];
- bar_.reset(
- [[BookmarkBarControllerNoOpen alloc]
- initWithBrowser:helper_.browser()
- initialWidth:NSWidth(parent_frame)
- delegate:nil
- resizeDelegate:resizeDelegate_.get()]);
-
+ bar_.reset([[BookmarkBarController alloc]
+ initWithBrowser:helper_.browser()
+ initialWidth:NSWidth(parent_frame)
+ delegate:nil
+ resizeDelegate:resizeDelegate_.get()]);
InstallAndToggleBar(bar_.get());
-
- // Create a menu/item to act like a sender
- menu_.reset([[BookmarkMenu alloc] initWithTitle:@"I_dont_care"]);
- menu_item_.reset([[NSMenuItem alloc]
- initWithTitle:@"still_dont_care"
- action:NULL
- keyEquivalent:@""]);
- cell_.reset([[NSButtonCell alloc] init]);
- [menu_item_ setMenu:menu_.get()];
- [menu_ setDelegate:cell_.get()];
}
void InstallAndToggleBar(BookmarkBarController* bar) {
// Force loading of the nib.
[bar view];
// Awkwardness to look like we've been installed.
+ for (NSView* subView in [parent_view_ subviews])
+ [subView removeFromSuperview];
[parent_view_ addSubview:[bar view]];
NSRect frame = [[[bar view] superview] frame];
frame.origin.y = 100;
[[[bar view] superview] setFrame:frame];
// Make sure it's on in a window so viewDidMoveToWindow is called
- [[test_window() contentView] addSubview:parent_view_];
+ NSView* contentView = [test_window() contentView];
+ if (![parent_view_ isDescendantOf:contentView])
+ [contentView addSubview:parent_view_];
// Make sure it's open so certain things aren't no-ops.
[bar updateAndShowNormalBar:YES
showDetachedBar:NO
withAnimation:NO];
}
+};
+
+class BookmarkBarControllerTest : public BookmarkBarControllerTestBase {
+ public:
+ scoped_nsobject<BookmarkMenu> menu_;
+ scoped_nsobject<NSMenuItem> menu_item_;
+ scoped_nsobject<NSButtonCell> cell_;
+
+ BookmarkBarControllerTest() {
+ bar_.reset(
+ [[BookmarkBarControllerNoOpen alloc]
+ initWithBrowser:helper_.browser()
+ initialWidth:NSWidth([parent_view_ frame])
+ delegate:nil
+ resizeDelegate:resizeDelegate_.get()]);
+
+ InstallAndToggleBar(bar_.get());
+
+ // Create a menu/item to act like a sender
+ menu_.reset([[BookmarkMenu alloc] initWithTitle:@"I_dont_care"]);
+ menu_item_.reset([[NSMenuItem alloc]
+ initWithTitle:@"still_dont_care"
+ action:NULL
+ keyEquivalent:@""]);
+ cell_.reset([[NSButtonCell alloc] init]);
+ [menu_item_ setMenu:menu_.get()];
+ [menu_ setDelegate:cell_.get()];
+ }
// Return a menu item that points to the given URL.
NSMenuItem* ItemForBookmarkBarMenu(GURL& gurl) {
@@ -235,13 +259,9 @@ class BookmarkBarControllerTest : public CocoaTest {
return menu_item_;
}
- BrowserTestHelper helper_;
- scoped_nsobject<NSView> parent_view_;
- scoped_nsobject<ViewResizerPong> resizeDelegate_;
- scoped_nsobject<BookmarkBarControllerNoOpen> bar_;
- scoped_nsobject<BookmarkMenu> menu_;
- scoped_nsobject<NSMenuItem> menu_item_;
- scoped_nsobject<NSButtonCell> cell_;
+ BookmarkBarControllerNoOpen* noOpenBar() {
+ return (BookmarkBarControllerNoOpen*)bar_.get();
+ }
};
TEST_F(BookmarkBarControllerTest, ShowWhenShowBookmarkBarTrue) {
@@ -422,8 +442,8 @@ TEST_F(BookmarkBarControllerTest, OffTheSideButtonHidden) {
EXPECT_TRUE([bar_ offTheSideButtonIsHidden]);
}
+ const BookmarkNode* parent = model->GetBookmarkBarNode();
for (int i = 0; i < 20; i++) {
- const BookmarkNode* parent = model->GetBookmarkBarNode();
model->AddURL(parent, parent->GetChildCount(),
L"super duper wide title",
GURL("http://superfriends.hall-of-justice.edu"));
@@ -534,8 +554,8 @@ TEST_F(BookmarkBarControllerTest, OpenBookmark) {
[cell setRepresentedObject:[NSValue valueWithPointer:node.get()]];
[bar_ openBookmark:button];
- EXPECT_EQ(bar_.get()->urls_[0], node->GetURL());
- EXPECT_EQ(bar_.get()->dispositions_[0], CURRENT_TAB);
+ EXPECT_EQ(noOpenBar()->urls_[0], node->GetURL());
+ EXPECT_EQ(noOpenBar()->dispositions_[0], CURRENT_TAB);
}
// Confirm opening of bookmarks works from the menus (different
@@ -556,8 +576,8 @@ TEST_F(BookmarkBarControllerTest, OpenBookmarkFromMenus) {
GURL gurl(urls[i]);
[bar_ performSelector:selectors[i]
withObject:ItemForBookmarkBarMenu(gurl)];
- EXPECT_EQ(bar_.get()->urls_[0], gurl);
- EXPECT_EQ(bar_.get()->dispositions_[0], dispositions[i]);
+ EXPECT_EQ(noOpenBar()->urls_[0], gurl);
+ EXPECT_EQ(noOpenBar()->dispositions_[0], dispositions[i]);
[bar_ clear];
}
}
@@ -616,7 +636,6 @@ TEST_F(BookmarkBarControllerTest, TestAddRemoveAndClear) {
TEST_F(BookmarkBarControllerTest, TestButtonLimits) {
BookmarkModel* model = helper_.profile()->GetBookmarkModel();
EXPECT_EQ(0U, [[bar_ buttons] count]);
-
// Add one; make sure we see it.
const BookmarkNode* parent = model->GetBookmarkBarNode();
model->AddURL(parent, parent->GetChildCount(),
@@ -625,7 +644,7 @@ TEST_F(BookmarkBarControllerTest, TestButtonLimits) {
// Add 30 which we expect to be 'too many'. Make sure we don't see
// 30 buttons.
- [bar_ clearBookmarkBar];
+ model->Remove(parent, 0);
EXPECT_EQ(0U, [[bar_ buttons] count]);
for (int i=0; i<30; i++) {
model->AddURL(parent, parent->GetChildCount(),
@@ -767,7 +786,7 @@ TEST_F(BookmarkBarControllerTest, MiddleClick) {
EXPECT_TRUE(first);
[first otherMouseUp:test_event_utils::MakeMouseEvent(NSOtherMouseUp, 0)];
- EXPECT_EQ(bar_.get()->urls_.size(), 1U);
+ EXPECT_EQ(noOpenBar()->urls_.size(), 1U);
}
TEST_F(BookmarkBarControllerTest, DisplaysHelpMessageOnEmpty) {
@@ -1239,12 +1258,12 @@ TEST_F(BookmarkBarControllerTest, NodeDeletedWhileMenuIsOpen) {
GURL("http://www.google.com"));
NSMenuItem* item = ItemForBookmarkBarMenu(initialNode);
- EXPECT_EQ(0U, bar_.get()->urls_.size());
+ EXPECT_EQ(0U, noOpenBar()->urls_.size());
// Basic check of the menu item and an IBOutlet it can call.
EXPECT_EQ(initialNode, [bar_ nodeFromMenuItem:item]);
[bar_ openBookmarkInNewWindow:item];
- EXPECT_EQ(1U, bar_.get()->urls_.size());
+ EXPECT_EQ(1U, noOpenBar()->urls_.size());
[bar_ clear];
// Now delete the node and make sure things are happy (no crash,
@@ -1254,7 +1273,7 @@ TEST_F(BookmarkBarControllerTest, NodeDeletedWhileMenuIsOpen) {
// Should not crash by referencing a deleted node.
[bar_ openBookmarkInNewWindow:item];
// Confirm the above did nothing in case it somehow didn't crash.
- EXPECT_EQ(0U, bar_.get()->urls_.size());
+ EXPECT_EQ(0U, noOpenBar()->urls_.size());
// Confirm some more non-crashes.
[bar_ openBookmarkInNewForegroundTab:item];
@@ -1300,6 +1319,60 @@ TEST_F(BookmarkBarControllerTest, CloseFolderOnAnimate) {
EXPECT_FALSE([bar_ folderController]);
}
+TEST_F(BookmarkBarControllerTest, MoveRemoveAddButtons) {
+ BookmarkModel& model(*helper_.profile()->GetBookmarkModel());
+ const BookmarkNode* root = model.GetBookmarkBarNode();
+ const std::wstring model_string(L"1b 2f:[ 2f1b 2f2b ] 3b ");
+ model_test_utils::AddNodesFromModelString(model, root, model_string);
+
+ // Validate initial model.
+ std::wstring actualModelString = model_test_utils::ModelStringFromNode(root);
+ EXPECT_EQ(model_string, actualModelString);
+
+ // Remember how many buttons are showing.
+ int oldDisplayedButtons = [bar_ displayedButtonCount];
+ NSArray* buttons = [bar_ buttons];
+
+ // Move a button around a bit.
+ [bar_ moveButtonFromIndex:0 toIndex:2];
+ EXPECT_TRUE([[[buttons objectAtIndex:0] title] isEqualToString:@"2f"]);
+ EXPECT_TRUE([[[buttons objectAtIndex:1] title] isEqualToString:@"3b"]);
+ EXPECT_TRUE([[[buttons objectAtIndex:2] title] isEqualToString:@"1b"]);
+ EXPECT_EQ(oldDisplayedButtons, [bar_ displayedButtonCount]);
+ [bar_ moveButtonFromIndex:2 toIndex:0];
+ EXPECT_TRUE([[[buttons objectAtIndex:0] title] isEqualToString:@"1b"]);
+ EXPECT_TRUE([[[buttons objectAtIndex:1] title] isEqualToString:@"2f"]);
+ EXPECT_TRUE([[[buttons objectAtIndex:2] title] isEqualToString:@"3b"]);
+ EXPECT_EQ(oldDisplayedButtons, [bar_ displayedButtonCount]);
+
+ // Add a couple of buttons.
+ const BookmarkNode* parent = root->GetChild(1); // Purloin an existing node.
+ const BookmarkNode* node = parent->GetChild(0);
+ [bar_ addButtonForNode:node atIndex:0];
+ EXPECT_TRUE([[[buttons objectAtIndex:0] title] isEqualToString:@"2f1b"]);
+ EXPECT_TRUE([[[buttons objectAtIndex:1] title] isEqualToString:@"1b"]);
+ EXPECT_TRUE([[[buttons objectAtIndex:2] title] isEqualToString:@"2f"]);
+ EXPECT_TRUE([[[buttons objectAtIndex:3] title] isEqualToString:@"3b"]);
+ EXPECT_EQ(oldDisplayedButtons + 1, [bar_ displayedButtonCount]);
+ node = parent->GetChild(1);
+ [bar_ addButtonForNode:node atIndex:-1];
+ EXPECT_TRUE([[[buttons objectAtIndex:0] title] isEqualToString:@"2f1b"]);
+ EXPECT_TRUE([[[buttons objectAtIndex:1] title] isEqualToString:@"1b"]);
+ EXPECT_TRUE([[[buttons objectAtIndex:2] title] isEqualToString:@"2f"]);
+ EXPECT_TRUE([[[buttons objectAtIndex:3] title] isEqualToString:@"3b"]);
+ EXPECT_TRUE([[[buttons objectAtIndex:4] title] isEqualToString:@"2f2b"]);
+ EXPECT_EQ(oldDisplayedButtons + 2, [bar_ displayedButtonCount]);
+
+ // Remove a couple of buttons.
+ [bar_ removeButton:4 animate:NO];
+ [bar_ removeButton:1 animate:NO];
+ EXPECT_TRUE([[[buttons objectAtIndex:0] title] isEqualToString:@"2f1b"]);
+ EXPECT_TRUE([[[buttons objectAtIndex:1] title] isEqualToString:@"2f"]);
+ EXPECT_TRUE([[[buttons objectAtIndex:2] title] isEqualToString:@"3b"]);
+ EXPECT_EQ(oldDisplayedButtons, [bar_ displayedButtonCount]);
+}
+
+
class BookmarkBarControllerOpenAllTest : public BookmarkBarControllerTest {
public:
BookmarkBarControllerOpenAllTest() {
@@ -1394,14 +1467,14 @@ TEST_F(BookmarkBarControllerOpenAllTest, CommandClickOnFolder) {
[[[fakeApp stub] andReturn:commandClick] currentEvent];
id oldApp = NSApp;
NSApp = fakeApp;
- size_t originalDispositionCount = bar_.get()->dispositions_.size();
+ size_t originalDispositionCount = noOpenBar()->dispositions_.size();
// Click!
[first performClick:first];
- size_t dispositionCount = bar_.get()->dispositions_.size();
+ size_t dispositionCount = noOpenBar()->dispositions_.size();
EXPECT_EQ(originalDispositionCount+1, dispositionCount);
- EXPECT_EQ(bar_.get()->dispositions_[dispositionCount-1], NEW_BACKGROUND_TAB);
+ EXPECT_EQ(noOpenBar()->dispositions_[dispositionCount-1], NEW_BACKGROUND_TAB);
// Replace NSApp
NSApp = oldApp;
@@ -1469,4 +1542,78 @@ TEST_F(BookmarkBarControllerNotificationTest, DeregistersForNotifications) {
// TODO(viettrungluu): figure out how to test animations.
+class BookmarkBarControllerDragDropTest : public BookmarkBarControllerTestBase {
+ public:
+ BookmarkBarControllerDragDropTest() {
+ InstallAndToggleBar(bar_.get());
+ }
+};
+
+TEST_F(BookmarkBarControllerDragDropTest, DragMoveBarBookmarkToOffTheSide) {
+ BookmarkModel& model(*helper_.profile()->GetBookmarkModel());
+ const BookmarkNode* root = model.GetBookmarkBarNode();
+ const std::wstring model_string(L"1bWithLongName 2fWithLongName:[ "
+ "2f1bWithLongName 2f2fWithLongName:[ 2f2f1bWithLongName "
+ "2f2f2bWithLongName 2f2f3bWithLongName 2f4b ] 2f3bWithLongName ] "
+ "3bWithLongName 4bWithLongName 5bWithLongName 6bWithLongName "
+ "7bWithLongName 8bWithLongName 9bWithLongName 10bWithLongName "
+ "11bWithLongName 12bWithLongName 13b ");
+ model_test_utils::AddNodesFromModelString(model, root, model_string);
+
+ // Validate initial model.
+ std::wstring actualModelString = model_test_utils::ModelStringFromNode(root);
+ EXPECT_EQ(model_string, actualModelString);
+
+ // Insure that the off-the-side is showing.
+ ASSERT_FALSE([bar_ offTheSideButtonIsHidden]);
+
+ // Remember how many buttons are showing and are available.
+ int oldDisplayedButtons = [bar_ displayedButtonCount];
+ int oldChildCount = root->GetChildCount();
+
+ // Pop up the off-the-side menu.
+ BookmarkButton* otsButton = (BookmarkButton*)[bar_ offTheSideButton];
+ ASSERT_TRUE(otsButton);
+ [[otsButton target] performSelector:@selector(openBookmarkFolderFromButton:)
+ withObject:otsButton];
+ BookmarkBarFolderController* otsController = [bar_ folderController];
+ EXPECT_TRUE(otsController);
+ NSWindow* toWindow = [otsController window];
+ EXPECT_TRUE(toWindow);
+ BookmarkButton* draggedButton = [bar_ buttonWithTitleEqualTo:@"3bWithLongName"];
+ ASSERT_TRUE(draggedButton);
+ int oldOTSCount = (int)[[otsController buttons] count];
+ EXPECT_EQ(oldOTSCount, oldChildCount - oldDisplayedButtons);
+ BookmarkButton* targetButton = [[otsController buttons] objectAtIndex:0];
+ ASSERT_TRUE(targetButton);
+ [otsController dragButton:draggedButton
+ to:[targetButton center]
+ copy:YES];
+ // There should still be the same number of buttons in the bar
+ // and off-the-side should have one more.
+ int newDisplayedButtons = [bar_ displayedButtonCount];
+ int newChildCount = root->GetChildCount();
+ int newOTSCount = (int)[[otsController buttons] count];
+ EXPECT_EQ(oldDisplayedButtons, newDisplayedButtons);
+ EXPECT_EQ(oldChildCount + 1, newChildCount);
+ EXPECT_EQ(oldOTSCount + 1, newOTSCount);
+ EXPECT_EQ(newOTSCount, newChildCount - newDisplayedButtons);
+}
+
+TEST_F(BookmarkBarControllerDragDropTest, ControllerForNode) {
+ BookmarkModel& model(*helper_.profile()->GetBookmarkModel());
+ const BookmarkNode* root = model.GetBookmarkBarNode();
+ const std::wstring model_string(L"1b 2f:[ 2f1b 2f2b ] 3b ");
+ model_test_utils::AddNodesFromModelString(model, root, model_string);
+
+ // Validate initial model.
+ std::wstring actualModelString = model_test_utils::ModelStringFromNode(root);
+ EXPECT_EQ(model_string, actualModelString);
+
+ // Find the main bar controller.
+ const void* expectedController = bar_;
+ const void* actualController = [bar_ controllerForNode:root];
+ EXPECT_EQ(expectedController, actualController);
+}
+
} // namespace
diff --git a/chrome/browser/cocoa/bookmark_bar_folder_controller.h b/chrome/browser/cocoa/bookmark_bar_folder_controller.h
index fef2a56..8b88253 100644
--- a/chrome/browser/cocoa/bookmark_bar_folder_controller.h
+++ b/chrome/browser/cocoa/bookmark_bar_folder_controller.h
@@ -110,6 +110,16 @@
// Return the parent button that owns the bookmark folder we represent.
- (BookmarkButton*)parentButton;
+// Offset our folder menu window. This is usually needed in response to a
+// parent folder menu window or the bookmark bar changing position due to
+// the dragging of a bookmark node from the parent into this folder menu.
+- (void)offsetFolderMenuWindow:(NSSize)offset;
+
+// Re-layout the window menu in case some buttons were added or removed,
+// specifically as a result of the bookmark bar changing configuration
+// and altering the contents of the off-the-side folder.
+- (void)reconfigureMenu;
+
// Actions from a context menu over a button or folder.
- (IBAction)cutBookmark:(id)sender;
- (IBAction)copyBookmark:(id)sender;
diff --git a/chrome/browser/cocoa/bookmark_bar_folder_controller.mm b/chrome/browser/cocoa/bookmark_bar_folder_controller.mm
index 29d7185..461d555 100644
--- a/chrome/browser/cocoa/bookmark_bar_folder_controller.mm
+++ b/chrome/browser/cocoa/bookmark_bar_folder_controller.mm
@@ -17,7 +17,6 @@
#import "chrome/browser/cocoa/browser_window_controller.h"
#import "chrome/browser/cocoa/event_utils.h"
-
namespace {
// Frequency of the scrolling timer in seconds.
@@ -57,6 +56,12 @@ const CGFloat kScrollWindowVerticalMargin = 0.0;
- (void)removeScrollTracking;
- (void)endScroll;
- (void)addScrollTimerWithDelta:(CGFloat)delta;
+
+// Determine the best button width (which will be the widest button or the
+// maximum allowable button width, whichever is less) and resize all buttons.
+// Return the new width (so that the window can be adjusted, if necessary).
+- (CGFloat)adjustButtonWidths;
+
@end
@implementation BookmarkBarFolderController
@@ -69,6 +74,10 @@ const CGFloat kScrollWindowVerticalMargin = 0.0;
ofType:@"nib"];
if ((self = [super initWithWindowNibPath:nibPath owner:self])) {
parentButton_.reset([button retain]);
+#if 1
+ // We want the button to remain bordered as part of the menu path.
+ [[button cell] setShowsBorderOnlyWhileMouseInside:NO];
+#endif
parentController_.reset([parentController retain]);
barController_ = barController; // WEAK
buttons_.reset([[NSMutableArray alloc] init]);
@@ -82,6 +91,11 @@ const CGFloat kScrollWindowVerticalMargin = 0.0;
}
- (void)dealloc {
+#if 1
+ // The button is no longer part of the menu path.
+ [[parentButton_ cell] setShowsBorderOnlyWhileMouseInside:YES];
+ [parentButton_ setNeedsDisplay];
+#endif
[self removeScrollTracking];
[self endScroll];
[hoverState_ draggingExited];
@@ -143,9 +157,13 @@ const CGFloat kScrollWindowVerticalMargin = 0.0;
// The "+2" is needed because, sometimes, Cocoa is off by a tad when
// returning the value it thinks it needs.
CGFloat desired = [cell cellSize].width + 2;
- frame.size.width = std::min(
- std::max(bookmarks::kBookmarkMenuButtonMinimumWidth, desired),
- bookmarks::kBookmarkMenuButtonMaximumWidth);
+ // The width is determined from the maximum of the proposed width
+ // (provided in |frame|) or the natural width of the title, then
+ // limited by the abolute minimum and maximum allowable widths.
+ frame.size.width =
+ std::min(std::max(bookmarks::kBookmarkMenuButtonMinimumWidth,
+ std::max(frame.size.width, desired)),
+ bookmarks::kBookmarkMenuButtonMaximumWidth);
BookmarkButton* button = [[[BookmarkButton alloc] initWithFrame:frame]
autorelease];
@@ -210,11 +228,13 @@ const CGFloat kScrollWindowVerticalMargin = 0.0;
bookmarkBarBottomLeftInScreen.y);
} else {
// Our parent controller is another BookmarkBarFolderController.
- // In this case, start ot the RIGHT of the parent button.
+ // In this case, start with a slight overlap on the RIGHT of the
+ // parent button, which looks much more menu-like than with none.
// Start to RIGHT of the button.
// TODO(jrg): If too far to right, pop left again.
// http://crbug.com/36225
- newWindowTopLeft.x = NSMaxX([[parentButton_ window] frame]);
+ newWindowTopLeft.x = NSMaxX([[parentButton_ window] frame]) -
+ bookmarks::kBookmarkMenuOverlap;
NSPoint top = NSMakePoint(0, (NSMaxY([parentButton_ frame]) +
bookmarks::kBookmarkVerticalPadding));
NSPoint topOfWindow =
@@ -254,7 +274,7 @@ const CGFloat kScrollWindowVerticalMargin = 0.0;
NSRect buttonsOuterFrame = NSMakeRect(
bookmarks::kBookmarkHorizontalPadding,
height - (bookmarks::kBookmarkBarHeight -
- bookmarks::kBookmarkHorizontalPadding),
+ bookmarks::kBookmarkVerticalPadding),
bookmarks::kDefaultBookmarkWidth,
(bookmarks::kBookmarkBarHeight -
2 * bookmarks::kBookmarkVerticalPadding));
@@ -281,24 +301,10 @@ const CGFloat kScrollWindowVerticalMargin = 0.0;
}
}
- // Now that we have all our buttons we can determine the real size
- // of our window.
- CGFloat width = 0.0;
- for (BookmarkButton* button in buttons_.get()) {
- width = std::max(width, NSWidth([button bounds]));
- }
- width = std::min(width, bookmarks::kBookmarkMenuButtonMaximumWidth);
-
- // Things look and feel more menu-like if all the buttons are the
- // full width of the window, especially if there are submenus.
- for (BookmarkButton* button in buttons_.get()) {
- NSRect buttonFrame = [button frame];
- buttonFrame.size.width = width;
- [button setFrame:buttonFrame];
- }
- width += (2 * bookmarks::kBookmarkVerticalPadding);
-
- // Finally, set our window size (make sure it fits on screen).
+ // Now that all buttons have been installed, adjust their sizes to be
+ // consistent, determine the best size for the window, and set the window.
+ CGFloat width =
+ [self adjustButtonWidths] + (2 * bookmarks::kBookmarkVerticalPadding);
NSRect windowFrame = NSMakeRect(newWindowTopLeft.x,
newWindowTopLeft.y - height,
width + kScrollViewContentWidthMargin,
@@ -341,6 +347,169 @@ const CGFloat kScrollWindowVerticalMargin = 0.0;
[self configureWindowLevel];
}
+- (void)offsetFolderMenuWindow:(NSSize)offset {
+ NSWindow* window = [self window];
+ NSRect windowFrame = [window frame];
+ windowFrame.origin.x -= offset.width;
+ windowFrame.origin.y += offset.height; // Yes, in the opposite direction!
+ [window setFrame:windowFrame display:YES];
+ [folderController_ offsetFolderMenuWindow:offset];
+}
+
+// TODO(mrossetti): See if the following can be moved into view's viewWillDraw:.
+- (CGFloat)adjustButtonWidths {
+ CGFloat width = 0.0;
+ for (BookmarkButton* button in buttons_.get()) {
+ width = std::max(width, NSWidth([button bounds]));
+ }
+ width = std::min(width, bookmarks::kBookmarkMenuButtonMaximumWidth);
+ // Things look and feel more menu-like if all the buttons are the
+ // full width of the window, especially if there are submenus.
+ for (BookmarkButton* button in buttons_.get()) {
+ NSRect buttonFrame = [button frame];
+ buttonFrame.size.width = width;
+ [button setFrame:buttonFrame];
+ }
+ return width;
+}
+
+#pragma mark BookmarkButtonControllerProtocol
+
+- (void)addButtonForNode:(const BookmarkNode*)node
+ atIndex:(NSInteger)buttonIndex {
+ if (buttonIndex == -1)
+ buttonIndex = [buttons_ count];
+
+ // Remember the last moved button's frame or the default.
+ NSRect buttonFrame = NSMakeRect(bookmarks::kBookmarkHorizontalPadding,
+ NSHeight([mainView_ frame]) + bookmarks::kBookmarkBarHeight +
+ bookmarks::kBookmarkVerticalPadding,
+ NSWidth([mainView_ frame]) - 2 * bookmarks::kBookmarkHorizontalPadding
+ - kScrollViewContentWidthMargin,
+ bookmarks::kBookmarkBarHeight - 2 * bookmarks::kBookmarkVerticalPadding);
+ BookmarkButton* button = nil; // Remember so it can be inavalidated.
+ for (NSInteger i = 0; i < buttonIndex; ++i) {
+ button = [buttons_ objectAtIndex:i];
+ buttonFrame = [button frame];
+ buttonFrame.origin.y += bookmarks::kBookmarkBarHeight;
+ [button setFrame:buttonFrame];
+ }
+ [[button cell] mouseExited:nil]; // De-highlight.
+ buttonFrame.origin.y -= bookmarks::kBookmarkBarHeight;
+ BookmarkButton* newButton = [self makeButtonForNode:node
+ frame:buttonFrame];
+ [buttons_ insertObject:newButton atIndex:buttonIndex];
+ [mainView_ addSubview:newButton];
+
+ // Close any child folder(s) which may still be open.
+ [self closeBookmarkFolder:self];
+
+ // Update all button widths in case more width is needed.
+ CGFloat windowWidth =
+ [self adjustButtonWidths] + (2 * bookmarks::kBookmarkVerticalPadding) +
+ kScrollViewContentWidthMargin;
+
+ // Update vertical metrics of the window and the main view (using sizers
+ // does not do what we want).
+ NSWindow* window = [self window];
+ NSRect frame = [mainView_ frame];
+ const CGFloat verticalDelta =
+ bookmarks::kBookmarkBarHeight - bookmarks::kBookmarkVerticalPadding;
+ frame.size.height += verticalDelta;
+ frame.size.width = windowWidth;
+ [mainView_ setFrame:frame];
+ frame = [window frame];
+ frame.origin.y -= bookmarks::kBookmarkBarHeight;
+ frame.size.height += bookmarks::kBookmarkBarHeight;
+ frame.size.width = windowWidth;
+ [window setFrame:frame display:YES];
+}
+
+- (void)moveButtonFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex {
+ if (fromIndex != toIndex) {
+ if (toIndex == -1)
+ toIndex = [buttons_ count];
+ BookmarkButton* movedButton = [buttons_ objectAtIndex:fromIndex];
+ [buttons_ removeObjectAtIndex:fromIndex];
+ NSRect movedFrame = [movedButton frame];
+ NSPoint toOrigin = movedFrame.origin;
+ [movedButton setHidden:YES];
+ if (fromIndex < toIndex) {
+ BookmarkButton* targetButton = [buttons_ objectAtIndex:toIndex - 1];
+ toOrigin = [targetButton frame].origin;
+ for (NSInteger i = fromIndex; i < toIndex; ++i) {
+ BookmarkButton* button = [buttons_ objectAtIndex:i];
+ NSRect frame = [button frame];
+ frame.origin.y += bookmarks::kBookmarkBarHeight;
+ [button setFrameOrigin:frame.origin];
+ }
+ } else {
+ BookmarkButton* targetButton = [buttons_ objectAtIndex:toIndex];
+ toOrigin = [targetButton frame].origin;
+ for (NSInteger i = fromIndex - 1; i >= toIndex; --i) {
+ BookmarkButton* button = [buttons_ objectAtIndex:i];
+ NSRect buttonFrame = [button frame];
+ buttonFrame.origin.y -= bookmarks::kBookmarkBarHeight;
+ [button setFrameOrigin:buttonFrame.origin];
+ }
+ }
+ [buttons_ insertObject:movedButton atIndex:toIndex];
+ [movedButton setFrameOrigin:toOrigin];
+ [movedButton setHidden:NO];
+ }
+}
+
+// TODO(jrg): Refactor BookmarkBarFolder common code. http://crbug.com/35966
+- (void)removeButton:(NSInteger)buttonIndex animate:(BOOL)animate {
+ // TODO(mrossetti): Get disappearing animation to work. http://crbug.com/42360
+ NSWindow* window = [self window];
+ BookmarkButton* oldButton = [buttons_ objectAtIndex:buttonIndex];
+ NSRect poofFrame = [oldButton bounds];
+ NSPoint poofPoint = NSMakePoint(NSMidX(poofFrame), NSMidY(poofFrame));
+ poofPoint = [oldButton convertPoint:poofPoint toView:nil];
+ poofPoint = [[oldButton window] convertBaseToScreen:poofPoint];
+ [oldButton removeFromSuperview];
+ if (animate)
+ NSShowAnimationEffect(NSAnimationEffectDisappearingItemDefault, poofPoint,
+ NSZeroSize, nil, nil, nil);
+ [buttons_ removeObjectAtIndex:buttonIndex];
+ for (NSInteger i = 0; i < buttonIndex; ++i) {
+ BookmarkButton* button = [buttons_ objectAtIndex:i];
+ NSRect buttonFrame = [button frame];
+ buttonFrame.origin.y -= bookmarks::kBookmarkBarHeight;
+ [button setFrame:buttonFrame];
+ }
+ // Search for and adjust submenus, if necessary.
+ NSInteger buttonCount = [buttons_ count];
+ BookmarkButton* subButton = [folderController_ parentButton];
+ for (NSInteger i = buttonIndex; i < buttonCount; ++i) {
+ BookmarkButton* aButton = [buttons_ objectAtIndex:i];
+ // If this button is showing its menu then we need to move the menu, too.
+ if (aButton == subButton)
+ [folderController_ offsetFolderMenuWindow:NSMakeSize(0.0,
+ bookmarks::kBookmarkBarHeight)];
+ }
+
+ // Resize the window and the main view (using sizers does not work).
+ NSRect frame = [window frame];
+ frame.origin.y += bookmarks::kBookmarkBarHeight;
+ frame.size.height -= bookmarks::kBookmarkBarHeight;
+ [window setFrame:frame display:YES];
+ frame = [mainView_ frame];
+ frame.origin.y += bookmarks::kBookmarkBarHeight;
+ frame.size.height -= bookmarks::kBookmarkBarHeight;
+ [mainView_ setFrame:frame];
+}
+
+- (id<BookmarkButtonControllerProtocol>)controllerForNode:
+ (const BookmarkNode*)node {
+ // See if we are holding this node, otherwise see if it is in our
+ // hierarchy of visible folder menus.
+ if ([parentButton_ bookmarkNode] == node)
+ return self;
+ return [folderController_ controllerForNode:node];
+}
+
// Start a "scroll up" timer.
- (void)beginScrollWindowUp {
[self addScrollTimerWithDelta:kBookmarkBarFolderScrollAmount];
@@ -381,10 +550,12 @@ const CGFloat kScrollWindowVerticalMargin = 0.0;
// view that contains the button. It appears that a mouseExited:
// gets lost, so the button stays highlit forever. We accomodate
// here.
+#if 0
if (buttonThatMouseIsIn_) {
[[buttonThatMouseIsIn_ cell] setShowsBorderOnlyWhileMouseInside:NO];
[[buttonThatMouseIsIn_ cell] setShowsBorderOnlyWhileMouseInside:YES];
}
+#endif
// We update the window size after shifting the scroll to avoid a race.
CGFloat screenHeightMinusMargin = (NSHeight(screenFrame) -
@@ -540,6 +711,9 @@ const CGFloat kScrollWindowVerticalMargin = 0.0;
- (void)fillPasteboard:(NSPasteboard*)pboard
forDragOfButton:(BookmarkButton*)button {
[[self folderTarget] fillPasteboard:pboard forDragOfButton:button];
+
+ // Close our folder menu and submenus since we know we're going to be dragged.
+ [self closeBookmarkFolder:self];
}
// Find something like std::is_between<T>? I can't believe one doesn't exist.
@@ -588,6 +762,7 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
return nil;
}
+// TODO(jrg): Refactor BookmarkBarFolder common code. http://crbug.com/35966
// Most of the work (e.g. drop indicator) is taken care of in the
// folder_view. Here we handle hover open issues for subfolders.
// Caution: there are subtle differences between this one and
@@ -596,6 +771,19 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
NSPoint currentLocation = [info draggingLocation];
BookmarkButton* button = [self buttonForDroppingOnAtPoint:currentLocation];
+ // Don't allow drops that would result in cycles.
+ if (button) {
+ NSData* data = [[info draggingPasteboard]
+ dataForType:kBookmarkButtonDragType];
+ if (data && [info draggingSource]) {
+ BookmarkButton* sourceButton = nil;
+ [data getBytes:&sourceButton length:sizeof(sourceButton)];
+ const BookmarkNode* sourceNode = [sourceButton bookmarkNode];
+ const BookmarkNode* destNode = [button bookmarkNode];
+ if (destNode->HasAncestor(sourceNode))
+ button = nil;
+ }
+ }
// Delegate handling of dragging over a button to the |hoverState_| member.
return [hoverState_ draggingEnteredButton:button];
}
@@ -698,17 +886,19 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
destIndex += [[parentButton_ cell] startingChildIndex];
}
- if (copy)
- [self bookmarkModel]->Copy(sourceNode, destParent, destIndex);
- else
- [self bookmarkModel]->Move(sourceNode, destParent, destIndex);
-
- [self closeAllBookmarkFolders]; // For a hover open, if needed.
-
- // Movement of a node triggers observers (like us) to rebuild the
- // bar so we don't have to do so explicitly.
+ // Prevent cycles.
+ BOOL wasCopiedOrMoved = NO;
+ if (!destParent->HasAncestor(sourceNode)) {
+ if (copy)
+ [self bookmarkModel]->Copy(sourceNode, destParent, destIndex);
+ else
+ [self bookmarkModel]->Move(sourceNode, destParent, destIndex);
+ wasCopiedOrMoved = YES;
+ // Movement of a node triggers observers (like us) to rebuild the
+ // bar so we don't have to do so explicitly.
+ }
- return YES;
+ return wasCopiedOrMoved;
}
// Return YES if we should show the drop indicator, else NO.
@@ -844,14 +1034,22 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
}
}
+- (void)reconfigureMenu {
+ for (BookmarkButton* button in buttons_.get())
+ [button removeFromSuperview];
+ [buttons_ removeAllObjects];
+ [self configureWindow];
+}
+
#pragma mark Methods Forwarded to BookmarkBarController
- (IBAction)cutBookmark:(id)sender {
+ [self closeBookmarkFolder:self];
[barController_ cutBookmark:sender];
}
- (IBAction)copyBookmark:(id)sender {
- [barController_ cutBookmark:sender];
+ [barController_ copyBookmark:sender];
}
- (IBAction)pasteBookmark:(id)sender {
@@ -859,6 +1057,7 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
}
- (IBAction)deleteBookmark:(id)sender {
+ [self closeBookmarkFolder:self];
[barController_ deleteBookmark:sender];
}
diff --git a/chrome/browser/cocoa/bookmark_bar_folder_controller_unittest.mm b/chrome/browser/cocoa/bookmark_bar_folder_controller_unittest.mm
index a0f8d8e..65b0c90 100644
--- a/chrome/browser/cocoa/bookmark_bar_folder_controller_unittest.mm
+++ b/chrome/browser/cocoa/bookmark_bar_folder_controller_unittest.mm
@@ -6,11 +6,15 @@
#include "base/basictypes.h"
#include "base/scoped_nsobject.h"
+#import "chrome/browser/cocoa/bookmark_bar_constants.h" // namespace bookmarks
#import "chrome/browser/cocoa/bookmark_bar_controller.h"
#import "chrome/browser/cocoa/bookmark_bar_folder_button_cell.h"
#import "chrome/browser/cocoa/bookmark_bar_folder_controller.h"
+#import "chrome/browser/cocoa/bookmark_bar_unittest_helper.h"
#include "chrome/browser/cocoa/browser_test_helper.h"
#import "chrome/browser/cocoa/cocoa_test_helper.h"
+#import "chrome/browser/cocoa/view_resizer_pong.h"
+#include "chrome/test/model_test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
@@ -222,7 +226,8 @@ TEST_F(BookmarkBarFolderControllerTest, Position) {
[bbfc2 window];
[bbfc2 setRealTopLeft:YES];
pt = [bbfc2 windowTopLeft];
- EXPECT_EQ(pt.x, NSMaxX([[bbfc.get() window] frame]));
+ // We're now overlapping the window a bit.
+ EXPECT_EQ(pt.x, NSMaxX([[bbfc.get() window] frame]) - 1);
}
TEST_F(BookmarkBarFolderControllerTest, DropDestination) {
@@ -366,6 +371,573 @@ TEST_F(BookmarkBarFolderControllerTest, SimpleScroll) {
}
}
+class BookmarkBarFolderControllerDragDropTest : public CocoaTest {
+ public:
+ BrowserTestHelper helper_;
+ scoped_nsobject<NSView> parent_view_;
+ scoped_nsobject<ViewResizerPong> resizeDelegate_;
+ scoped_nsobject<BookmarkBarController> bar_;
+
+ BookmarkBarFolderControllerDragDropTest() {
+ resizeDelegate_.reset([[ViewResizerPong alloc] init]);
+ NSRect parent_frame = NSMakeRect(0, 0, 800, 50);
+ parent_view_.reset([[NSView alloc] initWithFrame:parent_frame]);
+ [parent_view_ setHidden:YES];
+ bar_.reset([[BookmarkBarController alloc]
+ initWithBrowser:helper_.browser()
+ initialWidth:NSWidth(parent_frame)
+ delegate:nil
+ resizeDelegate:resizeDelegate_.get()]);
+ InstallAndToggleBar(bar_.get());
+ }
+
+ void InstallAndToggleBar(BookmarkBarController* bar) {
+ // Force loading of the nib.
+ [bar view];
+ // Awkwardness to look like we've been installed.
+ [parent_view_ addSubview:[bar view]];
+ NSRect frame = [[[bar view] superview] frame];
+ frame.origin.y = 100;
+ [[[bar view] superview] setFrame:frame];
+
+ // Make sure it's on in a window so viewDidMoveToWindow is called
+ [[test_window() contentView] addSubview:parent_view_];
+
+ // Make sure it's open so certain things aren't no-ops.
+ [bar updateAndShowNormalBar:YES
+ showDetachedBar:NO
+ withAnimation:NO];
+ }
+};
+
+#if CGFLOAT_IS_DOUBLE
+#define CGFLOAT_EPSILON DBL_EPSILON
+#else
+#define CGFLOAT_EPSILON FLT_EPSILON
+#endif
+
+#define CGFLOAT_EQ(expected, actual) \
+ (actual >= (expected - CGFLOAT_EPSILON) && \
+ actual <= (expected + CGFLOAT_EPSILON))
+
+#define EXPECT_CGFLOAT_EQ(expected, actual) \
+ EXPECT_TRUE(CGFLOAT_EQ(actual, expected)
+
+#define EXPECT_NSRECT_EQ(expected, actual) \
+ EXPECT_TRUE(CGFLOAT_EQ(expected.origin.x, actual.origin.x) && \
+ CGFLOAT_EQ(expected.origin.y, actual.origin.y) && \
+ CGFLOAT_EQ(expected.size.width, actual.size.width) && \
+ CGFLOAT_EQ(expected.size.height, actual.size.height)) << \
+ "Rects do not match: " << \
+ [NSStringFromRect(expected) UTF8String] << \
+ " != " << [NSStringFromRect(actual) UTF8String];
+
+TEST_F(BookmarkBarFolderControllerDragDropTest, DragMoveBarBookmarkToFolder) {
+ BookmarkModel& model(*helper_.profile()->GetBookmarkModel());
+ const BookmarkNode* root = model.GetBookmarkBarNode();
+ const std::wstring model_string(L"1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b "
+ "2f2f3b ] 2f3b ] 3b 4f:[ 4f1f:[ 4f1f1b 4f1f2b 4f1f3b ] 4f2f:[ 4f2f1b "
+ "4f2f2b 4f2f3b ] 4f3f:[ 4f3f1b 4f3f2b 4f3f3b ] ] 5b ");
+ model_test_utils::AddNodesFromModelString(model, root, model_string);
+
+ // Validate initial model.
+ std::wstring actualModelString = model_test_utils::ModelStringFromNode(root);
+ EXPECT_EQ(model_string, actualModelString);
+
+ // Pop up a folder menu and drag in a button from the bar.
+ BookmarkButton* toFolder = [bar_ buttonWithTitleEqualTo:@"2f"];
+ NSRect oldToFolderFrame = [toFolder frame];
+ [[toFolder target] performSelector:@selector(openBookmarkFolderFromButton:)
+ withObject:toFolder];
+ BookmarkBarFolderController* folderController = [bar_ folderController];
+ EXPECT_TRUE(folderController);
+ NSWindow* toWindow = [folderController window];
+ EXPECT_TRUE(toWindow);
+ NSRect oldToWindowFrame = [toWindow frame];
+ // Drag a bar button onto a bookmark (i.e. not a folder) in a folder
+ // so it should end up below the target bookmark.
+ BookmarkButton* draggedButton = [bar_ buttonWithTitleEqualTo:@"1b"];
+ ASSERT_TRUE(draggedButton);
+ CGFloat horizontalShift =
+ NSWidth([draggedButton frame]) + bookmarks::kBookmarkHorizontalPadding;
+ BookmarkButton* targetButton = [folderController buttonWithTitleEqualTo:@"2f1b"];
+ ASSERT_TRUE(targetButton);
+ [folderController dragButton:draggedButton
+ to:[targetButton center]
+ copy:NO];
+ // The button should have landed just after "2f1b".
+ const std::wstring expected_string(L"2f:[ 2f1b 1b 2f2f:[ 2f2f1b "
+ "2f2f2b 2f2f3b ] 2f3b ] 3b 4f:[ 4f1f:[ 4f1f1b 4f1f2b 4f1f3b ] 4f2f:[ "
+ "4f2f1b 4f2f2b 4f2f3b ] 4f3f:[ 4f3f1b 4f3f2b 4f3f3b ] ] 5b ");
+ EXPECT_EQ(expected_string, model_test_utils::ModelStringFromNode(root));
+
+ // Verify the window still appears by looking for its controller.
+ EXPECT_TRUE([bar_ folderController]);
+
+ // Gather the new frames.
+ NSRect newToFolderFrame = [toFolder frame];
+ NSRect newToWindowFrame = [toWindow frame];
+ // The toFolder should have shifted left horizontally but not vertically.
+ NSRect expectedToFolderFrame =
+ NSOffsetRect(oldToFolderFrame, -horizontalShift, 0);
+ EXPECT_NSRECT_EQ(expectedToFolderFrame, newToFolderFrame);
+ // The toWindow should have shifted left horizontally, down vertically,
+ // and grown vertically.
+ NSRect expectedToWindowFrame = oldToWindowFrame;
+ expectedToWindowFrame.origin.x -= horizontalShift;
+ expectedToWindowFrame.origin.y -= bookmarks::kBookmarkBarHeight;
+ expectedToWindowFrame.size.height += bookmarks::kBookmarkBarHeight;
+ EXPECT_NSRECT_EQ(expectedToWindowFrame, newToWindowFrame);
+
+ // Move the button back to the bar at the beginning.
+ draggedButton = [folderController buttonWithTitleEqualTo:@"1b"];
+ ASSERT_TRUE(draggedButton);
+ targetButton = [bar_ buttonWithTitleEqualTo:@"2f"];
+ ASSERT_TRUE(targetButton);
+ [bar_ dragButton:draggedButton
+ to:[targetButton left]
+ copy:NO];
+ EXPECT_EQ(model_string, model_test_utils::ModelStringFromNode(root));
+ // Don't check the folder window since it's not supposed to be showing.
+}
+
+TEST_F(BookmarkBarFolderControllerDragDropTest, DragCopyBarBookmarkToFolder) {
+ BookmarkModel& model(*helper_.profile()->GetBookmarkModel());
+ const BookmarkNode* root = model.GetBookmarkBarNode();
+ const std::wstring model_string(L"1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b "
+ "2f2f3b ] 2f3b ] 3b 4f:[ 4f1f:[ 4f1f1b 4f1f2b 4f1f3b ] 4f2f:[ 4f2f1b "
+ "4f2f2b 4f2f3b ] 4f3f:[ 4f3f1b 4f3f2b 4f3f3b ] ] 5b ");
+ model_test_utils::AddNodesFromModelString(model, root, model_string);
+
+ // Validate initial model.
+ std::wstring actualModelString = model_test_utils::ModelStringFromNode(root);
+ EXPECT_EQ(model_string, actualModelString);
+
+ // Pop up a folder menu and copy in a button from the bar.
+ BookmarkButton* toFolder = [bar_ buttonWithTitleEqualTo:@"2f"];
+ ASSERT_TRUE(toFolder);
+ NSRect oldToFolderFrame = [toFolder frame];
+ [[toFolder target] performSelector:@selector(openBookmarkFolderFromButton:)
+ withObject:toFolder];
+ BookmarkBarFolderController* folderController = [bar_ folderController];
+ EXPECT_TRUE(folderController);
+ NSWindow* toWindow = [folderController window];
+ EXPECT_TRUE(toWindow);
+ NSRect oldToWindowFrame = [toWindow frame];
+ // Drag a bar button onto a bookmark (i.e. not a folder) in a folder
+ // so it should end up below the target bookmark.
+ BookmarkButton* draggedButton = [bar_ buttonWithTitleEqualTo:@"1b"];
+ ASSERT_TRUE(draggedButton);
+ BookmarkButton* targetButton = [folderController buttonWithTitleEqualTo:@"2f1b"];
+ ASSERT_TRUE(targetButton);
+ [folderController dragButton:draggedButton
+ to:[targetButton center]
+ copy:YES];
+ // The button should have landed just after "2f1b".
+ const std::wstring expected_1(L"1b 2f:[ 2f1b 1b 2f2f:[ 2f2f1b "
+ "2f2f2b 2f2f3b ] 2f3b ] 3b 4f:[ 4f1f:[ 4f1f1b 4f1f2b 4f1f3b ] 4f2f:[ "
+ "4f2f1b 4f2f2b 4f2f3b ] 4f3f:[ 4f3f1b 4f3f2b 4f3f3b ] ] 5b ");
+ EXPECT_EQ(expected_1, model_test_utils::ModelStringFromNode(root));
+
+ // Gather the new frames.
+ NSRect newToFolderFrame = [toFolder frame];
+ NSRect newToWindowFrame = [toWindow frame];
+ // The toFolder should have shifted.
+ EXPECT_NSRECT_EQ(oldToFolderFrame, newToFolderFrame);
+ // The toWindow should have shifted down vertically and grown vertically.
+ NSRect expectedToWindowFrame = oldToWindowFrame;
+ expectedToWindowFrame.origin.y -= bookmarks::kBookmarkBarHeight;
+ expectedToWindowFrame.size.height += bookmarks::kBookmarkBarHeight;
+ EXPECT_NSRECT_EQ(expectedToWindowFrame, newToWindowFrame);
+
+ // Copy the button back to the bar after "3b".
+ draggedButton = [folderController buttonWithTitleEqualTo:@"1b"];
+ ASSERT_TRUE(draggedButton);
+ targetButton = [bar_ buttonWithTitleEqualTo:@"4f"];
+ ASSERT_TRUE(targetButton);
+ [bar_ dragButton:draggedButton
+ to:[targetButton left]
+ copy:YES];
+ const std::wstring expected_2(L"1b 2f:[ 2f1b 1b 2f2f:[ 2f2f1b "
+ "2f2f2b 2f2f3b ] 2f3b ] 3b 1b 4f:[ 4f1f:[ 4f1f1b 4f1f2b 4f1f3b ] 4f2f:[ "
+ "4f2f1b 4f2f2b 4f2f3b ] 4f3f:[ 4f3f1b 4f3f2b 4f3f3b ] ] 5b ");
+ EXPECT_EQ(expected_2, model_test_utils::ModelStringFromNode(root));
+}
+
+TEST_F(BookmarkBarFolderControllerDragDropTest,
+ DragMoveBarBookmarkToSubfolder) {
+ BookmarkModel& model(*helper_.profile()->GetBookmarkModel());
+ const BookmarkNode* root = model.GetBookmarkBarNode();
+ const std::wstring model_string(L"1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b "
+ "2f2f3b ] 2f3b ] 3b 4f:[ 4f1f:[ 4f1f1b 4f1f2b 4f1f3b ] 4f2f:[ 4f2f1b "
+ "4f2f2b 4f2f3b ] 4f3f:[ 4f3f1b 4f3f2b 4f3f3b ] ] 5b ");
+ model_test_utils::AddNodesFromModelString(model, root, model_string);
+
+ // Validate initial model.
+ std::wstring actualModelString = model_test_utils::ModelStringFromNode(root);
+ EXPECT_EQ(model_string, actualModelString);
+
+ // Pop up a folder menu and a subfolder menu.
+ BookmarkButton* toFolder = [bar_ buttonWithTitleEqualTo:@"4f"];
+ ASSERT_TRUE(toFolder);
+ [[toFolder target] performSelector:@selector(openBookmarkFolderFromButton:)
+ withObject:toFolder];
+ BookmarkBarFolderController* folderController = [bar_ folderController];
+ EXPECT_TRUE(folderController);
+ NSWindow* toWindow = [folderController window];
+ EXPECT_TRUE(toWindow);
+ NSRect oldToWindowFrame = [toWindow frame];
+ BookmarkButton* toSubfolder = [folderController buttonWithTitleEqualTo:@"4f2f"];
+ ASSERT_TRUE(toSubfolder);
+ NSRect oldToSubfolderFrame = [toSubfolder frame];
+ [[toSubfolder target] performSelector:@selector(openBookmarkFolderFromButton:)
+ withObject:toSubfolder];
+ BookmarkBarFolderController* subfolderController =
+ [folderController folderController];
+ EXPECT_TRUE(subfolderController);
+ NSWindow* toSubwindow = [subfolderController window];
+ EXPECT_TRUE(toSubwindow);
+ NSRect oldToSubwindowFrame = [toSubwindow frame];
+ // Drag a bar button onto a bookmark (i.e. not a folder) in a folder
+ // so it should end up below the target bookmark.
+ BookmarkButton* draggedButton = [bar_ buttonWithTitleEqualTo:@"5b"];
+ ASSERT_TRUE(draggedButton);
+ BookmarkButton* targetButton =
+ [subfolderController buttonWithTitleEqualTo:@"4f2f3b"];
+ ASSERT_TRUE(targetButton);
+ [subfolderController dragButton:draggedButton
+ to:[targetButton center]
+ copy:NO];
+ // The button should have landed just after "2f".
+ const std::wstring expected_string(L"1b 2f:[ 2f1b 2f2f:[ 2f2f1b "
+ "2f2f2b 2f2f3b ] 2f3b ] 3b 4f:[ 4f1f:[ 4f1f1b 4f1f2b 4f1f3b ] 4f2f:[ "
+ "4f2f1b 4f2f2b 4f2f3b 5b ] 4f3f:[ 4f3f1b 4f3f2b 4f3f3b ] ] ");
+ EXPECT_EQ(expected_string, model_test_utils::ModelStringFromNode(root));
+
+ // Check the window layouts. The folder window should not have changed,
+ // but the subfolder window should have shifted vertically and grown.
+ NSRect newToWindowFrame = [toWindow frame];
+ EXPECT_NSRECT_EQ(oldToWindowFrame, newToWindowFrame);
+ NSRect newToSubwindowFrame = [toSubwindow frame];
+ NSRect expectedToSubwindowFrame = oldToSubwindowFrame;
+ expectedToSubwindowFrame.origin.y -= bookmarks::kBookmarkBarHeight;
+ expectedToSubwindowFrame.size.height += bookmarks::kBookmarkBarHeight;
+ EXPECT_NSRECT_EQ(expectedToSubwindowFrame, newToSubwindowFrame);
+}
+
+TEST_F(BookmarkBarFolderControllerDragDropTest, DragMoveWithinFolder) {
+ BookmarkModel& model(*helper_.profile()->GetBookmarkModel());
+ const BookmarkNode* root = model.GetBookmarkBarNode();
+ const std::wstring model_string(L"1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b "
+ "2f2f3b ] 2f3b ] 3b 4f:[ 4f1f:[ 4f1f1b 4f1f2b 4f1f3b ] 4f2f:[ 4f2f1b "
+ "4f2f2b 4f2f3b ] 4f3f:[ 4f3f1b 4f3f2b 4f3f3b ] ] 5b ");
+ model_test_utils::AddNodesFromModelString(model, root, model_string);
+
+ // Validate initial model.
+ std::wstring actualModelString = model_test_utils::ModelStringFromNode(root);
+ EXPECT_EQ(model_string, actualModelString);
+
+ // Pop up a folder menu.
+ BookmarkButton* toFolder = [bar_ buttonWithTitleEqualTo:@"4f"];
+ ASSERT_TRUE(toFolder);
+ [[toFolder target] performSelector:@selector(openBookmarkFolderFromButton:)
+ withObject:toFolder];
+ BookmarkBarFolderController* folderController = [bar_ folderController];
+ EXPECT_TRUE(folderController);
+ NSWindow* toWindow = [folderController window];
+ EXPECT_TRUE(toWindow);
+ NSRect oldToWindowFrame = [toWindow frame];
+ // Drag a folder button to the top within the same parent.
+ BookmarkButton* draggedButton = [folderController buttonWithTitleEqualTo:@"4f2f"];
+ ASSERT_TRUE(draggedButton);
+ BookmarkButton* targetButton = [folderController buttonWithTitleEqualTo:@"4f1f"];
+ ASSERT_TRUE(targetButton);
+ [folderController dragButton:draggedButton
+ to:[targetButton top]
+ copy:NO];
+ // The button should have landed above "4f1f".
+ const std::wstring expected_string(L"1b 2f:[ 2f1b 2f2f:[ 2f2f1b "
+ "2f2f2b 2f2f3b ] 2f3b ] 3b 4f:[ 4f2f:[ 4f2f1b 4f2f2b 4f2f3b ] "
+ "4f1f:[ 4f1f1b 4f1f2b 4f1f3b ] 4f3f:[ 4f3f1b 4f3f2b 4f3f3b ] ] 5b ");
+ EXPECT_EQ(expected_string, model_test_utils::ModelStringFromNode(root));
+
+ // The window should not have gone away.
+ EXPECT_TRUE([bar_ folderController]);
+
+ // The folder window should not have changed.
+ NSRect newToWindowFrame = [toWindow frame];
+ EXPECT_NSRECT_EQ(oldToWindowFrame, newToWindowFrame);
+}
+
+TEST_F(BookmarkBarFolderControllerDragDropTest, DragParentOntoChild) {
+ BookmarkModel& model(*helper_.profile()->GetBookmarkModel());
+ const BookmarkNode* root = model.GetBookmarkBarNode();
+ const std::wstring model_string(L"1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b "
+ "2f2f3b ] 2f3b ] 3b 4f:[ 4f1f:[ 4f1f1b 4f1f2b 4f1f3b ] 4f2f:[ 4f2f1b "
+ "4f2f2b 4f2f3b ] 4f3f:[ 4f3f1b 4f3f2b 4f3f3b ] ] 5b ");
+ model_test_utils::AddNodesFromModelString(model, root, model_string);
+
+ // Validate initial model.
+ std::wstring actualModelString = model_test_utils::ModelStringFromNode(root);
+ EXPECT_EQ(model_string, actualModelString);
+
+ // Pop up a folder menu.
+ BookmarkButton* toFolder = [bar_ buttonWithTitleEqualTo:@"4f"];
+ ASSERT_TRUE(toFolder);
+ [[toFolder target] performSelector:@selector(openBookmarkFolderFromButton:)
+ withObject:toFolder];
+ BookmarkBarFolderController* folderController = [bar_ folderController];
+ EXPECT_TRUE(folderController);
+ NSWindow* toWindow = [folderController window];
+ EXPECT_TRUE(toWindow);
+ // Drag a folder button to one of its children.
+ BookmarkButton* draggedButton = [bar_ buttonWithTitleEqualTo:@"4f"];
+ ASSERT_TRUE(draggedButton);
+ BookmarkButton* targetButton = [folderController buttonWithTitleEqualTo:@"4f3f"];
+ ASSERT_TRUE(targetButton);
+ [folderController dragButton:draggedButton
+ to:[targetButton top]
+ copy:NO];
+ // The model should not have changed.
+ EXPECT_EQ(model_string, model_test_utils::ModelStringFromNode(root));
+}
+
+TEST_F(BookmarkBarFolderControllerDragDropTest, DragMoveChildToParent) {
+ BookmarkModel& model(*helper_.profile()->GetBookmarkModel());
+ const BookmarkNode* root = model.GetBookmarkBarNode();
+ const std::wstring model_string(L"1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b "
+ "2f2f3b ] 2f3b ] 3b 4f:[ 4f1f:[ 4f1f1b 4f1f2b 4f1f3b ] 4f2f:[ 4f2f1b "
+ "4f2f2b 4f2f3b ] 4f3f:[ 4f3f1b 4f3f2b 4f3f3b ] ] 5b ");
+ model_test_utils::AddNodesFromModelString(model, root, model_string);
+
+ // Validate initial model.
+ std::wstring actualModelString = model_test_utils::ModelStringFromNode(root);
+ EXPECT_EQ(model_string, actualModelString);
+
+ // Pop up a folder menu and a subfolder menu.
+ BookmarkButton* toFolder = [bar_ buttonWithTitleEqualTo:@"4f"];
+ ASSERT_TRUE(toFolder);
+ [[toFolder target] performSelector:@selector(openBookmarkFolderFromButton:)
+ withObject:toFolder];
+ BookmarkBarFolderController* folderController = [bar_ folderController];
+ EXPECT_TRUE(folderController);
+ BookmarkButton* toSubfolder = [folderController buttonWithTitleEqualTo:@"4f2f"];
+ ASSERT_TRUE(toSubfolder);
+ [[toSubfolder target] performSelector:@selector(openBookmarkFolderFromButton:)
+ withObject:toSubfolder];
+ BookmarkBarFolderController* subfolderController =
+ [folderController folderController];
+ EXPECT_TRUE(subfolderController);
+
+ // Drag a subfolder bookmark to the parent folder.
+ BookmarkButton* draggedButton =
+ [subfolderController buttonWithTitleEqualTo:@"4f2f3b"];
+ ASSERT_TRUE(draggedButton);
+ BookmarkButton* targetButton = [folderController buttonWithTitleEqualTo:@"4f2f"];
+ ASSERT_TRUE(targetButton);
+ [folderController dragButton:draggedButton
+ to:[targetButton top]
+ copy:NO];
+ // The button should have landed above "4f2f".
+ const std::wstring expected_string(L"1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b "
+ "2f2f3b ] 2f3b ] 3b 4f:[ 4f1f:[ 4f1f1b 4f1f2b 4f1f3b ] 4f2f3b 4f2f:[ "
+ "4f2f1b 4f2f2b ] 4f3f:[ 4f3f1b 4f3f2b 4f3f3b ] ] 5b ");
+ EXPECT_EQ(expected_string, model_test_utils::ModelStringFromNode(root));
+
+ // The window should not have gone away.
+ EXPECT_TRUE([bar_ folderController]);
+ // The subfolder should have gone away.
+ EXPECT_FALSE([folderController folderController]);
+}
+
+TEST_F(BookmarkBarFolderControllerDragDropTest, DragWindowResizing) {
+ BookmarkModel& model(*helper_.profile()->GetBookmarkModel());
+ const BookmarkNode* root = model.GetBookmarkBarNode();
+ const std::wstring
+ model_string(L"a b:[ b1 b2 b3 ] reallyReallyLongBookmarkName c ");
+ model_test_utils::AddNodesFromModelString(model, root, model_string);
+
+ // Validate initial model.
+ std::wstring actualModelString = model_test_utils::ModelStringFromNode(root);
+ EXPECT_EQ(model_string, actualModelString);
+
+ // Pop up a folder menu.
+ BookmarkButton* toFolder = [bar_ buttonWithTitleEqualTo:@"b"];
+ ASSERT_TRUE(toFolder);
+ [[toFolder target] performSelector:@selector(openBookmarkFolderFromButton:)
+ withObject:toFolder];
+ BookmarkBarFolderController* folderController = [bar_ folderController];
+ EXPECT_TRUE(folderController);
+ NSWindow* toWindow = [folderController window];
+ EXPECT_TRUE(toWindow);
+ CGFloat oldWidth = NSWidth([toWindow frame]);
+ // Drag the bookmark with a long name to the folder.
+ BookmarkButton* draggedButton =
+ [bar_ buttonWithTitleEqualTo:@"reallyReallyLongBookmarkName"];
+ ASSERT_TRUE(draggedButton);
+ BookmarkButton* targetButton = [folderController buttonWithTitleEqualTo:@"b1"];
+ ASSERT_TRUE(targetButton);
+ [folderController dragButton:draggedButton
+ to:[targetButton center]
+ copy:NO];
+ // Verify the model change.
+ const std::wstring
+ expected_string(L"a b:[ b1 reallyReallyLongBookmarkName b2 b3 ] c ");
+ EXPECT_EQ(expected_string, model_test_utils::ModelStringFromNode(root));
+ // Verify the window grew. Just test a reasonable width gain.
+ CGFloat newWidth = NSWidth([toWindow frame]);
+ EXPECT_LT(oldWidth + 30.0, newWidth);
+}
+
+TEST_F(BookmarkBarFolderControllerDragDropTest, MoveRemoveAddButtons) {
+ BookmarkModel& model(*helper_.profile()->GetBookmarkModel());
+ const BookmarkNode* root = model.GetBookmarkBarNode();
+ const std::wstring model_string(L"1b 2f:[ 2f1b 2f2b 2f3b ] 3b 4b ");
+ model_test_utils::AddNodesFromModelString(model, root, model_string);
+
+ // Validate initial model.
+ std::wstring actualModelString = model_test_utils::ModelStringFromNode(root);
+ EXPECT_EQ(model_string, actualModelString);
+
+ // Pop up a folder menu.
+ BookmarkButton* toFolder = [bar_ buttonWithTitleEqualTo:@"2f"];
+ ASSERT_TRUE(toFolder);
+ [[toFolder target] performSelector:@selector(openBookmarkFolderFromButton:)
+ withObject:toFolder];
+ BookmarkBarFolderController* folder = [bar_ folderController];
+ EXPECT_TRUE(folder);
+
+ // Remember how many buttons are showing.
+ NSArray* buttons = [folder buttons];
+ NSUInteger oldDisplayedButtons = [buttons count];
+
+ // Move a button around a bit.
+ [folder moveButtonFromIndex:0 toIndex:2];
+ EXPECT_TRUE([[[buttons objectAtIndex:0] title] isEqualToString:@"2f2b"]);
+ EXPECT_TRUE([[[buttons objectAtIndex:1] title] isEqualToString:@"2f3b"]);
+ EXPECT_TRUE([[[buttons objectAtIndex:2] title] isEqualToString:@"2f1b"]);
+ EXPECT_EQ(oldDisplayedButtons, [buttons count]);
+ [folder moveButtonFromIndex:2 toIndex:0];
+ EXPECT_TRUE([[[buttons objectAtIndex:0] title] isEqualToString:@"2f1b"]);
+ EXPECT_TRUE([[[buttons objectAtIndex:1] title] isEqualToString:@"2f2b"]);
+ EXPECT_TRUE([[[buttons objectAtIndex:2] title] isEqualToString:@"2f3b"]);
+ EXPECT_EQ(oldDisplayedButtons, [buttons count]);
+
+ // Add a couple of buttons.
+ const BookmarkNode* node = root->GetChild(2); // Purloin an existing node.
+ [folder addButtonForNode:node atIndex:0];
+ EXPECT_TRUE([[[buttons objectAtIndex:0] title] isEqualToString:@"3b"]);
+ EXPECT_TRUE([[[buttons objectAtIndex:1] title] isEqualToString:@"2f1b"]);
+ EXPECT_TRUE([[[buttons objectAtIndex:2] title] isEqualToString:@"2f2b"]);
+ EXPECT_TRUE([[[buttons objectAtIndex:3] title] isEqualToString:@"2f3b"]);
+ EXPECT_EQ(oldDisplayedButtons + 1, [buttons count]);
+ node = root->GetChild(3);
+ [folder addButtonForNode:node atIndex:-1];
+ EXPECT_TRUE([[[buttons objectAtIndex:0] title] isEqualToString:@"3b"]);
+ EXPECT_TRUE([[[buttons objectAtIndex:1] title] isEqualToString:@"2f1b"]);
+ EXPECT_TRUE([[[buttons objectAtIndex:2] title] isEqualToString:@"2f2b"]);
+ EXPECT_TRUE([[[buttons objectAtIndex:3] title] isEqualToString:@"2f3b"]);
+ EXPECT_TRUE([[[buttons objectAtIndex:4] title] isEqualToString:@"4b"]);
+ EXPECT_EQ(oldDisplayedButtons + 2, [buttons count]);
+
+ // Remove a couple of buttons.
+ [folder removeButton:4 animate:NO];
+ [folder removeButton:1 animate:NO];
+ EXPECT_TRUE([[[buttons objectAtIndex:0] title] isEqualToString:@"3b"]);
+ EXPECT_TRUE([[[buttons objectAtIndex:1] title] isEqualToString:@"2f2b"]);
+ EXPECT_TRUE([[[buttons objectAtIndex:2] title] isEqualToString:@"2f3b"]);
+ EXPECT_EQ(oldDisplayedButtons, [buttons count]);
+}
+
+TEST_F(BookmarkBarFolderControllerDragDropTest, ControllerForNode) {
+ BookmarkModel& model(*helper_.profile()->GetBookmarkModel());
+ const BookmarkNode* root = model.GetBookmarkBarNode();
+ const std::wstring model_string(L"1b 2f:[ 2f1b 2f2b ] 3b ");
+ model_test_utils::AddNodesFromModelString(model, root, model_string);
+
+ // Validate initial model.
+ std::wstring actualModelString = model_test_utils::ModelStringFromNode(root);
+ EXPECT_EQ(model_string, actualModelString);
+
+ // Find the main bar controller.
+ const void* expectedController = bar_;
+ const void* actualController = [bar_ controllerForNode:root];
+ EXPECT_EQ(expectedController, actualController);
+
+ // Pop up the folder menu.
+ BookmarkButton* targetFolder = [bar_ buttonWithTitleEqualTo:@"2f"];
+ ASSERT_TRUE(targetFolder);
+ [[targetFolder target]
+ performSelector:@selector(openBookmarkFolderFromButton:)
+ withObject:targetFolder];
+ BookmarkBarFolderController* folder = [bar_ folderController];
+ EXPECT_TRUE(folder);
+
+ // Find the folder controller using the folder controller.
+ const BookmarkNode* targetNode = root->GetChild(1);
+ expectedController = folder;
+ actualController = [bar_ controllerForNode:targetNode];
+ EXPECT_EQ(expectedController, actualController);
+
+ // Find the folder controller from the bar.
+ actualController = [folder controllerForNode:targetNode];
+ EXPECT_EQ(expectedController, actualController);
+}
+
+@interface BookmarkBarControllerNoDelete : BookmarkBarController
+- (IBAction)deleteBookmark:(id)sender;
+@end
+
+@implementation BookmarkBarControllerNoDelete
+- (IBAction)deleteBookmark:(id)sender {
+ // NOP
+}
+@end
+
+class BookmarkBarFolderControllerClosingTest : public
+ BookmarkBarFolderControllerDragDropTest {
+ public:
+ BookmarkBarFolderControllerClosingTest() {
+ bar_.reset([[BookmarkBarControllerNoDelete alloc]
+ initWithBrowser:helper_.browser()
+ initialWidth:NSWidth([parent_view_ frame])
+ delegate:nil
+ resizeDelegate:resizeDelegate_.get()]);
+ InstallAndToggleBar(bar_.get());
+ }
+};
+
+TEST_F(BookmarkBarFolderControllerClosingTest, DeleteClosesFolder) {
+ BookmarkModel& model(*helper_.profile()->GetBookmarkModel());
+ const BookmarkNode* root = model.GetBookmarkBarNode();
+ const std::wstring model_string(L"1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b ] "
+ "2f3b ] 3b ");
+ model_test_utils::AddNodesFromModelString(model, root, model_string);
+
+ // Validate initial model.
+ std::wstring actualModelString = model_test_utils::ModelStringFromNode(root);
+ EXPECT_EQ(model_string, actualModelString);
+
+ // Open the folder menu and submenu.
+ BookmarkButton* target = [bar_ buttonWithTitleEqualTo:@"2f"];
+ ASSERT_TRUE(target);
+ [[target target] performSelector:@selector(openBookmarkFolderFromButton:)
+ withObject:target];
+ BookmarkBarFolderController* folder = [bar_ folderController];
+ EXPECT_TRUE(folder);
+ BookmarkButton* subTarget = [folder buttonWithTitleEqualTo:@"2f2f"];
+ ASSERT_TRUE(subTarget);
+ [[subTarget target] performSelector:@selector(openBookmarkFolderFromButton:)
+ withObject:subTarget];
+ BookmarkBarFolderController* subFolder = [folder folderController];
+ EXPECT_TRUE(subFolder);
+
+ // Delete the folder node and verify the window closed down by looking
+ // for its controller again.
+ [folder deleteBookmark:folder];
+ EXPECT_FALSE([folder folderController]);
+}
+
// TODO(jrg): draggingEntered: and draggingExited: trigger timers so
// they are hard to test. Factor out "fire timers" into routines
// which can be overridden to fire immediately to make behavior
diff --git a/chrome/browser/cocoa/bookmark_bar_folder_view.mm b/chrome/browser/cocoa/bookmark_bar_folder_view.mm
index d712944..15a4db6 100644
--- a/chrome/browser/cocoa/bookmark_bar_folder_view.mm
+++ b/chrome/browser/cocoa/bookmark_bar_folder_view.mm
@@ -22,7 +22,6 @@
}
- (void)drawRect:(NSRect)rect {
-
// TODO(jrg): copied from bookmark_bar_view but orientation changed.
// Code dup sucks but I'm not sure I can take 16 lines and make it
// generic for horiz vs vertical while keeping things simple.
@@ -105,10 +104,6 @@
// Awkwardness since views open and close out from under us.
if (inDrag_) {
inDrag_ = NO;
-
- // This line makes sure menus get closed when a drag isn't
- // completed.
- [[self controller] closeAllBookmarkFolders];
}
[self draggingExited:info];
diff --git a/chrome/browser/cocoa/bookmark_bar_folder_view_unittest.mm b/chrome/browser/cocoa/bookmark_bar_folder_view_unittest.mm
index 96189f5..83472af 100644
--- a/chrome/browser/cocoa/bookmark_bar_folder_view_unittest.mm
+++ b/chrome/browser/cocoa/bookmark_bar_folder_view_unittest.mm
@@ -122,6 +122,21 @@
disposition:(WindowOpenDisposition)disposition {
}
+- (void)removeButton:(NSInteger)buttonIndex animate:(BOOL)animate {
+}
+
+- (id<BookmarkButtonControllerProtocol>)controllerForNode:
+ (const BookmarkNode*)node {
+ return nil;
+}
+
+- (void)moveButtonFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex {
+}
+
+- (void)addButtonForNode:(const BookmarkNode*)node
+ atIndex:(NSInteger)buttonIndex {
+}
+
@end
namespace {
diff --git a/chrome/browser/cocoa/bookmark_bar_unittest_helper.h b/chrome/browser/cocoa/bookmark_bar_unittest_helper.h
new file mode 100644
index 0000000..402f8a1
--- /dev/null
+++ b/chrome/browser/cocoa/bookmark_bar_unittest_helper.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_COCOA_AUTOCOMPLETE_BOOKMARK_BAR_UNITTEST_HELPER_H_
+#define CHROME_BROWSER_COCOA_AUTOCOMPLETE_BOOKMARK_BAR_UNITTEST_HELPER_H_
+
+#import <Foundation/Foundation.h>
+
+#import "chrome/browser/cocoa/bookmark_bar_controller.h"
+#import "chrome/browser/cocoa/bookmark_bar_folder_controller.h"
+#import "chrome/browser/cocoa/bookmark_button.h"
+
+@interface BookmarkBarController (BookmarkBarUnitTestHelper)
+
+// Return the bookmark button from this bar controller with the given
+// |title|, otherwise nil. This does not recurse into folders.
+- (BookmarkButton*)buttonWithTitleEqualTo:(NSString*)title;
+
+@end
+
+
+@interface BookmarkBarFolderController (BookmarkBarUnitTestHelper)
+
+// Return the bookmark button from this folder controller with the given
+// |title|, otherwise nil. This does not recurse into subfolders.
+- (BookmarkButton*)buttonWithTitleEqualTo:(NSString*)title;
+
+@end
+
+
+@interface BookmarkButton (BookmarkBarUnitTestHelper)
+
+// Return the center of the button in the base coordinate system of the
+// containing window. Useful for simulating mouse clicks or drags.
+- (NSPoint)center;
+
+// Return the top of the button in the base coordinate system of the
+// containing window.
+- (NSPoint)top;
+
+// Return the center-left point of the button in the base coordinate system
+// of the containing window.
+- (NSPoint)left;
+
+@end
+
+#endif // CHROME_BROWSER_COCOA_AUTOCOMPLETE_TEXT_FIELD_UNITTEST_HELPER_H_
diff --git a/chrome/browser/cocoa/bookmark_bar_unittest_helper.mm b/chrome/browser/cocoa/bookmark_bar_unittest_helper.mm
new file mode 100644
index 0000000..114197e
--- /dev/null
+++ b/chrome/browser/cocoa/bookmark_bar_unittest_helper.mm
@@ -0,0 +1,67 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "chrome/browser/cocoa/bookmark_bar_unittest_helper.h"
+
+@interface NSArray (BookmarkBarUnitTestHelper)
+
+// A helper function for scanning an array of buttons looking for the
+// button with the given |title|.
+- (BookmarkButton*)buttonWithTitleEqualTo:(NSString*)title;
+
+@end
+
+
+@implementation NSArray (BookmarkBarUnitTestHelper)
+
+- (BookmarkButton*)buttonWithTitleEqualTo:(NSString*)title {
+ for (BookmarkButton* button in self) {
+ if ([[button title] isEqualToString:title])
+ return button;
+ }
+ return nil;
+}
+
+@end
+
+@implementation BookmarkBarController (BookmarkBarUnitTestHelper)
+
+- (BookmarkButton*)buttonWithTitleEqualTo:(NSString*)title {
+ return [[self buttons] buttonWithTitleEqualTo:title];
+}
+
+@end
+
+@implementation BookmarkBarFolderController(BookmarkBarUnitTestHelper)
+
+- (BookmarkButton*)buttonWithTitleEqualTo:(NSString*)title {
+ return [[self buttons] buttonWithTitleEqualTo:title];
+}
+
+@end
+
+@implementation BookmarkButton(BookmarkBarUnitTestHelper)
+
+- (NSPoint)center {
+ NSRect frame = [self frame];
+ NSPoint center = NSMakePoint(NSMidX(frame), NSMidY(frame));
+ center = [[self superview] convertPoint:center toView:nil];
+ return center;
+}
+
+- (NSPoint)top {
+ NSRect frame = [self frame];
+ NSPoint top = NSMakePoint(NSMidX(frame), NSMaxY(frame));
+ top = [[self superview] convertPoint:top toView:nil];
+ return top;
+}
+
+- (NSPoint)left {
+ NSRect frame = [self frame];
+ NSPoint left = NSMakePoint(NSMinX(frame), NSMidY(frame));
+ left = [[self superview] convertPoint:left toView:nil];
+ return left;
+}
+
+@end
diff --git a/chrome/browser/cocoa/bookmark_button.h b/chrome/browser/cocoa/bookmark_button.h
index cb62053..62acadc 100644
--- a/chrome/browser/cocoa/bookmark_button.h
+++ b/chrome/browser/cocoa/bookmark_button.h
@@ -46,6 +46,7 @@ class ThemeProvider;
// or NSWindowController. The BookmarkButton doesn't use this
// protocol directly; it is used when BookmarkButton controllers talk
// to each other.
+//
// Other than the top level owner (the bookmark bar), all bookmark
// button controllers have a parent controller.
@protocol BookmarkButtonControllerProtocol
@@ -116,6 +117,35 @@ class ThemeProvider;
- (void)openAll:(const BookmarkNode*)node
disposition:(WindowOpenDisposition)disposition;
+// There are several operations which may affect the contents of a bookmark
+// button controller after it has been created, primary of which are
+// cut/paste/delete and drag/drop. Such changes may involve coordinating
+// the bookmark button contents of two controllers (such as when a bookmark is
+// dragged from one folder to another). The bookmark bar controller
+// coordinates in response to notifications propogated by the bookmark model
+// through BookmarkBarBridge calls. The following three functions are
+// implemented by the controllers and are dispatched by the bookmark bar
+// controller in response to notifications coming in from the BookmarkBarBridge.
+
+// Add a button for the given node to the bar or folder menu. This is safe
+// to call when a folder menu window is open as that window will be updated.
+// And index of -1 means to append to the end (bottom).
+- (void)addButtonForNode:(const BookmarkNode*)node
+ atIndex:(NSInteger)buttonIndex;
+
+// Move a button from one place in the menu to another. This is safe
+// to call when a folder menu window is open as that window will be updated.
+- (void)moveButtonFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex;
+
+// Remove the bookmark button at the given index. Show the poof animation
+// if |animate:| is YES. It may be obvious, but this is safe
+// to call when a folder menu window is open as that window will be updated.
+- (void)removeButton:(NSInteger)buttonIndex animate:(BOOL)poof;
+
+// Determine the controller containing the button representing |node|, if any.
+- (id<BookmarkButtonControllerProtocol>)controllerForNode:
+ (const BookmarkNode*)node;
+
@end // @protocol BookmarkButtonControllerProtocol
diff --git a/chrome/browser/cocoa/bookmark_button.mm b/chrome/browser/cocoa/bookmark_button.mm
index fea9d25..00a6b77 100644
--- a/chrome/browser/cocoa/bookmark_button.mm
+++ b/chrome/browser/cocoa/bookmark_button.mm
@@ -53,7 +53,6 @@ static const CGFloat kDragImageOpacity = 0.7;
if ([self delegate]) {
// Ask our delegate to fill the pasteboard for us.
NSPasteboard* pboard = [NSPasteboard pasteboardWithName:NSDragPboard];
-
[[self delegate] fillPasteboard:pboard forDragOfButton:self];
// At the moment, moving bookmarks causes their buttons (like me!)
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 1087624..3b4cc66 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -128,6 +128,8 @@
'test/chrome_process_util.cc',
'test/chrome_process_util.h',
'test/chrome_process_util_mac.cc',
+ 'test/model_test_utils.cc',
+ 'test/model_test_utils.h',
'test/profile_mock.h',
'test/test_browser_window.h',
'test/test_location_bar.h',
@@ -637,6 +639,8 @@
'browser/cocoa/bookmark_bar_folder_view_unittest.mm',
'browser/cocoa/bookmark_bar_folder_window_unittest.mm',
'browser/cocoa/bookmark_bar_toolbar_view_unittest.mm',
+ 'browser/cocoa/bookmark_bar_unittest_helper.h',
+ 'browser/cocoa/bookmark_bar_unittest_helper.mm',
'browser/cocoa/bookmark_bar_view_unittest.mm',
'browser/cocoa/bookmark_bubble_controller_unittest.mm',
'browser/cocoa/bookmark_button_unittest.mm',
diff --git a/chrome/test/model_test_utils.cc b/chrome/test/model_test_utils.cc
new file mode 100644
index 0000000..218cfd8
--- /dev/null
+++ b/chrome/test/model_test_utils.cc
@@ -0,0 +1,82 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "model_test_utils.h"
+
+#include "chrome/browser/bookmarks/bookmark_model.h"
+#include "googleurl/src/gurl.h"
+
+namespace model_test_utils {
+
+std::wstring ModelStringFromNode(const BookmarkNode* node) {
+ // Since the children of the node are not available as a vector,
+ // we'll just have to do it the hard way.
+ int child_count = node->GetChildCount();
+ std::wstring child_string;
+ for (int i = 0; i < child_count; ++i) {
+ const BookmarkNode* child = node->GetChild(i);
+ if (child->is_folder()) {
+ child_string += child->GetTitle() + L":[ " + ModelStringFromNode(child)
+ + L"] ";
+ } else {
+ child_string += child->GetTitle() + L" ";
+ }
+ }
+ return child_string;
+}
+
+// Helper function which does the actual work of creating the nodes for
+// a particular level in the hierarchy.
+std::wstring::size_type AddNodesFromString(BookmarkModel& model,
+ const BookmarkNode* node,
+ const std::wstring& model_string,
+ std::wstring::size_type start_pos) {
+ DCHECK(node);
+ int index = node->GetChildCount();
+ static const std::wstring folder_tell(L":[");
+ std::wstring::size_type end_pos = model_string.find(' ', start_pos);
+ while (end_pos != std::wstring::npos) {
+ std::wstring::size_type part_length = end_pos - start_pos;
+ std::wstring node_name = model_string.substr(start_pos, part_length);
+ // Are we at the end of a folder group?
+ if (node_name != L"]") {
+ // No, is it a folder?
+ std::wstring tell;
+ if (part_length > 2)
+ tell = node_name.substr(part_length - 2, 2);
+ if (tell == folder_tell) {
+ node_name = node_name.substr(0, part_length - 2);
+ const BookmarkNode* new_node = model.AddGroup(node, index, node_name);
+ end_pos = AddNodesFromString(model, new_node, model_string,
+ end_pos + 1);
+ } else {
+ std::string url_string("http://");
+ url_string += std::string(node_name.begin(), node_name.end());
+ url_string += ".com";
+ model.AddURL(node, index, node_name, GURL(url_string));
+ ++end_pos;
+ }
+ ++index;
+ start_pos = end_pos;
+ end_pos = model_string.find(' ', start_pos);
+ } else {
+ ++end_pos;
+ break;
+ }
+ }
+ return end_pos;
+}
+
+void AddNodesFromModelString(BookmarkModel& model,
+ const BookmarkNode* node,
+ const std::wstring& model_string) {
+ DCHECK(node);
+ const std::wstring folder_tell(L":[");
+ std::wstring::size_type start_pos = 0;
+ std::wstring::size_type end_pos =
+ AddNodesFromString(model, node, model_string, start_pos);
+ DCHECK(end_pos == std::wstring::npos);
+}
+
+} // namespace model_test_utils
diff --git a/chrome/test/model_test_utils.h b/chrome/test/model_test_utils.h
new file mode 100644
index 0000000..1d5c3c6
--- /dev/null
+++ b/chrome/test/model_test_utils.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_TEST_MODEL_TEST_UTILS_H_
+#define CHROME_TEST_MODEL_TEST_UTILS_H_
+
+#include <string>
+
+class BookmarkModel;
+class BookmarkNode;
+
+namespace model_test_utils {
+
+// Return the descendants of |node| as a string useful for verifying node
+// modifications. The format of the resulting string is:
+//
+// result = node " " , { node " " }
+// node = bookmark title | folder
+// folder = folder title ":[ " { node " " } "]"
+// bookmark title = (* string with no spaces *)
+// folder title = (* string with no spaces *)
+//
+// Example: "a f1:[ b d c ] d f2:[ e f g ] h "
+std::wstring ModelStringFromNode(const BookmarkNode* node);
+
+// Create and add the node hierarchy specified by |nodeString| to the
+// bookmark node given by |node|. The string has the same format as
+// specified for ModelStringFromNode. The new nodes added to |node|
+// are appended to the end of node's existing subnodes, if any.
+// |model| must be the model of which |node| is a member.
+// NOTE: The string format is very rigid and easily broken if not followed
+// exactly (since we're using a very simple parser).
+void AddNodesFromModelString(BookmarkModel& model,
+ const BookmarkNode* node,
+ const std::wstring& model_string);
+
+} // namespace model_test_utils
+
+#endif // CHROME_TEST_MODEL_TEST_UTILS_H_