diff options
author | jrg@chromium.org <jrg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-07-23 23:56:19 +0000 |
---|---|---|
committer | jrg@chromium.org <jrg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-07-23 23:56:19 +0000 |
commit | cb514b8024f425b974c680b06dcace135b32de39 (patch) | |
tree | 79a1530164118302eee0562b6bdf58f3a022f880 /chrome | |
parent | b1e8cb31f85869171dcb383c7ec5e8176833ec04 (diff) | |
download | chromium_src-cb514b8024f425b974c680b06dcace135b32de39.zip chromium_src-cb514b8024f425b974c680b06dcace135b32de39.tar.gz chromium_src-cb514b8024f425b974c680b06dcace135b32de39.tar.bz2 |
Added menus for bookmark bar folders. This is NOT based on the Cole
prototype; it is an attempt to get something functional in the short
term, and have a visual baseline before doing something new.
Added folder icons for bookmark bar folder buttons. Added an "off the
side" button/menu for bookmark buttons which don't fit on the bar.
Updated "Add page..." item to allow creating bookmarks in the folders
(if selected over a folder button).
BUG=http://crbug.com/8381
TEST=Here we go:
1) Make sure bookmark bar folders have the "folder" icon.
2) Right click on a folder --> Add Page, and add a bookmark.
Make sure bookmark is now in the folder, not at the top level.
3) (Oh, you just implicitly verified you can open bookmark folders!)
4) Add 5 bookmarks then shrink the window thinner so all bookmark
buttons don't fit. Make sure "off the right" button gets enabled
(on right side of bar) and shows bookmarks in a pop-up menu (when
clicked) that don't completely fit on the bar.
5) Make it super-wide so the all fit and make sure "off the right"
button is disabled.
6) Add a bunch of bookmarks to a folder; make sure they all work.
7) Add nested folders (by editing the bookmark pref file and restarting
Chrome) and make sure bookmark folder buttons have nested/cascading
menus.
Review URL: http://codereview.chromium.org/159286
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@21479 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r-- | chrome/app/nibs/BookmarkBar.xib | 147 | ||||
-rw-r--r-- | chrome/browser/cocoa/bookmark_bar_controller.h | 21 | ||||
-rw-r--r-- | chrome/browser/cocoa/bookmark_bar_controller.mm | 208 | ||||
-rw-r--r-- | chrome/browser/cocoa/bookmark_bar_controller_unittest.mm | 166 | ||||
-rw-r--r-- | chrome/browser/cocoa/bookmark_bar_view.h | 25 | ||||
-rw-r--r-- | chrome/browser/cocoa/bookmark_bar_view.mm | 22 | ||||
-rw-r--r-- | chrome/browser/cocoa/bookmark_bar_view_unittest.mm | 58 | ||||
-rw-r--r-- | chrome/browser/cocoa/bookmark_menu_bridge.mm | 26 | ||||
-rw-r--r-- | chrome/browser/cocoa/bookmark_menu_cocoa_controller.h | 4 | ||||
-rw-r--r-- | chrome/browser/cocoa/bookmark_menu_cocoa_controller.mm | 24 | ||||
-rw-r--r-- | chrome/chrome.gyp | 5 |
11 files changed, 506 insertions, 200 deletions
diff --git a/chrome/app/nibs/BookmarkBar.xib b/chrome/app/nibs/BookmarkBar.xib index e719a99..cc39ec8 100644 --- a/chrome/app/nibs/BookmarkBar.xib +++ b/chrome/app/nibs/BookmarkBar.xib @@ -39,9 +39,42 @@ <object class="NSCustomView" id="620641226"> <reference key="NSNextResponder"/> <int key="NSvFlags">-2147483358</int> - <string key="NSFrameSize">{468, 65}</string> + <object class="NSMutableArray" key="NSSubviews"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSCustomView" id="610146462"> + <reference key="NSNextResponder" ref="620641226"/> + <int key="NSvFlags">274</int> + <string key="NSFrameSize">{584, 144}</string> + <reference key="NSSuperview" ref="620641226"/> + <string key="NSClassName">NSView</string> + </object> + <object class="NSButton" id="1071220197"> + <reference key="NSNextResponder" ref="620641226"/> + <int key="NSvFlags">305</int> + <string key="NSFrame">{{586, 2}, {14, 28}}</string> + <reference key="NSSuperview" ref="620641226"/> + <bool key="NSEnabled">YES</bool> + <object class="NSButtonCell" key="NSCell" id="1055451269"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">134217728</int> + <string key="NSContents"/> + <reference key="NSControlView" ref="1071220197"/> + <int key="NSButtonFlags">-2042347265</int> + <int key="NSButtonFlags2">134</int> + <object class="NSCustomResource" key="NSNormalImage"> + <string key="NSClassName">NSImage</string> + <string key="NSResourceName">chevron</string> + </object> + <string key="NSAlternateContents"/> + <string key="NSKeyEquivalent"/> + <int key="NSPeriodicDelay">400</int> + <int key="NSPeriodicInterval">75</int> + </object> + </object> + </object> + <string key="NSFrameSize">{600, 144}</string> <reference key="NSSuperview"/> - <string key="NSClassName">BookmarkBarView</string> + <string key="NSClassName">NSView</string> </object> <object class="NSMenu" id="183701277"> <string key="NSTitle"/> @@ -283,14 +316,6 @@ </object> <object class="IBConnectionRecord"> <object class="IBOutletConnection" key="connection"> - <string key="label">barContextualMenu_</string> - <reference key="source" ref="620641226"/> - <reference key="destination" ref="183701277"/> - </object> - <int key="connectionID">17</int> - </object> - <object class="IBConnectionRecord"> - <object class="IBOutletConnection" key="connection"> <string key="label">buttonContextMenu_</string> <reference key="source" ref="1001"/> <reference key="destination" ref="672481054"/> @@ -441,6 +466,46 @@ </object> <int key="connectionID">61</int> </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">buttonView_</string> + <reference key="source" ref="1001"/> + <reference key="destination" ref="610146462"/> + </object> + <int key="connectionID">65</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">offTheSideButton_</string> + <reference key="source" ref="1001"/> + <reference key="destination" ref="1071220197"/> + </object> + <int key="connectionID">66</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">openOffTheSideMenuFromButton:</string> + <reference key="source" ref="1001"/> + <reference key="destination" ref="1071220197"/> + </object> + <int key="connectionID">69</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">menu</string> + <reference key="source" ref="610146462"/> + <reference key="destination" ref="183701277"/> + </object> + <int key="connectionID">70</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">menu</string> + <reference key="source" ref="620641226"/> + <reference key="destination" ref="183701277"/> + </object> + <int key="connectionID">71</int> + </object> </object> <object class="IBMutableOrderedSet" key="objectRecords"> <object class="NSArray" key="orderedObjects"> @@ -474,6 +539,11 @@ <object class="IBObjectRecord"> <int key="objectID">1</int> <reference key="object" ref="620641226"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="610146462"/> + <reference ref="1071220197"/> + </object> <reference key="parent" ref="1002"/> </object> <object class="IBObjectRecord"> @@ -638,6 +708,25 @@ <reference key="object" ref="1071747565"/> <reference key="parent" ref="672481054"/> </object> + <object class="IBObjectRecord"> + <int key="objectID">62</int> + <reference key="object" ref="610146462"/> + <reference key="parent" ref="620641226"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">63</int> + <reference key="object" ref="1071220197"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="1055451269"/> + </object> + <reference key="parent" ref="620641226"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">64</int> + <reference key="object" ref="1055451269"/> + <reference key="parent" ref="1071220197"/> + </object> </object> </object> <object class="NSMutableDictionary" key="flattenedProperties"> @@ -674,6 +763,9 @@ <string>4.IBPluginDependency</string> <string>5.IBPluginDependency</string> <string>6.IBPluginDependency</string> + <string>62.IBPluginDependency</string> + <string>63.IBPluginDependency</string> + <string>64.IBPluginDependency</string> <string>7.IBPluginDependency</string> <string>8.IBPluginDependency</string> <string>9.IBPluginDependency</string> @@ -683,7 +775,7 @@ <string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string> - <string>{{519, 625}, {468, 65}}</string> + <string>{{519, 546}, {600, 144}}</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string> @@ -713,6 +805,9 @@ <string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> </object> </object> <object class="NSMutableDictionary" key="unlocalizedProperties"> @@ -735,20 +830,12 @@ </object> </object> <nil key="sourceID"/> - <int key="maxID">61</int> + <int key="maxID">71</int> </object> <object class="IBClassDescriber" key="IBDocument.Classes"> <object class="NSMutableArray" key="referencedPartialClassDescriptions"> <bool key="EncodedWithXMLCoder">YES</bool> <object class="IBPartialClassDescription"> - <string key="className">BackgroundGradientView</string> - <string key="superclassName">NSView</string> - <object class="IBClassDescriptionSource" key="sourceIdentifier"> - <string key="majorKey">IBProjectSource</string> - <string key="minorKey">browser/cocoa/background_gradient_view.h</string> - </object> - </object> - <object class="IBPartialClassDescription"> <string key="className">BookmarkBarController</string> <string key="superclassName">NSViewController</string> <object class="NSMutableDictionary" key="actions"> @@ -764,6 +851,8 @@ <string>openBookmarkInIncognitoWindow:</string> <string>openBookmarkInNewForegroundTab:</string> <string>openBookmarkInNewWindow:</string> + <string>openFolderMenuFromButton:</string> + <string>openOffTheSideMenuFromButton:</string> </object> <object class="NSMutableArray" key="dict.values"> <bool key="EncodedWithXMLCoder">YES</bool> @@ -776,6 +865,8 @@ <string>id</string> <string>id</string> <string>id</string> + <string>id</string> + <string>id</string> </object> </object> <object class="NSMutableDictionary" key="outlets"> @@ -783,12 +874,16 @@ <object class="NSMutableArray" key="dict.sortedKeys"> <bool key="EncodedWithXMLCoder">YES</bool> <string>buttonContextMenu_</string> + <string>buttonView_</string> <string>delegate_</string> + <string>offTheSideButton_</string> </object> <object class="NSMutableArray" key="dict.values"> <bool key="EncodedWithXMLCoder">YES</bool> <string>NSMenu</string> + <string>NSView</string> <string>id</string> + <string>NSButton</string> </object> </object> <object class="IBClassDescriptionSource" key="sourceIdentifier"> @@ -797,18 +892,6 @@ </object> </object> <object class="IBPartialClassDescription"> - <string key="className">BookmarkBarView</string> - <string key="superclassName">BackgroundGradientView</string> - <object class="NSMutableDictionary" key="outlets"> - <string key="NS.key.0">barContextualMenu_</string> - <string key="NS.object.0">NSMenu</string> - </object> - <object class="IBClassDescriptionSource" key="sourceIdentifier"> - <string key="majorKey">IBProjectSource</string> - <string key="minorKey">browser/cocoa/bookmark_bar_view.h</string> - </object> - </object> - <object class="IBPartialClassDescription"> <string key="className">NSObject</string> <object class="IBClassDescriptionSource" key="sourceIdentifier"> <string key="majorKey">IBProjectSource</string> diff --git a/chrome/browser/cocoa/bookmark_bar_controller.h b/chrome/browser/cocoa/bookmark_bar_controller.h index 40ea772..c035fc2 100644 --- a/chrome/browser/cocoa/bookmark_bar_controller.h +++ b/chrome/browser/cocoa/bookmark_bar_controller.h @@ -6,6 +6,7 @@ #define CHROME_BROWSER_COCOA_BOOKMARK_BAR_CONTROLLER_H_ #import <Cocoa/Cocoa.h> +#include <map> #include "base/scoped_nsobject.h" #include "base/scoped_ptr.h" @@ -42,6 +43,13 @@ class PrefService; BOOL contentViewHasOffset_; BOOL barShouldBeShown_; + // BookmarkNodes have a 64bit id. NSMenuItems have a 32bit tag used + // to represent the bookmark node they refer to. This map provides + // a mapping from one to the other, so we can properly identify the + // node from the item. When adding items in, we start with seedId_. + int32 seedId_; + std::map<int32,int64> menuTagMap_; + // Our bookmark buttons, ordered from L-->R. scoped_nsobject<NSMutableArray> buttons_; @@ -60,6 +68,8 @@ class PrefService; // Delegate which can open URLs for us. id<BookmarkURLOpener> delegate_; // weak + IBOutlet NSView* buttonView_; + IBOutlet NSButton* offTheSideButton_; IBOutlet NSMenu* buttonContextMenu_; } @@ -88,6 +98,8 @@ class PrefService; // Actions for manipulating bookmarks. // From a button, ... - (IBAction)openBookmark:(id)sender; +- (IBAction)openFolderMenuFromButton:(id)sender; +- (IBAction)openOffTheSideMenuFromButton:(id)sender; // From a context menu over the button, ... - (IBAction)openBookmarkInNewForegroundTab:(id)sender; - (IBAction)openBookmarkInNewWindow:(id)sender; @@ -100,7 +112,6 @@ class PrefService; - (IBAction)addPage:(id)sender; - (IBAction)addOrRenameFolder:(id)sender; - @end // Redirects from BookmarkBarBridge, the C++ object which glues us to @@ -125,13 +136,19 @@ class PrefService; // These APIs should only be used by unit tests (or used internally). -@interface BookmarkBarController(TestingAPI) +@interface BookmarkBarController(InternalOrTestingAPI) // Set the delegate for a unit test. - (void)setDelegate:(id<BookmarkURLOpener>)delegate; - (void)clearBookmarkBar; +- (NSView*)buttonView; - (NSArray*)buttons; - (NSRect)frameForBookmarkButtonFromCell:(NSCell*)cell xOffset:(int*)xOffset; - (void)checkForBookmarkButtonGrowth:(NSButton*)button; +- (void)frameDidChange; +- (BOOL)offTheSideButtonIsEnabled; +- (NSMenu *)menuForFolderNode:(const BookmarkNode*)node; +- (int64)nodeIdFromMenuTag:(int32)tag; +- (int32)menuTagFromNodeId:(int64)menuid; @end #endif // CHROME_BROWSER_COCOA_BOOKMARK_BAR_CONTROLLER_H_ diff --git a/chrome/browser/cocoa/bookmark_bar_controller.mm b/chrome/browser/cocoa/bookmark_bar_controller.mm index fd2e0fe..d32ecbf 100644 --- a/chrome/browser/cocoa/bookmark_bar_controller.mm +++ b/chrome/browser/cocoa/bookmark_bar_controller.mm @@ -10,10 +10,11 @@ #include "chrome/browser/browser_list.h" #import "chrome/browser/cocoa/bookmark_bar_bridge.h" #import "chrome/browser/cocoa/bookmark_bar_controller.h" -#import "chrome/browser/cocoa/bookmark_bar_view.h" #import "chrome/browser/cocoa/bookmark_button_cell.h" #import "chrome/browser/cocoa/bookmark_editor_controller.h" #import "chrome/browser/cocoa/bookmark_name_folder_controller.h" +#import "chrome/browser/cocoa/bookmark_menu_cocoa_controller.h" +#include "chrome/browser/cocoa/nsimage_cache.h" #include "chrome/browser/profile.h" #include "chrome/common/pref_names.h" #include "chrome/common/pref_service.h" @@ -22,6 +23,10 @@ @interface BookmarkBarController(Private) - (void)applyContentAreaOffset:(BOOL)apply immediately:(BOOL)immediately; - (void)showBookmarkBar:(BOOL)enable immediately:(BOOL)immediately; +- (void)addNode:(const BookmarkNode*)child toMenu:(NSMenu*)menu; +- (void)addFolderNode:(const BookmarkNode*)node toMenu:(NSMenu*)menu; +- (void)tagEmptyMenu:(NSMenu*)menu; +- (void)clearMenuTagMap; @end namespace { @@ -62,6 +67,11 @@ const CGFloat kBookmarkHorizontalPadding = 1.0; return self; } +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [super dealloc]; +} + - (void)awakeFromNib { // We default to NOT open, which means height=0. DCHECK([[self view] isHidden]); // Hidden so it's OK to change. @@ -81,6 +91,40 @@ const CGFloat kBookmarkHorizontalPadding = 1.0; // Don't pass ourself along (as 'self') until our init is completely // done. Thus, this call is (almost) last. bridge_.reset(new BookmarkBarBridge(self, bookmarkModel_)); + + // When resized we may need to add new buttons, or remove them (if + // no longer visible), or add/remove the "off the side" menu. + [[self view] setPostsFrameChangedNotifications:YES]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(frameDidChange) + name:NSViewFrameDidChangeNotification + object:[self view]]; +} + +// Check if we should enable the off-the-side button. +// TODO(jrg): when we are smarter about creating buttons (e.g. don't +// bother creating buttons which aren't visible), we'll have to be +// smarter here too. +- (void)checkEnableOffTheSideButton { + NSButton* button = [buttons_ lastObject]; + if ((!button) || + (NSMaxX([button frame]) <= + NSMaxX([[button superview] frame]))) { + [offTheSideButton_ setEnabled:NO]; + } else { + [offTheSideButton_ setEnabled:YES]; + } +} + +- (BOOL)offTheSideButtonIsEnabled { + return [offTheSideButton_ isEnabled]; +} + +// Called when our controlled frame has changed size. +// TODO(jrg): be smarter (e.g. add/remove buttons as appropriate). +- (void)frameDidChange { + [self checkEnableOffTheSideButton]; } // Show or hide the bar based on the value of |show|. Handles @@ -218,6 +262,120 @@ const CGFloat kBookmarkHorizontalPadding = 1.0; [delegate_ openBookmarkURL:node->GetURL() disposition:CURRENT_TAB]; } +// 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(); +} + +// 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 { + NSString* title = [BookmarkMenuCocoaController menuTitleForNode:child]; + NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle:title + action:nil + keyEquivalent:@""] autorelease]; + [menu addItem:item]; + if (child->is_folder()) { + NSMenu* submenu = [[[NSMenu alloc] initWithTitle:title] autorelease]; + [menu setSubmenu:submenu forItem:item]; + if (child->GetChildCount()) { + [self addFolderNode:child toMenu:submenu]; // potentially recursive + } else { + [self tagEmptyMenu:submenu]; + } + } else { + [item setTarget:self]; + [item setAction:@selector(openBookmarkMenuItem:)]; + [item setTag:[self menuTagFromNodeId:child->id()]]; + // Add a tooltip + std::string url_string = child->GetURL().possibly_invalid_spec(); + NSString* tooltip = [NSString stringWithFormat:@"%@\n%s", + base::SysWideToNSString(child->GetTitle()), + url_string.c_str()]; + [item setToolTip:tooltip]; + + } +} + +// Empty menus are odd; if empty, add something to look at. +// Matches windows behavior. +// TODO(jrg): localize. +- (void)tagEmptyMenu:(NSMenu*)menu { + [menu addItem:[[[NSMenuItem alloc] initWithTitle:@"(empty)" + action:NULL + keyEquivalent:@""] autorelease]]; +} + +// Add the children of the given bookmark node (and their children...) +// to menu, one menu item per node. +- (void)addFolderNode:(const BookmarkNode*)node toMenu:(NSMenu*)menu { + for (int i = 0; i < node->GetChildCount(); i++) { + const BookmarkNode* child = node->GetChild(i); + [self addNode:child toMenu:menu]; + } +} + +// Return an autoreleased NSMenu that represents the given bookmark +// folder node. +- (NSMenu *)menuForFolderNode:(const BookmarkNode*)node { + if (!node->is_folder()) + return nil; + NSString* title = base::SysWideToNSString(node->GetTitle()); + NSMenu* menu = [[[NSMenu alloc] initWithTitle:title] autorelease]; + [self addFolderNode:node toMenu:menu]; + + if (![menu numberOfItems]) { + [self tagEmptyMenu:menu]; + } + return menu; +} + +// Called from a Folder bookmark button. +- (IBAction)openFolderMenuFromButton:(id)sender { + NSMenu* menu = [self menuForFolderNode:[self nodeFromButton:sender]]; + if (menu) { + [NSMenu popUpContextMenu:menu + withEvent:[NSApp currentEvent] + forView:sender]; + } +} + +// TODO(jrg): cache the menu so we don't need to build it every time. +// TODO(jrg): if we get smarter such that we don't even bother +// creating buttons which aren't visible, we'll need to be smarter +// here. +- (IBAction)openOffTheSideMenuFromButton:(id)sender { + scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:@""]); + for (NSButton* each_button in buttons_.get()) { + if (NSMaxX([each_button frame]) > + NSMaxX([[each_button superview] frame])) { + [self addNode:[self nodeFromButton:each_button] toMenu:menu.get()]; + } + } + + // TODO(jrg): once we disable the button when the menu should be + // empty, remove this 'helper'. + if (![menu numberOfItems]) { + [self tagEmptyMenu:menu]; + } + + [NSMenu popUpContextMenu:menu + withEvent:[NSApp currentEvent] + forView:sender]; +} + // 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]. @@ -289,10 +447,15 @@ const CGFloat kBookmarkHorizontalPadding = 1.0; [self openBookmarkNodesRecursive:node]; } +// 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] contentView], profile_, - bookmarkModel_->GetBookmarkBarNode(), + parent, nil, BookmarkEditor::SHOW_TREE, nil); @@ -314,10 +477,16 @@ const CGFloat kBookmarkHorizontalPadding = 1.0; // ends. } -// Delete all bookmarks from the bookmark bar. +- (NSView*)buttonView { + return buttonView_; +} + +// Delete all bookmarks from the bookmark bar, and reset knowledge of +// bookmarks. - (void)clearBookmarkBar { [buttons_ makeObjectsPerformSelector:@selector(removeFromSuperview)]; [buttons_ removeAllObjects]; + [self clearMenuTagMap]; } // Return an autoreleased NSCell suitable for a bookmark button. @@ -329,16 +498,19 @@ const CGFloat kBookmarkHorizontalPadding = 1.0; DCHECK(cell); [cell setRepresentedObject:[NSValue valueWithPointer:node]]; - // The favicon may be NULL if we haven't loaded it yet. Bookmarks - // (and their icons) are loaded on the IO thread to speed launch. - const SkBitmap& favicon = bookmarkModel_->GetFavIcon(node); - if (!favicon.isNull()) { - NSImage* image = gfx::SkBitmapToNSImage(favicon); - if (image) { - [cell setImage:image]; - [cell setImagePosition:NSImageLeft]; + NSImage* image = NULL; + if (node->is_folder()) { + image = nsimage_cache::ImageNamed(@"bookmark_bar_folder.png"); + } else { + const SkBitmap& favicon = bookmarkModel_->GetFavIcon(node); + if (!favicon.isNull()) { + image = gfx::SkBitmapToNSImage(favicon); } } + if (image) { + [cell setImage:image]; + [cell setImagePosition:NSImageLeft]; + } [cell setTitle:title]; [cell setMenu:buttonContextMenu_]; return cell; @@ -397,6 +569,12 @@ const CGFloat kBookmarkHorizontalPadding = 1.0; } } +- (IBAction)openBookmarkMenuItem:(id)sender { + int64 tag = [self nodeIdFromMenuTag:[sender tag]]; + const BookmarkNode* node = bookmarkModel_->GetNodeByID(tag); + [delegate_ openBookmarkURL:node->GetURL() disposition:CURRENT_TAB]; +} + // Add all items from the given model to our bookmark bar. // TODO(jrg): lots of things! // - bookmark folders (e.g. menu from the button) @@ -427,9 +605,8 @@ const CGFloat kBookmarkHorizontalPadding = 1.0; [button setCell:cell]; if (child->is_folder()) { - // For now just disable the button if it's a folder. - // TODO(jrg): recurse. - [button setEnabled:NO]; + [button setTarget:self]; + [button setAction:@selector(openFolderMenuFromButton:)]; } else { // Make the button do something [button setTarget:self]; @@ -442,7 +619,7 @@ const CGFloat kBookmarkHorizontalPadding = 1.0; [button setToolTip:tooltip]; } // Finally, add it to the bookmark bar. - [[self view] addSubview:button]; + [buttonView_ addSubview:button]; } } @@ -456,6 +633,7 @@ const CGFloat kBookmarkHorizontalPadding = 1.0; const BookmarkNode* node = model->GetBookmarkBarNode(); [self clearBookmarkBar]; [self addNodesToBar:node]; + [self checkEnableOffTheSideButton]; } - (void)beingDeleted:(BookmarkModel*)model { diff --git a/chrome/browser/cocoa/bookmark_bar_controller_unittest.mm b/chrome/browser/cocoa/bookmark_bar_controller_unittest.mm index 095e3bd..2f4b9f6 100644 --- a/chrome/browser/cocoa/bookmark_bar_controller_unittest.mm +++ b/chrome/browser/cocoa/bookmark_bar_controller_unittest.mm @@ -54,6 +54,24 @@ @end +// Remember the number of times we've gotten a frameDidChange notification. +@interface BookmarkBarControllerTogglePong : BookmarkBarController { +@private + int toggles_; +} +@property (readonly) int toggles; +@end + +@implementation BookmarkBarControllerTogglePong + +@synthesize toggles = toggles_; + +- (void)frameDidChange { + toggles_++; +} + +@end + namespace { @@ -78,16 +96,8 @@ class BookmarkBarControllerTest : public testing::Test { webContentView:content_area_.get() infoBarsView:infobar_view_.get() delegate:nil]); - [bar_ view]; // force loading of the nib - // Awkwardness to look like we've been installed. - [parent_view_ addSubview:[bar_ view]]; - NSRect frame = [[[bar_ view] superview] frame]; - frame.origin.y = 100; - [[[bar_ view] superview] setFrame:frame]; - - // make sure it's open so certain things aren't no-ops - [bar_ toggleBookmarkBar]; + InstallAndToggleBar(bar_.get()); // Create a menu/item to act like a sender menu_.reset([[NSMenu alloc] initWithTitle:@"I_dont_care"]); @@ -100,6 +110,20 @@ class BookmarkBarControllerTest : public testing::Test { [menu_ setDelegate:cell_.get()]; } + void InstallAndToggleBar(BookmarkBarController* bar) { + // Force loading of the nib. + [bar view]; + // Awkwardness to look like we've been installed. + [parent_view_ addSubview:[bar view]]; + NSRect frame = [[[bar view] superview] frame]; + frame.origin.y = 100; + [[[bar view] superview] setFrame:frame]; + + // make sure it's open so certain things aren't no-ops + [bar toggleBookmarkBar]; + } + + // Return a menu item that points to the right URL. NSMenuItem* ItemForBookmarkBarMenu(GURL& gurl) { node_.reset(new BookmarkNode(gurl)); @@ -158,6 +182,112 @@ TEST_F(BookmarkBarControllerTest, ShowHide) { EXPECT_EQ([[bar_ view] frame].size.height, 0); } +// Make sure we're watching for frame change notifications. +TEST_F(BookmarkBarControllerTest, FrameChangeNotification) { + scoped_nsobject<BookmarkBarControllerTogglePong> bar; + bar.reset( + [[BookmarkBarControllerTogglePong alloc] + initWithProfile:helper_.profile() + parentView:parent_view_.get() + webContentView:content_area_.get() + infoBarsView:infobar_view_.get() + delegate:nil]); + InstallAndToggleBar(bar.get()); + + EXPECT_GT([bar toggles], 0); + + // Hard to force toggles -- simple whacking the frame is inadequate. + // TODO(jrg): find a way to set frame, force a toggle, and verify it. +} + +// Confirm off the side button only enabled when reasonable. +TEST_F(BookmarkBarControllerTest, OffTheSideButtonEnable) { + BookmarkModel* model = helper_.profile()->GetBookmarkModel(); + + [bar_ loaded:model]; + EXPECT_FALSE([bar_ offTheSideButtonIsEnabled]); + + for (int i = 0; i < 2; i++) { + model->SetURLStarred(GURL("http://www.foo.com"), L"small", true); + EXPECT_FALSE([bar_ offTheSideButtonIsEnabled]); + } + + for (int i = 0; i < 20; i++) { + const BookmarkNode* parent = model->GetBookmarkBarNode(); + model->AddURL(parent, parent->GetChildCount(), + L"super duper wide title", + GURL("http://superfriends.hall-of-justice.edu")); + } + EXPECT_TRUE([bar_ offTheSideButtonIsEnabled]); +} + +TEST_F(BookmarkBarControllerTest, TagMap) { + int64 ids[] = { 1, 3, 4, 40, 400, 4000, 800000000, 2, 123456789 }; + std::vector<int32> tags; + + // Generate some tags + for (unsigned int i = 0; i < arraysize(ids); i++) { + tags.push_back([bar_ menuTagFromNodeId:ids[i]]); + } + + // Confirm reverse mapping. + for (unsigned int i = 0; i < arraysize(ids); i++) { + EXPECT_EQ(ids[i], [bar_ nodeIdFromMenuTag:tags[i]]); + } + + // Confirm uniqueness. + std::sort(tags.begin(), tags.end()); + for (unsigned int i=0; i<(tags.size()-1); i++) { + EXPECT_NE(tags[i], tags[i+1]); + } +} + +TEST_F(BookmarkBarControllerTest, MenuForFolderNode) { + BookmarkModel* model = helper_.profile()->GetBookmarkModel(); + + // First make sure something (e.g. "(empty)" string) is always present. + NSMenu* menu = [bar_ menuForFolderNode:model->GetBookmarkBarNode()]; + EXPECT_GT([menu numberOfItems], 0); + + // Test two bookmarks. + GURL gurl("http://www.foo.com"); + model->SetURLStarred(gurl, L"small", true); + model->SetURLStarred(GURL("http://www.cnn.com"), L"bigger title", true); + menu = [bar_ menuForFolderNode:model->GetBookmarkBarNode()]; + EXPECT_EQ([menu numberOfItems], 2); + NSMenuItem *item = [menu itemWithTitle:@"bigger title"]; + EXPECT_TRUE(item); + item = [menu itemWithTitle:@"small"]; + EXPECT_TRUE(item); + if (item) { + int64 tag = [bar_ nodeIdFromMenuTag:[item tag]]; + const BookmarkNode* node = model->GetNodeByID(tag); + EXPECT_TRUE(node); + EXPECT_EQ(gurl, node->GetURL()); + } + + // Test with an actual folder as well + const BookmarkNode* parent = model->GetBookmarkBarNode(); + const BookmarkNode* folder = model->AddGroup(parent, + parent->GetChildCount(), + L"group"); + model->AddURL(folder, folder->GetChildCount(), + L"f1", GURL("http://framma-lamma.com")); + model->AddURL(folder, folder->GetChildCount(), + L"f2", GURL("http://framma-lamma-ding-dong.com")); + menu = [bar_ menuForFolderNode:model->GetBookmarkBarNode()]; + EXPECT_EQ([menu numberOfItems], 3); + + item = [menu itemWithTitle:@"group"]; + EXPECT_TRUE(item); + EXPECT_TRUE([item hasSubmenu]); + NSMenu *submenu = [item submenu]; + EXPECT_TRUE(submenu); + EXPECT_EQ(2, [submenu numberOfItems]); + EXPECT_TRUE([submenu itemWithTitle:@"f1"]); + EXPECT_TRUE([submenu itemWithTitle:@"f2"]); +} + // Confirm openBookmark: forwards the request to the controller's delegate TEST_F(BookmarkBarControllerTest, OpenBookmark) { GURL gurl("http://walla.walla.ding.dong.com"); @@ -209,47 +339,47 @@ TEST_F(BookmarkBarControllerTest, OpenBookmarkFromMenus) { TEST_F(BookmarkBarControllerTest, TestAddRemoveAndClear) { BookmarkModel* model = helper_.profile()->GetBookmarkModel(); - + NSView* buttonView = [bar_ buttonView]; EXPECT_EQ(0U, [[bar_ buttons] count]); - unsigned int initial_subview_count = [[[bar_ view] subviews] count]; + unsigned int initial_subview_count = [[buttonView subviews] count]; // Make sure a redundant call doesn't choke [bar_ clearBookmarkBar]; EXPECT_EQ(0U, [[bar_ buttons] count]); - EXPECT_EQ(initial_subview_count, [[[bar_ view] subviews] count]); + EXPECT_EQ(initial_subview_count, [[buttonView subviews] count]); GURL gurl1("http://superfriends.hall-of-justice.edu"); std::wstring title1(L"Protectors of the Universe"); model->SetURLStarred(gurl1, title1, true); EXPECT_EQ(1U, [[bar_ buttons] count]); - EXPECT_EQ(1+initial_subview_count, [[[bar_ view] subviews] count]); + EXPECT_EQ(1+initial_subview_count, [[buttonView subviews] count]); GURL gurl2("http://legion-of-doom.gov"); std::wstring title2(L"Bad doodz"); model->SetURLStarred(gurl2, title2, true); EXPECT_EQ(2U, [[bar_ buttons] count]); - EXPECT_EQ(2+initial_subview_count, [[[bar_ view] subviews] count]); + EXPECT_EQ(2+initial_subview_count, [[buttonView subviews] count]); for (int i = 0; i < 3; i++) { // is_starred=false --> remove the bookmark model->SetURLStarred(gurl2, title2, false); EXPECT_EQ(1U, [[bar_ buttons] count]); - EXPECT_EQ(1+initial_subview_count, [[[bar_ view] subviews] count]); + EXPECT_EQ(1+initial_subview_count, [[buttonView subviews] count]); // and bring it back model->SetURLStarred(gurl2, title2, true); EXPECT_EQ(2U, [[bar_ buttons] count]); - EXPECT_EQ(2+initial_subview_count, [[[bar_ view] subviews] count]); + EXPECT_EQ(2+initial_subview_count, [[buttonView subviews] count]); } [bar_ clearBookmarkBar]; EXPECT_EQ(0U, [[bar_ buttons] count]); - EXPECT_EQ(initial_subview_count, [[[bar_ view] subviews] count]); + EXPECT_EQ(initial_subview_count, [[buttonView subviews] count]); // Explicit test of loaded: since this is a convenient spot [bar_ loaded:model]; EXPECT_EQ(2U, [[bar_ buttons] count]); - EXPECT_EQ(2+initial_subview_count, [[[bar_ view] subviews] count]); + EXPECT_EQ(2+initial_subview_count, [[buttonView subviews] count]); } // Make sure that each button we add marches to the right and does not diff --git a/chrome/browser/cocoa/bookmark_bar_view.h b/chrome/browser/cocoa/bookmark_bar_view.h deleted file mode 100644 index c92cc7f..0000000 --- a/chrome/browser/cocoa/bookmark_bar_view.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CHROME_BROWSER_COCOA_BOOKMARK_BAR_VIEW_H_ -#define CHROME_BROWSER_COCOA_BOOKMARK_BAR_VIEW_H_ - -#import <Cocoa/Cocoa.h> -#import "chrome/browser/cocoa/background_gradient_view.h" - -// A view that handles any special rendering of the bookmark bar. At -// this time it only draws a gradient like the toolbar. In the future -// I expect changes for new features (themes, extensions, etc). - -@interface BookmarkBarView : BackgroundGradientView { - @private - IBOutlet NSMenu* barContextualMenu_; -} -@end - -@interface BookmarkBarView(TestingAPI) -- (void)setContextMenu:(NSMenu*)menu; -@end - -#endif // CHROME_BROWSER_COCOA_BOOKMARK_BAR_VIEW_H_ diff --git a/chrome/browser/cocoa/bookmark_bar_view.mm b/chrome/browser/cocoa/bookmark_bar_view.mm deleted file mode 100644 index 9bdab26..0000000 --- a/chrome/browser/cocoa/bookmark_bar_view.mm +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chrome/browser/cocoa/bookmark_bar_view.h" - -@implementation BookmarkBarView - -// Only hit in a unit test. -- (void)setContextMenu:(NSMenu*)menu { - barContextualMenu_ = menu; -} - -// Unlike controls, generic views don't have a well-defined context -// menu (e.g. responds to the "menu" selector). So we add our own. -- (NSMenu *)menuForEvent:(NSEvent *)event { - if ([event type] == NSRightMouseDown) - return barContextualMenu_; - return nil; -} - -@end diff --git a/chrome/browser/cocoa/bookmark_bar_view_unittest.mm b/chrome/browser/cocoa/bookmark_bar_view_unittest.mm deleted file mode 100644 index a1cb121..0000000 --- a/chrome/browser/cocoa/bookmark_bar_view_unittest.mm +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import <Cocoa/Cocoa.h> - -#import "chrome/browser/cocoa/bookmark_bar_view.h" -#import "chrome/browser/cocoa/cocoa_test_helper.h" -#include "testing/gtest/include/gtest/gtest.h" - -class BookmarkBarViewTest : public testing::Test { - CocoaTestHelper cocoa_helper_; // Inits Cocoa, creates window, etc... -}; - -// Make sure we only get a menu from a right-click. Not left-click or keyDown. -TEST_F(BookmarkBarViewTest, TestMenu) { - scoped_nsobject<BookmarkBarView> view([[BookmarkBarView alloc] - initWithFrame:NSMakeRect(0,0,10,10)]); - EXPECT_TRUE(view.get()); - - // Not loaded from a nib so we must set it explicitly - scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:@"spork"]); - [view setContextMenu:menu.get()]; - - EXPECT_TRUE([view menuForEvent:[NSEvent mouseEventWithType:NSRightMouseDown - location:NSMakePoint(0,0) - modifierFlags:0 - timestamp:0 - windowNumber:0 - context:nil - eventNumber:0 - clickCount:0 - pressure:0.0]]); - EXPECT_FALSE([view menuForEvent:[NSEvent mouseEventWithType:NSLeftMouseDown - location:NSMakePoint(0,0) - modifierFlags:0 - timestamp:0 - windowNumber:0 - context:nil - eventNumber:0 - clickCount:0 - pressure:0.0]]); - EXPECT_FALSE([view menuForEvent:[NSEvent keyEventWithType:NSKeyDown - location:NSMakePoint(0,0) - modifierFlags:0 - timestamp:0 - windowNumber:0 - context:nil - characters:@"x" - charactersIgnoringModifiers:@"x" - isARepeat:NO - keyCode:7]]); - - [view setContextMenu:nil]; -} - - - diff --git a/chrome/browser/cocoa/bookmark_menu_bridge.mm b/chrome/browser/cocoa/bookmark_menu_bridge.mm index f4d4c7d..d37ea02 100644 --- a/chrome/browser/cocoa/bookmark_menu_bridge.mm +++ b/chrome/browser/cocoa/bookmark_menu_bridge.mm @@ -170,31 +170,10 @@ void BookmarkMenuBridge::ClearBookmarkMenu(NSMenu* menu) { } } -namespace { - -// Menus more than this many chars long will get trimmed -const NSUInteger kMaximumMenuWidthInChars = 65; - -// When trimming, use this many chars from each side -const NSUInteger kMenuTrimSizeInChars = 30; - -} - void BookmarkMenuBridge::AddNodeToMenu(const BookmarkNode* node, NSMenu* menu) { for (int i = 0; i < node->GetChildCount(); i++) { const BookmarkNode* child = node->GetChild(i); - NSString* full_title = base::SysWideToNSString(child->GetTitle()); - NSString* title = full_title; - if ([title length] > kMaximumMenuWidthInChars) { - // TODO(jrg): add a better heuristic? I'd really like to trim this - // by pixels, not by chars (font is not fixed width). - // For Safari, it appears that menu names >60 chars get split up to - // 30char + "..." + 30char. - title = [NSString stringWithFormat:@"%@…%@", - [title substringToIndex:kMenuTrimSizeInChars], - [title substringFromIndex:([title length] - - kMenuTrimSizeInChars)]]; - } + NSString* title = [BookmarkMenuCocoaController menuTitleForNode:child]; NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle:title action:nil keyEquivalent:@""] autorelease]; @@ -209,7 +188,8 @@ void BookmarkMenuBridge::AddNodeToMenu(const BookmarkNode* node, NSMenu* menu) { [item setTag:child->id()]; // Add a tooltip std::string url_string = child->GetURL().possibly_invalid_spec(); - NSString* tooltip = [NSString stringWithFormat:@"%@\n%s", full_title, + NSString* tooltip = [NSString stringWithFormat:@"%@\n%s", + base::SysWideToNSString(child->GetTitle()), url_string.c_str()]; [item setToolTip:tooltip]; diff --git a/chrome/browser/cocoa/bookmark_menu_cocoa_controller.h b/chrome/browser/cocoa/bookmark_menu_cocoa_controller.h index b1e9bde..228773e 100644 --- a/chrome/browser/cocoa/bookmark_menu_cocoa_controller.h +++ b/chrome/browser/cocoa/bookmark_menu_cocoa_controller.h @@ -19,6 +19,10 @@ class BookmarkMenuBridge; BookmarkMenuBridge* bridge_; // weak; owns me } +// Return an autoreleased string to be used as a menu title for the +// given bookmark node. ++ (NSString*)menuTitleForNode:(const BookmarkNode*)node; + - (id)initWithBridge:(BookmarkMenuBridge *)bridge; // Called by any Bookmark menu item. diff --git a/chrome/browser/cocoa/bookmark_menu_cocoa_controller.mm b/chrome/browser/cocoa/bookmark_menu_cocoa_controller.mm index a62103f..46f17bf 100644 --- a/chrome/browser/cocoa/bookmark_menu_cocoa_controller.mm +++ b/chrome/browser/cocoa/bookmark_menu_cocoa_controller.mm @@ -2,17 +2,37 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import "chrome/browser/cocoa/bookmark_menu_cocoa_controller.h" - +#include "app/gfx/text_elider.h" +#include "base/sys_string_conversions.h" #include "chrome/browser/bookmarks/bookmark_model.h" #include "chrome/browser/browser.h" #import "chrome/browser/cocoa/bookmark_menu_bridge.h" +#import "chrome/browser/cocoa/bookmark_menu_cocoa_controller.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/tab_contents/tab_contents.h" #include "webkit/glue/window_open_disposition.h" // CURRENT_TAB +namespace { + +// Menus more than this many pixels wide will get trimmed +// TODO(jrg): ask UI dudes what a good value is. +const NSUInteger kMaximumMenuPixelsWide = 300; + +} + @implementation BookmarkMenuCocoaController ++ (NSString*)menuTitleForNode:(const BookmarkNode*)node { + NSFont* nsfont = [NSFont menuBarFontOfSize:0]; // 0 means "default" + gfx::Font font = gfx::Font::CreateFont(base::SysNSStringToWide([nsfont + fontName]), + (int)[nsfont pointSize]); + std::wstring title = gfx::ElideText(node->GetTitle(), + font, + kMaximumMenuPixelsWide); + return base::SysWideToNSString(title); +} + - (id)initWithBridge:(BookmarkMenuBridge *)bridge { if ((self = [super init])) { bridge_ = bridge; diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index c39beda..d23f1f0 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -746,8 +746,6 @@ 'browser/cocoa/bookmark_bar_bridge.mm', 'browser/cocoa/bookmark_bar_controller.h', 'browser/cocoa/bookmark_bar_controller.mm', - 'browser/cocoa/bookmark_bar_view.h', - 'browser/cocoa/bookmark_bar_view.mm', 'browser/cocoa/bookmark_button_cell.h', 'browser/cocoa/bookmark_button_cell.mm', 'browser/cocoa/bookmark_editor_controller.h', @@ -2663,6 +2661,8 @@ 'app/nibs/TabView.xib', 'app/nibs/Toolbar.xib', 'app/theme/back_Template.pdf', + 'app/theme/bookmark_bar_folder.png', + 'app/theme/chevron.png', # TODO(jrg): get (and use) a pdf version 'app/theme/close_bar.pdf', 'app/theme/close_bar_h.pdf', 'app/theme/close_bar_p.pdf', @@ -3699,7 +3699,6 @@ 'browser/cocoa/blocked_popup_container_controller_unittest.mm', 'browser/cocoa/bookmark_bar_bridge_unittest.mm', 'browser/cocoa/bookmark_bar_controller_unittest.mm', - 'browser/cocoa/bookmark_bar_view_unittest.mm', 'browser/cocoa/bookmark_button_cell_unittest.mm', 'browser/cocoa/bookmark_editor_controller_unittest.mm', 'browser/cocoa/bookmark_menu_bridge_unittest.mm', |