diff options
author | mrossetti@chromium.org <mrossetti@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-05-20 20:19:43 +0000 |
---|---|---|
committer | mrossetti@chromium.org <mrossetti@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-05-20 20:19:43 +0000 |
commit | 8056a7541d58f0c1bf7e0c1c6954c2d027b93848 (patch) | |
tree | 66741623a28e68256bb21ee9c47a753f666b07cb /chrome/browser | |
parent | 2c607924904d6801cbf50f290f2cf4dc0145bf8d (diff) | |
download | chromium_src-8056a7541d58f0c1bf7e0c1c6954c2d027b93848.zip chromium_src-8056a7541d58f0c1bf7e0c1c6954c2d027b93848.tar.gz chromium_src-8056a7541d58f0c1bf7e0c1c6954c2d027b93848.tar.bz2 |
Simple code rearranging to better group by function area and protocol.
BUG=None
TEST=None
Review URL: http://codereview.chromium.org/2136020
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@47838 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
-rw-r--r-- | chrome/browser/cocoa/bookmark_bar_controller.h | 33 | ||||
-rw-r--r-- | chrome/browser/cocoa/bookmark_bar_controller.mm | 2324 | ||||
-rw-r--r-- | chrome/browser/cocoa/bookmark_bar_folder_controller.mm | 827 |
3 files changed, 1601 insertions, 1583 deletions
diff --git a/chrome/browser/cocoa/bookmark_bar_controller.h b/chrome/browser/cocoa/bookmark_bar_controller.h index 7efc2a6..51ebf6f 100644 --- a/chrome/browser/cocoa/bookmark_bar_controller.h +++ b/chrome/browser/cocoa/bookmark_bar_controller.h @@ -319,15 +319,15 @@ willAnimateFromState:(bookmarks::VisualState)oldState @interface BookmarkBarController(BridgeRedirect) - (void)loaded:(BookmarkModel*)model; - (void)beingDeleted:(BookmarkModel*)model; +- (void)nodeAdded:(BookmarkModel*)model + parent:(const BookmarkNode*)oldParent index:(int)index; +- (void)nodeChanged:(BookmarkModel*)model + node:(const BookmarkNode*)node; - (void)nodeMoved:(BookmarkModel*)model oldParent:(const BookmarkNode*)oldParent oldIndex:(int)oldIndex newParent:(const BookmarkNode*)newParent newIndex:(int)newIndex; -- (void)nodeAdded:(BookmarkModel*)model - parent:(const BookmarkNode*)oldParent index:(int)index; - (void)nodeRemoved:(BookmarkModel*)model parent:(const BookmarkNode*)oldParent index:(int)index; -- (void)nodeChanged:(BookmarkModel*)model - node:(const BookmarkNode*)node; - (void)nodeFavIconLoaded:(BookmarkModel*)model node:(const BookmarkNode*)node; - (void)nodeChildrenReordered:(BookmarkModel*)model @@ -336,29 +336,30 @@ willAnimateFromState:(bookmarks::VisualState)oldState // These APIs should only be used by unit tests (or used internally). @interface BookmarkBarController(InternalOrTestingAPI) -- (void)openURL:(GURL)url disposition:(WindowOpenDisposition)disposition; -- (NSCell*)cellForBookmarkNode:(const BookmarkNode*)node; -- (void)clearBookmarkBar; - (BookmarkBarView*)buttonView; - (NSMutableArray*)buttons; +- (NSMenu*)offTheSideMenu; +- (NSButton*)offTheSideButton; +- (BOOL)offTheSideButtonIsHidden; +- (NSButton*)otherBookmarksButton; +- (BookmarkBarFolderController*)folderController; +- (id)folderTarget; +- (int)displayedButtonCount; +- (void)openURL:(GURL)url disposition:(WindowOpenDisposition)disposition; +- (void)clearBookmarkBar; +- (NSCell*)cellForBookmarkNode:(const BookmarkNode*)node; - (NSRect)frameForBookmarkButtonFromCell:(NSCell*)cell xOffset:(int*)xOffset; - (void)checkForBookmarkButtonGrowth:(NSButton*)button; - (void)frameDidChange; -- (BOOL)offTheSideButtonIsHidden; -- (NSMenu *)menuForFolderNode:(const BookmarkNode*)node; - (int64)nodeIdFromMenuTag:(int32)tag; - (int32)menuTagFromNodeId:(int64)menuid; -- (void)buildOffTheSideMenuIfNeeded; -- (NSMenu*)offTheSideMenu; -- (NSButton*)offTheSideButton; -- (NSButton*)otherBookmarksButton; - (const BookmarkNode*)nodeFromMenuItem:(id)sender; - (void)updateTheme:(ThemeProvider*)themeProvider; -- (BookmarkBarFolderController*)folderController; - (BookmarkButton*)buttonForDroppingOnAtPoint:(NSPoint)point; - (BOOL)isEventAnExitEvent:(NSEvent*)event; -- (id)folderTarget; -- (int)displayedButtonCount; + +// The following are for testing purposes only and are not used internally. +- (NSMenu *)menuForFolderNode:(const BookmarkNode*)node; - (NSMenu*)buttonContextMenu; - (void)setButtonContextMenu:(id)menu; // Set to YES in order to prevent animations. diff --git a/chrome/browser/cocoa/bookmark_bar_controller.mm b/chrome/browser/cocoa/bookmark_bar_controller.mm index c585070..ed04eb0 100644 --- a/chrome/browser/cocoa/bookmark_bar_controller.mm +++ b/chrome/browser/cocoa/bookmark_bar_controller.mm @@ -267,37 +267,6 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12; [super dealloc]; } -// Adapt appearance of buttons to the current theme. Called after -// theme changes, or when our view is added to the view hierarchy. -// Oddly, the view pings us instead of us pinging our view. This is -// because our trigger is an [NSView viewWillMoveToWindow:], which the -// controller doesn't normally know about. Otherwise we don't have -// access to the theme before we know what window we will be on. -- (void)updateTheme:(ThemeProvider*)themeProvider { - if (!themeProvider) - return; - NSColor* color = - themeProvider->GetNSColor(BrowserThemeProvider::COLOR_BOOKMARK_TEXT, - true); - for (BookmarkButton* button in buttons_.get()) { - BookmarkButtonCell* cell = [button cell]; - [cell setTextColor:color]; - } - [[otherBookmarksButton_ cell] setTextColor:color]; -} - -// Exposed purely for testing. -- (BookmarkBarFolderController*)folderController { - return folderController_; -} - -// Called after the current theme has changed. -- (void)themeDidChangeNotification:(NSNotification*)aNotification { - ThemeProvider* themeProvider = - static_cast<ThemeProvider*>([[aNotification object] pointerValue]); - [self updateTheme:themeProvider]; -} - - (void)awakeFromNib { // We default to NOT open, which means height=0. DCHECK([[self view] isHidden]); // Hidden so it's OK to change. @@ -360,11 +329,312 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12; object:[[self view] window]]; } +// NSNotificationCenter callback. +- (void)parentWindowWillClose:(NSNotification*)notification { + [self closeFolderAndStopTrackingMenus]; +} + +// NSNotificationCenter callback. +- (void)parentWindowDidResignKey:(NSNotification*)notification { + [self closeFolderAndStopTrackingMenus]; +} + +// Change the layout of the bookmark bar's subviews in response to a visibility +// change (e.g., show or hide the bar) or style change (attached or floating). +- (void)layoutSubviews { + NSRect frame = [[self view] frame]; + NSRect buttonViewFrame = NSMakeRect(0, 0, NSWidth(frame), NSHeight(frame)); + + // The state of our morph (if any); 1 is total bubble, 0 is the regular bar. + CGFloat morph = [self detachedMorphProgress]; + + // Add padding to the detached bookmark bar. + buttonViewFrame = NSInsetRect(buttonViewFrame, + morph * bookmarks::kNTPBookmarkBarPadding, + morph * bookmarks::kNTPBookmarkBarPadding); + + [buttonView_ setFrame:buttonViewFrame]; +} + +// We don't change a preference; we only change visibility. Preference changing +// (global state) is handled in |BrowserWindowCocoa::ToggleBookmarkBar()|. We +// simply update based on what we're told. +- (void)updateVisibility { + [self showBookmarkBarWithAnimation:NO]; +} + +- (void)setBookmarkBarEnabled:(BOOL)enabled { + if (enabled != barIsEnabled_) { + barIsEnabled_ = enabled; + [self updateVisibility]; + } +} + +- (CGFloat)getDesiredToolbarHeightCompression { + // Some special cases.... + if (!barIsEnabled_) + return 0; + + if ([self isAnimationRunning]) { + // No toolbar compression when animating between hidden and showing, nor + // between showing and detached. + if ([self isAnimatingBetweenState:bookmarks::kHiddenState + andState:bookmarks::kShowingState] || + [self isAnimatingBetweenState:bookmarks::kShowingState + andState:bookmarks::kDetachedState]) + return 0; + + // If we ever need any other animation cases, code would go here. + } + + return [self isInState:bookmarks::kShowingState] ? kBookmarkBarOverlap : 0; +} + +- (CGFloat)toolbarDividerOpacity { + // Some special cases.... + if ([self isAnimationRunning]) { + // In general, the toolbar shouldn't show a divider while we're animating + // between showing and hidden. The exception is when our height is < 1, in + // which case we can't draw it. It's all-or-nothing (no partial opacity). + if ([self isAnimatingBetweenState:bookmarks::kHiddenState + andState:bookmarks::kShowingState]) + return (NSHeight([[self view] frame]) < 1) ? 1 : 0; + + // The toolbar should show the divider when animating between showing and + // detached (but opacity will vary). + if ([self isAnimatingBetweenState:bookmarks::kShowingState + andState:bookmarks::kDetachedState]) + return static_cast<CGFloat>([self detachedMorphProgress]); + + // If we ever need any other animation cases, code would go here. + } + + // In general, only show the divider when it's in the normal showing state. + return [self isInState:bookmarks::kShowingState] ? 0 : 1; +} + +- (NSImage*)favIconForNode:(const BookmarkNode*)node { + if (!node) + return defaultImage_; + + if (node->is_folder()) + return folderImage_; + + const SkBitmap& favIcon = bookmarkModel_->GetFavIcon(node); + if (!favIcon.isNull()) + return gfx::SkBitmapToNSImage(favIcon); + + return defaultImage_; +} + +- (void)closeFolderAndStopTrackingMenus { + showFolderMenus_ = NO; + [self closeAllBookmarkFolders]; +} + +#pragma mark Actions + +- (IBAction)openBookmark:(id)sender { + [self closeFolderAndStopTrackingMenus]; + DCHECK([sender respondsToSelector:@selector(bookmarkNode)]); + const BookmarkNode* node = [sender bookmarkNode]; + WindowOpenDisposition disposition = + event_utils::WindowOpenDispositionFromNSEvent([NSApp currentEvent]); + [self openURL:node->GetURL() disposition:disposition]; +} + +// Redirect to our logic shared with BookmarkBarFolderController. +- (IBAction)openBookmarkFolderFromButton:(id)sender { + // Toggle presentation of bar folder menus. + showFolderMenus_ = !showFolderMenus_; + [folderTarget_ openBookmarkFolderFromButton:sender]; +} + +// The button that sends this one is special; the "off the side" +// button (chevron) opens like a folder button but isn't exactly a +// parent folder. +- (IBAction)openOffTheSideFolderFromButton:(id)sender { + DCHECK([sender isKindOfClass:[BookmarkButton class]]); + DCHECK([[sender cell] isKindOfClass:[BookmarkButtonCell class]]); + [[sender cell] setStartingChildIndex:displayedButtonCount_]; + [folderTarget_ openBookmarkFolderFromButton:sender]; +} + +- (IBAction)openBookmarkInNewForegroundTab:(id)sender { + const BookmarkNode* node = [self nodeFromMenuItem:sender]; + if (node) + [self openURL:node->GetURL() disposition:NEW_FOREGROUND_TAB]; +} + +- (IBAction)openBookmarkInNewWindow:(id)sender { + const BookmarkNode* node = [self nodeFromMenuItem:sender]; + if (node) + [self openURL:node->GetURL() disposition:NEW_WINDOW]; +} + +- (IBAction)openBookmarkInIncognitoWindow:(id)sender { + const BookmarkNode* node = [self nodeFromMenuItem:sender]; + if (node) + [self openURL:node->GetURL() disposition:OFF_THE_RECORD]; +} + +- (IBAction)editBookmark:(id)sender { + const BookmarkNode* node = [self nodeFromMenuItem:sender]; + if (!node) + return; + + if (node->is_folder()) { + BookmarkNameFolderController* controller = + [[BookmarkNameFolderController alloc] + initWithParentWindow:[[self view] window] + profile:browser_->profile() + node:node]; + [controller runAsModalSheet]; + return; + } + + // There is no real need to jump to a platform-common routine at + // this point (which just jumps back to objc) other than consistency + // across platforms. + // + // TODO(jrg): identify when we NO_TREE. I can see it in the code + // for the other platforms but can't find a way to trigger it in the + // UI. + BookmarkEditor::Show([[self view] window], + browser_->profile(), + node->GetParent(), + BookmarkEditor::EditDetails(node), + BookmarkEditor::SHOW_TREE); +} + +- (IBAction)cutBookmark:(id)sender { + const BookmarkNode* node = [self nodeFromMenuItem:sender]; + if (node) { + std::vector<const BookmarkNode*> nodes; + nodes.push_back(node); + bookmark_utils::CopyToClipboard(bookmarkModel_, nodes, true); + } +} + +- (IBAction)copyBookmark:(id)sender { + const BookmarkNode* node = [self nodeFromMenuItem:sender]; + if (node) { + std::vector<const BookmarkNode*> nodes; + nodes.push_back(node); + bookmark_utils::CopyToClipboard(bookmarkModel_, nodes, false); + } +} + +// Paste the copied node immediately after the node for which the context +// menu has been presented if the node is a non-folder bookmark, otherwise +// past at the end of the folder node. +- (IBAction)pasteBookmark:(id)sender { + const BookmarkNode* node = [self nodeFromMenuItem:sender]; + if (node) { + int index = -1; + if (node != bookmarkModel_->GetBookmarkBarNode() && !node->is_folder()) { + const BookmarkNode* parent = node->GetParent(); + index = parent->IndexOfChild(node) + 1; + if (index > parent->GetChildCount()) + index = -1; + node = parent; + } + bookmark_utils::PasteFromClipboard(bookmarkModel_, node, index); + } +} + +- (IBAction)deleteBookmark:(id)sender { + const BookmarkNode* node = [self nodeFromMenuItem:sender]; + if (node) { + bookmarkModel_->Remove(node->GetParent(), + node->GetParent()->IndexOfChild(node)); + } +} + +- (IBAction)openAllBookmarks:(id)sender { + const BookmarkNode* node = [self nodeFromMenuItem:sender]; + if (node) { + [self openAll:node disposition:NEW_FOREGROUND_TAB]; + UserMetrics::RecordAction(UserMetricsAction("OpenAllBookmarks"), + browser_->profile()); + } +} + +- (IBAction)openAllBookmarksNewWindow:(id)sender { + const BookmarkNode* node = [self nodeFromMenuItem:sender]; + if (node) { + [self openAll:node disposition:NEW_WINDOW]; + UserMetrics::RecordAction(UserMetricsAction("OpenAllBookmarksNewWindow"), + browser_->profile()); + } +} + +- (IBAction)openAllBookmarksIncognitoWindow:(id)sender { + const BookmarkNode* node = [self nodeFromMenuItem:sender]; + if (node) { + [self openAll:node disposition:OFF_THE_RECORD]; + UserMetrics::RecordAction( + UserMetricsAction("OpenAllBookmarksIncognitoWindow"), + browser_->profile()); + } +} + +// May be called from the bar or from a folder button. +// If called from a button, that button becomes the parent. +- (IBAction)addPage:(id)sender { + const BookmarkNode* parent = [self nodeFromMenuItem:sender]; + if (!parent) + parent = bookmarkModel_->GetBookmarkBarNode(); + BookmarkEditor::Show([[self view] window], + browser_->profile(), + parent, + BookmarkEditor::EditDetails(), + BookmarkEditor::SHOW_TREE); +} + +// Might be called from the context menu over the bar OR over a +// button. If called from a button, that button becomes a sibling of +// the new node. If called from the bar, add to the end of the bar. +- (IBAction)addFolder:(id)sender { + const BookmarkNode* senderNode = [self nodeFromMenuItem:sender]; + const BookmarkNode* parent = NULL; + int newIndex = 0; + // If triggered from the bar, folder or "others" folder - add as a child to + // the end. + // If triggered from a bookmark, add as next sibling. + BookmarkNode::Type type = senderNode->type(); + if (type == BookmarkNode::BOOKMARK_BAR || + type == BookmarkNode::OTHER_NODE || + type == BookmarkNode::FOLDER) { + parent = senderNode; + newIndex = parent->GetChildCount(); + } else { + parent = senderNode->GetParent(); + newIndex = parent->IndexOfChild(senderNode) + 1; + } + BookmarkNameFolderController* controller = + [[BookmarkNameFolderController alloc] + initWithParentWindow:[[self view] window] + profile:browser_->profile() + parent:parent + newIndex:newIndex]; + [controller runAsModalSheet]; +} + - (IBAction)importBookmarks:(id)sender { [ImportSettingsDialogController showImportSettingsDialogForProfile: browser_->profile()]; } +#pragma mark Private Methods + +// Called after the current theme has changed. +- (void)themeDidChangeNotification:(NSNotification*)aNotification { + ThemeProvider* themeProvider = + static_cast<ThemeProvider*>([[aNotification object] pointerValue]); + [self updateTheme:themeProvider]; +} + // (Private) Method is the same as [self view], but is provided to be explicit. - (BackgroundGradientView*)backgroundGradientView { DCHECK([[self view] isKindOfClass:[BackgroundGradientView class]]); @@ -408,87 +678,6 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12; } } -- (BOOL)offTheSideButtonIsHidden { - return [offTheSideButton_ isHidden]; -} - -// Called when our controlled frame has changed size. -- (void)frameDidChange { - if (!bookmarkModel_->IsLoaded()) - return; - [self updateTheme:[[[self view] window] themeProvider]]; - [self reconfigureBookmarkBar]; -} - -// Close all bookmark folders. "Folder" here is the fake menu for -// bookmark folders, not a button context menu. -- (void)closeAllBookmarkFolders { - [self watchForExitEvent:NO]; - [folderController_ close]; - folderController_ = nil; -} - -- (void)closeBookmarkFolder:(id)sender { - // We're the top level, so close one means close them all. - [self closeAllBookmarkFolders]; -} - -- (void)closeFolderAndStopTrackingMenus { - showFolderMenus_ = NO; - [self closeAllBookmarkFolders]; -} - -- (BookmarkModel*)bookmarkModel { - return bookmarkModel_; -} - -// NSNotificationCenter callback. -- (void)parentWindowWillClose:(NSNotification*)notification { - [self closeFolderAndStopTrackingMenus]; -} - -// NSNotificationCenter callback. -- (void)parentWindowDidResignKey:(NSNotification*)notification { - [self closeFolderAndStopTrackingMenus]; -} - -// BookmarkButtonDelegate protocol implementation. When menus are -// "active" (e.g. you clicked to open one), moving the mouse over -// another folder button should close the 1st and open the 2nd (like -// real menus). We detect and act here. -- (void)mouseEnteredButton:(id)sender event:(NSEvent*)event { - DCHECK([sender isKindOfClass:[BookmarkButton class]]); - - // If folder menus are not being shown, do nothing. This is different from - // BookmarkBarFolderController's implementation because the bar should NOT - // automatically open folder menus when the mouse passes over a folder - // button while the BookmarkBarFolderController DOES automically open - // a subfolder menu. - if (!showFolderMenus_) - return; - - // From here down: same logic as BookmarkBarFolderController. - // TODO(jrg): find a way to share these 4 non-comment lines? - // http://crbug.com/35966 - // If already opened, then we exited but re-entered the button, so do nothing. - if ([folderController_ parentButton] == sender) - return; - // Else open a new one if it makes sense to do so. - if ([sender bookmarkNode]->is_folder()) { - [folderTarget_ openBookmarkFolderFromButton:sender]; - } else { - // We're over a non-folder bookmark so close any old folders. - [folderController_ close]; - folderController_ = nil; - } -} - -// BookmarkButtonDelegate protocol implementation. -- (void)mouseExitedButton:(id)sender event:(NSEvent*)event { - // Don't care; do nothing. - // This is different behavior that the folder menus. -} - // Begin (or end) watching for a click outside this window. Unlike // normal NSWindows, bookmark folder "fake menu" windows do not become // key or main. Thus, traditional notification (e.g. WillResignKey) @@ -509,72 +698,6 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12; watchingForExitEvent_ = watch; } -// Implementation of CrApplicationEventHookProtocol. -// NOT an override of a standard Cocoa call made to NSViewControllers. -- (void)hookForEvent:(NSEvent*)theEvent { - if ([self isEventAnExitEvent:theEvent]) - [self closeFolderAndStopTrackingMenus]; -} - -// Return YES if the event indicates an exit from the bookmark bar -// folder menus. E.g. "click outside" of the area we are watching. -// At this time we are watching the area that includes all popup -// bookmark folder windows. -- (BOOL)isEventAnExitEvent:(NSEvent*)event { - NSWindow* eventWindow = [event window]; - NSWindow* myWindow = [[self view] window]; - switch ([event type]) { - case NSLeftMouseDown: - case NSRightMouseDown: - // If the click is in my window but NOT in the bookmark bar, consider - // it a click 'outside'. Clicks directly on an active button (i.e. one - // that is a folder and for which its folder menu is showing) are 'in'. - // All other clicks on the bookmarks bar are counted as 'outside' - // because they should close any open bookmark folder menu. - if (eventWindow == myWindow) { - NSView* hitView = - [[eventWindow contentView] hitTest:[event locationInWindow]]; - if (hitView == [folderController_ parentButton]) - return NO; - if (![hitView isDescendantOf:[self view]] || hitView == buttonView_) - return YES; - } - // If a click in a bookmark bar folder window and that isn't - // one of my bookmark bar folders, YES is click outside. - if (![eventWindow isKindOfClass:[BookmarkBarFolderWindow - class]]) { - return YES; - } - break; - case NSKeyDown: - case NSKeyUp: - // Any key press ends things. - return YES; - default: - break; - } - return NO; -} - -// Exposed for testing. -- (id)folderTarget { - return folderTarget_.get(); -} - -- (int)displayedButtonCount { - return displayedButtonCount_; -} - -- (NSMenu*)buttonContextMenu { - return buttonContextMenu_; -} - -// Intentionally ignores ownership issues; used for testing and we try -// to minimize touching the object passed in (likely a mock). -- (void)setButtonContextMenu:(id)menu { - buttonContextMenu_ = menu; -} - // Keep the "no items" label centered in response to a frame size change. - (void)centerNoItemsLabel { // Note that this computation is done in the parent's coordinate system, @@ -587,23 +710,6 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12; [[buttonView_ noItemContainer] setFrameOrigin:NSMakePoint(0, yoffset)]; } -// Change the layout of the bookmark bar's subviews in response to a visibility -// change (e.g., show or hide the bar) or style change (attached or floating). -- (void)layoutSubviews { - NSRect frame = [[self view] frame]; - NSRect buttonViewFrame = NSMakeRect(0, 0, NSWidth(frame), NSHeight(frame)); - - // The state of our morph (if any); 1 is total bubble, 0 is the regular bar. - CGFloat morph = [self detachedMorphProgress]; - - // Add padding to the detached bookmark bar. - buttonViewFrame = NSInsetRect(buttonViewFrame, - morph * bookmarks::kNTPBookmarkBarPadding, - morph * bookmarks::kNTPBookmarkBarPadding); - - [buttonView_ setFrame:buttonViewFrame]; -} - // (Private) - (void)showBookmarkBarWithAnimation:(BOOL)animate { if (animate && !ignoreAnimations_) { @@ -674,432 +780,6 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12; return YES; } -// We don't change a preference; we only change visibility. Preference changing -// (global state) is handled in |BrowserWindowCocoa::ToggleBookmarkBar()|. We -// simply update based on what we're told. -- (void)updateVisibility { - [self showBookmarkBarWithAnimation:NO]; -} - -- (void)setBookmarkBarEnabled:(BOOL)enabled { - if (enabled != barIsEnabled_) { - barIsEnabled_ = enabled; - [self updateVisibility]; - } -} - -- (CGFloat)getDesiredToolbarHeightCompression { - // Some special cases.... - if (!barIsEnabled_) - return 0; - - if ([self isAnimationRunning]) { - // No toolbar compression when animating between hidden and showing, nor - // between showing and detached. - if ([self isAnimatingBetweenState:bookmarks::kHiddenState - andState:bookmarks::kShowingState] || - [self isAnimatingBetweenState:bookmarks::kShowingState - andState:bookmarks::kDetachedState]) - return 0; - - // If we ever need any other animation cases, code would go here. - } - - return [self isInState:bookmarks::kShowingState] ? kBookmarkBarOverlap : 0; -} - -- (CGFloat)toolbarDividerOpacity { - // Some special cases.... - if ([self isAnimationRunning]) { - // In general, the toolbar shouldn't show a divider while we're animating - // between showing and hidden. The exception is when our height is < 1, in - // which case we can't draw it. It's all-or-nothing (no partial opacity). - if ([self isAnimatingBetweenState:bookmarks::kHiddenState - andState:bookmarks::kShowingState]) - return (NSHeight([[self view] frame]) < 1) ? 1 : 0; - - // The toolbar should show the divider when animating between showing and - // detached (but opacity will vary). - if ([self isAnimatingBetweenState:bookmarks::kShowingState - andState:bookmarks::kDetachedState]) - return static_cast<CGFloat>([self detachedMorphProgress]); - - // If we ever need any other animation cases, code would go here. - } - - // In general, only show the divider when it's in the normal showing state. - return [self isInState:bookmarks::kShowingState] ? 0 : 1; -} - -// TODO(mrossetti): Duplicate code with BookmarkBarFolderController. -// http://crbug.com/35966 -- (BOOL)addURLs:(NSArray*)urls withTitles:(NSArray*)titles at:(NSPoint)point { - DCHECK([urls count] == [titles count]); - BOOL nodesWereAdded = NO; - // Figure out where these new bookmarks nodes are to be added. - BookmarkButton* button = [self buttonForDroppingOnAtPoint:point]; - const BookmarkNode* destParent = NULL; - int destIndex = 0; - if ([button isFolder]) { - destParent = [button bookmarkNode]; - // Drop it at the end. - destIndex = [button bookmarkNode]->GetChildCount(); - } else { - // Else we're dropping somewhere on the bar, so find the right spot. - destParent = bookmarkModel_->GetBookmarkBarNode(); - destIndex = [self indexForDragToPoint:point]; - } - - // Don't add the bookmarks if the destination index shows an error. - if (destIndex >= 0) { - // Create and add the new bookmark nodes. - size_t urlCount = [urls count]; - for (size_t i = 0; i < urlCount; ++i) { - // URLs come in several forms (see NSPasteboard+Utils.mm). - GURL gurl; - const char* string = [[urls objectAtIndex:i] UTF8String]; - if (string) - gurl = GURL(string); - if (!gurl.is_valid() && string) { - gurl = GURL([[NSString stringWithFormat:@"file://%s", string] - UTF8String]); - } - DCHECK(gurl.is_valid()); - if (gurl.is_valid()) { - bookmarkModel_->AddURL(destParent, - destIndex++, - base::SysNSStringToWide([titles - objectAtIndex:i]), - gurl); - nodesWereAdded = YES; - } - } - } - return nodesWereAdded; -} - -- (int)indexForDragToPoint:(NSPoint)point { - // TODO(jrg): revisit position info based on UI team feedback. - // dropLocation is in bar local coordinates. - NSPoint dropLocation = - [[self view] convertPoint:point - fromView:[[[self view] window] contentView]]; - BookmarkButton* buttonToTheRightOfDraggedButton = nil; - for (BookmarkButton* button in buttons_.get()) { - CGFloat midpoint = NSMidX([button frame]); - if (dropLocation.x <= midpoint) { - buttonToTheRightOfDraggedButton = button; - break; - } - } - if (buttonToTheRightOfDraggedButton) { - const BookmarkNode* afterNode = - [buttonToTheRightOfDraggedButton bookmarkNode]; - 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, displayedButtonCount_); - } - - // If nothing is to my right I am at the end! - return displayedButtonCount_; -} - -// TODO(mrossetti,jrg): Yet more code dup with BookmarkBarFolderController. -// http://crbug.com/35966 -- (BOOL)dragButton:(BookmarkButton*)sourceButton - to:(NSPoint)point - copy:(BOOL)copy { - DCHECK([sourceButton isKindOfClass:[BookmarkButton class]]); - const BookmarkNode* sourceNode = [sourceButton bookmarkNode]; - return [self dragBookmark:sourceNode to:point copy:copy]; -} - -// TODO(mrossetti,jrg): Yet more duplicated code. -// http://crbug.com/35966 -- (BOOL)dragBookmark:(const BookmarkNode*)sourceNode - to:(NSPoint)point - copy:(BOOL)copy { - DCHECK(sourceNode); - // Drop destination. - const BookmarkNode* destParent = NULL; - int destIndex = 0; - - // First check if we're dropping on a button. If we have one, and - // it's a folder, drop in it. - BookmarkButton* button = [self buttonForDroppingOnAtPoint:point]; - if ([button isFolder]) { - destParent = [button bookmarkNode]; - // Drop it at the end. - destIndex = [button bookmarkNode]->GetChildCount(); - } else { - // Else we're dropping somewhere on the bar, so find the right spot. - destParent = bookmarkModel_->GetBookmarkBarNode(); - destIndex = [self indexForDragToPoint:point]; - } - - // Be sure we don't try and drop a folder into itself. - if (sourceNode != destParent) { - if (copy) - bookmarkModel_->Copy(sourceNode, destParent, destIndex); - else - 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. - - return YES; -} - -// Find something like std::is_between<T>? I can't believe one doesn't exist. -static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) { - return ((value >= low) && (value <= high)); -} - -// Return the proposed drop target for a hover open button from the -// given array, or nil if none. We use this for distinguishing -// between a hover-open candidate or drop-indicator draw. -// Helper for buttonForDroppingOnAtPoint:. -// Get UI review on "middle half" ness. -// http://crbug.com/36276 -- (BookmarkButton*)buttonForDroppingOnAtPoint:(NSPoint)point - fromArray:(NSArray*)array { - for (BookmarkButton* button in array) { - // Break early if we've gone too far. - if ((NSMinX([button frame]) > point.x) || (![button superview])) - return nil; - // Careful -- this only applies to the bar with horiz buttons. - // Intentionally NOT using NSPointInRect() so that scrolling into - // a submenu doesn't cause it to be closed. - if (ValueInRangeInclusive(NSMinX([button frame]), - point.x, - NSMaxX([button frame]))) { - // Over a button but let's be a little more specific (make sure - // it's over the middle half, not just over it). - NSRect frame = [button frame]; - NSRect middleHalfOfButton = NSInsetRect(frame, frame.size.width / 4, 0); - if (ValueInRangeInclusive(NSMinX(middleHalfOfButton), - point.x, - NSMaxX(middleHalfOfButton))) { - // It makes no sense to drop on a non-folder; there is no hover. - if (![button isFolder]) - return nil; - // Got it! - return button; - } else { - // Over a button but not over the middle half. - return nil; - } - } - } - // Not hovering over a button. - return nil; -} - -// Return the proposed drop target for a hover open button, or nil if -// none. Works with both the bookmark buttons and the "Other -// Bookmarks" button. Point is in [self view] coordinates. -- (BookmarkButton*)buttonForDroppingOnAtPoint:(NSPoint)point { - point = [[self view] convertPoint:point - fromView:[[[self view] window] contentView]]; - BookmarkButton* button = [self buttonForDroppingOnAtPoint:point - fromArray:buttons_.get()]; - // One more chance -- try "Other Bookmarks" and "off the side" (if visible). - // This is different than BookmarkBarFolderController. - if (!button) { - NSMutableArray* array = [NSMutableArray array]; - if (![self offTheSideButtonIsHidden]) - [array addObject:offTheSideButton_]; - [array addObject:otherBookmarksButton_]; - button = [self buttonForDroppingOnAtPoint:point - fromArray:array]; - } - return button; -} - -// TODO(jrg): much of this logic is duped with -// [BookmarkBarFolderController draggingEntered:] except when noted. -// http://crbug.com/35966 -- (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 - } - if (hoverButton_) { - // Oops, another one triggered or open. - [NSObject cancelPreviousPerformRequestsWithTarget:[hoverButton_ - target]]; - // Unlike BookmarkBarFolderController, we do not delay the close - // of the previous one. Given the lack of diagonal movement, - // there is no need, and it feels awkward to do so. See - // comments about kDragHoverCloseDelay in - // bookmark_bar_folder_controller.mm for more details. - [[hoverButton_ target] closeBookmarkFolder:hoverButton_]; - hoverButton_.reset(); - } - hoverButton_.reset([button retain]); - DCHECK([[hoverButton_ target] - respondsToSelector:@selector(openBookmarkFolderFromButton:)]); - [[hoverButton_ target] - performSelector:@selector(openBookmarkFolderFromButton:) - withObject:hoverButton_ - afterDelay:bookmarks::kDragHoverOpenDelay]; - } - if (!button) { - if (hoverButton_) { - [NSObject cancelPreviousPerformRequestsWithTarget:[hoverButton_ target]]; - [[hoverButton_ target] closeBookmarkFolder:hoverButton_]; - hoverButton_.reset(); - } - } - - // Thrown away but kept to be consistent with the draggingEntered: interface. - return NSDragOperationMove; -} - -- (void)draggingExited:(id<NSDraggingInfo>)info { - // NOT the same as a cancel --> we may have moved the mouse into the submenu. - if (hoverButton_) { - [NSObject cancelPreviousPerformRequestsWithTarget:[hoverButton_ target]]; - hoverButton_.reset(); - } -} - -- (void)draggingEnded:(id<NSDraggingInfo>)info { - [self closeFolderAndStopTrackingMenus]; -} - -// Return YES if we should show the drop indicator, else NO. -- (BOOL)shouldShowIndicatorShownForPoint:(NSPoint)point { - return ![self buttonForDroppingOnAtPoint:point]; -} - -// Return the x position for a drop indicator. -- (CGFloat)indicatorPosForDragToPoint:(NSPoint)point { - CGFloat x = 0; - int destIndex = [self indexForDragToPoint:point]; - int numButtons = displayedButtonCount_; - - // If it's a drop strictly between existing buttons ... - if (destIndex >= 0 && destIndex < numButtons) { - // ... put the indicator right between the buttons. - BookmarkButton* button = - [buttons_ objectAtIndex:static_cast<NSUInteger>(destIndex)]; - DCHECK(button); - NSRect buttonFrame = [button frame]; - x = buttonFrame.origin.x - 0.5 * bookmarks::kBookmarkHorizontalPadding; - - // If it's a drop at the end (past the last button, if there are any) ... - } else if (destIndex == numButtons) { - // and if it's past the last button ... - if (numButtons > 0) { - // ... find the last button, and put the indicator to its right. - BookmarkButton* button = - [buttons_ objectAtIndex:static_cast<NSUInteger>(destIndex - 1)]; - DCHECK(button); - NSRect buttonFrame = [button frame]; - x = NSMaxX(buttonFrame) + 0.5 * bookmarks::kBookmarkHorizontalPadding; - - // Otherwise, put it right at the beginning. - } else { - x = 0.5 * bookmarks::kBookmarkHorizontalPadding; - } - } else { - NOTREACHED(); - } - - return x; -} - -- (int)currentTabContentsHeight { - return browser_->GetSelectedTabContents() ? - browser_->GetSelectedTabContents()->view()->GetContainerSize().height() : - 0; -} - -- (ThemeProvider*)themeProvider { - return browser_->profile()->GetThemeProvider(); -} - -- (void)childFolderWillShow:(id<BookmarkButtonControllerProtocol>)child { - // If the bookmarkbar is not in detached mode, lock bar visibility, forcing - // the overlay to stay open when in fullscreen mode. - if (![self isInState:bookmarks::kDetachedState] && - ![self isAnimatingToState:bookmarks::kDetachedState]) { - BrowserWindowController* browserController = - [BrowserWindowController browserWindowControllerForView:[self view]]; - [browserController lockBarVisibilityForOwner:child - withAnimation:NO - delay:NO]; - } -} - -- (void)childFolderWillClose:(id<BookmarkButtonControllerProtocol>)child { - // Release bar visibility, allowing the overlay to close if in fullscreen - // mode. - BrowserWindowController* browserController = - [BrowserWindowController browserWindowControllerForView:[self view]]; - [browserController releaseBarVisibilityForOwner:child - withAnimation:NO - delay:NO]; -} - -- (BOOL)dragShouldLockBarVisibility { - return ![self isInState:bookmarks::kDetachedState] && - ![self isAnimatingToState:bookmarks::kDetachedState]; -} - -- (std::vector<const BookmarkNode*>)retrieveBookmarkDragDataNodes { - std::vector<const BookmarkNode*> dragDataNodes; - BookmarkDragData dragData; - if(dragData.ReadFromDragClipboard()) { - BookmarkModel* bookmarkModel = [self bookmarkModel]; - Profile* profile = bookmarkModel->profile(); - std::vector<const BookmarkNode*> nodes(dragData.GetNodes(profile)); - dragDataNodes.assign(nodes.begin(), nodes.end()); - } - return dragDataNodes; -} - -- (BOOL)dragBookmarkData:(id<NSDraggingInfo>)info { - BOOL dragged = NO; - std::vector<const BookmarkNode*> nodes([self retrieveBookmarkDragDataNodes]); - if (nodes.size()) { - BOOL copy = !([info draggingSourceOperationMask] & NSDragOperationMove); - NSPoint dropPoint = [info draggingLocation]; - for (std::vector<const BookmarkNode*>::const_iterator it = nodes.begin(); - it != nodes.end(); ++it) { - const BookmarkNode* sourceNode = *it; - dragged = [self dragBookmark:sourceNode to:dropPoint copy:copy]; - } - } - return dragged; -} - -- (NSWindow*)browserWindow { - return [[self view] window]; -} - // Enable or disable items. We are the menu delegate for both the bar // and for bookmark folder buttons. - (BOOL)validateUserInterfaceItem:(id)item { @@ -1172,27 +852,6 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) { browser_->OpenURL(url, GURL(), disposition, PageTransition::AUTO_BOOKMARK); } -- (IBAction)openBookmark:(id)sender { - [self closeFolderAndStopTrackingMenus]; - DCHECK([sender respondsToSelector:@selector(bookmarkNode)]); - const BookmarkNode* node = [sender bookmarkNode]; - WindowOpenDisposition disposition = - event_utils::WindowOpenDispositionFromNSEvent([NSApp currentEvent]); - [self openURL:node->GetURL() disposition:disposition]; -} - -// Given a NSMenuItem tag, return the appropriate bookmark node id. -- (int64)nodeIdFromMenuTag:(int32)tag { - return menuTagMap_[tag]; -} - -// Create and return a new tag for the given node id. -- (int32)menuTagFromNodeId:(int64)menuid { - int tag = seedId_++; - menuTagMap_[tag] = menuid; - return tag; -} - - (void)clearMenuTagMap { seedId_ = 0; menuTagMap_.clear(); @@ -1218,23 +877,6 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) { } } -// Redirect to our logic shared with BookmarkBarFolderController. -- (IBAction)openBookmarkFolderFromButton:(id)sender { - // Toggle presentation of bar folder menus. - showFolderMenus_ = !showFolderMenus_; - [folderTarget_ openBookmarkFolderFromButton:sender]; -} - -// The button that sends this one is special; the "off the side" -// button (chevron) opens like a folder button but isn't exactly a -// parent folder. -- (IBAction)openOffTheSideFolderFromButton:(id)sender { - DCHECK([sender isKindOfClass:[BookmarkButton class]]); - DCHECK([[sender cell] isKindOfClass:[BookmarkButtonCell class]]); - [[sender cell] setStartingChildIndex:displayedButtonCount_]; - [folderTarget_ openBookmarkFolderFromButton:sender]; -} - // Recursively add the given bookmark node and all its children to // menu, one menu item per node. - (void)addNode:(const BookmarkNode*)child toMenu:(NSMenu*)menu { @@ -1298,255 +940,6 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) { return menu; } -// Add a new folder controller as triggered by the given folder button. -- (void)addNewFolderControllerWithParentButton:(BookmarkButton*)parentButton { - if (folderController_) - [self closeAllBookmarkFolders]; - - // Folder controller, like many window controllers, owns itself. - folderController_ = - [[BookmarkBarFolderController alloc] initWithParentButton:parentButton - parentController:nil - barController:self]; - [folderController_ showWindow:self]; - - // Only BookmarkBarController has this; the - // BookmarkBarFolderController does not. - [self watchForExitEvent:YES]; -} - -// As a convention we set the menu's delegate to be the button's cell -// so we can easily obtain bookmark info. Convention applied in -// -[BookmarkButtonCell menu]. - -- (IBAction)openBookmarkInNewForegroundTab:(id)sender { - const BookmarkNode* node = [self nodeFromMenuItem:sender]; - if (node) - [self openURL:node->GetURL() disposition:NEW_FOREGROUND_TAB]; -} - -- (IBAction)openBookmarkInNewWindow:(id)sender { - const BookmarkNode* node = [self nodeFromMenuItem:sender]; - if (node) - [self openURL:node->GetURL() disposition:NEW_WINDOW]; -} - -- (IBAction)openBookmarkInIncognitoWindow:(id)sender { - const BookmarkNode* node = [self nodeFromMenuItem:sender]; - if (node) - [self openURL:node->GetURL() disposition:OFF_THE_RECORD]; -} - -- (IBAction)editBookmark:(id)sender { - const BookmarkNode* node = [self nodeFromMenuItem:sender]; - if (!node) - return; - - if (node->is_folder()) { - BookmarkNameFolderController* controller = - [[BookmarkNameFolderController alloc] - initWithParentWindow:[[self view] window] - profile:browser_->profile() - node:node]; - [controller runAsModalSheet]; - return; - } - - // There is no real need to jump to a platform-common routine at - // this point (which just jumps back to objc) other than consistency - // across platforms. - // - // TODO(jrg): identify when we NO_TREE. I can see it in the code - // for the other platforms but can't find a way to trigger it in the - // UI. - BookmarkEditor::Show([[self view] window], - browser_->profile(), - node->GetParent(), - BookmarkEditor::EditDetails(node), - BookmarkEditor::SHOW_TREE); -} - -- (IBAction)cutBookmark:(id)sender { - const BookmarkNode* node = [self nodeFromMenuItem:sender]; - if (node) { - std::vector<const BookmarkNode*> nodes; - nodes.push_back(node); - bookmark_utils::CopyToClipboard(bookmarkModel_, nodes, true); - } -} - -- (IBAction)copyBookmark:(id)sender { - const BookmarkNode* node = [self nodeFromMenuItem:sender]; - if (node) { - std::vector<const BookmarkNode*> nodes; - nodes.push_back(node); - bookmark_utils::CopyToClipboard(bookmarkModel_, nodes, false); - } -} - -// Paste the copied node immediately after the node for which the context -// menu has been presented if the node is a non-folder bookmark, otherwise -// past at the end of the folder node. -- (IBAction)pasteBookmark:(id)sender { - const BookmarkNode* node = [self nodeFromMenuItem:sender]; - if (node) { - int index = -1; - if (node != bookmarkModel_->GetBookmarkBarNode() && !node->is_folder()) { - const BookmarkNode* parent = node->GetParent(); - index = parent->IndexOfChild(node) + 1; - if (index > parent->GetChildCount()) - index = -1; - node = parent; - } - bookmark_utils::PasteFromClipboard(bookmarkModel_, node, index); - } -} - -- (IBAction)deleteBookmark:(id)sender { - const BookmarkNode* node = [self nodeFromMenuItem:sender]; - if (node) { - bookmarkModel_->Remove(node->GetParent(), - node->GetParent()->IndexOfChild(node)); - } -} - -// Return the BookmarkNode associated with the given NSMenuItem. Can -// return NULL which means "do nothing". One case where it would -// return NULL is if the bookmark model gets modified while you have a -// context menu open. -- (const BookmarkNode*)nodeFromMenuItem:(id)sender { - const BookmarkNode* node = NULL; - BookmarkMenu* menu = (BookmarkMenu*)[sender menu]; - if ([menu isKindOfClass:[BookmarkMenu class]]) { - int64 id = [menu id]; - node = bookmarkModel_->GetNodeByID(id); - } - return node; -} - -- (void)openAll:(const BookmarkNode*)node - disposition:(WindowOpenDisposition)disposition { - [self closeFolderAndStopTrackingMenus]; - bookmark_utils::OpenAll([[self view] window], - browser_->profile(), - browser_, - node, - disposition); -} - -- (IBAction)openAllBookmarks:(id)sender { - const BookmarkNode* node = [self nodeFromMenuItem:sender]; - if (node) { - [self openAll:node disposition:NEW_FOREGROUND_TAB]; - UserMetrics::RecordAction(UserMetricsAction("OpenAllBookmarks"), - browser_->profile()); - } -} - -- (IBAction)openAllBookmarksNewWindow:(id)sender { - const BookmarkNode* node = [self nodeFromMenuItem:sender]; - if (node) { - [self openAll:node disposition:NEW_WINDOW]; - UserMetrics::RecordAction(UserMetricsAction("OpenAllBookmarksNewWindow"), - browser_->profile()); - } -} - -- (IBAction)openAllBookmarksIncognitoWindow:(id)sender { - const BookmarkNode* node = [self nodeFromMenuItem:sender]; - if (node) { - [self openAll:node disposition:OFF_THE_RECORD]; - UserMetrics::RecordAction( - UserMetricsAction("OpenAllBookmarksIncognitoWindow"), - browser_->profile()); - } -} - -// May be called from the bar or from a folder button. -// If called from a button, that button becomes the parent. -- (IBAction)addPage:(id)sender { - const BookmarkNode* parent = [self nodeFromMenuItem:sender]; - if (!parent) - parent = bookmarkModel_->GetBookmarkBarNode(); - BookmarkEditor::Show([[self view] window], - browser_->profile(), - parent, - BookmarkEditor::EditDetails(), - BookmarkEditor::SHOW_TREE); -} - -// Might be called from the context menu over the bar OR over a -// button. If called from a button, that button becomes a sibling of -// the new node. If called from the bar, add to the end of the bar. -- (IBAction)addFolder:(id)sender { - const BookmarkNode* senderNode = [self nodeFromMenuItem:sender]; - const BookmarkNode* parent = NULL; - int newIndex = 0; - // If triggered from the bar, folder or "others" folder - add as a child to - // the end. - // If triggered from a bookmark, add as next sibling. - BookmarkNode::Type type = senderNode->type(); - if (type == BookmarkNode::BOOKMARK_BAR || - type == BookmarkNode::OTHER_NODE || - type == BookmarkNode::FOLDER) { - parent = senderNode; - newIndex = parent->GetChildCount(); - } else { - parent = senderNode->GetParent(); - newIndex = parent->IndexOfChild(senderNode) + 1; - } - BookmarkNameFolderController* controller = - [[BookmarkNameFolderController alloc] - initWithParentWindow:[[self view] window] - profile:browser_->profile() - parent:parent - newIndex:newIndex]; - [controller runAsModalSheet]; -} - -- (BookmarkBarView*)buttonView { - return buttonView_; -} - -// Delete all buttons (bookmarks, chevron, "other bookmarks") from the -// bookmark bar; reset knowledge of bookmarks. -- (void)clearBookmarkBar { - [buttons_ makeObjectsPerformSelector:@selector(removeFromSuperview)]; - [buttons_ removeAllObjects]; - [self clearMenuTagMap]; - displayedButtonCount_ = 0; - - // Make sure there are no stale pointers in the pasteboard. This - // can be important if a bookmark is deleted (via bookmark sync) - // while in the middle of a drag. The "drag completed" code - // (e.g. [BookmarkBarView performDragOperationForBookmarkButton:]) is - // careful enough to bail if there is no data found at "drop" time. - // - // Unfortunately the clearContents selector is 10.6 only. The best - // we can do is make sure something else is present in place of the - // stale bookmark. - NSPasteboard* pboard = [NSPasteboard pasteboardWithName:NSDragPboard]; - [pboard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:self]; - [pboard setString:@"" forType:NSStringPboardType]; -} - -// Return an autoreleased NSCell suitable for a bookmark button. -// 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 = node && node->is_folder() ? buttonFolderContextMenu_ : - buttonContextMenu_; - BookmarkButtonCell* cell = [BookmarkButtonCell buttonCellForNode:node - contextMenu:menu - cellText:nil - cellImage:image]; - - // Note: a quirk of setting a cell's text color is that it won't work - // until the cell is associated with a button, so we can't theme the cell yet. - - return cell; -} - // 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 @@ -1557,53 +950,6 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) { return std::min(desired, bookmarks::kDefaultBookmarkWidth); } -// Returns a frame appropriate for the given bookmark cell, suitable -// for creating an NSButton that will contain it. |xOffset| is the X -// offset for the frame; it is increased to be an appropriate X offset -// for the next button. -- (NSRect)frameForBookmarkButtonFromCell:(NSCell*)cell - xOffset:(int*)xOffset { - DCHECK(xOffset); - NSRect bounds = [buttonView_ bounds]; - bounds.size.height = bookmarks::kBookmarkButtonHeight; - - NSRect frame = NSInsetRect(bounds, - bookmarks::kBookmarkHorizontalPadding, - bookmarks::kBookmarkVerticalPadding); - frame.size.width = [self widthForBookmarkButtonCell:cell]; - - // Add an X offset based on what we've already done - frame.origin.x += *xOffset; - - // And up the X offset for next time. - *xOffset = NSMaxX(frame); - - return frame; -} - -// A bookmark button's contents changed. Check for growth -// (e.g. increase the width up to the maximum). If we grew, move -// other bookmark buttons over. -- (void)checkForBookmarkButtonGrowth:(NSButton*)button { - NSRect frame = [button frame]; - CGFloat desiredSize = [self widthForBookmarkButtonCell:[button cell]]; - CGFloat delta = desiredSize - frame.size.width; - if (delta) { - frame.size.width = desiredSize; - [button setFrame:frame]; - for (NSButton* button in buttons_.get()) { - NSRect buttonFrame = [button frame]; - if (buttonFrame.origin.x > frame.origin.x) { - buttonFrame.origin.x += delta; - [button setFrame:buttonFrame]; - } - } - } - // We may have just crossed a threshold to enable the off-the-side - // button. - [self configureOffTheSideButtonContentsAndVisibility]; -} - - (IBAction)openBookmarkMenuItem:(id)sender { int64 tag = [self nodeIdFromMenuTag:[sender tag]]; const BookmarkNode* node = bookmarkModel_->GetNodeByID(tag); @@ -1737,33 +1083,6 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) { [self positionOffTheSideButton]; } -// TODO(jrg): for now this is brute force. -- (void)loaded:(BookmarkModel*)model { - DCHECK(model == bookmarkModel_); - if (!model->IsLoaded()) - return; - - // If this is a rebuild request while we have a folder open, close it. - // TODO(mrossetti): Eliminate the need for this because it causes the folder - // menu to disappear after a cut/copy/paste/delete change. - // See: http://crbug.com/36614 - if (folderController_) - [self closeAllBookmarkFolders]; - - // Brute force nuke and build. - savedFrameWidth_ = NSWidth([[self view] frame]); - const BookmarkNode* node = model->GetBookmarkBarNode(); - [self clearBookmarkBar]; - [self addNodesToButtonList:node]; - [self createOtherBookmarksButton]; - [self updateTheme:[[[self view] window] themeProvider]]; - [self positionOffTheSideButton]; - [self addNonBookmarkButtonsToView]; - [self addButtonsToView]; - [self configureOffTheSideButtonContentsAndVisibility]; - [self setNodeForBarMenu]; -} - // Now that the model is loaded, set the bookmark bar root as the node // represented by the bookmark bar (default, background) menu. - (void)setNodeForBarMenu { @@ -1775,31 +1094,6 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) { [menu setRepresentedObject:[NSNumber numberWithLongLong:node->id()]]; } -- (void)beingDeleted:(BookmarkModel*)model { - // The browser may be being torn down; little is safe to do. As an - // example, it may not be safe to clear the pasteboard. - // http://crbug.com/38665 -} - -- (void)nodeMoved:(BookmarkModel*)model - oldParent:(const BookmarkNode*)oldParent oldIndex:(int)oldIndex - newParent:(const BookmarkNode*)newParent newIndex:(int)newIndex { - 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]; - } - // If we moved the only item on the "off the side" menu somewhere - // else, we may no longer need to show it. - [self configureOffTheSideButtonContentsAndVisibility]; -} - // To avoid problems with sync, changes that may impact the current // bookmark (e.g. deletion) make sure context menus are closed. This // prevents deleting a node which no longer exists. @@ -1808,99 +1102,6 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) { [buttonFolderContextMenu_ cancelTracking]; } -- (void)nodeAdded:(BookmarkModel*)model - parent:(const BookmarkNode*)newParent index:(int)newIndex { - // If a context menu is open, close it. - [self cancelMenuTracking]; - - const BookmarkNode* newNode = newParent->GetChild(newIndex); - id<BookmarkButtonControllerProtocol> newController = - [self controllerForNode:newParent]; - [newController addButtonForNode:newNode atIndex:newIndex]; - // If we go from 0 --> 1 bookmarks we may need to hide the - // "bookmarks go here" text container. - [self showOrHideNoItemContainerForNode:model->GetBookmarkBarNode()]; -} - -- (void)nodeRemoved:(BookmarkModel*)model - parent:(const BookmarkNode*)oldParent index:(int)index { - // If a context menu is open, close it. - [self cancelMenuTracking]; - - // 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]; - // If we go from 1 --> 0 bookmarks we may need to show the - // "bookmarks go here" text container. - [self showOrHideNoItemContainerForNode:model->GetBookmarkBarNode()]; - // If we deleted the only item on the "off the side" menu we no - // longer need to show it. - [self configureOffTheSideButtonContentsAndVisibility]; -} - -// TODO(jrg): for now this is brute force. -- (void)nodeChanged:(BookmarkModel*)model - node:(const BookmarkNode*)node { - [self loaded:model]; -} - -// TODO(jrg): linear searching is bad. -// Need a BookmarkNode-->NSCell mapping. -// -// TODO(jrg): if the bookmark bar is open on launch, we see the -// buttons all placed, then "scooted over" as the favicons load. If -// this looks bad I may need to change widthForBookmarkButtonCell to -// add space for an image even if not there on the assumption that -// favicons will eventually load. -- (void)nodeFavIconLoaded:(BookmarkModel*)model - node:(const BookmarkNode*)node { - for (BookmarkButton* button in buttons_.get()) { - const BookmarkNode* cellnode = [button bookmarkNode]; - if (cellnode == node) { - [[button cell] setBookmarkCellText:nil - image:[self favIconForNode:node]]; - // Adding an image means we might need more room for the - // bookmark. Test for it by growing the button (if needed) - // and shifting everything else over. - [self checkForBookmarkButtonGrowth:button]; - } - } -} - -// TODO(jrg): for now this is brute force. -- (void)nodeChildrenReordered:(BookmarkModel*)model - node:(const BookmarkNode*)node { - [self loaded:model]; -} - -- (NSMutableArray*)buttons { - return buttons_.get(); -} - -- (NSButton*)offTheSideButton { - return offTheSideButton_; -} - -- (NSButton*)otherBookmarksButton { - return otherBookmarksButton_.get(); -} - -- (NSImage*)favIconForNode:(const BookmarkNode*)node { - if (!node) - return defaultImage_; - - if (node->is_folder()) - return folderImage_; - - const SkBitmap& favIcon = bookmarkModel_->GetFavIcon(node); - if (!favIcon.isNull()) - return gfx::SkBitmapToNSImage(favIcon); - - return defaultImage_; -} - // Determines the appropriate state for the given situation. + (bookmarks::VisualState)visualStateToShowNormalBar:(BOOL)showNormalBar showDetachedBar:(BOOL)showDetachedBar { @@ -2004,6 +1205,536 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) { [self finalizeVisualState]; } +- (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 Private Methods Exposed for Testing + +- (BookmarkBarView*)buttonView { + return buttonView_; +} + +- (NSMutableArray*)buttons { + return buttons_.get(); +} + +- (NSButton*)offTheSideButton { + return offTheSideButton_; +} + +- (BOOL)offTheSideButtonIsHidden { + return [offTheSideButton_ isHidden]; +} + +- (NSButton*)otherBookmarksButton { + return otherBookmarksButton_.get(); +} + +- (BookmarkBarFolderController*)folderController { + return folderController_; +} + +- (id)folderTarget { + return folderTarget_.get(); +} + +- (int)displayedButtonCount { + return displayedButtonCount_; +} + +// Delete all buttons (bookmarks, chevron, "other bookmarks") from the +// bookmark bar; reset knowledge of bookmarks. +- (void)clearBookmarkBar { + [buttons_ makeObjectsPerformSelector:@selector(removeFromSuperview)]; + [buttons_ removeAllObjects]; + [self clearMenuTagMap]; + displayedButtonCount_ = 0; + + // Make sure there are no stale pointers in the pasteboard. This + // can be important if a bookmark is deleted (via bookmark sync) + // while in the middle of a drag. The "drag completed" code + // (e.g. [BookmarkBarView performDragOperationForBookmarkButton:]) is + // careful enough to bail if there is no data found at "drop" time. + // + // Unfortunately the clearContents selector is 10.6 only. The best + // we can do is make sure something else is present in place of the + // stale bookmark. + NSPasteboard* pboard = [NSPasteboard pasteboardWithName:NSDragPboard]; + [pboard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:self]; + [pboard setString:@"" forType:NSStringPboardType]; +} + +// Return an autoreleased NSCell suitable for a bookmark button. +// 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 = node && node->is_folder() ? buttonFolderContextMenu_ : + buttonContextMenu_; + BookmarkButtonCell* cell = [BookmarkButtonCell buttonCellForNode:node + contextMenu:menu + cellText:nil + cellImage:image]; + + // Note: a quirk of setting a cell's text color is that it won't work + // until the cell is associated with a button, so we can't theme the cell yet. + + return cell; +} + +// Returns a frame appropriate for the given bookmark cell, suitable +// for creating an NSButton that will contain it. |xOffset| is the X +// offset for the frame; it is increased to be an appropriate X offset +// for the next button. +- (NSRect)frameForBookmarkButtonFromCell:(NSCell*)cell + xOffset:(int*)xOffset { + DCHECK(xOffset); + NSRect bounds = [buttonView_ bounds]; + bounds.size.height = bookmarks::kBookmarkButtonHeight; + + NSRect frame = NSInsetRect(bounds, + bookmarks::kBookmarkHorizontalPadding, + bookmarks::kBookmarkVerticalPadding); + frame.size.width = [self widthForBookmarkButtonCell:cell]; + + // Add an X offset based on what we've already done + frame.origin.x += *xOffset; + + // And up the X offset for next time. + *xOffset = NSMaxX(frame); + + return frame; +} + +// A bookmark button's contents changed. Check for growth +// (e.g. increase the width up to the maximum). If we grew, move +// other bookmark buttons over. +- (void)checkForBookmarkButtonGrowth:(NSButton*)button { + NSRect frame = [button frame]; + CGFloat desiredSize = [self widthForBookmarkButtonCell:[button cell]]; + CGFloat delta = desiredSize - frame.size.width; + if (delta) { + frame.size.width = desiredSize; + [button setFrame:frame]; + for (NSButton* button in buttons_.get()) { + NSRect buttonFrame = [button frame]; + if (buttonFrame.origin.x > frame.origin.x) { + buttonFrame.origin.x += delta; + [button setFrame:buttonFrame]; + } + } + } + // We may have just crossed a threshold to enable the off-the-side + // button. + [self configureOffTheSideButtonContentsAndVisibility]; +} + +// Called when our controlled frame has changed size. +- (void)frameDidChange { + if (!bookmarkModel_->IsLoaded()) + return; + [self updateTheme:[[[self view] window] themeProvider]]; + [self reconfigureBookmarkBar]; +} + +// Given a NSMenuItem tag, return the appropriate bookmark node id. +- (int64)nodeIdFromMenuTag:(int32)tag { + return menuTagMap_[tag]; +} + +// Create and return a new tag for the given node id. +- (int32)menuTagFromNodeId:(int64)menuid { + int tag = seedId_++; + menuTagMap_[tag] = menuid; + return tag; +} + +// Return the BookmarkNode associated with the given NSMenuItem. Can +// return NULL which means "do nothing". One case where it would +// return NULL is if the bookmark model gets modified while you have a +// context menu open. +- (const BookmarkNode*)nodeFromMenuItem:(id)sender { + const BookmarkNode* node = NULL; + BookmarkMenu* menu = (BookmarkMenu*)[sender menu]; + if ([menu isKindOfClass:[BookmarkMenu class]]) { + int64 id = [menu id]; + node = bookmarkModel_->GetNodeByID(id); + } + return node; +} + +// Adapt appearance of buttons to the current theme. Called after +// theme changes, or when our view is added to the view hierarchy. +// Oddly, the view pings us instead of us pinging our view. This is +// because our trigger is an [NSView viewWillMoveToWindow:], which the +// controller doesn't normally know about. Otherwise we don't have +// access to the theme before we know what window we will be on. +- (void)updateTheme:(ThemeProvider*)themeProvider { + if (!themeProvider) + return; + NSColor* color = + themeProvider->GetNSColor(BrowserThemeProvider::COLOR_BOOKMARK_TEXT, + true); + for (BookmarkButton* button in buttons_.get()) { + BookmarkButtonCell* cell = [button cell]; + [cell setTextColor:color]; + } + [[otherBookmarksButton_ cell] setTextColor:color]; +} + +// Return YES if the event indicates an exit from the bookmark bar +// folder menus. E.g. "click outside" of the area we are watching. +// At this time we are watching the area that includes all popup +// bookmark folder windows. +- (BOOL)isEventAnExitEvent:(NSEvent*)event { + NSWindow* eventWindow = [event window]; + NSWindow* myWindow = [[self view] window]; + switch ([event type]) { + case NSLeftMouseDown: + case NSRightMouseDown: + // If the click is in my window but NOT in the bookmark bar, consider + // it a click 'outside'. Clicks directly on an active button (i.e. one + // that is a folder and for which its folder menu is showing) are 'in'. + // All other clicks on the bookmarks bar are counted as 'outside' + // because they should close any open bookmark folder menu. + if (eventWindow == myWindow) { + NSView* hitView = + [[eventWindow contentView] hitTest:[event locationInWindow]]; + if (hitView == [folderController_ parentButton]) + return NO; + if (![hitView isDescendantOf:[self view]] || hitView == buttonView_) + return YES; + } + // If a click in a bookmark bar folder window and that isn't + // one of my bookmark bar folders, YES is click outside. + if (![eventWindow isKindOfClass:[BookmarkBarFolderWindow + class]]) { + return YES; + } + break; + case NSKeyDown: + case NSKeyUp: + // Any key press ends things. + return YES; + default: + break; + } + return NO; +} + +#pragma mark Drag & Drop + +// Find something like std::is_between<T>? I can't believe one doesn't exist. +static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) { + return ((value >= low) && (value <= high)); +} + +// Return the proposed drop target for a hover open button from the +// given array, or nil if none. We use this for distinguishing +// between a hover-open candidate or drop-indicator draw. +// Helper for buttonForDroppingOnAtPoint:. +// Get UI review on "middle half" ness. +// http://crbug.com/36276 +- (BookmarkButton*)buttonForDroppingOnAtPoint:(NSPoint)point + fromArray:(NSArray*)array { + for (BookmarkButton* button in array) { + // Break early if we've gone too far. + if ((NSMinX([button frame]) > point.x) || (![button superview])) + return nil; + // Careful -- this only applies to the bar with horiz buttons. + // Intentionally NOT using NSPointInRect() so that scrolling into + // a submenu doesn't cause it to be closed. + if (ValueInRangeInclusive(NSMinX([button frame]), + point.x, + NSMaxX([button frame]))) { + // Over a button but let's be a little more specific (make sure + // it's over the middle half, not just over it). + NSRect frame = [button frame]; + NSRect middleHalfOfButton = NSInsetRect(frame, frame.size.width / 4, 0); + if (ValueInRangeInclusive(NSMinX(middleHalfOfButton), + point.x, + NSMaxX(middleHalfOfButton))) { + // It makes no sense to drop on a non-folder; there is no hover. + if (![button isFolder]) + return nil; + // Got it! + return button; + } else { + // Over a button but not over the middle half. + return nil; + } + } + } + // Not hovering over a button. + return nil; +} + +// Return the proposed drop target for a hover open button, or nil if +// none. Works with both the bookmark buttons and the "Other +// Bookmarks" button. Point is in [self view] coordinates. +- (BookmarkButton*)buttonForDroppingOnAtPoint:(NSPoint)point { + point = [[self view] convertPoint:point + fromView:[[[self view] window] contentView]]; + BookmarkButton* button = [self buttonForDroppingOnAtPoint:point + fromArray:buttons_.get()]; + // One more chance -- try "Other Bookmarks" and "off the side" (if visible). + // This is different than BookmarkBarFolderController. + if (!button) { + NSMutableArray* array = [NSMutableArray array]; + if (![self offTheSideButtonIsHidden]) + [array addObject:offTheSideButton_]; + [array addObject:otherBookmarksButton_]; + button = [self buttonForDroppingOnAtPoint:point + fromArray:array]; + } + return button; +} + +- (int)indexForDragToPoint:(NSPoint)point { + // TODO(jrg): revisit position info based on UI team feedback. + // dropLocation is in bar local coordinates. + NSPoint dropLocation = + [[self view] convertPoint:point + fromView:[[[self view] window] contentView]]; + BookmarkButton* buttonToTheRightOfDraggedButton = nil; + for (BookmarkButton* button in buttons_.get()) { + CGFloat midpoint = NSMidX([button frame]); + if (dropLocation.x <= midpoint) { + buttonToTheRightOfDraggedButton = button; + break; + } + } + if (buttonToTheRightOfDraggedButton) { + const BookmarkNode* afterNode = + [buttonToTheRightOfDraggedButton bookmarkNode]; + 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, displayedButtonCount_); + } + + // If nothing is to my right I am at the end! + return displayedButtonCount_; +} + +// TODO(mrossetti,jrg): Yet more duplicated code. +// http://crbug.com/35966 +- (BOOL)dragBookmark:(const BookmarkNode*)sourceNode + to:(NSPoint)point + copy:(BOOL)copy { + DCHECK(sourceNode); + // Drop destination. + const BookmarkNode* destParent = NULL; + int destIndex = 0; + + // First check if we're dropping on a button. If we have one, and + // it's a folder, drop in it. + BookmarkButton* button = [self buttonForDroppingOnAtPoint:point]; + if ([button isFolder]) { + destParent = [button bookmarkNode]; + // Drop it at the end. + destIndex = [button bookmarkNode]->GetChildCount(); + } else { + // Else we're dropping somewhere on the bar, so find the right spot. + destParent = bookmarkModel_->GetBookmarkBarNode(); + destIndex = [self indexForDragToPoint:point]; + } + + // Be sure we don't try and drop a folder into itself. + if (sourceNode != destParent) { + if (copy) + bookmarkModel_->Copy(sourceNode, destParent, destIndex); + else + 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. + + return YES; +} + +- (void)draggingEnded:(id<NSDraggingInfo>)info { + [self closeFolderAndStopTrackingMenus]; +} + +#pragma mark Bridge Notification Handlers + +// TODO(jrg): for now this is brute force. +- (void)loaded:(BookmarkModel*)model { + DCHECK(model == bookmarkModel_); + if (!model->IsLoaded()) + return; + + // If this is a rebuild request while we have a folder open, close it. + // TODO(mrossetti): Eliminate the need for this because it causes the folder + // menu to disappear after a cut/copy/paste/delete change. + // See: http://crbug.com/36614 + if (folderController_) + [self closeAllBookmarkFolders]; + + // Brute force nuke and build. + savedFrameWidth_ = NSWidth([[self view] frame]); + const BookmarkNode* node = model->GetBookmarkBarNode(); + [self clearBookmarkBar]; + [self addNodesToButtonList:node]; + [self createOtherBookmarksButton]; + [self updateTheme:[[[self view] window] themeProvider]]; + [self positionOffTheSideButton]; + [self addNonBookmarkButtonsToView]; + [self addButtonsToView]; + [self configureOffTheSideButtonContentsAndVisibility]; + [self setNodeForBarMenu]; +} + +- (void)beingDeleted:(BookmarkModel*)model { + // The browser may be being torn down; little is safe to do. As an + // example, it may not be safe to clear the pasteboard. + // http://crbug.com/38665 +} + +- (void)nodeAdded:(BookmarkModel*)model + parent:(const BookmarkNode*)newParent index:(int)newIndex { + // If a context menu is open, close it. + [self cancelMenuTracking]; + + const BookmarkNode* newNode = newParent->GetChild(newIndex); + id<BookmarkButtonControllerProtocol> newController = + [self controllerForNode:newParent]; + [newController addButtonForNode:newNode atIndex:newIndex]; + // If we go from 0 --> 1 bookmarks we may need to hide the + // "bookmarks go here" text container. + [self showOrHideNoItemContainerForNode:model->GetBookmarkBarNode()]; +} + +// TODO(jrg): for now this is brute force. +- (void)nodeChanged:(BookmarkModel*)model + node:(const BookmarkNode*)node { + [self loaded:model]; +} + +- (void)nodeMoved:(BookmarkModel*)model + oldParent:(const BookmarkNode*)oldParent oldIndex:(int)oldIndex + newParent:(const BookmarkNode*)newParent newIndex:(int)newIndex { + 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]; + } + // If we moved the only item on the "off the side" menu somewhere + // else, we may no longer need to show it. + [self configureOffTheSideButtonContentsAndVisibility]; +} + +- (void)nodeRemoved:(BookmarkModel*)model + parent:(const BookmarkNode*)oldParent index:(int)index { + // If a context menu is open, close it. + [self cancelMenuTracking]; + + // 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]; + // If we go from 1 --> 0 bookmarks we may need to show the + // "bookmarks go here" text container. + [self showOrHideNoItemContainerForNode:model->GetBookmarkBarNode()]; + // If we deleted the only item on the "off the side" menu we no + // longer need to show it. + [self configureOffTheSideButtonContentsAndVisibility]; +} + +// TODO(jrg): linear searching is bad. +// Need a BookmarkNode-->NSCell mapping. +// +// TODO(jrg): if the bookmark bar is open on launch, we see the +// buttons all placed, then "scooted over" as the favicons load. If +// this looks bad I may need to change widthForBookmarkButtonCell to +// add space for an image even if not there on the assumption that +// favicons will eventually load. +- (void)nodeFavIconLoaded:(BookmarkModel*)model + node:(const BookmarkNode*)node { + for (BookmarkButton* button in buttons_.get()) { + const BookmarkNode* cellnode = [button bookmarkNode]; + if (cellnode == node) { + [[button cell] setBookmarkCellText:nil + image:[self favIconForNode:node]]; + // Adding an image means we might need more room for the + // bookmark. Test for it by growing the button (if needed) + // and shifting everything else over. + [self checkForBookmarkButtonGrowth:button]; + } + } +} + +// TODO(jrg): for now this is brute force. +- (void)nodeChildrenReordered:(BookmarkModel*)model + node:(const BookmarkNode*)node { + [self loaded:model]; +} + +#pragma mark BookmarkBarState Protocol + // (BookmarkBarState protocol) - (BOOL)isVisible { return barIsEnabled_ && (visualState_ == bookmarks::kShowingState || @@ -2063,78 +1794,283 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) { return 0; } -// (BookmarkButtonDelegate protocol) +#pragma mark BookmarkBarToolbarViewController Protocol + +- (int)currentTabContentsHeight { + return browser_->GetSelectedTabContents() ? + browser_->GetSelectedTabContents()->view()->GetContainerSize().height() : + 0; +} + +- (ThemeProvider*)themeProvider { + return browser_->profile()->GetThemeProvider(); +} + +#pragma mark BookmarkButtonDelegate Protocol + - (void)fillPasteboard:(NSPasteboard*)pboard forDragOfButton:(BookmarkButton*)button { [[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]; +// BookmarkButtonDelegate protocol implementation. When menus are +// "active" (e.g. you clicked to open one), moving the mouse over +// another folder button should close the 1st and open the 2nd (like +// real menus). We detect and act here. +- (void)mouseEnteredButton:(id)sender event:(NSEvent*)event { + DCHECK([sender isKindOfClass:[BookmarkButton class]]); + + // If folder menus are not being shown, do nothing. This is different from + // BookmarkBarFolderController's implementation because the bar should NOT + // automatically open folder menus when the mouse passes over a folder + // button while the BookmarkBarFolderController DOES automically open + // a subfolder menu. + if (!showFolderMenus_) + return; + + // From here down: same logic as BookmarkBarFolderController. + // TODO(jrg): find a way to share these 4 non-comment lines? + // http://crbug.com/35966 + // If already opened, then we exited but re-entered the button, so do nothing. + if ([folderController_ parentButton] == sender) + return; + // Else open a new one if it makes sense to do so. + if ([sender bookmarkNode]->is_folder()) { + [folderTarget_ openBookmarkFolderFromButton:sender]; + } else { + // We're over a non-folder bookmark so close any old folders. + [folderController_ close]; + folderController_ = nil; + } } -- (void)reconfigureBookmarkBar { - [self redistributeButtonsOnBarAsNeeded]; - [self positionOffTheSideButton]; - [self configureOffTheSideButtonContentsAndVisibility]; - [self centerNoItemsLabel]; +// BookmarkButtonDelegate protocol implementation. +- (void)mouseExitedButton:(id)sender event:(NSEvent*)event { + // Don't care; do nothing. + // This is different behavior that the folder menus. } -- (void)redistributeButtonsOnBarAsNeeded { - const BookmarkNode* node = bookmarkModel_->GetBookmarkBarNode(); - NSInteger barCount = node->GetChildCount(); +- (NSWindow*)browserWindow { + return [[self view] window]; +} - // 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; +#pragma mark BookmarkButtonControllerProtocol - // 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_; +// Close all bookmark folders. "Folder" here is the fake menu for +// bookmark folders, not a button context menu. +- (void)closeAllBookmarkFolders { + [self watchForExitEvent:NO]; + [folderController_ close]; + folderController_ = nil; +} + +- (void)closeBookmarkFolder:(id)sender { + // We're the top level, so close one means close them all. + [self closeAllBookmarkFolders]; +} + +- (BookmarkModel*)bookmarkModel { + return bookmarkModel_; +} + +// TODO(jrg): much of this logic is duped with +// [BookmarkBarFolderController draggingEntered:] except when noted. +// http://crbug.com/35966 +- (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; + } } - // 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]; + if ([button isFolder]) { + if (hoverButton_ == button) { + return NSDragOperationMove; // already open or timed to open + } + if (hoverButton_) { + // Oops, another one triggered or open. + [NSObject cancelPreviousPerformRequestsWithTarget:[hoverButton_ + target]]; + // Unlike BookmarkBarFolderController, we do not delay the close + // of the previous one. Given the lack of diagonal movement, + // there is no need, and it feels awkward to do so. See + // comments about kDragHoverCloseDelay in + // bookmark_bar_folder_controller.mm for more details. + [[hoverButton_ target] closeBookmarkFolder:hoverButton_]; + hoverButton_.reset(); + } + hoverButton_.reset([button retain]); + DCHECK([[hoverButton_ target] + respondsToSelector:@selector(openBookmarkFolderFromButton:)]); + [[hoverButton_ target] + performSelector:@selector(openBookmarkFolderFromButton:) + withObject:hoverButton_ + afterDelay:bookmarks::kDragHoverOpenDelay]; + } + if (!button) { + if (hoverButton_) { + [NSObject cancelPreviousPerformRequestsWithTarget:[hoverButton_ target]]; + [[hoverButton_ target] closeBookmarkFolder:hoverButton_]; + hoverButton_.reset(); + } + } + + // Thrown away but kept to be consistent with the draggingEntered: interface. + return NSDragOperationMove; +} + +- (void)draggingExited:(id<NSDraggingInfo>)info { + // NOT the same as a cancel --> we may have moved the mouse into the submenu. + if (hoverButton_) { + [NSObject cancelPreviousPerformRequestsWithTarget:[hoverButton_ target]]; + hoverButton_.reset(); } } -#pragma mark BookmarkButtonControllerProtocol +- (BOOL)dragShouldLockBarVisibility { + return ![self isInState:bookmarks::kDetachedState] && + ![self isAnimatingToState:bookmarks::kDetachedState]; +} + +// TODO(mrossetti,jrg): Yet more code dup with BookmarkBarFolderController. +// http://crbug.com/35966 +- (BOOL)dragButton:(BookmarkButton*)sourceButton + to:(NSPoint)point + copy:(BOOL)copy { + DCHECK([sourceButton isKindOfClass:[BookmarkButton class]]); + const BookmarkNode* sourceNode = [sourceButton bookmarkNode]; + return [self dragBookmark:sourceNode to:point copy:copy]; +} + +- (BOOL)dragBookmarkData:(id<NSDraggingInfo>)info { + BOOL dragged = NO; + std::vector<const BookmarkNode*> nodes([self retrieveBookmarkDragDataNodes]); + if (nodes.size()) { + BOOL copy = !([info draggingSourceOperationMask] & NSDragOperationMove); + NSPoint dropPoint = [info draggingLocation]; + for (std::vector<const BookmarkNode*>::const_iterator it = nodes.begin(); + it != nodes.end(); ++it) { + const BookmarkNode* sourceNode = *it; + dragged = [self dragBookmark:sourceNode to:dropPoint copy:copy]; + } + } + return dragged; +} + +- (std::vector<const BookmarkNode*>)retrieveBookmarkDragDataNodes { + std::vector<const BookmarkNode*> dragDataNodes; + BookmarkDragData dragData; + if(dragData.ReadFromDragClipboard()) { + BookmarkModel* bookmarkModel = [self bookmarkModel]; + Profile* profile = bookmarkModel->profile(); + std::vector<const BookmarkNode*> nodes(dragData.GetNodes(profile)); + dragDataNodes.assign(nodes.begin(), nodes.end()); + } + return dragDataNodes; +} + +// Return YES if we should show the drop indicator, else NO. +- (BOOL)shouldShowIndicatorShownForPoint:(NSPoint)point { + return ![self buttonForDroppingOnAtPoint:point]; +} + +// Return the x position for a drop indicator. +- (CGFloat)indicatorPosForDragToPoint:(NSPoint)point { + CGFloat x = 0; + int destIndex = [self indexForDragToPoint:point]; + int numButtons = displayedButtonCount_; + + // If it's a drop strictly between existing buttons ... + if (destIndex >= 0 && destIndex < numButtons) { + // ... put the indicator right between the buttons. + BookmarkButton* button = + [buttons_ objectAtIndex:static_cast<NSUInteger>(destIndex)]; + DCHECK(button); + NSRect buttonFrame = [button frame]; + x = buttonFrame.origin.x - 0.5 * bookmarks::kBookmarkHorizontalPadding; + + // If it's a drop at the end (past the last button, if there are any) ... + } else if (destIndex == numButtons) { + // and if it's past the last button ... + if (numButtons > 0) { + // ... find the last button, and put the indicator to its right. + BookmarkButton* button = + [buttons_ objectAtIndex:static_cast<NSUInteger>(destIndex - 1)]; + DCHECK(button); + NSRect buttonFrame = [button frame]; + x = NSMaxX(buttonFrame) + 0.5 * bookmarks::kBookmarkHorizontalPadding; + + // Otherwise, put it right at the beginning. + } else { + x = 0.5 * bookmarks::kBookmarkHorizontalPadding; + } + } else { + NOTREACHED(); + } + + return x; +} + +- (void)childFolderWillShow:(id<BookmarkButtonControllerProtocol>)child { + // If the bookmarkbar is not in detached mode, lock bar visibility, forcing + // the overlay to stay open when in fullscreen mode. + if (![self isInState:bookmarks::kDetachedState] && + ![self isAnimatingToState:bookmarks::kDetachedState]) { + BrowserWindowController* browserController = + [BrowserWindowController browserWindowControllerForView:[self view]]; + [browserController lockBarVisibilityForOwner:child + withAnimation:NO + delay:NO]; + } +} + +- (void)childFolderWillClose:(id<BookmarkButtonControllerProtocol>)child { + // Release bar visibility, allowing the overlay to close if in fullscreen + // mode. + BrowserWindowController* browserController = + [BrowserWindowController browserWindowControllerForView:[self view]]; + [browserController releaseBarVisibilityForOwner:child + withAnimation:NO + delay:NO]; +} + +// Add a new folder controller as triggered by the given folder button. +- (void)addNewFolderControllerWithParentButton:(BookmarkButton*)parentButton { + if (folderController_) + [self closeAllBookmarkFolders]; + + // Folder controller, like many window controllers, owns itself. + folderController_ = + [[BookmarkBarFolderController alloc] initWithParentButton:parentButton + parentController:nil + barController:self]; + [folderController_ showWindow:self]; + + // Only BookmarkBarController has this; the + // BookmarkBarFolderController does not. + [self watchForExitEvent:YES]; +} + +- (void)openAll:(const BookmarkNode*)node + disposition:(WindowOpenDisposition)disposition { + [self closeFolderAndStopTrackingMenus]; + bookmark_utils::OpenAll([[self view] window], + browser_->profile(), + browser_, + node, + disposition); +} - (void)addButtonForNode:(const BookmarkNode*)node atIndex:(NSInteger)buttonIndex { @@ -2172,6 +2108,53 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) { } } +// TODO(mrossetti): Duplicate code with BookmarkBarFolderController. +// http://crbug.com/35966 +- (BOOL)addURLs:(NSArray*)urls withTitles:(NSArray*)titles at:(NSPoint)point { + DCHECK([urls count] == [titles count]); + BOOL nodesWereAdded = NO; + // Figure out where these new bookmarks nodes are to be added. + BookmarkButton* button = [self buttonForDroppingOnAtPoint:point]; + const BookmarkNode* destParent = NULL; + int destIndex = 0; + if ([button isFolder]) { + destParent = [button bookmarkNode]; + // Drop it at the end. + destIndex = [button bookmarkNode]->GetChildCount(); + } else { + // Else we're dropping somewhere on the bar, so find the right spot. + destParent = bookmarkModel_->GetBookmarkBarNode(); + destIndex = [self indexForDragToPoint:point]; + } + + // Don't add the bookmarks if the destination index shows an error. + if (destIndex >= 0) { + // Create and add the new bookmark nodes. + size_t urlCount = [urls count]; + for (size_t i = 0; i < urlCount; ++i) { + // URLs come in several forms (see NSPasteboard+Utils.mm). + GURL gurl; + const char* string = [[urls objectAtIndex:i] UTF8String]; + if (string) + gurl = GURL(string); + if (!gurl.is_valid() && string) { + gurl = GURL([[NSString stringWithFormat:@"file://%s", string] + UTF8String]); + } + DCHECK(gurl.is_valid()); + if (gurl.is_valid()) { + bookmarkModel_->AddURL(destParent, + destIndex++, + base::SysNSStringToWide([titles + objectAtIndex:i]), + gurl); + nodesWereAdded = YES; + } + } + } + return nodesWereAdded; +} + // TODO(mrossetti): jrg wants this broken up into smaller functions. - (void)moveButtonFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex { if (fromIndex != toIndex) { @@ -2280,6 +2263,35 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) { } } +- (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]; +} + +#pragma mark BookmarkButtonControllerProtocol + +// NOT an override of a standard Cocoa call made to NSViewControllers. +- (void)hookForEvent:(NSEvent*)theEvent { + if ([self isEventAnExitEvent:theEvent]) + [self closeFolderAndStopTrackingMenus]; +} + +#pragma mark TestingAPI Only + +- (NSMenu*)buttonContextMenu { + return buttonContextMenu_; +} + +// Intentionally ignores ownership issues; used for testing and we try +// to minimize touching the object passed in (likely a mock). +- (void)setButtonContextMenu:(id)menu { + buttonContextMenu_ = menu; +} + - (void)setIgnoreAnimations:(BOOL)ignore { ignoreAnimations_ = ignore; } diff --git a/chrome/browser/cocoa/bookmark_bar_folder_controller.mm b/chrome/browser/cocoa/bookmark_bar_folder_controller.mm index 75464f6..2f8f70f 100644 --- a/chrome/browser/cocoa/bookmark_bar_folder_controller.mm +++ b/chrome/browser/cocoa/bookmark_bar_folder_controller.mm @@ -152,6 +152,28 @@ const CGFloat kScrollWindowVerticalMargin = 0.0; [super showWindow:sender]; } +- (BookmarkButton*)parentButton { + return parentButton_.get(); +} + +- (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]; +} + +- (void)reconfigureMenu { + for (BookmarkButton* button in buttons_.get()) + [button removeFromSuperview]; + [buttons_ removeAllObjects]; + [self configureWindow]; +} + +#pragma mark Private Methods + - (BookmarkButtonCell*)cellForBookmarkNode:(const BookmarkNode*)child { NSImage* image = child ? [barController_ favIconForNode:child] : nil; NSMenu* menu = child ? child->is_folder() ? folderMenu_ : buttonMenu_ : nil; @@ -237,10 +259,6 @@ const CGFloat kScrollWindowVerticalMargin = 0.0; return mainView_; } -- (BookmarkBarFolderController*)folderController { - return folderController_; -} - - (id)folderTarget { return folderTarget_.get(); } @@ -399,15 +417,6 @@ 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 = bookmarks::kBookmarkMenuButtonMinimumWidth; @@ -509,150 +518,6 @@ const CGFloat kScrollWindowVerticalMargin = 0.0; return scrollable_; } -#pragma mark BookmarkButtonControllerProtocol - -- (void)addButtonForNode:(const BookmarkNode*)node - atIndex:(NSInteger)buttonIndex { - NSRect buttonFrame = NSMakeRect(bookmarks::kBookmarkHorizontalPadding, - bookmarks::kBookmarkVerticalPadding, - NSWidth([mainView_ frame]) - 2 * bookmarks::kBookmarkHorizontalPadding - - bookmarks::kScrollViewContentWidthMargin, - bookmarks::kBookmarkBarHeight - 2 * bookmarks::kBookmarkVerticalPadding); - // When adding a button to an empty folder we must remove the 'empty' - // placeholder button. This can be detected by checking for a parent - // child count of 1. - const BookmarkNode* parentNode = node->GetParent(); - if (parentNode->GetChildCount() == 1) { - BookmarkButton* emptyButton = [buttons_ lastObject]; - [emptyButton removeFromSuperview]; - [buttons_ removeLastObject]; - } else { - // Set us up to remember the last moved button's frame. - buttonFrame.origin.y += NSHeight([mainView_ frame]) + - bookmarks::kBookmarkBarHeight; - } - - if (buttonIndex == -1) - buttonIndex = [buttons_ count]; - - BookmarkButton* button = nil; // Remember so it can be de-highlighted. - 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. - if (parentNode->GetChildCount() > 1) - 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]; - - // Prelim height of the window. We'll trim later as needed. - int height = [buttons_ count] * bookmarks::kBookmarkButtonHeight; - [self adjustWindowForHeight:height]; -} - -- (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 - 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 && !ignoreAnimations_) - 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]; - if (buttonCount) { - 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)]; - } - } else { - // If all nodes have been removed from this folder then add in the - // 'empty' placeholder button. - NSRect buttonFrame = NSMakeRect(bookmarks::kBookmarkHorizontalPadding, - bookmarks::kBookmarkButtonHeight - - (bookmarks::kBookmarkBarHeight - - bookmarks::kBookmarkVerticalPadding), - bookmarks::kDefaultBookmarkWidth, - (bookmarks::kBookmarkBarHeight - - 2 * bookmarks::kBookmarkVerticalPadding)); - BookmarkButton* button = [self makeButtonForNode:nil - frame:buttonFrame]; - [buttons_ addObject:button]; - [mainView_ addSubview:button]; - buttonCount = 1; - } - - // Propose a height for the window. We'll trim later as needed. - int height = buttonCount * bookmarks::kBookmarkButtonHeight; - [self adjustWindowForHeight:height]; -} - -- (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]; @@ -813,51 +678,105 @@ const CGFloat kScrollWindowVerticalMargin = 0.0; scrollTrackingArea_.reset(); } -- (ThemeProvider*)themeProvider { - return [parentController_ themeProvider]; +// Delegate callback. +- (void)windowWillClose:(NSNotification*)notification { + [parentController_ childFolderWillClose:self]; + [self closeBookmarkFolder:self]; + [self autorelease]; } -- (void)childFolderWillShow:(id<BookmarkButtonControllerProtocol>)child { - // Do nothing. +// Close the old hover-open bookmark folder, and open a new one. We +// do both in one step to allow for a delay in closing the old one. +// See comments above kDragHoverCloseDelay (bookmark_bar_controller.h) +// for more details. +- (void)openBookmarkFolderFromButtonAndCloseOldOne:(id)sender { + // If an old submenu exists, close it immediately. + [self closeBookmarkFolder:sender]; + + // Open a new one if meaningful. + if ([sender isFolder]) + [folderTarget_ openBookmarkFolderFromButton:sender]; } -- (void)childFolderWillClose:(id<BookmarkButtonControllerProtocol>)child { - // Do nothing. +- (NSArray*)buttons { + return buttons_.get(); } -// Recursively close all bookmark folders. -- (void)closeAllBookmarkFolders { - // Closing the top level implicitly closes all children. - [barController_ closeAllBookmarkFolders]; +- (void)close { + [folderController_ close]; + [super close]; } -// Close our bookmark folder (a sub-controller) if we have one. -- (void)closeBookmarkFolder:(id)sender { - // folderController_ may be nil but that's OK. - [[folderController_ window] close]; - folderController_ = nil; +- (void)scrollWheel:(NSEvent *)theEvent { + if (scrollable_) { + // We go negative since an NSScrollView has a flipped coordinate frame. + CGFloat amt = kBookmarkBarFolderScrollWheelAmount * -[theEvent deltaY]; + [self performOneScroll:amt]; + } } -// Delegate callback. -- (void)windowWillClose:(NSNotification*)notification { - [parentController_ childFolderWillClose:self]; +#pragma mark Actions Forwarded to Parent BookmarkBarController + +- (IBAction)openBookmark:(id)sender { + [barController_ openBookmark:sender]; +} + +- (IBAction)openBookmarkInNewForegroundTab:(id)sender { + [barController_ openBookmarkInNewForegroundTab:sender]; +} + +- (IBAction)openBookmarkInNewWindow:(id)sender { + [barController_ openBookmarkInNewWindow:sender]; +} + +- (IBAction)openBookmarkInIncognitoWindow:(id)sender { + [barController_ openBookmarkInIncognitoWindow:sender]; +} + +- (IBAction)editBookmark:(id)sender { + [barController_ editBookmark:sender]; +} + +- (IBAction)cutBookmark:(id)sender { [self closeBookmarkFolder:self]; - [self autorelease]; + [barController_ cutBookmark:sender]; } -- (BookmarkButton*)parentButton { - return parentButton_.get(); +- (IBAction)copyBookmark:(id)sender { + [barController_ copyBookmark:sender]; } -// Delegate method. Shared implementation with BookmarkBarController. -- (void)fillPasteboard:(NSPasteboard*)pboard - forDragOfButton:(BookmarkButton*)button { - [[self folderTarget] fillPasteboard:pboard forDragOfButton:button]; +- (IBAction)pasteBookmark:(id)sender { + [barController_ pasteBookmark:sender]; +} - // Close our folder menu and submenus since we know we're going to be dragged. +- (IBAction)deleteBookmark:(id)sender { [self closeBookmarkFolder:self]; + [barController_ deleteBookmark:sender]; } +- (IBAction)openAllBookmarks:(id)sender { + [barController_ openAllBookmarks:sender]; +} + +- (IBAction)openAllBookmarksNewWindow:(id)sender { + [barController_ openAllBookmarksNewWindow:sender]; +} + +- (IBAction)openAllBookmarksIncognitoWindow:(id)sender { + [barController_ openAllBookmarksIncognitoWindow:sender]; +} + +- (IBAction)addPage:(id)sender { + [barController_ addPage:sender]; +} + +- (IBAction)addFolder:(id)sender { + [barController_ addFolder:sender]; +} + +#pragma mark Drag & Drop + // Find something like std::is_between<T>? I can't believe one doesn't exist. // http://crbug.com/35966 static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) { @@ -904,48 +823,6 @@ 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 -// bookmark_bar_controller.mm's version. -- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)info { - 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]; -} - -// Unlike bookmark_bar_controller, we need to keep track of dragging state. -// We also need to make sure we cancel the delayed hover close. -- (void)draggingExited:(id<NSDraggingInfo>)info { - // NOT the same as a cancel --> we may have moved the mouse into the submenu. - // Delegate handling of the hover button to the |hoverState_| member. - [hoverState_ draggingExited]; -} - -- (BOOL)dragShouldLockBarVisibility { - return [parentController_ dragShouldLockBarVisibility]; -} - -- (NSWindow*)browserWindow { - return [parentController_ browserWindow]; -} - // TODO(jrg): again we have code dup, sort of, with // bookmark_bar_controller.mm, but the axis is changed. One minor // difference is accomodation for the "empty" button (which may not @@ -992,66 +869,6 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) { [[parentButton_ cell] startingChildIndex]); } -// More code which essentially duplicates that of BookmarkBarController. -// TODO(mrossetti,jrg): http://crbug.com/35966 -- (BOOL)addURLs:(NSArray*)urls withTitles:(NSArray*)titles at:(NSPoint)point { - DCHECK([urls count] == [titles count]); - BOOL nodesWereAdded = NO; - // Figure out where these new bookmarks nodes are to be added. - BookmarkButton* button = [self buttonForDroppingOnAtPoint:point]; - BookmarkModel* bookmarkModel = [self bookmarkModel]; - const BookmarkNode* destParent = NULL; - int destIndex = 0; - if ([button isFolder]) { - destParent = [button bookmarkNode]; - // Drop it at the end. - destIndex = [button bookmarkNode]->GetChildCount(); - } else { - // Else we're dropping somewhere in the folder, so find the right spot. - destParent = [parentButton_ bookmarkNode]; - destIndex = [self indexForDragToPoint:point]; - // Be careful if the number of buttons != number of nodes. - destIndex += [[parentButton_ cell] startingChildIndex]; - } - - // Create and add the new bookmark nodes. - size_t urlCount = [urls count]; - for (size_t i = 0; i < urlCount; ++i) { - // URLs come in several forms (see NSPasteboard+Utils.mm). - GURL gurl; - const char* string = [[urls objectAtIndex:i] UTF8String]; - if (string) - gurl = GURL(string); - if (!gurl.is_valid() && string) { - gurl = GURL([[NSString stringWithFormat:@"file://%s", string] - UTF8String]); - } - DCHECK(gurl.is_valid()); - if (gurl.is_valid()) { - bookmarkModel->AddURL(destParent, - destIndex++, - base::SysNSStringToWide([titles objectAtIndex:i]), - gurl); - nodesWereAdded = YES; - } - } - return nodesWereAdded; -} - -- (BookmarkModel*)bookmarkModel { - return [barController_ bookmarkModel]; -} - -// TODO(jrg): ARGH more code dup. -// http://crbug.com/35966 -- (BOOL)dragButton:(BookmarkButton*)sourceButton - to:(NSPoint)point - copy:(BOOL)copy { - DCHECK([sourceButton isKindOfClass:[BookmarkButton class]]); - const BookmarkNode* sourceNode = [sourceButton bookmarkNode]; - return [self dragBookmark:sourceNode to:point copy:copy]; -} - // TODO(jrg): Yet more code dup. // http://crbug.com/35966 - (BOOL)dragBookmark:(const BookmarkNode*)sourceNode @@ -1093,18 +910,120 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) { return wasCopiedOrMoved; } -// TODO(mrossetti,jrg): Identical to the same function in BookmarkBarController. -// http://crbug.com/35966 -- (std::vector<const BookmarkNode*>)retrieveBookmarkDragDataNodes { - std::vector<const BookmarkNode*> dragDataNodes; - BookmarkDragData dragData; - if(dragData.ReadFromDragClipboard()) { - BookmarkModel* bookmarkModel = [self bookmarkModel]; - Profile* profile = bookmarkModel->profile(); - std::vector<const BookmarkNode*> nodes(dragData.GetNodes(profile)); - dragDataNodes.assign(nodes.begin(), nodes.end()); +#pragma mark BookmarkButtonDelegate Protocol + +- (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]; +} + +// Called from BookmarkButton. +// Unlike bookmark_bar_controller's version, we DO default to being enabled. +- (void)mouseEnteredButton:(id)sender event:(NSEvent*)event { + buttonThatMouseIsIn_ = sender; + + // Cancel a previous hover if needed. + [NSObject cancelPreviousPerformRequestsWithTarget:self]; + + // If already opened, then we exited but re-entered the button + // (without entering another button open), do nothing. + if ([folderController_ parentButton] == sender) + return; + + [self performSelector:@selector(openBookmarkFolderFromButtonAndCloseOldOne:) + withObject:sender + afterDelay:bookmarks::kHoverOpenDelay]; +} + +// Called from the BookmarkButton +- (void)mouseExitedButton:(id)sender event:(NSEvent*)event { + if (buttonThatMouseIsIn_ == sender) + buttonThatMouseIsIn_ = nil; + + // Stop any timer about opening a new hover-open folder. + + // Since a performSelector:withDelay: on self retains self, it is + // possible that a cancelPreviousPerformRequestsWithTarget: reduces + // the refcount to 0, releasing us. That's a bad thing to do while + // this object (or others it may own) is in the event chain. Thus + // we have a retain/autorelease. + [self retain]; + [NSObject cancelPreviousPerformRequestsWithTarget:self]; + [self autorelease]; +} + +- (NSWindow*)browserWindow { + return [parentController_ browserWindow]; +} + +#pragma mark BookmarkButtonControllerProtocol + +// Recursively close all bookmark folders. +- (void)closeAllBookmarkFolders { + // Closing the top level implicitly closes all children. + [barController_ closeAllBookmarkFolders]; +} + +// Close our bookmark folder (a sub-controller) if we have one. +- (void)closeBookmarkFolder:(id)sender { + // folderController_ may be nil but that's OK. + [[folderController_ window] close]; + folderController_ = nil; +} + +- (BookmarkModel*)bookmarkModel { + return [barController_ bookmarkModel]; +} + +// 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 +// bookmark_bar_controller.mm's version. +- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)info { + 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; + } } - return dragDataNodes; + // Delegate handling of dragging over a button to the |hoverState_| member. + return [hoverState_ draggingEnteredButton:button]; +} + +// Unlike bookmark_bar_controller, we need to keep track of dragging state. +// We also need to make sure we cancel the delayed hover close. +- (void)draggingExited:(id<NSDraggingInfo>)info { + // NOT the same as a cancel --> we may have moved the mouse into the submenu. + // Delegate handling of the hover button to the |hoverState_| member. + [hoverState_ draggingExited]; +} + +- (BOOL)dragShouldLockBarVisibility { + return [parentController_ dragShouldLockBarVisibility]; +} + +// TODO(jrg): ARGH more code dup. +// http://crbug.com/35966 +- (BOOL)dragButton:(BookmarkButton*)sourceButton + to:(NSPoint)point + copy:(BOOL)copy { + DCHECK([sourceButton isKindOfClass:[BookmarkButton class]]); + const BookmarkNode* sourceNode = [sourceButton bookmarkNode]; + return [self dragBookmark:sourceNode to:point copy:copy]; } // TODO(mrossetti,jrg): Identical to the same function in BookmarkBarController. @@ -1124,6 +1043,20 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) { return dragged; } +// TODO(mrossetti,jrg): Identical to the same function in BookmarkBarController. +// http://crbug.com/35966 +- (std::vector<const BookmarkNode*>)retrieveBookmarkDragDataNodes { + std::vector<const BookmarkNode*> dragDataNodes; + BookmarkDragData dragData; + if(dragData.ReadFromDragClipboard()) { + BookmarkModel* bookmarkModel = [self bookmarkModel]; + Profile* profile = bookmarkModel->profile(); + std::vector<const BookmarkNode*> nodes(dragData.GetNodes(profile)); + dragDataNodes.assign(nodes.begin(), nodes.end()); + } + return dragDataNodes; +} + // Return YES if we should show the drop indicator, else NO. // TODO(jrg): ARGH code dup! // http://crbug.com/35966 @@ -1131,7 +1064,6 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) { return ![self buttonForDroppingOnAtPoint:point]; } - // Return the y position for a drop indicator. // // TODO(jrg): again we have code dup, sort of, with @@ -1151,7 +1083,7 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) { NSRect buttonFrame = [button frame]; y = NSMaxY(buttonFrame) + 0.5 * bookmarks::kBookmarkVerticalPadding; - // If it's a drop at the end (past the last button, if there are any) ... + // If it's a drop at the end (past the last button, if there are any) ... } else if (destIndex == numButtons) { // and if it's past the last button ... if (numButtons > 0) { @@ -1160,8 +1092,7 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) { [buttons_ objectAtIndex:static_cast<NSUInteger>(destIndex - 1)]; DCHECK(button); NSRect buttonFrame = [button frame]; - y = buttonFrame.origin.y - - 0.5 * bookmarks::kBookmarkVerticalPadding; + y = buttonFrame.origin.y - 0.5 * bookmarks::kBookmarkVerticalPadding; } } else { @@ -1171,57 +1102,20 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) { return y; } -// Close the old hover-open bookmark folder, and open a new one. We -// do both in one step to allow for a delay in closing the old one. -// See comments above kDragHoverCloseDelay (bookmark_bar_controller.h) -// for more details. -- (void)openBookmarkFolderFromButtonAndCloseOldOne:(id)sender { - // If an old submenu exists, close it immediately. - [self closeBookmarkFolder:sender]; - - // Open a new one if meaningful. - if ([sender isFolder]) - [folderTarget_ openBookmarkFolderFromButton:sender]; +- (ThemeProvider*)themeProvider { + return [parentController_ themeProvider]; } -// Called from BookmarkButton. -// Unlike bookmark_bar_controller's version, we DO default to being enabled. -- (void)mouseEnteredButton:(id)sender event:(NSEvent*)event { - buttonThatMouseIsIn_ = sender; - - // Cancel a previous hover if needed. - [NSObject cancelPreviousPerformRequestsWithTarget:self]; - - // If already opened, then we exited but re-entered the button - // (without entering another button open), do nothing. - if ([folderController_ parentButton] == sender) - return; - - [self performSelector:@selector(openBookmarkFolderFromButtonAndCloseOldOne:) - withObject:sender - afterDelay:bookmarks::kHoverOpenDelay]; +- (void)childFolderWillShow:(id<BookmarkButtonControllerProtocol>)child { + // Do nothing. } -// Called from the BookmarkButton -- (void)mouseExitedButton:(id)sender event:(NSEvent*)event { - if (buttonThatMouseIsIn_ == sender) - buttonThatMouseIsIn_ = nil; - - // Stop any timer about opening a new hover-open folder. - - // Since a performSelector:withDelay: on self retains self, it is - // possible that a cancelPreviousPerformRequestsWithTarget: reduces - // the refcount to 0, releasing us. That's a bad thing to do while - // this object (or others it may own) is in the event chain. Thus - // we have a retain/autorelease. - [self retain]; - [NSObject cancelPreviousPerformRequestsWithTarget:self]; - [self autorelease]; +- (void)childFolderWillClose:(id<BookmarkButtonControllerProtocol>)child { + // Do nothing. } -- (void)openAll:(const BookmarkNode*)node - disposition:(WindowOpenDisposition)disposition { - [parentController_ openAll:node disposition:disposition]; +- (BookmarkBarFolderController*)folderController { + return folderController_; } // Add a new folder controller as triggered by the given folder button. @@ -1237,93 +1131,204 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) { [folderController_ showWindow:self]; } -- (NSArray*)buttons { - return buttons_.get(); -} - -- (void)close { - [folderController_ close]; - [super close]; +- (void)openAll:(const BookmarkNode*)node + disposition:(WindowOpenDisposition)disposition { + [parentController_ openAll:node disposition:disposition]; } -- (void)scrollWheel:(NSEvent *)theEvent { - if (scrollable_) { - // We go negative since an NSScrollView has a flipped coordinate frame. - CGFloat amt = kBookmarkBarFolderScrollWheelAmount * -[theEvent deltaY]; - [self performOneScroll:amt]; +- (void)addButtonForNode:(const BookmarkNode*)node + atIndex:(NSInteger)buttonIndex { + NSRect buttonFrame = NSMakeRect(bookmarks::kBookmarkHorizontalPadding, + bookmarks::kBookmarkVerticalPadding, + NSWidth([mainView_ frame]) - 2 * bookmarks::kBookmarkHorizontalPadding - + bookmarks::kScrollViewContentWidthMargin, + bookmarks::kBookmarkBarHeight - 2 * + bookmarks::kBookmarkVerticalPadding); + // When adding a button to an empty folder we must remove the 'empty' + // placeholder button. This can be detected by checking for a parent + // child count of 1. + const BookmarkNode* parentNode = node->GetParent(); + if (parentNode->GetChildCount() == 1) { + BookmarkButton* emptyButton = [buttons_ lastObject]; + [emptyButton removeFromSuperview]; + [buttons_ removeLastObject]; + } else { + // Set us up to remember the last moved button's frame. + buttonFrame.origin.y += NSHeight([mainView_ frame]) + + bookmarks::kBookmarkBarHeight; } -} -- (void)reconfigureMenu { - for (BookmarkButton* button in buttons_.get()) - [button removeFromSuperview]; - [buttons_ removeAllObjects]; - [self configureWindow]; -} - -- (void)setIgnoreAnimations:(BOOL)ignore { - ignoreAnimations_ = ignore; -} - -#pragma mark Methods Forwarded to BookmarkBarController - -- (IBAction)cutBookmark:(id)sender { - [self closeBookmarkFolder:self]; - [barController_ cutBookmark:sender]; -} - -- (IBAction)copyBookmark:(id)sender { - [barController_ copyBookmark:sender]; -} + if (buttonIndex == -1) + buttonIndex = [buttons_ count]; -- (IBAction)pasteBookmark:(id)sender { - [barController_ pasteBookmark:sender]; -} + BookmarkButton* button = nil; // Remember so it can be de-highlighted. + 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. + if (parentNode->GetChildCount() > 1) + buttonFrame.origin.y -= bookmarks::kBookmarkBarHeight; + BookmarkButton* newButton = [self makeButtonForNode:node + frame:buttonFrame]; + [buttons_ insertObject:newButton atIndex:buttonIndex]; + [mainView_ addSubview:newButton]; -- (IBAction)deleteBookmark:(id)sender { + // Close any child folder(s) which may still be open. [self closeBookmarkFolder:self]; - [barController_ deleteBookmark:sender]; -} - -- (IBAction)openBookmark:(id)sender { - [barController_ openBookmark:sender]; -} -- (IBAction)addFolder:(id)sender { - [barController_ addFolder:sender]; + // Prelim height of the window. We'll trim later as needed. + int height = [buttons_ count] * bookmarks::kBookmarkButtonHeight; + [self adjustWindowForHeight:height]; } -- (IBAction)addPage:(id)sender { - [barController_ addPage:sender]; -} +// More code which essentially duplicates that of BookmarkBarController. +// TODO(mrossetti,jrg): http://crbug.com/35966 +- (BOOL)addURLs:(NSArray*)urls withTitles:(NSArray*)titles at:(NSPoint)point { + DCHECK([urls count] == [titles count]); + BOOL nodesWereAdded = NO; + // Figure out where these new bookmarks nodes are to be added. + BookmarkButton* button = [self buttonForDroppingOnAtPoint:point]; + BookmarkModel* bookmarkModel = [self bookmarkModel]; + const BookmarkNode* destParent = NULL; + int destIndex = 0; + if ([button isFolder]) { + destParent = [button bookmarkNode]; + // Drop it at the end. + destIndex = [button bookmarkNode]->GetChildCount(); + } else { + // Else we're dropping somewhere in the folder, so find the right spot. + destParent = [parentButton_ bookmarkNode]; + destIndex = [self indexForDragToPoint:point]; + // Be careful if the number of buttons != number of nodes. + destIndex += [[parentButton_ cell] startingChildIndex]; + } -- (IBAction)editBookmark:(id)sender { - [barController_ editBookmark:sender]; + // Create and add the new bookmark nodes. + size_t urlCount = [urls count]; + for (size_t i = 0; i < urlCount; ++i) { + // URLs come in several forms (see NSPasteboard+Utils.mm). + GURL gurl; + const char* string = [[urls objectAtIndex:i] UTF8String]; + if (string) + gurl = GURL(string); + if (!gurl.is_valid() && string) { + gurl = GURL([[NSString stringWithFormat:@"file://%s", string] + UTF8String]); + } + DCHECK(gurl.is_valid()); + if (gurl.is_valid()) { + bookmarkModel->AddURL(destParent, + destIndex++, + base::SysNSStringToWide([titles objectAtIndex:i]), + gurl); + nodesWereAdded = YES; + } + } + return nodesWereAdded; } -- (IBAction)openAllBookmarks:(id)sender { - [barController_ openAllBookmarks:sender]; +- (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]; + } } -- (IBAction)openAllBookmarksIncognitoWindow:(id)sender { - [barController_ openAllBookmarksIncognitoWindow:sender]; -} +// 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 + 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 && !ignoreAnimations_) + 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]; + if (buttonCount) { + 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)]; + } + } else { + // If all nodes have been removed from this folder then add in the + // 'empty' placeholder button. + NSRect buttonFrame = NSMakeRect(bookmarks::kBookmarkHorizontalPadding, + bookmarks::kBookmarkButtonHeight - + (bookmarks::kBookmarkBarHeight - + bookmarks::kBookmarkVerticalPadding), + bookmarks::kDefaultBookmarkWidth, + (bookmarks::kBookmarkBarHeight - + 2 * bookmarks::kBookmarkVerticalPadding)); + BookmarkButton* button = [self makeButtonForNode:nil + frame:buttonFrame]; + [buttons_ addObject:button]; + [mainView_ addSubview:button]; + buttonCount = 1; + } -- (IBAction)openAllBookmarksNewWindow:(id)sender { - [barController_ openAllBookmarksNewWindow:sender]; + // Propose a height for the window. We'll trim later as needed. + int height = buttonCount * bookmarks::kBookmarkButtonHeight; + [self adjustWindowForHeight:height]; } -- (IBAction)openBookmarkInIncognitoWindow:(id)sender { - [barController_ openBookmarkInIncognitoWindow:sender]; +- (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]; } -- (IBAction)openBookmarkInNewForegroundTab:(id)sender { - [barController_ openBookmarkInNewForegroundTab:sender]; -} +#pragma mark TestingAPI Only -- (IBAction)openBookmarkInNewWindow:(id)sender { - [barController_ openBookmarkInNewWindow:sender]; +- (void)setIgnoreAnimations:(BOOL)ignore { + ignoreAnimations_ = ignore; } - @end // BookmarkBarFolderController |