summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authorjrg@chromium.org <jrg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-02-24 23:26:27 +0000
committerjrg@chromium.org <jrg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-02-24 23:26:27 +0000
commitd27fd0e73b0620cc3af740d2dd2138c20ca4372a (patch)
treee97bc4d580ed7893b035168f2380b3b2f28908bb /chrome
parent38eb08a5fd1ea4f529b9d4651a2f2eb31b0d2192 (diff)
downloadchromium_src-d27fd0e73b0620cc3af740d2dd2138c20ca4372a.zip
chromium_src-d27fd0e73b0620cc3af740d2dd2138c20ca4372a.tar.gz
chromium_src-d27fd0e73b0620cc3af740d2dd2138c20ca4372a.tar.bz2
Custom "menus" for the bookmark bar folders.
Full behavior: http://JRG_WRITE_FULL_DOC_AND_TEST_PLAN_TOMORROW BUG=17608 (and a slew of others) Brief details on how to test: - add some bookmarks and bookmark folders. - at a basic level, make sure bookmark folders feel like menus e.g. -- click to open -- can open "submenus" and sub-sub-menus -- can open (click on) bookmarks in any of these submenus - click-drag does NOT open a menu (different than Mac menus); it initiates a Drag - click on folder in bookmark bar initiates "hover open"; moving mouse over other folders will pop them open immediately (much like Mac menus) - Bookmark bar non-drag hover-open is immediate, but bookmark folder hover-open has a brief delay so quick "move down" a folder does not trigger them all to open while you travel (much like Mac menus). - DnD of bookmarks and folders on bookmark bar. - While doing DnD of bookmark, "hover" over a folder and see it open. - Bookmark folder menus have normal DnD "drop indicators" like the bookmark bar. - Can "hover open" a nested subfolder. - Can drag a bookmark from one deep sub-sub-folder to a different deep one. - Confirm buttons and folders in submenus are themed, both with the theme set at launch time and the theme we change to after launch. - Empty folders have an "(empty)" item which is not selectable. - Intentional delay in closing a sub-sub-folder when hovering over another one. E.g. When moving to a sub-sub-menu, 'brief' travel over a different submenu does not close the destination sub-menu. - can use bookmark context menus in folder "menus". - confirm DnD from "Other bookmarks" to any other random folder and vice versa. - While non-drag hover open is active, clicking anywhere other than the bookmark bar or folder (e.g. the main web view) turns it off. TODO: - random bugs (e.g. "add folder" over a folder doesn't put it in there) - (empty) needs to be revisited, both visually and for a drop indication - core animations instead of drop indicators - ... Review URL: http://codereview.chromium.org/551226 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@39947 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r--chrome/app/nibs/BookmarkBarFolderWindow.xib886
-rw-r--r--chrome/browser/cocoa/bookmark_bar_controller.h81
-rw-r--r--chrome/browser/cocoa/bookmark_bar_controller.mm409
-rw-r--r--chrome/browser/cocoa/bookmark_bar_controller_unittest.mm140
-rw-r--r--chrome/browser/cocoa/bookmark_bar_folder_controller.h92
-rw-r--r--chrome/browser/cocoa/bookmark_bar_folder_controller.mm587
-rw-r--r--chrome/browser/cocoa/bookmark_bar_folder_controller_unittest.mm204
-rw-r--r--chrome/browser/cocoa/bookmark_bar_folder_view.h25
-rw-r--r--chrome/browser/cocoa/bookmark_bar_folder_view.mm185
-rw-r--r--chrome/browser/cocoa/bookmark_bar_folder_view_unittest.mm139
-rw-r--r--chrome/browser/cocoa/bookmark_bar_folder_window.h16
-rw-r--r--chrome/browser/cocoa/bookmark_bar_folder_window.mm19
-rw-r--r--chrome/browser/cocoa/bookmark_bar_folder_window_unittest.mm22
-rw-r--r--chrome/browser/cocoa/bookmark_bar_view.h7
-rw-r--r--chrome/browser/cocoa/bookmark_bar_view.mm56
-rw-r--r--chrome/browser/cocoa/bookmark_bar_view_unittest.mm41
-rw-r--r--chrome/browser/cocoa/bookmark_button.h109
-rw-r--r--chrome/browser/cocoa/bookmark_button.mm44
-rw-r--r--chrome/browser/cocoa/bookmark_button_cell.h16
-rw-r--r--chrome/browser/cocoa/bookmark_button_cell.mm41
-rw-r--r--chrome/browser/cocoa/bookmark_button_cell_unittest.mm71
-rw-r--r--chrome/browser/cocoa/bookmark_button_unittest.mm89
-rw-r--r--chrome/browser/cocoa/test_event_utils.h9
-rw-r--r--chrome/browser/cocoa/test_event_utils.mm23
-rwxr-xr-xchrome/chrome_browser.gypi7
-rw-r--r--chrome/chrome_dll.gypi1
-rwxr-xr-xchrome/chrome_tests.gypi3
27 files changed, 3193 insertions, 129 deletions
diff --git a/chrome/app/nibs/BookmarkBarFolderWindow.xib b/chrome/app/nibs/BookmarkBarFolderWindow.xib
new file mode 100644
index 0000000..13dd230
--- /dev/null
+++ b/chrome/app/nibs/BookmarkBarFolderWindow.xib
@@ -0,0 +1,886 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10">
+ <data>
+ <int key="IBDocument.SystemTarget">1050</int>
+ <string key="IBDocument.SystemVersion">10B504</string>
+ <string key="IBDocument.InterfaceBuilderVersion">732</string>
+ <string key="IBDocument.AppKitVersion">1038.2</string>
+ <string key="IBDocument.HIToolboxVersion">437.00</string>
+ <object class="NSMutableDictionary" key="IBDocument.PluginVersions">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="NS.object.0">732</string>
+ </object>
+ <object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <integer value="2"/>
+ </object>
+ <object class="NSArray" key="IBDocument.PluginDependencies">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ </object>
+ <object class="NSMutableDictionary" key="IBDocument.Metadata">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys" id="0">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ </object>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ </object>
+ </object>
+ <object class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSCustomObject" id="1001">
+ <string key="NSClassName">BookmarkBarFolderController</string>
+ </object>
+ <object class="NSCustomObject" id="1003">
+ <string key="NSClassName">FirstResponder</string>
+ </object>
+ <object class="NSCustomObject" id="1004">
+ <string key="NSClassName">NSApplication</string>
+ </object>
+ <object class="NSWindowTemplate" id="1005">
+ <int key="NSWindowStyleMask">15</int>
+ <int key="NSWindowBacking">2</int>
+ <string key="NSWindowRect">{{196, 240}, {480, 270}}</string>
+ <int key="NSWTFlags">536870912</int>
+ <string key="NSWindowTitle">BmbPopUpWindow</string>
+ <string key="NSWindowClass">BookmarkBarFolderWindow</string>
+ <nil key="NSViewClass"/>
+ <string key="NSWindowContentMaxSize">{1.79769e+308, 1.79769e+308}</string>
+ <object class="NSView" key="NSWindowView" id="1006">
+ <reference key="NSNextResponder"/>
+ <int key="NSvFlags">256</int>
+ <object class="NSMutableArray" key="NSSubviews">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSCustomView" id="762431297">
+ <reference key="NSNextResponder" ref="1006"/>
+ <int key="NSvFlags">274</int>
+ <string key="NSFrameSize">{480, 270}</string>
+ <reference key="NSSuperview" ref="1006"/>
+ <string key="NSClassName">BookmarkBarFolderView</string>
+ </object>
+ </object>
+ <string key="NSFrameSize">{480, 270}</string>
+ <reference key="NSSuperview"/>
+ </object>
+ <string key="NSScreenRect">{{0, 0}, {1680, 1028}}</string>
+ <string key="NSMaxSize">{1.79769e+308, 1.79769e+308}</string>
+ </object>
+ </object>
+ <object class="IBObjectContainer" key="IBDocument.Objects">
+ <object class="NSMutableArray" key="connectionRecords">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBConnectionRecord">
+ <object class="IBOutletConnection" key="connection">
+ <string key="label">mainView_</string>
+ <reference key="source" ref="1001"/>
+ <reference key="destination" ref="762431297"/>
+ </object>
+ <int key="connectionID">7</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBOutletConnection" key="connection">
+ <string key="label">window</string>
+ <reference key="source" ref="1001"/>
+ <reference key="destination" ref="1005"/>
+ </object>
+ <int key="connectionID">8</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBOutletConnection" key="connection">
+ <string key="label">delegate</string>
+ <reference key="source" ref="1005"/>
+ <reference key="destination" ref="1001"/>
+ </object>
+ <int key="connectionID">9</int>
+ </object>
+ </object>
+ <object class="IBMutableOrderedSet" key="objectRecords">
+ <object class="NSArray" key="orderedObjects">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBObjectRecord">
+ <int key="objectID">0</int>
+ <reference key="object" ref="0"/>
+ <reference key="children" ref="1000"/>
+ <nil key="parent"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-2</int>
+ <reference key="object" ref="1001"/>
+ <reference key="parent" ref="0"/>
+ <string key="objectName">File's Owner</string>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-1</int>
+ <reference key="object" ref="1003"/>
+ <reference key="parent" ref="0"/>
+ <string key="objectName">First Responder</string>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-3</int>
+ <reference key="object" ref="1004"/>
+ <reference key="parent" ref="0"/>
+ <string key="objectName">Application</string>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">1</int>
+ <reference key="object" ref="1005"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="1006"/>
+ </object>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">2</int>
+ <reference key="object" ref="1006"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="762431297"/>
+ </object>
+ <reference key="parent" ref="1005"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">4</int>
+ <reference key="object" ref="762431297"/>
+ <reference key="parent" ref="1006"/>
+ </object>
+ </object>
+ </object>
+ <object class="NSMutableDictionary" key="flattenedProperties">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>-1.IBPluginDependency</string>
+ <string>-2.IBPluginDependency</string>
+ <string>-3.IBPluginDependency</string>
+ <string>1.IBEditorWindowLastContentRect</string>
+ <string>1.IBPluginDependency</string>
+ <string>1.IBWindowTemplateEditedContentRect</string>
+ <string>1.NSWindowTemplate.visibleAtLaunch</string>
+ <string>1.WindowOrigin</string>
+ <string>1.editorWindowContentRectSynchronizationRect</string>
+ <string>2.IBPluginDependency</string>
+ <string>4.IBPluginDependency</string>
+ </object>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>{{867, 900}, {480, 270}}</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>{{867, 900}, {480, 270}}</string>
+ <boolean value="NO"/>
+ <string>{196, 240}</string>
+ <string>{{357, 418}, {480, 270}}</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ </object>
+ </object>
+ <object class="NSMutableDictionary" key="unlocalizedProperties">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference key="dict.sortedKeys" ref="0"/>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ </object>
+ </object>
+ <nil key="activeLocalization"/>
+ <object class="NSMutableDictionary" key="localizations">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference key="dict.sortedKeys" ref="0"/>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ </object>
+ </object>
+ <nil key="sourceID"/>
+ <int key="maxID">9</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">BookmarkBarFolderController</string>
+ <string key="superclassName">NSWindowController</string>
+ <object class="NSMutableDictionary" key="outlets">
+ <string key="NS.key.0">mainView_</string>
+ <string key="NS.object.0">BookmarkBarFolderView</string>
+ </object>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBProjectSource</string>
+ <string key="minorKey">browser/cocoa/bookmark_bar_folder_controller.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">BookmarkBarFolderView</string>
+ <string key="superclassName">BackgroundGradientView</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBProjectSource</string>
+ <string key="minorKey">browser/cocoa/bookmark_bar_folder_view.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">BookmarkBarFolderWindow</string>
+ <string key="superclassName">NSWindow</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBProjectSource</string>
+ <string key="minorKey">browser/cocoa/bookmark_bar_folder_window.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBProjectSource</string>
+ <string key="minorKey">browser/cocoa/status_bubble_mac.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBProjectSource</string>
+ <string key="minorKey">browser/cocoa/tab_strip_model_observer_bridge.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSWindow</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBProjectSource</string>
+ <string key="minorKey">browser/cocoa/nswindow_local_state.h</string>
+ </object>
+ </object>
+ </object>
+ <object class="NSMutableArray" key="referencedPartialClassDescriptionsV3.2+">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSApplication</string>
+ <string key="superclassName">NSResponder</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier" id="321325912">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSApplication.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSApplication</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier" id="587048469">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSApplicationScripting.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSApplication</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier" id="448641522">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSColorPanel.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSApplication</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSHelpManager.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSApplication</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSPageLayout.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSMenu</string>
+ <string key="superclassName">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier" id="636743587">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSMenu.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSAccessibility.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSAlert.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSAnimation.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <reference key="sourceIdentifier" ref="321325912"/>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <reference key="sourceIdentifier" ref="587048469"/>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSBrowser.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <reference key="sourceIdentifier" ref="448641522"/>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSComboBox.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSComboBoxCell.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSControl.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSDatePickerCell.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSDictionaryController.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSDragging.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier" id="429966883">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSDrawer.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSFontManager.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSFontPanel.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSImage.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSKeyValueBinding.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <reference key="sourceIdentifier" ref="636743587"/>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSNibLoading.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSOutlineView.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSPasteboard.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSRuleEditor.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSSavePanel.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSSound.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSSpeechRecognizer.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSSpeechSynthesizer.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSSplitView.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSTabView.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSTableView.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSText.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSTextStorage.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSTextView.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSTokenField.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSTokenFieldCell.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSToolbar.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSToolbarItem.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier" id="293622296">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSView.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier" id="617292003">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSWindow.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSArchiver.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSClassDescription.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSConnection.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSError.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSFileManager.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSKeyValueCoding.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSKeyValueObserving.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSKeyedArchiver.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSMetadata.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSNetServices.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSObject.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSObjectScripting.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSPort.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSPortCoder.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSRunLoop.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSScriptClassDescription.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSScriptKeyValueCoding.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSScriptObjectSpecifiers.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSScriptWhoseTests.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSSpellServer.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSStream.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSThread.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSURL.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSURLConnection.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSURLDownload.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Foundation.framework/Headers/NSXMLParser.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">Print.framework/Headers/PDEPluginInterface.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">QuartzCore.framework/Headers/CAAnimation.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">QuartzCore.framework/Headers/CALayer.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">QuartzCore.framework/Headers/CIImageProvider.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">SecurityInterface.framework/Headers/SFAuthorizationView.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">SecurityInterface.framework/Headers/SFCertificatePanel.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">SecurityInterface.framework/Headers/SFChooseIdentityPanel.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSResponder</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSInterfaceStyle.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSResponder</string>
+ <string key="superclassName">NSObject</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSResponder.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSView</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSClipView.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSView</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSMenuItem.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSView</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSRulerView.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSView</string>
+ <string key="superclassName">NSResponder</string>
+ <reference key="sourceIdentifier" ref="293622296"/>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSWindow</string>
+ <reference key="sourceIdentifier" ref="429966883"/>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSWindow</string>
+ <string key="superclassName">NSResponder</string>
+ <reference key="sourceIdentifier" ref="617292003"/>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSWindow</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSWindowScripting.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSWindowController</string>
+ <string key="superclassName">NSResponder</string>
+ <object class="NSMutableDictionary" key="actions">
+ <string key="NS.key.0">showWindow:</string>
+ <string key="NS.object.0">id</string>
+ </object>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBFrameworkSource</string>
+ <string key="minorKey">AppKit.framework/Headers/NSWindowController.h</string>
+ </object>
+ </object>
+ </object>
+ </object>
+ <int key="IBDocument.localizationMode">0</int>
+ <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencies">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
+ <integer value="1050" key="NS.object.0"/>
+ </object>
+ <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencyDefaults">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
+ <integer value="1050" key="NS.object.0"/>
+ </object>
+ <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3</string>
+ <integer value="3000" key="NS.object.0"/>
+ </object>
+ <bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
+ <string key="IBDocument.LastKnownRelativeProjectPath">../../chrome.xcodeproj</string>
+ <int key="IBDocument.defaultPropertyAccessControl">3</int>
+ </data>
+</archive>
diff --git a/chrome/browser/cocoa/bookmark_bar_controller.h b/chrome/browser/cocoa/bookmark_bar_controller.h
index 874987a..f36a76e 100644
--- a/chrome/browser/cocoa/bookmark_bar_controller.h
+++ b/chrome/browser/cocoa/bookmark_bar_controller.h
@@ -8,6 +8,7 @@
#import <Cocoa/Cocoa.h>
#include <map>
+#import "base/chrome_application_mac.h"
#include "base/scoped_nsobject.h"
#include "base/scoped_ptr.h"
#include "chrome/browser/cocoa/bookmark_bar_bridge.h"
@@ -18,6 +19,7 @@
#include "webkit/glue/window_open_disposition.h"
@class BookmarkBarController;
+@class BookmarkBarFolderController;
@class BookmarkBarView;
@class BookmarkButton;
class BookmarkModel;
@@ -35,6 +37,8 @@ class TabContents;
namespace bookmarks {
// Magic numbers from Cole
+// TODO(jrg): create an objc-friendly version of bookmark_bar_constants.h?
+
const CGFloat kDefaultBookmarkWidth = 150.0;
const CGFloat kBookmarkVerticalPadding = 2.0;
const CGFloat kBookmarkHorizontalPadding = 1.0;
@@ -42,6 +46,45 @@ const CGFloat kBookmarkHorizontalPadding = 1.0;
const CGFloat kNoBookmarksHorizontalOffset = 5.0;
const CGFloat kNoBookmarksVerticalOffset = 6.0;
+// (end magic numbers from Cole)
+
+// Delay before opening a subfolder (and closing the previous one)
+// when hovering over a folder button.
+const NSTimeInterval kHoverOpenDelay = 0.3;
+
+// Delay on hover before a submenu opens when dragging.
+// Experimentally a drag hover open delay needs to be bigger than a
+// normal (non-drag) menu hover open such as used in the bookmark folder.
+// TODO(jrg): confirm feel of this constant with ui-team.
+// http://crbug.com/36276
+const NSTimeInterval kDragHoverOpenDelay = 0.7;
+
+// Notes on use of kDragHoverCloseDelay in
+// -[BookmarkBarFolderController draggingEntered:].
+//
+// We have an implicit delay on stop-hover-open before a submenu
+// closes. This cannot be zero since it's nice to move the mouse in a
+// direct line from "current position" to "position of item in
+// submenu". However, by doing so, it's possible to overlap a
+// different button on the current menu. Example:
+//
+// Folder1
+// Folder2 ---> Sub1
+// Folder3 Sub2
+// Sub3
+//
+// If you hover over the F in Folder2 to open the sub, and then want to
+// select Sub3, a direct line movement of the mouse may cross over
+// Folder3. Without this delay, that'll cause Sub to be closed before
+// you get there, since a "hover over" of Folder3 gets activated.
+// It's subtle but without the delay it feels broken.
+//
+// This is only really a problem with vertical menu --> vertical menu
+// movement; the bookmark bar (horizontal menu, sort of) seems fine,
+// perhaps because mouse move direction is purely vertical so there is
+// no opportunity for overlap.
+const NSTimeInterval kDragHoverCloseDelay = 0.4;
+
} // namespace bookmarks
// The interface for the bookmark bar controller's delegate. Currently, the
@@ -69,7 +112,9 @@ willAnimateFromState:(bookmarks::VisualState)oldState
@interface BookmarkBarController :
NSViewController<BookmarkBarState,
BookmarkBarToolbarViewController,
- BookmarkButtonDelegate> {
+ BookmarkButtonDelegate,
+ BookmarkButtonControllerProtocol,
+ CrApplicationEventHookProtocol> {
@private
// The visual state of the bookmark bar. If an animation is running, this is
// set to the "destination" and |lastVisualState_| is set to the "original"
@@ -118,6 +163,13 @@ willAnimateFromState:(bookmarks::VisualState)oldState
// Delegate that can resize us.
id<ViewResizer> resizeDelegate_; // weak
+ // A controller for a pop-up bookmark folder window (custom menu).
+ // This is not a scoped_nsobject because it owns itself (when its
+ // window closes the controller gets autoreleased).
+ BookmarkBarFolderController* folderController_;
+
+ BOOL watchingForClickOutside_; // Are watching for a "click outside"?
+
IBOutlet BookmarkBarView* buttonView_;
IBOutlet MenuButton* offTheSideButton_; // aka the chevron
IBOutlet NSMenu* buttonContextMenu_;
@@ -128,6 +180,13 @@ willAnimateFromState:(bookmarks::VisualState)oldState
// We have a special menu for folder buttons. This starts as a copy
// of the bar menu.
scoped_nsobject<BookmarkMenu> buttonFolderContextMenu_;
+
+ // When doing a drag, this is folder button "hovered over" which we
+ // may want to open after a short delay. There are cases where a
+ // mouse-enter can open a folder (e.g. if the menus are "active")
+ // but that doesn't use this variable or need a delay so "hover" is
+ // the wrong term.
+ scoped_nsobject<BookmarkButton> hoverButton_;
}
@property(readonly, nonatomic) bookmarks::VisualState visualState;
@@ -170,22 +229,13 @@ willAnimateFromState:(bookmarks::VisualState)oldState
// need it for animations. Try not to propagate its use.
- (void)layoutSubviews;
-// Complete a drag of a bookmark button to the given point (given in window
-// coordinates) on the main bar.
-// TODO(jrg): submenu DnD.
-// Returns YES on success.
-- (BOOL)dragButton:(BookmarkButton*)sourceButton to:(NSPoint)point;
-
-// The x-coordinate of (the middle of) the indicator to draw for a drag of the
-// source button to the given point (given in window coordinates).
-// TODO(viettrungluu,jrg): instead of this, make buttons move around.
-- (CGFloat)indicatorPosForDragOfButton:(BookmarkButton*)sourceButton
- toPoint:(NSPoint)point;
+// Called by our view when it is moved to a window.
+- (void)viewDidMoveToWindow;
// Actions for manipulating bookmarks.
-// From a button, ...
+// Open a normal bookmark or folder from a button, ...
- (IBAction)openBookmark:(id)sender;
-- (IBAction)openFolderMenuFromButton:(id)sender;
+- (IBAction)openBookmarkFolderFromButton:(id)sender;
// From a context menu over the button, ...
- (IBAction)openBookmarkInNewForegroundTab:(id)sender;
- (IBAction)openBookmarkInNewWindow:(id)sender;
@@ -243,6 +293,9 @@ willAnimateFromState:(bookmarks::VisualState)oldState
- (NSButton*)otherBookmarksButton;
- (BookmarkNode*)nodeFromMenuItem:(id)sender;
- (void)updateTheme:(ThemeProvider*)themeProvider;
+- (BookmarkBarFolderController*)folderController;
+- (BookmarkButton*)buttonForDroppingOnAtPoint:(NSPoint)point;
+- (BOOL)isEventAClickOutside:(NSEvent*)event;
@end
// The (internal) |NSPasteboard| type string for bookmark button drags, used for
diff --git a/chrome/browser/cocoa/bookmark_bar_controller.mm b/chrome/browser/cocoa/bookmark_bar_controller.mm
index adb6e13..6d376f37 100644
--- a/chrome/browser/cocoa/bookmark_bar_controller.mm
+++ b/chrome/browser/cocoa/bookmark_bar_controller.mm
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#import "chrome/browser/cocoa/bookmark_bar_controller.h"
#include "app/l10n_util_mac.h"
#include "app/resource_bundle.h"
#include "base/mac_util.h"
@@ -14,7 +15,8 @@
#import "chrome/browser/cocoa/background_gradient_view.h"
#import "chrome/browser/cocoa/bookmark_bar_bridge.h"
#import "chrome/browser/cocoa/bookmark_bar_constants.h"
-#import "chrome/browser/cocoa/bookmark_bar_controller.h"
+#import "chrome/browser/cocoa/bookmark_bar_folder_controller.h"
+#import "chrome/browser/cocoa/bookmark_bar_folder_window.h"
#import "chrome/browser/cocoa/bookmark_bar_toolbar_view.h"
#import "chrome/browser/cocoa/bookmark_bar_view.h"
#import "chrome/browser/cocoa/bookmark_button.h"
@@ -171,6 +173,8 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
- (NSImage*)getFavIconForNode:(const BookmarkNode*)node;
- (void)setNodeForBarMenu;
+- (void)watchForClickOutside:(BOOL)watch;
+
@end
@implementation BookmarkBarController
@@ -200,12 +204,12 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
folderImage_.reset([rb.GetNSImageNamed(IDR_BOOKMARK_BAR_FOLDER) retain]);
defaultImage_.reset([rb.GetNSImageNamed(IDR_DEFAULT_FAVICON) retain]);
- // Register for theme changes.
- NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
- [defaultCenter addObserver:self
- selector:@selector(themeDidChangeNotification:)
- name:kBrowserThemeDidChangeNotification
- object:nil];
+ // Register for theme changes.
+ NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
+ [defaultCenter addObserver:self
+ selector:@selector(themeDidChangeNotification:)
+ name:kBrowserThemeDidChangeNotification
+ object:nil];
// This call triggers an awakeFromNib, which builds the bar, which
// might uses folderImage_. So make sure it happens after
@@ -224,6 +228,10 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
//TODO(dmaclach): Remove -- http://crbug.com/25845
[[self view] removeFromSuperview];
+ // Be sure there is no dangling pointer.
+ if ([[self view] respondsToSelector:@selector(setController:)])
+ [[self view] performSelector:@selector(setController:) withObject:nil];
+
// For safety, make sure the buttons can no longer call us.
for (BookmarkButton* button in buttons_.get()) {
[button setDelegate:nil];
@@ -233,6 +241,7 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
bridge_.reset(NULL);
[[NSNotificationCenter defaultCenter] removeObserver:self];
+ [self watchForClickOutside:NO];
[super dealloc];
}
@@ -255,6 +264,11 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
[[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 =
@@ -288,16 +302,32 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
// 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]];
+ addObserver:self
+ selector:@selector(frameDidChange)
+ name:NSViewFrameDidChangeNotification
+ object:[self view]];
// 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_));
}
+// Called by our main view (a BookmarkBarView) when it gets moved to a
+// window. We perform operations which need to know the relevant
+// window (e.g. watch for a window close) so they can't be performed
+// earlier (such as in awakeFromNib).
+- (void)viewDidMoveToWindow {
+ NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
+ [defaultCenter addObserver:self
+ selector:@selector(parentWindowWillClose:)
+ name:NSWindowWillCloseNotification
+ object:[[self view] window]];
+ [defaultCenter addObserver:self
+ selector:@selector(parentWindowDidResignKey:)
+ name:NSWindowDidResignKeyNotification
+ object:[[self view] window]];
+}
+
// (Private) Method is the same as [self view], but is provided to be explicit.
- (BackgroundGradientView*)backgroundGradientView {
DCHECK([[self view] isKindOfClass:[BackgroundGradientView class]]);
@@ -315,7 +345,8 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
NSRect frame = [offTheSideButton_ frame];
if (otherBookmarksButton_.get()) {
frame.origin.x = ([otherBookmarksButton_ frame].origin.x -
- (frame.size.width + bookmarks::kBookmarkHorizontalPadding));
+ (frame.size.width +
+ bookmarks::kBookmarkHorizontalPadding));
[offTheSideButton_ setFrame:frame];
}
}
@@ -350,6 +381,121 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
[self centerNoItemsLabel];
}
+// Close all bookmark folders. "Folder" here is the fake menu for
+// bookmark folders, not a button context menu.
+- (void)closeAllBookmarkFolders {
+ [self watchForClickOutside:NO];
+ [[folderController_ window] 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_;
+}
+
+// NSNotificationCenter callback.
+- (void)parentWindowWillClose:(NSNotification*)notification {
+ [self closeAllBookmarkFolders];
+}
+
+// NSNotificationCenter callback.
+- (void)parentWindowDidResignKey:(NSNotification*)notification {
+ [self closeAllBookmarkFolders];
+}
+
+// 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 nothing is open, do nothing. Different from
+ // BookmarkBarFolderController since we default to NOT being enabled.
+ if (!folderController_)
+ 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())
+ [self openBookmarkFolderFromButton:sender];
+}
+
+// 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)
+// won't work. Our strategy is to watch (at the app level) for a
+// "click outside" these windows to detect when they logically lose
+// focus.
+- (void)watchForClickOutside:(BOOL)watch {
+ CrApplication* app = static_cast<CrApplication*>([NSApplication
+ sharedApplication]);
+ DCHECK([app isKindOfClass:[CrApplication class]]);
+ if (watch) {
+ if (!watchingForClickOutside_)
+ [app addEventHook:self];
+ } else {
+ if (watchingForClickOutside_)
+ [app removeEventHook:self];
+ }
+ watchingForClickOutside_ = watch;
+}
+
+// Implementation of CrApplicationEventHookProtocol.
+// NOT an override of a standard Cocoa call made to NSViewControllers.
+- (void)hookForEvent:(NSEvent*)theEvent {
+ if ([self isEventAClickOutside:theEvent]) {
+ [self watchForClickOutside:NO];
+ [self closeAllBookmarkFolders];
+ }
+}
+
+// Return YES if the event represents a "click outside" of the area we
+// are watching. At this time we are watching the area that includes
+// all popup bookmark folder windows.
+- (BOOL)isEventAClickOutside:(NSEvent*)event {
+ NSWindow* eventWindow = [event window];
+ NSWindow* myWindow = [[self view] window];
+ switch ([event type]) {
+ case NSLeftMouseDown:
+ case NSRightMouseDown:
+ // If a click in my window and NOT in the bookmark bar,
+ // then is a click outside.
+ if ((eventWindow == myWindow) &&
+ ([[eventWindow contentView]
+ hitTest:[event locationInWindow]] != [self view])) {
+ 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]]) &&
+ ([eventWindow parentWindow] != myWindow)) {
+ return YES;
+ }
+ break;
+ default:
+ break;
+ }
+ return NO;
+}
+
// 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, which
@@ -525,13 +671,6 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
toPoint:(NSPoint)point {
DCHECK([sourceButton isKindOfClass:[BookmarkButton class]]);
- void* pointer = [[[sourceButton cell] representedObject] pointerValue];
- const BookmarkNode* sourceNode = static_cast<const BookmarkNode*>(pointer);
- if (!sourceNode) {
- NOTREACHED();
- return -1;
- }
-
// Identify which buttons we are between. For now, assume a button
// location is at the center point of its view, and that an exact
// match means "place before".
@@ -540,8 +679,8 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
NSPoint dropLocation =
[[self view] convertPoint:point
fromView:[[[self view] window] contentView]];
- NSButton* buttonToTheRightOfDraggedButton = nil;
- for (NSButton* button in buttons_.get()) {
+ BookmarkButton* buttonToTheRightOfDraggedButton = nil;
+ for (BookmarkButton* button in buttons_.get()) {
CGFloat midpoint = NSMidX([button frame]);
if (dropLocation.x <= midpoint) {
buttonToTheRightOfDraggedButton = button;
@@ -549,26 +688,28 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
}
}
if (buttonToTheRightOfDraggedButton) {
- pointer = [[[buttonToTheRightOfDraggedButton cell]
- representedObject] pointerValue];
- const BookmarkNode* afterNode = static_cast<const BookmarkNode*>(pointer);
+ const BookmarkNode* afterNode =
+ [buttonToTheRightOfDraggedButton bookmarkNode];
+ DCHECK(afterNode);
return afterNode->GetParent()->IndexOfChild(afterNode);
}
// If nothing is to my right I am at the end!
- return sourceNode->GetParent()->GetChildCount();
+ return [buttons_ count];
}
- (BOOL)dragButton:(BookmarkButton*)sourceButton to:(NSPoint)point {
DCHECK([sourceButton isKindOfClass:[BookmarkButton class]]);
- void* pointer = [[[sourceButton cell] representedObject] pointerValue];
- const BookmarkNode* sourceNode = static_cast<const BookmarkNode*>(pointer);
+ const BookmarkNode* sourceNode = [sourceButton bookmarkNode];
DCHECK(sourceNode);
int destIndex = [self indexForDragOfButton:sourceButton toPoint:point];
if (destIndex >= 0 && sourceNode) {
- bookmarkModel_->Move(sourceNode, sourceNode->GetParent(), destIndex);
+ // Our destination parent is not sourceNode->GetParent()!
+ bookmarkModel_->Move(sourceNode,
+ bookmarkModel_->GetBookmarkBarNode(),
+ destIndex);
} else {
NOTREACHED();
}
@@ -579,6 +720,121 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
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)
+ 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 {
+ BookmarkButton* button = [self buttonForDroppingOnAtPoint:point
+ fromArray:buttons_.get()];
+ // One more chance -- try "Other Bookmarks".
+ // This is different than BookmarkBarFolderController.
+ if (!button) {
+ NSArray* array = [NSArray arrayWithObject: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];
+ 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]);
+ [[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();
+ }
+}
+
+// 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)indicatorPosForDragOfButton:(BookmarkButton*)sourceButton
toPoint:(NSPoint)point {
CGFloat x = 0;
@@ -617,6 +873,11 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
return x;
}
+// Return the parent window for all BookmarkBarFolderController windows.
+- (NSWindow*)parentWindow {
+ return [[self view] window];
+}
+
- (int)currentTabContentsHeight {
return browser_->GetSelectedTabContents() ?
browser_->GetSelectedTabContents()->view()->GetContainerSize().height() :
@@ -627,14 +888,6 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
return browser_->profile()->GetThemeProvider();
}
-- (BookmarkNode*)nodeFromButton:(id)button {
- NSCell* cell = [button cell];
- BookmarkNode* node = static_cast<BookmarkNode*>(
- [[cell representedObject] pointerValue]);
- DCHECK(node);
- return node;
-}
-
// Enable or disable items. We are the menu delegate for both the bar
// and for bookmark folder buttons.
- (BOOL)validateUserInterfaceItem:(id)item {
@@ -695,7 +948,9 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
}
- (IBAction)openBookmark:(id)sender {
- BookmarkNode* node = [self nodeFromButton:sender];
+ [self closeAllBookmarkFolders];
+ DCHECK([sender respondsToSelector:@selector(bookmarkNode)]);
+ const BookmarkNode* node = [sender bookmarkNode];
WindowOpenDisposition disposition =
event_utils::WindowOpenDispositionFromNSEvent([NSApp currentEvent]);
[self openURL:node->GetURL() disposition:disposition];
@@ -801,14 +1056,31 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
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];
+// Heuristics:
+// 1: click and hold (without moving much): opens it
+// 2: click and release (normal button push): opens it.
+// This is called on the second one.
+//
+// Called from a Folder bookmark button to open the folder.
+- (IBAction)openBookmarkFolderFromButton:(id)sender {
+ DCHECK(sender);
+ if (folderController_) {
+ // closeAllBookmarkFolders sets folderController_ to nil
+ // so we need the SAME check to happen first.
+ BOOL same = ([folderController_ parentButton] == sender);
+ [self closeAllBookmarkFolders];
+ // If click on same folder, close it and be done.
+ // Else we clicked on a different folder so more work to do.
+ if (same)
+ return;
}
+
+ // Folder controller, like many window controllers, owns itself.
+ folderController_ = [[BookmarkBarFolderController alloc]
+ initWithParentButton:sender
+ parentController:self];
+ [folderController_ showWindow:self];
+ [self watchForClickOutside:YES];
}
// Rebuild the off-the-side menu, taking into account which buttons are
@@ -827,9 +1099,9 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
[menu removeItemAtIndex:i];
// Add items corresponding to buttons which aren't displayed.
- for (NSButton* button in buttons_.get()) {
+ for (BookmarkButton* button in buttons_.get()) {
if (![button superview])
- [self addNode:[self nodeFromButton:button] toMenu:menu];
+ [self addNode:[button bookmarkNode] toMenu:menu];
}
}
@@ -903,6 +1175,9 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
BookmarkNode* node = [self nodeFromMenuItem:sender];
bookmarkModel_->Remove(node->GetParent(),
node->GetParent()->IndexOfChild(node));
+ // TODO(jrg): don't close; rebuild.
+ // http://crbug.com/36614
+ [self closeAllBookmarkFolders];
}
// An ObjC version of bookmark_utils::OpenAllImpl().
@@ -986,19 +1261,29 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
// 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 {
- NSString* title = base::SysWideToNSString(node->GetTitle());
BookmarkButtonCell* cell =
[[[BookmarkButtonCell alloc] initTextCell:nil]
autorelease];
DCHECK(cell);
- [cell setRepresentedObject:[NSValue valueWithPointer:node]];
+ [cell setBookmarkNode:node];
+
+ if (node) {
+ NSString* title = base::SysWideToNSString(node->GetTitle());
+ NSImage* image = [self getFavIconForNode:node];
+ [cell setBookmarkCellText:title image:image];
+ if (node->is_folder())
+ [cell setMenu:buttonFolderContextMenu_];
+ else
+ [cell setMenu:buttonContextMenu_];
+ } else {
+ [cell setEmpty:YES];
+ [cell setBookmarkCellText:l10n_util::GetNSString(IDS_MENU_EMPTY_SUBMENU)
+ image:nil];
+ }
+
+ // 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.
- NSImage* image = [self getFavIconForNode:node];
- [cell setBookmarkCellText:title image:image];
- if (node->is_folder())
- [cell setMenu:buttonFolderContextMenu_];
- else
- [cell setMenu:buttonContextMenu_];
return cell;
}
@@ -1101,7 +1386,7 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
if (child->is_folder()) {
[button setTarget:self];
- [button setAction:@selector(openFolderMenuFromButton:)];
+ [button setAction:@selector(openBookmarkFolderFromButton:)];
} else {
// Make the button do something
[button setTarget:self];
@@ -1193,7 +1478,7 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
[button setCell:cell];
[button setDelegate:self];
[button setTarget:self];
- [button setAction:@selector(openFolderMenuFromButton:)];
+ [button setAction:@selector(openBookmarkFolderFromButton:)];
[buttonView_ addSubview:button];
// Now that it's here, move the chevron over.
@@ -1269,13 +1554,11 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
// favicons will eventually load.
- (void)nodeFavIconLoaded:(BookmarkModel*)model
node:(const BookmarkNode*)node {
- for (NSButton* button in buttons_.get()) {
- BookmarkButtonCell* cell = [button cell];
- void* pointer = [[cell representedObject] pointerValue];
- const BookmarkNode* cellnode = static_cast<const BookmarkNode*>(pointer);
+ for (BookmarkButton* button in buttons_.get()) {
+ const BookmarkNode* cellnode = [button bookmarkNode];
if (cellnode == node) {
- [cell setBookmarkCellText:nil
- image:[self getFavIconForNode:node]];
+ [[button cell] setBookmarkCellText:nil
+ image:[self getFavIconForNode: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.
@@ -1502,7 +1785,7 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
// (BookmarkButtonDelegate protocol)
- (void)fillPasteboard:(NSPasteboard*)pboard
forDragOfButton:(BookmarkButton*)button {
- if (BookmarkNode* node = [self nodeFromButton:button]) {
+ if (const BookmarkNode* node = [button bookmarkNode]) {
// Put the bookmark information into the pasteboard, and then write our own
// data for |kBookmarkButtonDragType|.
[self copyBookmarkNode:node
diff --git a/chrome/browser/cocoa/bookmark_bar_controller_unittest.mm b/chrome/browser/cocoa/bookmark_bar_controller_unittest.mm
index 4e1aa933..5f460a7 100644
--- a/chrome/browser/cocoa/bookmark_bar_controller_unittest.mm
+++ b/chrome/browser/cocoa/bookmark_bar_controller_unittest.mm
@@ -10,8 +10,10 @@
#include "base/sys_string_conversions.h"
#import "chrome/browser/cocoa/bookmark_bar_constants.h"
#import "chrome/browser/cocoa/bookmark_bar_controller.h"
+#import "chrome/browser/cocoa/bookmark_bar_folder_window.h"
#import "chrome/browser/cocoa/bookmark_bar_view.h"
#import "chrome/browser/cocoa/bookmark_button.h"
+#import "chrome/browser/cocoa/bookmark_button_cell.h"
#import "chrome/browser/cocoa/bookmark_menu.h"
#include "chrome/browser/cocoa/browser_test_helper.h"
#import "chrome/browser/cocoa/cocoa_test_helper.h"
@@ -146,6 +148,9 @@ class BookmarkBarControllerTest : public CocoaTest {
frame.origin.y = 100;
[[[bar view] superview] setFrame:frame];
+ // Make sure it's on in a window so viewDidMoveToWindow is called
+ [[test_window() contentView] addSubview:parent_view_];
+
// Make sure it's open so certain things aren't no-ops.
[bar updateAndShowNormalBar:YES
showDetachedBar:NO
@@ -434,8 +439,9 @@ TEST_F(BookmarkBarControllerTest, OpenBookmark) {
GURL gurl("http://walla.walla.ding.dong.com");
scoped_ptr<BookmarkNode> node(new BookmarkNode(gurl));
- scoped_nsobject<NSButtonCell> cell([[NSButtonCell alloc] init]);
- scoped_nsobject<NSButton> button([[NSButton alloc] init]);
+ scoped_nsobject<BookmarkButtonCell> cell([[BookmarkButtonCell alloc] init]);
+ [cell setBookmarkNode:node.get()];
+ scoped_nsobject<BookmarkButton> button([[BookmarkButton alloc] init]);
[button setCell:cell.get()];
[cell setRepresentedObject:[NSValue valueWithPointer:node.get()]];
@@ -651,6 +657,13 @@ TEST_F(BookmarkBarControllerTest, Cell) {
EXPECT_TRUE(cell);
EXPECT_TRUE([[cell title] isEqual:@"supertitle"]);
EXPECT_EQ(node, [[cell representedObject] pointerValue]);
+ EXPECT_TRUE([cell menu]);
+
+ // Empty cells have no menu.
+ cell = [bar_ cellForBookmarkNode:nil];
+ EXPECT_FALSE([cell menu]);
+ // Even empty cells have a title (of "(empty)")
+ EXPECT_TRUE([cell title]);
// cell is autoreleased; no need to release here
}
@@ -932,6 +945,129 @@ TEST_F(BookmarkBarControllerTest, TestClearOnDealloc) {
}
}
+TEST_F(BookmarkBarControllerTest, TestFolders) {
+ BookmarkModel* model = helper_.profile()->GetBookmarkModel();
+
+ // Create some folder buttons.
+ 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"));
+ folder = model->AddGroup(parent, parent->GetChildCount(), L"empty");
+
+ EXPECT_EQ([[bar_ buttons] count], 2U);
+ BookmarkButton* button = [[bar_ buttons] objectAtIndex:0]; // full one
+
+ EXPECT_FALSE([bar_ folderController]);
+ [bar_ openBookmarkFolderFromButton:button];
+ BookmarkBarFolderController* bbfc = [bar_ folderController];
+ EXPECT_TRUE(bbfc);
+
+ // Make sure a 2nd open on the same button closes things.
+ [bar_ openBookmarkFolderFromButton:button];
+ EXPECT_FALSE([bar_ folderController]);
+
+ // Next open is a different button.
+ [bar_ openBookmarkFolderFromButton:[[bar_ buttons] objectAtIndex:1]];
+ EXPECT_TRUE([bar_ folderController]);
+ EXPECT_NE(bbfc, [bar_ folderController]);
+
+ // Finally confirm a close removes the folder controller.
+ [bar_ closeBookmarkFolder:nil];
+ EXPECT_FALSE([bar_ folderController]);
+
+ // Next part of the test: similar actions but with mouseEntered/mouseExited.
+
+ // First confirm mouseEntered does nothing if "menus" aren't active.
+ NSEvent* event = test_event_utils::MakeMouseEvent(NSOtherMouseUp, 0);
+ [bar_ mouseEnteredButton:[[bar_ buttons] objectAtIndex:0] event:event];
+ EXPECT_FALSE([bar_ folderController]);
+
+ // Make one active. Entering it is now a no-op.
+ [bar_ openBookmarkFolderFromButton:[[bar_ buttons] objectAtIndex:0]];
+ bbfc = [bar_ folderController];
+ EXPECT_TRUE(bbfc);
+ [bar_ mouseEnteredButton:[[bar_ buttons] objectAtIndex:0] event:event];
+ EXPECT_EQ(bbfc, [bar_ folderController]);
+
+ // Enter a different one; a new folderController is active.
+ [bar_ mouseEnteredButton:[[bar_ buttons] objectAtIndex:1] event:event];
+ EXPECT_NE(bbfc, [bar_ folderController]);
+
+ // Confirm exited is a no-op.
+ [bar_ mouseExitedButton:[[bar_ buttons] objectAtIndex:1] event:event];
+ EXPECT_NE(bbfc, [bar_ folderController]);
+
+ // Clean up.
+ [bar_ closeBookmarkFolder:nil];
+}
+
+TEST_F(BookmarkBarControllerTest, ClickOutsideCheck) {
+ NSEvent* event = test_event_utils::MakeMouseEvent(NSMouseMoved, 0);
+ EXPECT_FALSE([bar_ isEventAClickOutside:event]);
+
+ BookmarkBarFolderWindow* folderWindow = [[[BookmarkBarFolderWindow alloc]
+ init] autorelease];
+ [[[bar_ view] window] addChildWindow:folderWindow
+ ordered:NSWindowAbove];
+ event = test_event_utils::LeftMouseDownAtPointInWindow(NSMakePoint(1,1),
+ folderWindow);
+ EXPECT_FALSE([bar_ isEventAClickOutside:event]);
+
+ event = test_event_utils::LeftMouseDownAtPointInWindow(NSMakePoint(100,100),
+ test_window());
+ EXPECT_TRUE([bar_ isEventAClickOutside:event]);
+ [[[bar_ view] window] removeChildWindow:folderWindow];
+}
+
+TEST_F(BookmarkBarControllerTest, DropDestination) {
+ // Make some buttons.
+ BookmarkModel* model = helper_.profile()->GetBookmarkModel();
+ const BookmarkNode* parent = model->GetBookmarkBarNode();
+ model->AddGroup(parent, parent->GetChildCount(), L"group 1");
+ model->AddGroup(parent, parent->GetChildCount(), L"group 2");
+ EXPECT_EQ([[bar_ buttons] count], 2U);
+
+ // Confirm "off to left" and "off to right" match nothing.
+ NSPoint p = NSMakePoint(-1, 2);
+ EXPECT_FALSE([bar_ buttonForDroppingOnAtPoint:p]);
+ EXPECT_TRUE([bar_ shouldShowIndicatorShownForPoint:p]);
+ p = NSMakePoint(50000, 10);
+ EXPECT_FALSE([bar_ buttonForDroppingOnAtPoint:p]);
+ EXPECT_TRUE([bar_ shouldShowIndicatorShownForPoint:p]);
+
+ // Confirm "right in the center" (give or take a pixel) is a match,
+ // and confirm "just barely in the button" is not. Anything more
+ // specific seems likely to be tweaked.
+ for (BookmarkButton* button in [bar_ buttons]) {
+ CGFloat x = NSMidX([button frame]);
+ // Somewhere near the center: a match
+ EXPECT_EQ(button,
+ [bar_ buttonForDroppingOnAtPoint:NSMakePoint(x-1, 10)]);
+ EXPECT_EQ(button,
+ [bar_ buttonForDroppingOnAtPoint:NSMakePoint(x+1, 10)]);
+ EXPECT_FALSE([bar_ shouldShowIndicatorShownForPoint:NSMakePoint(x, 10)]);;
+
+ // On the very edges: NOT a match
+ x = NSMinX([button frame]);
+ EXPECT_NE(button,
+ [bar_ buttonForDroppingOnAtPoint:NSMakePoint(x, 9)]);
+ x = NSMaxX([button frame]);
+ EXPECT_NE(button,
+ [bar_ buttonForDroppingOnAtPoint:NSMakePoint(x, 11)]);
+ }
+}
+
+// TODO(jrg): draggingEntered: and draggingExited: trigger timers so
+// they are hard to test. Factor out "fire timers" into routines
+// which can be overridden to fire immediately to make behavior
+// confirmable.
+
+// TODO(jrg): add unit test to make sure "Other Bookmarks" responds
+// properly to a hover open.
+
// TODO(viettrungluu): figure out how to test animations.
} // namespace
diff --git a/chrome/browser/cocoa/bookmark_bar_folder_controller.h b/chrome/browser/cocoa/bookmark_bar_folder_controller.h
new file mode 100644
index 0000000..5f199d4
--- /dev/null
+++ b/chrome/browser/cocoa/bookmark_bar_folder_controller.h
@@ -0,0 +1,92 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/scoped_nsobject.h"
+#import "chrome/browser/cocoa/bookmark_button.h"
+
+@class BookmarkBarFolderView;
+
+// A controller for the pop-up windows from bookmark folder buttons
+// which look sort of like menus.
+@interface BookmarkBarFolderController :
+ NSWindowController<BookmarkButtonDelegate,
+ BookmarkButtonControllerProtocol> {
+ @private
+ // The button whose click opened us.
+ scoped_nsobject<BookmarkButton> parentButton_;
+
+ // Bookmark bar folder controller chains are torn down in two ways:
+ // 1. Clicking "outside" the folder (see use of
+ // CrApplicationEventHookProtocol in the bookmark bar controller).
+ // 2. Engaging a different folder (via hover over or explicit click).
+ //
+ // In either case, the BookmarkButtonControllerProtocol method
+ // closeAllBookmarkFolders gets called. For bookmark bar folder
+ // controllers, this is passed up the chain so we begin with a top
+ // level "close".
+ // When any bookmark folder window closes, it necessarily tells
+ // subcontroller windows to close (down the chain), and autoreleases
+ // the controller. (Must autorelease since the controller can still
+ // get delegate events such as windowDidClose).
+ //
+ // Bookmark bar folder controllers own their buttons. When doing
+ // drag and drop of a button from one sub-sub-folder to a different
+ // sub-sub-folder, we need to make sure the button's pointers stay
+ // valid until we've dropped (or cancelled). Note that such a drag
+ // causes the source sub-sub-folder (previous parent window) to go
+ // away (windows close, controllers autoreleased) since you're
+ // hovering over a different folder chain for dropping. To keep
+ // things valid (like the button's target, its delegate, the parent
+ // cotroller that we have a pointer to below [below], etc), we heep
+ // strong pointers to our owning controller, so the entire chain
+ // stays owned.
+
+ // Our parent controller. This may be another
+ // BookmarkBarFolderController (if we are a nested folder) or it may
+ // be the BookmarkBarController (if not).
+ // Strong to insure the object lives as long as we need it.
+ scoped_nsobject<NSObject<BookmarkButtonControllerProtocol> >
+ parentController_;
+
+ // Our buttons. We do not have buttons for nested folders.
+ scoped_nsobject<NSMutableArray> buttons_;
+
+ // The main view of this window (where the buttons go).
+ IBOutlet BookmarkBarFolderView* mainView_;
+
+ // Like normal menus, hovering over a folder button causes it to
+ // open. This variable is set when a hover is initiated (but has
+ // not necessarily fired yet).
+ scoped_nsobject<BookmarkButton> hoverButton_;
+
+ // A controller for a pop-up bookmark folder window (custom menu).
+ // We (self) are the parentController_ for our folderController_.
+ // This is not a scoped_nsobject because it owns itself (when its
+ // window closes the controller gets autoreleased).
+ BookmarkBarFolderController* folderController_;
+
+ // Has a draggingExited been called? Only relevant for
+ // performSelector:after:delay: calls that get triggered in the
+ // middle of a drag.
+ BOOL draggingExited_;
+}
+
+- (id)initWithParentButton:(BookmarkButton*)button
+ parentController:(NSObject<BookmarkButtonControllerProtocol>*)controller;
+
+// Return the parent button that owns the bookmark folder we represent.
+- (BookmarkButton*)parentButton;
+
+@end
+
+
+@interface BookmarkBarFolderController(TestingAPI)
+- (NSView*)mainView;
+- (NSPoint)windowTopLeft;
+- (NSArray*)buttons;
+- (BookmarkBarFolderController*)folderController;
+@end
+
diff --git a/chrome/browser/cocoa/bookmark_bar_folder_controller.mm b/chrome/browser/cocoa/bookmark_bar_folder_controller.mm
new file mode 100644
index 0000000..64488cf
--- /dev/null
+++ b/chrome/browser/cocoa/bookmark_bar_folder_controller.mm
@@ -0,0 +1,587 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "chrome/browser/cocoa/bookmark_bar_folder_controller.h"
+#include "base/mac_util.h"
+#include "base/sys_string_conversions.h"
+#include "chrome/browser/bookmarks/bookmark_model.h"
+#import "chrome/browser/browser_theme_provider.h"
+#import "chrome/browser/cocoa/bookmark_bar_constants.h" // namespace bookmarks
+#import "chrome/browser/cocoa/bookmark_bar_controller.h" // namespace bookmarks
+#import "chrome/browser/cocoa/bookmark_bar_folder_view.h"
+#import "chrome/browser/cocoa/bookmark_button_cell.h"
+
+
+@interface BookmarkBarFolderController(Private)
+- (void)configureWindow;
+- (IBAction)openBookmarkFolderFromButton:(id)sender;
+@end
+
+
+@implementation BookmarkBarFolderController
+
+- (id)initWithParentButton:(BookmarkButton*)button
+ parentController:(NSObject<BookmarkButtonControllerProtocol>*)controller {
+ NSString* nibPath =
+ [mac_util::MainAppBundle() pathForResource:@"BookmarkBarFolderWindow"
+ ofType:@"nib"];
+ if ((self = [super initWithWindowNibPath:nibPath owner:self])) {
+ parentButton_.reset([button retain]);
+ parentController_.reset([controller retain]);
+ buttons_.reset([[NSMutableArray alloc] init]);
+
+ // Register for theme changes.
+ NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
+ [defaultCenter addObserver:self
+ selector:@selector(themeDidChangeNotification:)
+ name:kBrowserThemeDidChangeNotification
+ object:nil];
+
+ [self configureWindow];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ // Note: we don't need to
+ // [NSObject cancelPreviousPerformRequestsWithTarget:self];
+ // Because all of our performSelector: calls use withDelay: which
+ // retains us.
+ [super dealloc];
+}
+
+// Update theme information for all our buttons.
+- (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];
+ }
+}
+
+// Called after the current theme has changed.
+- (void)themeDidChangeNotification:(NSNotification*)aNotification {
+ ThemeProvider* themeProvider =
+ static_cast<ThemeProvider*>([[aNotification object] pointerValue]);
+ [self updateTheme:themeProvider];
+}
+
+// Redirect bookmark button cell creation to our parent to allow a
+// single implementation.
+- (NSCell*)cellForBookmarkNode:(const BookmarkNode*)child {
+ return [parentController_ cellForBookmarkNode:child];
+}
+
+// Create a bookmark button for the given node using frame.
+//
+// If |node| is NULL this is an "(empty)" button.
+// Does NOT add this button to our button list.
+// Returns an autoreleased button.
+//
+// TODO(jrg): combine with addNodesToButtonList: code from
+// bookmark_bar_controller.mm, and generalize that to use both x and y
+// offsets.
+// http://crbug.com/35966
+- (BookmarkButton*)makeButtonForNode:(const BookmarkNode*)node
+ frame:(NSRect)frame {
+ BookmarkButton* button = [[[BookmarkButton alloc] initWithFrame:frame]
+ autorelease];
+ DCHECK(button);
+ NSCell* cell = [self cellForBookmarkNode:node];
+ [button setCell:cell];
+ [button setDelegate:self];
+ if (node) {
+ if (node->is_folder()) {
+ [button setTarget:self];
+ [button setAction:@selector(openBookmarkFolderFromButton:)];
+ } else {
+ // Make the button do something.
+ [button setTarget:self];
+ [button setAction:@selector(openBookmark:)];
+ // Add a tooltip.
+ NSString* title = base::SysWideToNSString(node->GetTitle());
+ std::string urlString = node->GetURL().possibly_invalid_spec();
+ NSString* tooltip = [NSString stringWithFormat:@"%@\n%s", title,
+ urlString.c_str()];
+ [button setToolTip:tooltip];
+ }
+ } else {
+ [button setEnabled:NO];
+ }
+ return button;
+}
+
+// Exposed for testing.
+- (NSView*)mainView {
+ return mainView_;
+}
+
+// Exposed for testing.
+- (BookmarkBarFolderController*)folderController {
+ return folderController_;
+}
+
+// Compute and return the top left point of our window (screen
+// coordinates). The top left is positioned in a manner similar to
+// cascading menus.
+- (NSPoint)windowTopLeft {
+ NSPoint newWindowTopLeft;
+ if (![parentController_ isKindOfClass:[self class]]) {
+ // If we're not popping up from one of ourselves, we must be
+ // popping up from the bookmark bar itself. In this case, start
+ // BELOW the parent button. Our left is the button left; our top
+ // is bottom of button's parent view.
+ NSPoint buttonBottomLeftInScreen =
+ [[parentButton_ window]
+ convertBaseToScreen:[parentButton_
+ convertPointToBase:NSZeroPoint]];
+ NSPoint bookmarkBarBottomLeftInScreen =
+ [[parentButton_ window]
+ convertBaseToScreen:[[parentButton_ superview]
+ convertPointToBase:NSMakePoint(0,0)]];
+ newWindowTopLeft = NSMakePoint(buttonBottomLeftInScreen.x,
+ bookmarkBarBottomLeftInScreen.y);
+ } else {
+ // Our parent controller is another BookmarkBarFolderController.
+ // In this case, start ot the RIGHT of the parent button.
+ // Start to RIGHT of the button.
+ // TODO(jrg): If too far to right, pop left again.
+ // http://crbug.com/36225
+ newWindowTopLeft.x = NSMaxX([[parentButton_ window] frame]);
+ NSPoint top = NSMakePoint(0, (NSMaxY([parentButton_ frame]) +
+ bookmarks::kBookmarkVerticalPadding));
+ NSPoint topOfWindow =
+ [[parentButton_ window]
+ convertBaseToScreen:[[parentButton_ superview]
+ convertPointToBase:top]];
+ newWindowTopLeft.y = topOfWindow.y;
+ }
+ return newWindowTopLeft;
+}
+
+// Determine window size and position.
+// Create buttons for all our nodes.
+// TODO(jrg): break up into more and smaller routines for easier unit testing.
+- (void)configureWindow {
+ NSPoint newWindowTopLeft = [self windowTopLeft];
+ const BookmarkNode* node = [parentButton_ bookmarkNode];
+ DCHECK(node);
+ int buttons = node->GetChildCount();
+ if (buttons == 0)
+ buttons = 1; // the "empty" button
+ int height = buttons * bookmarks::kBookmarkButtonHeight;
+ // TODO(jrg): use full width for buttons, like menus?
+ // http://crbug.com/36487
+ int width = (bookmarks::kDefaultBookmarkWidth +
+ 2 * bookmarks::kBookmarkVerticalPadding);
+ [[self window] setFrame:NSMakeRect(newWindowTopLeft.x,
+ newWindowTopLeft.y - height,
+ width,
+ height)
+ display:YES];
+
+ // TODO(jrg): combine with frame code in bookmark_bar_controller.mm
+ // http://crbug.com/35966
+ NSRect frame = NSMakeRect(bookmarks::kBookmarkHorizontalPadding,
+ height - (bookmarks::kBookmarkBarHeight -
+ bookmarks::kBookmarkHorizontalPadding),
+ bookmarks::kDefaultBookmarkWidth,
+ (bookmarks::kBookmarkBarHeight -
+ 2 * bookmarks::kBookmarkVerticalPadding));
+
+ // TODO(jrg): combine with addNodesToButtonList: code from
+ // bookmark_bar_controller.mm (but use y offset)
+ // http://crbug.com/35966
+ if (!node->GetChildCount()) {
+ // If no children we are the empty button.
+ BookmarkButton* button = [self makeButtonForNode:nil
+ frame:frame];
+ [buttons_ addObject:button];
+ [mainView_ addSubview:button];
+ } else {
+ for (int i = 0; i < node->GetChildCount(); i++) {
+ const BookmarkNode* child = node->GetChild(i);
+ BookmarkButton* button = [self makeButtonForNode:child
+ frame:frame];
+ [buttons_ addObject:button];
+ [mainView_ addSubview:button];
+ frame.origin.y -= bookmarks::kBookmarkBarHeight;
+ }
+ }
+
+ [self updateTheme:[self themeProvider]];
+ [[parentController_ parentWindow] addChildWindow:[self window]
+ ordered:NSWindowAbove];
+}
+
+- (ThemeProvider*)themeProvider {
+ return [parentController_ themeProvider];
+}
+
+// Recursively close all bookmark folders.
+- (void)closeAllBookmarkFolders {
+ // Closing the top level implicitly closes all children.
+ [parentController_ 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;
+}
+
+// Called after a delay to close a previously hover-opened folder.
+- (void)closeBookmarkFolderOnHoverButton:(BookmarkButton*)button {
+ if (hoverButton_ || !draggingExited_) {
+ // If there is a new one which hover-opened, we're done. If we
+ // are still inside while dragging but have no hoverButton_ we
+ // must be hovering over something else (e.g. normal bookmark
+ // button), so we're still done.
+ [[button target] closeBookmarkFolder:button];
+ hoverButton_.reset();
+ } else {
+ // If there is no hoverOpen but we've exited our window, we may be
+ // in a subfolder. Restore our state so it's cleaned up later.
+ hoverButton_.reset([button retain]);
+ }
+}
+
+// Delegate callback.
+- (void)windowWillClose:(NSNotification*)notification {
+ [[self parentWindow] removeChildWindow:[self window]];
+ [self closeBookmarkFolder:self];
+ [self autorelease];
+}
+
+- (BookmarkButton*)parentButton {
+ return parentButton_.get();
+}
+
+// Ugh... copied from bookmark_bar_controller.mm
+// Is it worth it to factor out for, essentially, 2 lines?
+// TODO(jrg): answer is probably yes.
+// http://crbug.com/35966
+- (void)fillPasteboard:(NSPasteboard*)pboard
+ forDragOfButton:(BookmarkButton*)button {
+ const BookmarkNode* node = [button bookmarkNode];
+ if (node) {
+ // Put the bookmark information into the pasteboard, and then write our own
+ // data for |kBookmarkButtonDragType|.
+
+ /* // TODO(jrg): combine code
+ -[BookmarkBarController copyBookmarkNode:node
+ toPasteboard:pboard];
+ */
+ [pboard declareTypes:[NSArray arrayWithObject:kBookmarkButtonDragType]
+ owner:button];
+ [pboard setData:[NSData dataWithBytes:&button length:sizeof(button)]
+ forType:kBookmarkButtonDragType];
+
+ } else {
+ NOTREACHED();
+ }
+}
+
+// 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) {
+ return ((value >= low) && (value <= high));
+}
+
+// Return the proposed drop target for a hover open button, or nil if none.
+//
+// TODO(jrg): this is just like the version in
+// bookmark_bar_controller.mm, but vertical instead of horizontal.
+// Generalize to be axis independent then share code.
+// http://crbug.com/35966
+// Get UI review on "middle half" ness.
+// http://crbug.com/36276
+- (BookmarkButton*)buttonForDroppingOnAtPoint:(NSPoint)point {
+ for (BookmarkButton* button in buttons_.get()) {
+ // No early break -- makes no assumption about button ordering.
+
+ // Intentionally NOT using NSPointInRect() so that scrolling into
+ // a submenu doesn't cause it to be closed.
+ if (ValueInRangeInclusive(NSMinY([button frame]),
+ point.y,
+ NSMaxY([button frame]))) {
+
+ // Over a button but let's be a little more specific
+ // (e.g. over the middle half).
+ NSRect frame = [button frame];
+ NSRect middleHalfOfButton = NSInsetRect(frame, 0, frame.size.height / 4);
+ if (ValueInRangeInclusive(NSMinY(middleHalfOfButton),
+ point.y,
+ NSMaxY(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;
+}
+
+// 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 {
+ draggingExited_ = NO;
+ NSPoint currentLocation = [info draggingLocation];
+
+ BookmarkButton* button = [self buttonForDroppingOnAtPoint:currentLocation];
+ 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]];
+ [self performSelector:@selector(closeBookmarkFolderOnHoverButton:)
+ withObject:[hoverButton_ target]
+ afterDelay:bookmarks::kDragHoverCloseDelay];
+ }
+ hoverButton_.reset([button retain]);
+ [[hoverButton_ target]
+ performSelector:@selector(openBookmarkFolderFromButton:)
+ withObject:hoverButton_
+ afterDelay:bookmarks::kDragHoverOpenDelay];
+ }
+
+ // If we get here we may no longer have a hover button.
+ if (!button) {
+ if (hoverButton_) {
+ [NSObject cancelPreviousPerformRequestsWithTarget:[hoverButton_ target]];
+ [self performSelector:@selector(closeBookmarkFolderOnHoverButton:)
+ withObject:hoverButton_
+ afterDelay:bookmarks::kDragHoverCloseDelay];
+ hoverButton_.reset();
+ }
+ }
+
+ return NSDragOperationMove;
+}
+
+// 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 {
+ draggingExited_ = YES;
+ // NOT the same as a cancel --> we may have moved the mouse into the submenu.
+ if (hoverButton_) {
+ [NSObject cancelPreviousPerformRequestsWithTarget:[hoverButton_ target]];
+ [NSObject cancelPreviousPerformRequestsWithTarget:hoverButton_];
+ hoverButton_.reset();
+ }
+}
+
+// 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
+// exist in the future).
+// http://crbug.com/35966
+- (int)indexForDragOfButton:(BookmarkButton*)sourceButton
+ toPoint:(NSPoint)point {
+ DCHECK([sourceButton isKindOfClass:[BookmarkButton class]]);
+
+ // Identify which buttons we are between. For now, assume a button
+ // location is at the center point of its view, and that an exact
+ // match means "place before".
+ // TODO(jrg): revisit position info based on UI team feedback.
+ // dropLocation is in bar local coordinates.
+ // http://crbug.com/36276
+ NSPoint dropLocation =
+ [mainView_ convertPoint:point
+ fromView:[[self window] contentView]];
+ BookmarkButton* buttonToTheTopOfDraggedButton = nil;
+ // Buttons are laid out in this array from top to bottom (screen
+ // wise), which means "biggest y" --> "smallest y".
+ for (BookmarkButton* button in buttons_.get()) {
+ CGFloat midpoint = NSMidY([button frame]);
+ if (dropLocation.y > midpoint) {
+ break;
+ }
+ buttonToTheTopOfDraggedButton = button;
+ }
+
+ // TODO(jrg): On Windows, dropping onto (empty) highlights the
+ // entire drop location and does not use an insertion point.
+ // http://crbug.com/35967
+ if (!buttonToTheTopOfDraggedButton) {
+ // We are at the very top (we broke out of the loop on the first try).
+ return 0;
+ }
+ if ([buttonToTheTopOfDraggedButton isEmpty]) {
+ // There is a button but it's an empty placeholder.
+ // Default to inserting on top of it.
+ return 0;
+ }
+ const BookmarkNode* beforeNode = [buttonToTheTopOfDraggedButton
+ bookmarkNode];
+ DCHECK(beforeNode);
+ return beforeNode->GetParent()->IndexOfChild(beforeNode) + 1;
+}
+
+- (BookmarkModel*)bookmarkModel {
+ return [parentController_ bookmarkModel];
+}
+
+// TODO(jrg): ARGH more code dup.
+// http://crbug.com/35966
+- (BOOL)dragButton:(BookmarkButton*)sourceButton to:(NSPoint)point {
+ DCHECK([sourceButton isKindOfClass:[BookmarkButton class]]);
+
+ const BookmarkNode* sourceNode = [sourceButton bookmarkNode];
+ DCHECK(sourceNode);
+
+ int destIndex = [self indexForDragOfButton:sourceButton toPoint:point];
+ if (destIndex >= 0 && sourceNode) {
+ [parentController_ bookmarkModel]->Move(sourceNode,
+ [parentButton_ bookmarkNode],
+ destIndex);
+ } else {
+ NOTREACHED();
+ }
+
+ // Movement of a node triggers observers (like us) to rebuild the
+ // bar so we don't have to do so explicitly.
+
+ return YES;
+}
+
+// Return YES if we should show the drop indicator, else NO.
+// TODO(jrg): ARGH code dup!
+// http://crbug.com/35966
+- (BOOL)shouldShowIndicatorShownForPoint:(NSPoint)point {
+ return ![self buttonForDroppingOnAtPoint:point];
+}
+
+
+// Return the y position for a drop indicator.
+//
+// TODO(jrg): again we have code dup, sort of, with
+// bookmark_bar_controller.mm, but the axis is changed.
+// http://crbug.com/35966
+- (CGFloat)indicatorPosForDragOfButton:(BookmarkButton*)sourceButton
+ toPoint:(NSPoint)point {
+ CGFloat y = 0;
+ int destIndex = [self indexForDragOfButton:sourceButton toPoint:point];
+ int numButtons = static_cast<int>([buttons_ count]);
+
+ // If it's a drop strictly between existing buttons or at the very beginning
+ 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];
+ y = buttonFrame.origin.y +
+ buttonFrame.size.height +
+ 0.5 * bookmarks::kBookmarkVerticalPadding;
+
+ // 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 below it.
+ BookmarkButton* button =
+ [buttons_ objectAtIndex:static_cast<NSUInteger>(destIndex - 1)];
+ DCHECK(button);
+ NSRect buttonFrame = [button frame];
+ y = buttonFrame.origin.y -
+ 0.5 * bookmarks::kBookmarkVerticalPadding;
+
+ }
+ } else {
+ NOTREACHED();
+ }
+
+ return y;
+}
+
+- (NSWindow*)parentWindow {
+ return [parentController_ parentWindow];
+}
+
+// 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])
+ [self openBookmarkFolderFromButton:sender];
+}
+
+// Called from BookmarkButton.
+// Unlike bookmark_bar_controller's version, we DO default to being enabled.
+- (void)mouseEnteredButton:(id)sender event:(NSEvent*)event {
+ // 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 {
+ // 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];
+}
+
+- (IBAction)openBookmark:(id)sender {
+ // Carent controller closes it all...
+ [parentController_ openBookmark:sender];
+}
+
+// Unlike the bookmark_bar_controller, we do not watch for click outside.
+- (IBAction)openBookmarkFolderFromButton:(id)sender {
+ if (folderController_) {
+ // If the same we have nothing to do.
+ if ([folderController_ parentButton] == sender)
+ return;
+
+ [self closeBookmarkFolder:sender];
+ }
+ folderController_ = [[BookmarkBarFolderController alloc]
+ initWithParentButton:sender
+ parentController:self];
+ [folderController_ showWindow:self];
+}
+
+- (NSArray*)buttons {
+ return buttons_.get();
+}
+
+@end // BookmarkBarFolderController
diff --git a/chrome/browser/cocoa/bookmark_bar_folder_controller_unittest.mm b/chrome/browser/cocoa/bookmark_bar_folder_controller_unittest.mm
new file mode 100644
index 0000000..d523aaf
--- /dev/null
+++ b/chrome/browser/cocoa/bookmark_bar_folder_controller_unittest.mm
@@ -0,0 +1,204 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/basictypes.h"
+#include "base/scoped_nsobject.h"
+#import "chrome/browser/cocoa/bookmark_bar_controller.h"
+#import "chrome/browser/cocoa/bookmark_bar_folder_controller.h"
+#include "chrome/browser/cocoa/browser_test_helper.h"
+#import "chrome/browser/cocoa/cocoa_test_helper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+class BookmarkBarFolderControllerTest : public CocoaTest {
+ public:
+ scoped_nsobject<BookmarkBarController> parentBarController_;
+ BrowserTestHelper helper_;
+
+ BookmarkBarFolderControllerTest() {
+ BookmarkModel* model = helper_.profile()->GetBookmarkModel();
+ const BookmarkNode* parent = model->GetBookmarkBarNode();
+ const BookmarkNode* folderA = model->AddGroup(parent,
+ parent->GetChildCount(),
+ L"group");
+ model->AddGroup(parent, parent->GetChildCount(),
+ L"sibbling group");
+ const BookmarkNode* folderB = model->AddGroup(folderA,
+ folderA->GetChildCount(),
+ L"subgroup");
+ model->AddURL(folderA, folderA->GetChildCount(), L"title a",
+ GURL("http://www.google.com/a"));
+ model->AddURL(folderA, folderA->GetChildCount(),
+ L"title super duper long long whoa momma title you betcha",
+ GURL("http://www.google.com/b"));
+ model->AddURL(folderB, folderB->GetChildCount(), L"t",
+ GURL("http://www.google.com/c"));
+
+ parentBarController_.reset(
+ [[BookmarkBarController alloc]
+ initWithBrowser:helper_.browser()
+ initialWidth:300
+ delegate:nil
+ resizeDelegate:nil]);
+ [parentBarController_ loaded:model];
+ }
+
+ // Return a simple BookmarkBarFolderController.
+ BookmarkBarFolderController* SimpleBookmarkBarFolderController() {
+ BookmarkButton* parentButton = [[parentBarController_ buttons]
+ objectAtIndex:0];
+ return [[BookmarkBarFolderController alloc]
+ initWithParentButton:parentButton
+ parentController:parentBarController_];
+ }
+
+};
+
+TEST_F(BookmarkBarFolderControllerTest, InitCreateAndDelete) {
+ scoped_nsobject<BookmarkBarFolderController> bbfc;
+ bbfc.reset(SimpleBookmarkBarFolderController());
+
+ // Make sure none of the buttons overlap, and that all are inside
+ // the content frame.
+ NSArray* buttons = [bbfc buttons];
+ EXPECT_TRUE([buttons count]);
+ for (unsigned int i = 0; i < ([buttons count]-1); i++) {
+ EXPECT_FALSE(NSContainsRect([[buttons objectAtIndex:i] frame],
+ [[buttons objectAtIndex:i+1] frame]));
+ }
+ for (BookmarkButton* button in buttons) {
+ NSRect r = [[bbfc mainView] convertRect:[button frame] fromView:button];
+ EXPECT_TRUE(NSContainsRect([[bbfc mainView] frame], r));
+ }
+
+ // Confirm folder buttons have no tooltip. The important thing
+ // really is that we insure folders and non-folders are treated
+ // differently; not sure of any other generic way to do this.
+ for (BookmarkButton* button in buttons) {
+ if ([button isFolder])
+ EXPECT_FALSE([button toolTip]);
+ else
+ EXPECT_TRUE([button toolTip]);
+ }
+}
+
+// Make sure closing of the window releases the controller.
+// (e.g. valgrind shouldn't complain if we do this).
+TEST_F(BookmarkBarFolderControllerTest, ReleaseOnClose) {
+ scoped_nsobject<BookmarkBarFolderController> bbfc;
+ bbfc.reset(SimpleBookmarkBarFolderController());
+ EXPECT_TRUE(bbfc.get());
+
+ [bbfc retain]; // stop the scoped_nsobject from doing anything
+ [[bbfc window] close]; // trigger an autorelease of bbfc.get()
+}
+
+TEST_F(BookmarkBarFolderControllerTest, Position) {
+ BookmarkButton* parentButton = [[parentBarController_ buttons]
+ objectAtIndex:0];
+ EXPECT_TRUE(parentButton);
+
+ // If parent is a BookmarkBarController, grow down.
+ scoped_nsobject<BookmarkBarFolderController> bbfc;
+ bbfc.reset([[BookmarkBarFolderController alloc]
+ initWithParentButton:parentButton
+ parentController:parentBarController_]);
+ NSPoint pt = [bbfc windowTopLeft];
+ EXPECT_EQ(pt.y, NSMinY([[parentBarController_ view] frame]));
+
+ // If parent is a BookmarkBarFolderController, grow right.
+ scoped_nsobject<BookmarkBarFolderController> bbfc2;
+ bbfc2.reset([[BookmarkBarFolderController alloc]
+ initWithParentButton:[[bbfc buttons] objectAtIndex:0]
+ parentController:bbfc.get()]);
+ pt = [bbfc2 windowTopLeft];
+ EXPECT_EQ(pt.x, NSMaxX([[[bbfc.get() window] contentView] frame]));
+}
+
+TEST_F(BookmarkBarFolderControllerTest, DropDestination) {
+ scoped_nsobject<BookmarkBarFolderController> bbfc;
+ bbfc.reset(SimpleBookmarkBarFolderController());
+ EXPECT_TRUE(bbfc.get());
+
+ // Confirm "off the top" and "off the bottom" match no buttons.
+ NSPoint p = NSMakePoint(NSMidX([[bbfc mainView] frame]), 10000);
+ EXPECT_FALSE([bbfc buttonForDroppingOnAtPoint:p]);
+ EXPECT_TRUE([bbfc shouldShowIndicatorShownForPoint:p]);
+ p = NSMakePoint(NSMidX([[bbfc mainView] frame]), -1);
+ EXPECT_FALSE([bbfc buttonForDroppingOnAtPoint:p]);
+ EXPECT_TRUE([bbfc shouldShowIndicatorShownForPoint:p]);
+
+ // Confirm "right in the center" (give or take a pixel) is a match,
+ // and confirm "just barely in the button" is not. Anything more
+ // specific seems likely to be tweaked.
+ for (BookmarkButton* button in [bbfc buttons]) {
+ CGFloat x = NSMidX([button frame]);
+ CGFloat y = NSMidY([button frame]);
+ // Somewhere near the center: a match (but only if a folder!)
+ if ([button isFolder]) {
+ EXPECT_EQ(button,
+ [bbfc buttonForDroppingOnAtPoint:NSMakePoint(x-1, y+1)]);
+ EXPECT_EQ(button,
+ [bbfc buttonForDroppingOnAtPoint:NSMakePoint(x+1, y-1)]);
+ EXPECT_FALSE([bbfc shouldShowIndicatorShownForPoint:NSMakePoint(x, y)]);;
+ } else {
+ // If not a folder we don't drop into it.
+ EXPECT_FALSE([bbfc buttonForDroppingOnAtPoint:NSMakePoint(x-1, y+1)]);
+ EXPECT_FALSE([bbfc buttonForDroppingOnAtPoint:NSMakePoint(x+1, y-1)]);
+ EXPECT_TRUE([bbfc shouldShowIndicatorShownForPoint:NSMakePoint(x, y)]);;
+ }
+
+
+ // On some corners: NOT a match. Confirm that the indicator
+ // position for these two points is NOT the same.
+ BookmarkButton* dragButton = [[bbfc buttons] lastObject];
+ x = NSMinX([button frame]);
+ y = NSMinY([button frame]);
+ CGFloat pos1 = [bbfc indicatorPosForDragOfButton:dragButton
+ toPoint:NSMakePoint(x, y)];
+ EXPECT_NE(button,
+ [bbfc buttonForDroppingOnAtPoint:NSMakePoint(x, y)]);
+ x = NSMaxX([button frame]);
+ y = NSMaxY([button frame]);
+ CGFloat pos2 = [bbfc indicatorPosForDragOfButton:dragButton
+ toPoint:NSMakePoint(x, y)];
+ EXPECT_NE(button,
+ [bbfc buttonForDroppingOnAtPoint:NSMakePoint(x, y)]);
+ if (dragButton != button) {
+ EXPECT_NE(pos1, pos2);
+ }
+ }
+}
+
+TEST_F(BookmarkBarFolderControllerTest, OpenFolder) {
+ scoped_nsobject<BookmarkBarFolderController> bbfc;
+ bbfc.reset(SimpleBookmarkBarFolderController());
+ EXPECT_TRUE(bbfc.get());
+
+ EXPECT_FALSE([bbfc folderController]);
+ [bbfc openBookmarkFolderFromButton:[[bbfc buttons] objectAtIndex:0]];
+ id controller = [bbfc folderController];
+ EXPECT_TRUE(controller);
+
+ // Open the same one --> no change.
+ [bbfc openBookmarkFolderFromButton:[[bbfc buttons] objectAtIndex:0]];
+ EXPECT_EQ(controller, [bbfc folderController]);
+
+ // Open a new one --> change.
+ [bbfc openBookmarkFolderFromButton:[[bbfc buttons] objectAtIndex:1]];
+ EXPECT_NE(controller, [bbfc folderController]);
+
+ // Close it --> all gone!
+ [bbfc closeBookmarkFolder:nil];
+ EXPECT_FALSE([bbfc folderController]);
+}
+
+// TODO(jrg): draggingEntered: and draggingExited: trigger timers so
+// they are hard to test. Factor out "fire timers" into routines
+// which can be overridden to fire immediately to make behavior
+// confirmable.
+// There is a similar problem with mouseEnteredButton: and
+// mouseExitedButton:.
diff --git a/chrome/browser/cocoa/bookmark_bar_folder_view.h b/chrome/browser/cocoa/bookmark_bar_folder_view.h
new file mode 100644
index 0000000..a8fe85b
--- /dev/null
+++ b/chrome/browser/cocoa/bookmark_bar_folder_view.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Cocoa/Cocoa.h>
+#import "chrome/browser/cocoa/background_gradient_view.h"
+
+@protocol BookmarkButtonControllerProtocol;
+
+// Main content view for a bookmark bar folder "menu" window. This is
+// logically similar to a BookmarkBarView but is oriented vertically.
+@interface BookmarkBarFolderView : BackgroundGradientView {
+ @private
+ BOOL inDrag_; // Are we in the middle of a drag?
+ BOOL dropIndicatorShown_;
+ CGFloat dropIndicatorPosition_; // y position
+}
+// Return the controller that owns this view.
+- (id<BookmarkButtonControllerProtocol>)controller;
+@end
+
+@interface BookmarkBarFolderView(TestingAPI)
+- (void)setDropIndicatorShown:(BOOL)shown;
+@end
+
diff --git a/chrome/browser/cocoa/bookmark_bar_folder_view.mm b/chrome/browser/cocoa/bookmark_bar_folder_view.mm
new file mode 100644
index 0000000..e8e91dd
--- /dev/null
+++ b/chrome/browser/cocoa/bookmark_bar_folder_view.mm
@@ -0,0 +1,185 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "chrome/browser/cocoa/bookmark_bar_folder_view.h"
+
+#import "chrome/browser/browser_theme_provider.h"
+#import "chrome/browser/cocoa/bookmark_bar_controller.h"
+
+@implementation BookmarkBarFolderView
+
+- (id<BookmarkButtonControllerProtocol>)controller {
+ return [[self window] windowController];
+}
+
+- (void)awakeFromNib {
+ [super awakeFromNib];
+
+ // BackgroundGradientView's awakeFromNib does a |showsDivider_ = YES|.
+ // Make sure we turn it off.
+ [self setShowsDivider:NO];
+
+ NSArray* types = [NSArray arrayWithObject:kBookmarkButtonDragType];
+ [self registerForDraggedTypes:types];
+}
+
+- (void)dealloc {
+ [self unregisterDraggedTypes];
+ [super dealloc];
+}
+
+- (void)drawRect:(NSRect)rect {
+ [self drawBackground];
+
+ // TODO(jrg): copied from bookmark_bar_view but orientation changed.
+ // Code dup sucks but I'm not sure I can take 16 lines and make it
+ // generic for horiz vs vertical while keeping things simple.
+ // TODO(jrg): when throwing it all away and using animations, try
+ // hard to make a common routine for both.
+ // http://crbug.com/35966, http://crbug.com/35968
+
+ // Draw the bookmark-button-dragging drop indicator if necessary.
+ if (dropIndicatorShown_) {
+ const CGFloat kBarHeight = 1;
+ const CGFloat kBarHorizPad = 4;
+ const CGFloat kBarOpacity = 0.85;
+
+ NSRect uglyBlackBar =
+ NSMakeRect(kBarHorizPad, dropIndicatorPosition_,
+ NSWidth([self bounds]) - 2*kBarHorizPad,
+ kBarHeight);
+ // themeProvider is nil in unit tests.
+ ThemeProvider* themeProvider = [[self controller] themeProvider];
+ if (themeProvider) {
+ NSColor* uglyBlackBarColor = themeProvider->
+ GetNSColor(BrowserThemeProvider::COLOR_BOOKMARK_TEXT, true);
+ [[uglyBlackBarColor colorWithAlphaComponent:kBarOpacity] setFill];
+ [[NSBezierPath bezierPathWithRect:uglyBlackBar] fill];
+ }
+ }
+}
+
+// Virtually identical to [BookmarkBarView draggingEntered:].
+// TODO(jrg): find a way to share code. Lack of multiple inheritance
+// makes things more of a pain but there should be no excuse for laziness.
+// http://crbug.com/35966
+- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)info {
+ inDrag_ = YES;
+
+ NSData* data = [[info draggingPasteboard]
+ dataForType:kBookmarkButtonDragType];
+ // [info draggingSource] is nil if not the same application.
+ if (data && [info draggingSource]) {
+ // Find the position of the drop indicator.
+ BookmarkButton* button = nil;
+ [data getBytes:&button length:sizeof(button)];
+
+ BOOL showIt = [[self controller]
+ shouldShowIndicatorShownForPoint:[info draggingLocation]];
+ if (!showIt) {
+ if (dropIndicatorShown_) {
+ dropIndicatorShown_ = NO;
+ [self setNeedsDisplay:YES];
+ }
+ } else {
+ CGFloat y =
+ [[self controller]
+ indicatorPosForDragOfButton:button
+ toPoint:[info draggingLocation]];
+
+ // Need an update if the indicator wasn't previously shown or if it has
+ // moved.
+ if (!dropIndicatorShown_ || dropIndicatorPosition_ != y) {
+ dropIndicatorShown_ = YES;
+ dropIndicatorPosition_ = y;
+ [self setNeedsDisplay:YES];
+ }
+ }
+
+ [[self controller] draggingEntered:info]; // allow hover-open to work
+ return NSDragOperationMove;
+ }
+
+ return NSDragOperationNone;
+}
+
+- (void)draggingExited:(id<NSDraggingInfo>)info {
+ [[self controller] draggingExited:info];
+
+ // Regardless of the type of dragging which ended, we need to get rid of the
+ // drop indicator if one was shown.
+ if (dropIndicatorShown_) {
+ dropIndicatorShown_ = NO;
+ [self setNeedsDisplay:YES];
+ }
+}
+
+- (void)draggingEnded:(id<NSDraggingInfo>)info {
+ // Awkwardness since views open and close out from under us.
+ if (inDrag_) {
+ inDrag_ = NO;
+
+ // This line makes sure menus get closed when a drag isn't
+ // completed.
+ [[self controller] closeAllBookmarkFolders];
+ }
+
+ [self draggingExited:info];
+}
+
+- (BOOL)wantsPeriodicDraggingUpdates {
+ // TODO(jrg): This should probably return |YES| and the controller should
+ // slide the existing bookmark buttons interactively to the side to make
+ // room for the about-to-be-dropped bookmark.
+ // http://crbug.com/35968
+ return NO;
+}
+
+- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)info {
+ // For now it's the same as draggingEntered:.
+ // TODO(jrg): once we return YES for wantsPeriodicDraggingUpdates,
+ // this should ping the [self controller] to perform animations.
+ // http://crbug.com/35968
+ return [self draggingEntered:info];
+}
+
+- (BOOL)prepareForDragOperation:(id<NSDraggingInfo>)info {
+ return YES;
+}
+
+// Implement NSDraggingDestination protocol method
+// performDragOperation: for bookmarks.
+- (BOOL)performDragOperationForBookmark:(id<NSDraggingInfo>)info {
+ BOOL doDrag = NO;
+ NSData* data = [[info draggingPasteboard]
+ dataForType:kBookmarkButtonDragType];
+ // [info draggingSource] is nil if not the same application.
+ if (data && [info draggingSource]) {
+ BookmarkButton* button = nil;
+ [data getBytes:&button length:sizeof(button)];
+ doDrag = [[self controller] dragButton:button to:[info draggingLocation]];
+ }
+ return doDrag;
+}
+
+- (BOOL)performDragOperation:(id<NSDraggingInfo>)info {
+ NSPasteboard* pboard = [info draggingPasteboard];
+ if ([pboard dataForType:kBookmarkButtonDragType]) {
+ if ([self performDragOperationForBookmark:info])
+ return YES;
+ // Fall through....
+ }
+ return NO;
+}
+
+@end // BookmarkBarFolderView
+
+
+@implementation BookmarkBarFolderView(TestingAPI)
+
+- (void)setDropIndicatorShown:(BOOL)shown {
+ dropIndicatorShown_ = shown;
+}
+
+@end
diff --git a/chrome/browser/cocoa/bookmark_bar_folder_view_unittest.mm b/chrome/browser/cocoa/bookmark_bar_folder_view_unittest.mm
new file mode 100644
index 0000000..514c3b9
--- /dev/null
+++ b/chrome/browser/cocoa/bookmark_bar_folder_view_unittest.mm
@@ -0,0 +1,139 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/scoped_nsobject.h"
+#import "chrome/browser/cocoa/bookmark_bar_controller.h"
+#import "chrome/browser/cocoa/bookmark_bar_folder_view.h"
+#import "chrome/browser/cocoa/bookmark_button.h"
+#import "chrome/browser/cocoa/cocoa_test_helper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+// Fake NSDraggingInfo for testing
+@interface FakeDraggingInfo : NSObject
+@end
+
+@implementation FakeDraggingInfo
+
+- (id)draggingPasteboard {
+ return self;
+}
+
+- (NSData*)dataForType:(NSString*)type {
+ if ([type isEqual:kBookmarkButtonDragType])
+ return [NSData dataWithBytes:&self length:sizeof(self)];
+ return nil;
+}
+
+- (BOOL)draggingSource {
+ return YES; // pretend we're local
+}
+
+@end
+
+
+// We are our own controller for test convenience.
+@interface BookmarkBarFolderViewFakeController :
+ BookmarkBarFolderView<BookmarkButtonControllerProtocol> {
+ @public
+ BOOL closedAll_;
+ BOOL controllerEntered_;
+ BOOL controllerExited_;
+}
+@end
+
+@implementation BookmarkBarFolderViewFakeController
+
+- (id<BookmarkButtonControllerProtocol>)controller {
+ return self;
+}
+
+- (BOOL)shouldShowIndicatorShownForPoint:(NSPoint)pt {
+ return YES;
+}
+
+- (CGFloat)indicatorPosForDragOfButton:(BookmarkButton*)button
+ toPoint:(NSPoint)point {
+ return 101.0; // Arbitrary value.
+}
+
+- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)info {
+ controllerEntered_ = YES;
+ return NSDragOperationMove;
+}
+
+- (void)draggingExited:(id<NSDraggingInfo>)info {
+ controllerExited_ = YES;
+}
+
+- (void)closeAllBookmarkFolders {
+ closedAll_ = YES;
+}
+
+- (BOOL)dragButton:(BookmarkButton*)sourceButton to:(NSPoint)point {
+ return NO;
+}
+
+- (BookmarkModel*)bookmarkModel {
+ NOTREACHED();
+ return NULL;
+}
+
+- (void)closeBookmarkFolder:(id)sender {
+}
+
+- (NSWindow*)parentWindow {
+ return nil;
+}
+
+- (ThemeProvider*)themeProvider {
+ return nil;
+}
+
+@end
+
+namespace {
+
+class BookmarkBarFolderViewTest : public CocoaTest {
+ public:
+ virtual void SetUp() {
+ CocoaTest::SetUp();
+ view_.reset([[BookmarkBarFolderViewFakeController alloc] init]);
+ }
+
+ scoped_nsobject<BookmarkBarFolderViewFakeController> view_;
+};
+
+TEST_F(BookmarkBarFolderViewTest, Basics) {
+ [view_ awakeFromNib];
+ [[test_window() contentView] addSubview:view_];
+
+ // Confirm an assumption made in our awakeFromNib
+ EXPECT_FALSE([view_ showsDivider]);
+
+ // Make sure we're set up for DnD
+ NSArray* types = [view_ registeredDraggedTypes];
+ EXPECT_TRUE([types containsObject:kBookmarkButtonDragType]);
+
+ // This doesn't confirm results but it makes sure we don't crash or leak.
+ [view_ drawRect:NSMakeRect(0,0,10,10)];
+ [view_ setDropIndicatorShown:YES];
+ [view_ drawRect:NSMakeRect(0,0,10,10)];
+
+ [view_ removeFromSuperview];
+}
+
+TEST_F(BookmarkBarFolderViewTest, SimpleDragEnterExit) {
+ [view_ awakeFromNib];
+ scoped_nsobject<FakeDraggingInfo> info([[FakeDraggingInfo alloc] init]);
+
+ [view_ draggingEntered:(id<NSDraggingInfo>)info.get()];
+ // Confirms we got a chance to hover-open.
+ EXPECT_TRUE(view_.get()->controllerEntered_);
+
+ [view_ draggingEnded:(id<NSDraggingInfo>)info.get()];
+ EXPECT_TRUE(view_.get()->controllerExited_);
+}
+
+} // namespace
diff --git a/chrome/browser/cocoa/bookmark_bar_folder_window.h b/chrome/browser/cocoa/bookmark_bar_folder_window.h
new file mode 100644
index 0000000..41019b7
--- /dev/null
+++ b/chrome/browser/cocoa/bookmark_bar_folder_window.h
@@ -0,0 +1,16 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_COCOA_BOOKMARK_BAR_FOLDER_WINDOW_H_
+#define CHROME_BROWSER_COCOA_BOOKMARK_BAR_FOLDER_WINDOW_H_
+
+#import <Cocoa/Cocoa.h>
+
+// Window for a bookmark folder "menu". This menu pops up when you
+// click on a bookmark button that represents a folder of bookmarks.
+// This window is borderless.
+@interface BookmarkBarFolderWindow : NSWindow
+@end
+
+#endif // CHROME_BROWSER_COCOA_BOOKMARK_BAR_FOLDER_WINDOW_H_
diff --git a/chrome/browser/cocoa/bookmark_bar_folder_window.mm b/chrome/browser/cocoa/bookmark_bar_folder_window.mm
new file mode 100644
index 0000000..f911777
--- /dev/null
+++ b/chrome/browser/cocoa/bookmark_bar_folder_window.mm
@@ -0,0 +1,19 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "chrome/browser/cocoa/bookmark_bar_folder_window.h"
+
+@implementation BookmarkBarFolderWindow
+
+- (id)initWithContentRect:(NSRect)contentRect
+ styleMask:(NSUInteger)windowStyle
+ backing:(NSBackingStoreType)bufferingType
+ defer:(BOOL)deferCreation {
+ return [super initWithContentRect:contentRect
+ styleMask:NSBorderlessWindowMask // override
+ backing:bufferingType
+ defer:deferCreation];
+}
+
+@end
diff --git a/chrome/browser/cocoa/bookmark_bar_folder_window_unittest.mm b/chrome/browser/cocoa/bookmark_bar_folder_window_unittest.mm
new file mode 100644
index 0000000..f86d6d4
--- /dev/null
+++ b/chrome/browser/cocoa/bookmark_bar_folder_window_unittest.mm
@@ -0,0 +1,22 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/scoped_ptr.h"
+#include "chrome/browser/cocoa/bookmark_bar_folder_window.h"
+#include "chrome/browser/cocoa/cocoa_test_helper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+class BookmarkBarFolderWindowTest : public CocoaTest {
+};
+
+TEST_F(BookmarkBarFolderWindowTest, Borderless) {
+ scoped_nsobject<BookmarkBarFolderWindow> window_;
+ window_.reset([[BookmarkBarFolderWindow alloc]
+ initWithContentRect:NSMakeRect(0,0,20,20)
+ styleMask:0
+ backing:NSBackingStoreBuffered
+ defer:NO]);
+ EXPECT_EQ(NSBorderlessWindowMask, [window_ styleMask]);
+}
diff --git a/chrome/browser/cocoa/bookmark_bar_view.h b/chrome/browser/cocoa/bookmark_bar_view.h
index 567544d..d6f1344 100644
--- a/chrome/browser/cocoa/bookmark_bar_view.h
+++ b/chrome/browser/cocoa/bookmark_bar_view.h
@@ -15,15 +15,18 @@
@interface BookmarkBarView : NSView {
@private
BOOL dropIndicatorShown_;
- CGFloat dropIndicatorPosition_;
+ CGFloat dropIndicatorPosition_; // x position
IBOutlet BookmarkBarController* controller_;
IBOutlet NSTextField* noItemTextfield_;
}
- (NSTextField*)noItemTextfield;
+- (BookmarkBarController*)controller;
@end
-@interface BookmarkBarView(TestingAPI)
+@interface BookmarkBarView() // TestingOrInternalAPI
+@property (readonly) BOOL dropIndicatorShown;
+@property (readonly) CGFloat dropIndicatorPosition;
- (void)setController:(id)controller;
@end
diff --git a/chrome/browser/cocoa/bookmark_bar_view.mm b/chrome/browser/cocoa/bookmark_bar_view.mm
index 96b7594..b1099b2 100644
--- a/chrome/browser/cocoa/bookmark_bar_view.mm
+++ b/chrome/browser/cocoa/bookmark_bar_view.mm
@@ -17,11 +17,19 @@
@implementation BookmarkBarView
+@synthesize dropIndicatorShown = dropIndicatorShown_;
+@synthesize dropIndicatorPosition = dropIndicatorPosition_;
+
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
// This probably isn't strictly necessary, but can't hurt.
[self unregisterDraggedTypes];
[super dealloc];
+
+ // To be clear, our controller_ is an IBOutlet and owns us, so we
+ // don't deallocate it explicitly. It is owned by the browser
+ // window controller, so gets deleted with a browser window is
+ // closed.
}
- (void)awakeFromNib {
@@ -50,6 +58,10 @@
[controller_ updateTheme:themeProvider];
}
+- (void)viewDidMoveToWindow {
+ [controller_ viewDidMoveToWindow];
+}
+
// Called after the current theme has changed.
- (void)themeDidChangeNotification:(NSNotification*)aNotification {
ThemeProvider* themeProvider =
@@ -79,6 +91,10 @@
return noItemTextfield_;
}
+- (BookmarkBarController*)controller {
+ return controller_;
+}
+
-(void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
@@ -113,18 +129,31 @@
// Find the position of the drop indicator.
BookmarkButton* button = nil;
[data getBytes:&button length:sizeof(button)];
- CGFloat x =
- [controller_ indicatorPosForDragOfButton:button
- toPoint:[info draggingLocation]];
-
- // Need an update if the indicator wasn't previously shown or if it has
- // moved.
- if (!dropIndicatorShown_ || dropIndicatorPosition_ != x) {
- dropIndicatorShown_ = YES;
- dropIndicatorPosition_ = x;
- [self setNeedsDisplay:YES];
+
+ // We only show the drop indicator if we're not in a position to
+ // perform a hover-open since it doesn't make sense to do both.
+ BOOL showIt =
+ [controller_ shouldShowIndicatorShownForPoint:
+ [info draggingLocation]];
+ if (!showIt) {
+ if (dropIndicatorShown_) {
+ dropIndicatorShown_ = NO;
+ [self setNeedsDisplay:YES];
+ }
+ } else {
+ CGFloat x =
+ [controller_ indicatorPosForDragOfButton:button
+ toPoint:[info draggingLocation]];
+ // Need an update if the indicator wasn't previously shown or if it has
+ // moved.
+ if (!dropIndicatorShown_ || dropIndicatorPosition_ != x) {
+ dropIndicatorShown_ = YES;
+ dropIndicatorPosition_ = x;
+ [self setNeedsDisplay:YES];
+ }
}
+ [controller_ draggingEntered:info]; // allow hover-open to work.
return NSDragOperationMove;
}
// Fall through otherwise.
@@ -210,13 +239,8 @@
return NO;
}
-@end // @implementation BookmarkBarView
-
-
-@implementation BookmarkBarView(TestingAPI)
-
- (void)setController:(id)controller {
controller_ = controller;
}
-@end // @implementation BookmarkBarView(TestingAPI)
+@end // @implementation BookmarkBarView
diff --git a/chrome/browser/cocoa/bookmark_bar_view_unittest.mm b/chrome/browser/cocoa/bookmark_bar_view_unittest.mm
index e99dda1..600c91d 100644
--- a/chrome/browser/cocoa/bookmark_bar_view_unittest.mm
+++ b/chrome/browser/cocoa/bookmark_bar_view_unittest.mm
@@ -10,17 +10,31 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
+namespace {
+const CGFloat kFakeIndicatorPos = 7.0;
+};
+
// Fake DraggingInfo, fake BookmarkBarController, fake pasteboard...
@interface FakeBookmarkDraggingInfo : NSObject {
+ @public
scoped_nsobject<NSData> data_;
BOOL pong_;
+ BOOL dropIndicatorShown_;
+ BOOL draggingEnteredCalled_;
}
+@property (readwrite) BOOL dropIndicatorShown;
+@property (readwrite) BOOL draggingEnteredCalled;
@end
@implementation FakeBookmarkDraggingInfo
+@synthesize dropIndicatorShown = dropIndicatorShown_;
+@synthesize draggingEnteredCalled = draggingEnteredCalled_;
+
- (id)init {
if ((self = [super init])) {
+ dropIndicatorShown_ = YES;
+ draggingEnteredCalled_ = NO;
data_.reset([[NSData dataWithBytes:&self length:sizeof(self)] retain]);
}
return self;
@@ -63,7 +77,16 @@
- (CGFloat)indicatorPosForDragOfButton:(BookmarkButton*)sourceButton
toPoint:(NSPoint)point {
- return 0;
+ return kFakeIndicatorPos;
+}
+
+- (BOOL)shouldShowIndicatorShownForPoint:(NSPoint)point {
+ return dropIndicatorShown_;
+}
+
+- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)info {
+ draggingEnteredCalled_ = YES;
+ return NSDragOperationNone;
}
@end
@@ -94,4 +117,20 @@ TEST_F(BookmarkBarViewTest, BookmarkButtonDragAndDrop) {
EXPECT_TRUE([info dragButtonToPong]);
}
+TEST_F(BookmarkBarViewTest, BookmarkButtonDropIndicator) {
+ scoped_nsobject<FakeBookmarkDraggingInfo>
+ info([[FakeBookmarkDraggingInfo alloc] init]);
+
+ [view_ setController:info.get()];
+ EXPECT_FALSE([info draggingEnteredCalled]);
+ EXPECT_EQ([view_ draggingEntered:(id)info.get()], NSDragOperationMove);
+ EXPECT_TRUE([info draggingEnteredCalled]); // Ensure controller pingged.
+ EXPECT_TRUE([view_ dropIndicatorShown]);
+ EXPECT_EQ([view_ dropIndicatorPosition], kFakeIndicatorPos);
+
+ [info setDropIndicatorShown:NO];
+ EXPECT_EQ([view_ draggingEntered:(id)info.get()], NSDragOperationMove);
+ EXPECT_FALSE([view_ dropIndicatorShown]);
+}
+
} // namespace
diff --git a/chrome/browser/cocoa/bookmark_button.h b/chrome/browser/cocoa/bookmark_button.h
index 3034166..3144247 100644
--- a/chrome/browser/cocoa/bookmark_button.h
+++ b/chrome/browser/cocoa/bookmark_button.h
@@ -3,30 +3,111 @@
// found in the LICENSE file.
#import <Cocoa/Cocoa.h>
-
#import "chrome/browser/cocoa/draggable_button.h"
-@protocol BookmarkButtonDelegate;
+@class BookmarkButton;
+class BookmarkModel;
+class BookmarkNode;
+class ThemeProvider;
+
+// Protocol for a BookmarkButton's delegate, responsible for doing
+// things on behalf of a bookmark button.
+@protocol BookmarkButtonDelegate
+
+// Fill the given pasteboard with appropriate data when the given button is
+// dragged. Since the delegate has no way of providing pasteboard data later,
+// all data must actually be put into the pasteboard and not merely promised.
+- (void)fillPasteboard:(NSPasteboard*)pboard
+ forDragOfButton:(BookmarkButton*)button;
+
+// Bookmark buttons pass mouseEntered: and mouseExited: events to
+// their delegate. This allows the delegate to decide (for example)
+// which one, if any, should perform a hover-open.
+- (void)mouseEnteredButton:(id)button event:(NSEvent*)event;
+- (void)mouseExitedButton:(id)button event:(NSEvent*)event;
+
+@end
+
+
+// Protocol to be implemented by controllers that logically own
+// bookmark buttons. The controller may be either an NSViewController
+// or NSWindowController. The BookmarkButton doesn't use this
+// protocol directly; it is used when BookmarkButton controllers talk
+// to each other.
+// Other than the top level owner (the bookmark bar), all bookmark
+// button controllers have a parent controller.
+@protocol BookmarkButtonControllerProtocol
+
+// Close all bookmark folders, walking up the ownership chain.
+- (void)closeAllBookmarkFolders;
+
+// Close just my bookmark folder.
+- (void)closeBookmarkFolder:(id)sender;
+
+// Return the bookmark model for this controller.
+- (BookmarkModel*)bookmarkModel;
+
+// Perform drag enter/exit operations, such as hover-open and hover-close.
+- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)info;
+- (void)draggingExited:(id<NSDraggingInfo>)info;
+
+// Perform the actual DnD of a bookmark button.
+
+// |point| is in the base coordinate system of the destination window;
+// |it comes from an id<NSDraggingInfo>.
+- (BOOL)dragButton:(BookmarkButton*)sourceButton to:(NSPoint)point;
+
+// Return YES if we should show the drop indicator, else NO. In some
+// cases (e.g. hover open) we don't want to show the drop indicator.
+// |point| is in the base coordinate system of the destination window;
+// |it comes from an id<NSDraggingInfo>.
+- (BOOL)shouldShowIndicatorShownForPoint:(NSPoint)point;
+
+// The x or y coordinate of (the middle of) the indicator to draw for
+// a drag of the source button to the given point (given in window
+// coordinates).
+// |point| is in the base coordinate system of the destination window;
+// |it comes from an id<NSDraggingInfo>.
+// TODO(viettrungluu,jrg): instead of this, make buttons move around.
+// http://crbug.com/35968
+- (CGFloat)indicatorPosForDragOfButton:(BookmarkButton*)sourceButton
+ toPoint:(NSPoint)point;
+
+// Return the parent window for all BookmarkBarFolderController windows.
+- (NSWindow*)parentWindow;
+
+// Return the theme provider associated with this browser window.
+- (ThemeProvider*)themeProvider;
+
+@end // @protocol BookmarkButtonControllerProtocol
+
// Class for bookmark bar buttons that can be drag sources.
@interface BookmarkButton : DraggableButton {
@private
- id<BookmarkButtonDelegate> delegate_;
+ NSObject<BookmarkButtonDelegate>* delegate_; // weak like all delegates
}
-@property(assign, nonatomic) id<BookmarkButtonDelegate> delegate;
+@property(assign, nonatomic) NSObject<BookmarkButtonDelegate>* delegate;
+
+// Return the bookmark node associated with this button, or NULL.
+- (const BookmarkNode*)bookmarkNode;
+
+// Return YES if this is a folder button (the node has subnodes).
+- (BOOL)isFolder;
+
+// At this time we represent an empty folder (e.g. the string
+// '(empty)') as a disabled button with no associated node.
+//
+// TODO(jrg): improve; things work but are slightly ugly since "empty"
+// and "one disabled button" are not the same thing.
+// http://crbug.com/35967
+- (BOOL)isEmpty;
@end // @interface BookmarkButton
-// Protocol for a |BookmarkButton|'s delegate, which is responsible for doing
-// things which require information about the bookmark represented by this
-// button.
-@protocol BookmarkButtonDelegate
-// Fill the given pasteboard with appropriate data when the given button is
-// dragged. Since the delegate has no way of providing pasteboard data later,
-// all data must actually be put into the pasteboard and not merely promised.
-- (void)fillPasteboard:(NSPasteboard*)pboard
- forDragOfButton:(BookmarkButton*)button;
+@interface BookmarkButton(TestingAPI)
+- (void)beginDrag:(NSEvent*)event;
+@end
-@end // @protocol BookmarkButtonDelegate
diff --git a/chrome/browser/cocoa/bookmark_button.mm b/chrome/browser/cocoa/bookmark_button.mm
index b0b6ecd..5e87c09 100644
--- a/chrome/browser/cocoa/bookmark_button.mm
+++ b/chrome/browser/cocoa/bookmark_button.mm
@@ -2,9 +2,10 @@
// 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_button.h"
#include "base/logging.h"
#import "base/scoped_nsobject.h"
-#import "chrome/browser/cocoa/bookmark_button.h"
+#include "chrome/browser/bookmarks/bookmark_model.h"
#import "chrome/browser/cocoa/bookmark_button_cell.h"
// The opacity of the bookmark button drag image.
@@ -17,22 +18,42 @@ static const CGFloat kDragImageOpacity = 0.7;
@end // @interface BookmarkButton(Private)
+
@implementation BookmarkButton
@synthesize delegate = delegate_;
+- (const BookmarkNode*)bookmarkNode {
+ return [[self cell] bookmarkNode];
+}
+
+- (BOOL)isFolder {
+ const BookmarkNode* node = [self bookmarkNode];
+ return (node && node->is_folder());
+}
+
+- (BOOL)isEmpty {
+ return [self bookmarkNode] ? NO : YES;
+}
+
// By default, NSButton ignores middle-clicks.
+// But we want them.
- (void)otherMouseUp:(NSEvent*)event {
[self performClick:self];
}
// Overridden from DraggableButton.
- (void)beginDrag:(NSEvent*)event {
- if (delegate_) {
+ // Don't allow a drag of the empty node.
+ // The empty node is a placeholder for "(empty)", to be revisited.
+ if ([self isEmpty])
+ return;
+
+ if ([self delegate]) {
// Ask our delegate to fill the pasteboard for us.
NSPasteboard* pboard = [NSPasteboard pasteboardWithName:NSDragPboard];
- [delegate_ fillPasteboard:pboard forDragOfButton:self];
+ [[self delegate] fillPasteboard:pboard forDragOfButton:self];
// At the moment, moving bookmarks causes their buttons (like me!)
// to be destroyed and rebuilt. Make sure we don't go away while on
@@ -44,7 +65,7 @@ static const CGFloat kDragImageOpacity = 0.7;
[self dragImage:[self dragImage] at:NSMakePoint(0, yAt) offset:dragOffset
event:event pasteboard:pboard source:self slideBack:YES];
- // And we're done.
+ // And we're done.
[self autorelease];
} else {
// Avoid blowing up, but we really shouldn't get here.
@@ -63,6 +84,21 @@ static const CGFloat kDragImageOpacity = 0.7;
: NSDragOperationCopy;
}
+// mouseEntered: and mouseExited: are called from our
+// BookmarkButtonCell. We redirect this information to our delegate.
+// The controller can then perform menu-like actions (e.g. "hover over
+// to open menu").
+- (void)mouseEntered:(NSEvent*)event {
+ [delegate_ mouseEnteredButton:self event:event];
+ [super mouseEntered:event];
+}
+
+// See comments above mouseEntered:.
+- (void)mouseExited:(NSEvent*)event {
+ [delegate_ mouseExitedButton:self event:event];
+ [super mouseExited:event];
+}
+
@end
@implementation BookmarkButton(Private)
diff --git a/chrome/browser/cocoa/bookmark_button_cell.h b/chrome/browser/cocoa/bookmark_button_cell.h
index 89d461b..8a0c13d 100644
--- a/chrome/browser/cocoa/bookmark_button_cell.h
+++ b/chrome/browser/cocoa/bookmark_button_cell.h
@@ -8,11 +8,23 @@
#import "base/cocoa_protocols_mac.h"
#import "chrome/browser/cocoa/gradient_button_cell.h"
-// A button cell that handles drawing/highlighting of buttons in the
-// bookmark bar.
+class BookmarkNode;
+// A button cell that handles drawing/highlighting of buttons in the
+// bookmark bar. This cell forwards mouseEntered/mouseExited events
+// to its control view so that pseudo-menu operations
+// (e.g. hover-over to open) can be implemented.
@interface BookmarkButtonCell : GradientButtonCell<NSMenuDelegate> {
+ @private
+ BOOL empty_; // is this an "empty" button placeholder button cell?
}
+
+@property (readwrite, assign) const BookmarkNode* bookmarkNode;
+
+- (id)initTextCell:(NSString*)string; // Designated initializer
+- (BOOL)empty; // returns YES if empty.
+- (void)setEmpty:(BOOL)empty;
+
// |-setBookmarkCellText:image:| is used to set the text and image of
// a BookmarkButtonCell, and align the image to the left (NSImageLeft)
// if there is text in the title, and centered (NSImageCenter) if
diff --git a/chrome/browser/cocoa/bookmark_button_cell.mm b/chrome/browser/cocoa/bookmark_button_cell.mm
index 8be166a..0f790e3 100644
--- a/chrome/browser/cocoa/bookmark_button_cell.mm
+++ b/chrome/browser/cocoa/bookmark_button_cell.mm
@@ -28,6 +28,15 @@
return self;
}
+- (BOOL)empty {
+ return empty_;
+}
+
+- (void)setEmpty:(BOOL)empty {
+ empty_ = empty;
+ [self setShowsBorderOnlyWhileMouseInside:!empty];
+}
+
- (NSSize)cellSizeForBounds:(NSRect)aRect {
NSSize size = [super cellSizeForBounds:aRect];
size.width += 2;
@@ -58,10 +67,21 @@
[self setTitle:title];
}
+- (void)setBookmarkNode:(const BookmarkNode*)node {
+ [self setRepresentedObject:[NSValue valueWithPointer:node]];
+}
+
+- (const BookmarkNode*)bookmarkNode {
+ return static_cast<const BookmarkNode*>([[self representedObject]
+ pointerValue]);
+}
+
// We share the context menu among all bookmark buttons. To allow us
// to disambiguate when needed (e.g. "open bookmark"), we set the
// menu's associated node to be our represented object.
- (NSMenu*)menu {
+ if (empty_)
+ return nil;
BookmarkMenu* menu = (BookmarkMenu*)[super menu];
[menu setRepresentedObject:[self representedObject]];
return menu;
@@ -70,6 +90,13 @@
// Unfortunately, NSCell doesn't already have something like this.
// TODO(jrg): consider placing in GTM.
- (void)setTextColor:(NSColor*)color {
+
+ // We can't properly set the cell's text color without a control.
+ // In theory we could just save the next for later and wait until
+ // the cell is moved to a control, but there is no obvious way to
+ // accomplish that (e.g. no "cellDidMoveToControl" notification.)
+ DCHECK([self controlView]);
+
scoped_nsobject<NSMutableParagraphStyle> style([NSMutableParagraphStyle new]);
[style setAlignment:NSCenterTextAlignment];
NSDictionary* dict = [NSDictionary
@@ -88,4 +115,18 @@
}
}
+// To implement "hover open a bookmark button to open the folder"
+// which feels like menus, we override NSButtonCell's mouseEntered:
+// and mouseExited:, then and pass them along to our owning control.
+- (void)mouseEntered:(NSEvent*)event {
+ [super mouseEntered:event];
+ [[self controlView] mouseEntered:event];
+}
+
+// See comment above mouseEntered:, above.
+- (void)mouseExited:(NSEvent*)event {
+ [super mouseExited:event];
+ [[self controlView] mouseExited:event];
+}
+
@end
diff --git a/chrome/browser/cocoa/bookmark_button_cell_unittest.mm b/chrome/browser/cocoa/bookmark_button_cell_unittest.mm
index a1b7b40..07f9984 100644
--- a/chrome/browser/cocoa/bookmark_button_cell_unittest.mm
+++ b/chrome/browser/cocoa/bookmark_button_cell_unittest.mm
@@ -5,10 +5,31 @@
#include "base/scoped_nsobject.h"
#import "chrome/browser/cocoa/bookmark_button_cell.h"
#import "chrome/browser/cocoa/bookmark_menu.h"
+#include "chrome/browser/cocoa/browser_test_helper.h"
#import "chrome/browser/cocoa/cocoa_test_helper.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
+// Simple class to remember how many mouseEntered: and mouseExited:
+// calls it gets. Only used by BookmarkMouseForwarding but placed
+// at the top of the file to keep it outside the anon namespace.
+@interface ButtonRemembersMouseEnterExit : NSButton {
+ @public
+ int enters_;
+ int exits_;
+}
+@end
+
+@implementation ButtonRemembersMouseEnterExit
+- (void)mouseEntered:(NSEvent*)event {
+ enters_++;
+}
+- (void)mouseExited:(NSEvent*)event {
+ exits_++;
+}
+@end
+
+
namespace {
class BookmarkButtonCellTest : public CocoaTest {
@@ -29,11 +50,59 @@ TEST_F(BookmarkButtonCellTest, SizeForBounds) {
EXPECT_TRUE(size.width < 200 && size.height < 200);
}
-// Make sure the default from the base class is overridden
+// Make sure the default from the base class is overridden.
TEST_F(BookmarkButtonCellTest, MouseEnterStuff) {
scoped_nsobject<BookmarkButtonCell> cell(
[[BookmarkButtonCell alloc] initTextCell:@"Testing"]);
+ [cell setMenu:[[[BookmarkMenu alloc] initWithTitle:@"foo"] autorelease]];
EXPECT_TRUE([cell.get() showsBorderOnlyWhileMouseInside]);
+ EXPECT_TRUE([cell menu]);
+
+ [cell setEmpty:YES];
+ EXPECT_FALSE([cell.get() showsBorderOnlyWhileMouseInside]);
+ EXPECT_FALSE([cell menu]);
+}
+
+TEST_F(BookmarkButtonCellTest, BookmarkNode) {
+ BrowserTestHelper helper_;
+ BookmarkModel& model(*(helper_.profile()->GetBookmarkModel()));
+ scoped_nsobject<BookmarkButtonCell> cell(
+ [[BookmarkButtonCell alloc] initTextCell:@"Testing"]);
+
+ const BookmarkNode* node = model.GetBookmarkBarNode();
+ [cell setBookmarkNode:node];
+ EXPECT_EQ(node, [cell bookmarkNode]);
+
+ node = model.other_node();
+ [cell setBookmarkNode:node];
+ EXPECT_EQ(node, [cell bookmarkNode]);
+}
+
+TEST_F(BookmarkButtonCellTest, BookmarkMouseForwarding) {
+ scoped_nsobject<BookmarkButtonCell> cell(
+ [[BookmarkButtonCell alloc] initTextCell:@"Testing"]);
+ scoped_nsobject<ButtonRemembersMouseEnterExit>
+ button([[ButtonRemembersMouseEnterExit alloc]
+ initWithFrame:NSMakeRect(0,0,50,50)]);
+ [button setCell:cell.get()];
+ EXPECT_EQ(0, button.get()->enters_);
+ EXPECT_EQ(0, button.get()->exits_);
+ NSEvent* event = [NSEvent mouseEventWithType:NSMouseMoved
+ location:NSMakePoint(10,10)
+ modifierFlags:0
+ timestamp:0
+ windowNumber:0
+ context:nil
+ eventNumber:0
+ clickCount:0
+ pressure:0];
+ [cell mouseEntered:event];
+ EXPECT_TRUE(button.get()->enters_ && !button.get()->exits_);
+
+ for (int i = 0; i < 3; i++)
+ [cell mouseExited:event];
+ EXPECT_EQ(button.get()->enters_, 1);
+ EXPECT_EQ(button.get()->exits_, 3);
}
} // namespace
diff --git a/chrome/browser/cocoa/bookmark_button_unittest.mm b/chrome/browser/cocoa/bookmark_button_unittest.mm
index 5ec87bd..6175f43 100644
--- a/chrome/browser/cocoa/bookmark_button_unittest.mm
+++ b/chrome/browser/cocoa/bookmark_button_unittest.mm
@@ -4,12 +4,39 @@
#include "base/scoped_nsobject.h"
#import "chrome/browser/cocoa/bookmark_button.h"
+#import "chrome/browser/cocoa/bookmark_button_cell.h"
+#import "chrome/browser/cocoa/browser_test_helper.h"
#import "chrome/browser/cocoa/cocoa_test_helper.h"
+#import "chrome/browser/cocoa/test_event_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
+// Fake BookmarkButton delegate to get a pong on mouse entered/exited
+@interface FakeButtonDelegate : NSObject<BookmarkButtonDelegate> {
+ @public
+ int entered_;
+ int exited_;
+}
+@end
+
+@implementation FakeButtonDelegate
+
+- (void)fillPasteboard:(NSPasteboard*)pboard
+ forDragOfButton:(BookmarkButton*)button {
+}
+
+- (void)mouseEnteredButton:(id)buton event:(NSEvent*)event {
+ entered_++;
+}
+
+- (void)mouseExitedButton:(id)buton event:(NSEvent*)event {
+ exited_++;
+}
+@end
+
+namespace {
+
class BookmarkButtonTest : public CocoaTest {
- public:
};
// Make sure nothing leaks
@@ -17,3 +44,63 @@ TEST_F(BookmarkButtonTest, Create) {
scoped_nsobject<BookmarkButton> button;
button.reset([[BookmarkButton alloc] initWithFrame:NSMakeRect(0,0,500,500)]);
}
+
+// Test folder and empty node queries.
+TEST_F(BookmarkButtonTest, FolderAndEmptyOrNot) {
+ BrowserTestHelper helper_;
+ scoped_nsobject<BookmarkButton> button;
+ scoped_nsobject<BookmarkButtonCell> cell;
+
+ button.reset([[BookmarkButton alloc] initWithFrame:NSMakeRect(0,0,500,500)]);
+ cell.reset([[BookmarkButtonCell alloc] initTextCell:@"hi mom"]);
+ [button setCell:cell];
+
+ EXPECT_TRUE([button isEmpty]);
+ EXPECT_FALSE([button isFolder]);
+ EXPECT_FALSE([button bookmarkNode]);
+
+ NSEvent* downEvent =
+ test_event_utils::LeftMouseDownAtPoint(NSMakePoint(10,10));
+ // Since this returns (does not actually begin a modal drag), success!
+ [button beginDrag:downEvent];
+
+ BookmarkModel* model = helper_.profile()->GetBookmarkModel();
+ const BookmarkNode* node = model->GetBookmarkBarNode();
+ [cell setBookmarkNode:node];
+ EXPECT_FALSE([button isEmpty]);
+ EXPECT_TRUE([button isFolder]);
+ EXPECT_EQ([button bookmarkNode], node);
+
+ node = model->AddURL(node, 0, L"hi mom", GURL("http://www.google.com"));
+ [cell setBookmarkNode:node];
+ EXPECT_FALSE([button isEmpty]);
+ EXPECT_FALSE([button isFolder]);
+ EXPECT_EQ([button bookmarkNode], node);
+}
+
+TEST_F(BookmarkButtonTest, MouseEnterExitRedirect) {
+ NSEvent* moveEvent =
+ test_event_utils::MouseEventAtPoint(NSMakePoint(10,10), NSMouseMoved, 0);
+ scoped_nsobject<BookmarkButton> button;
+ scoped_nsobject<BookmarkButtonCell> cell;
+ scoped_nsobject<FakeButtonDelegate>
+ delegate([[FakeButtonDelegate alloc] init]);
+ button.reset([[BookmarkButton alloc] initWithFrame:NSMakeRect(0,0,500,500)]);
+ cell.reset([[BookmarkButtonCell alloc] initTextCell:@"hi mom"]);
+ [button setCell:cell];
+ [button setDelegate:delegate];
+
+ EXPECT_EQ(0, delegate.get()->entered_);
+ EXPECT_EQ(0, delegate.get()->exited_);
+
+ [button mouseEntered:moveEvent];
+ EXPECT_EQ(1, delegate.get()->entered_);
+ EXPECT_EQ(0, delegate.get()->exited_);
+
+ [button mouseExited:moveEvent];
+ [button mouseExited:moveEvent];
+ EXPECT_EQ(1, delegate.get()->entered_);
+ EXPECT_EQ(2, delegate.get()->exited_);
+}
+
+}
diff --git a/chrome/browser/cocoa/test_event_utils.h b/chrome/browser/cocoa/test_event_utils.h
index ac4b0f5..6b2eba5 100644
--- a/chrome/browser/cocoa/test_event_utils.h
+++ b/chrome/browser/cocoa/test_event_utils.h
@@ -25,10 +25,15 @@ class ScopedClassSwizzler {
namespace test_event_utils {
-// Create synthetic mouse events for testing. Currently these are very basic,
-// flesh out as needed.
+// Create synthetic mouse events for testing. Currently these are very
+// basic, flesh out as needed. Points are all in window coordinates;
+// where the window is not specified, coordinate system is undefined
+// (but will be repeated when the event is queried).
NSEvent* MakeMouseEvent(NSEventType type, NSUInteger modifiers);
+NSEvent* MouseEventAtPoint(NSPoint point, NSEventType type,
+ NSUInteger modifiers);
NSEvent* LeftMouseDownAtPoint(NSPoint point);
+NSEvent* LeftMouseDownAtPointInWindow(NSPoint point, NSWindow* window);
} // namespace test_event_utils
diff --git a/chrome/browser/cocoa/test_event_utils.mm b/chrome/browser/cocoa/test_event_utils.mm
index f0c4fc7..23b0535 100644
--- a/chrome/browser/cocoa/test_event_utils.mm
+++ b/chrome/browser/cocoa/test_event_utils.mm
@@ -19,22 +19,23 @@ ScopedClassSwizzler::~ScopedClassSwizzler() {
namespace test_event_utils {
-NSEvent* MakeMouseEvent(NSEventType type, NSUInteger modifiers) {
+NSEvent* MouseEventAtPoint(NSPoint point, NSEventType type,
+ NSUInteger modifiers) {
if (type == NSOtherMouseUp) {
// To synthesize middle clicks we need to create a CGEvent with the
// "center" button flags so that our resulting NSEvent will have the
// appropriate buttonNumber field. NSEvent provides no way to create a
// mouse event with a buttonNumber directly.
- CGPoint location = { 0, 0 };
+ CGPoint location = { point.x, point.y };
CGEventRef cg_event = CGEventCreateMouseEvent(NULL, kCGEventOtherMouseUp,
- location,
- kCGMouseButtonCenter);
+ location,
+ kCGMouseButtonCenter);
NSEvent* event = [NSEvent eventWithCGEvent:cg_event];
CFRelease(cg_event);
return event;
}
return [NSEvent mouseEventWithType:type
- location:NSMakePoint(0, 0)
+ location:point
modifierFlags:modifiers
timestamp:0
windowNumber:0
@@ -44,16 +45,24 @@ NSEvent* MakeMouseEvent(NSEventType type, NSUInteger modifiers) {
pressure:1.0];
}
-NSEvent* LeftMouseDownAtPoint(NSPoint point) {
+NSEvent* MakeMouseEvent(NSEventType type, NSUInteger modifiers) {
+ return MouseEventAtPoint(NSMakePoint(0, 0), type, modifiers);
+}
+
+NSEvent* LeftMouseDownAtPointInWindow(NSPoint point, NSWindow* window) {
return [NSEvent mouseEventWithType:NSLeftMouseDown
location:point
modifierFlags:0
timestamp:0
- windowNumber:0
+ windowNumber:[window windowNumber]
context:nil
eventNumber:0
clickCount:1
pressure:1.0];
}
+NSEvent* LeftMouseDownAtPoint(NSPoint point) {
+ return LeftMouseDownAtPointInWindow(point, nil);
+}
+
} // namespace test_event_utils
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 7d43fc1..d74a627 100755
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -427,6 +427,12 @@
'browser/cocoa/bookmark_bar_constants.h',
'browser/cocoa/bookmark_bar_controller.h',
'browser/cocoa/bookmark_bar_controller.mm',
+ 'browser/cocoa/bookmark_bar_folder_controller.h',
+ 'browser/cocoa/bookmark_bar_folder_controller.mm',
+ 'browser/cocoa/bookmark_bar_folder_view.h',
+ 'browser/cocoa/bookmark_bar_folder_view.mm',
+ 'browser/cocoa/bookmark_bar_folder_window.h',
+ 'browser/cocoa/bookmark_bar_folder_window.mm',
'browser/cocoa/bookmark_bar_state.h',
'browser/cocoa/bookmark_bar_toolbar_view.h',
'browser/cocoa/bookmark_bar_toolbar_view.mm',
@@ -2350,6 +2356,7 @@
'app/nibs/AutoFillDialog.xib',
'app/nibs/BookmarkAllTabs.xib',
'app/nibs/BookmarkBar.xib',
+ 'app/nibs/BookmarkBarFolderWindow.xib',
'app/nibs/BookmarkBubble.xib',
'app/nibs/BookmarkEditor.xib',
'app/nibs/BookmarkManager.xib',
diff --git a/chrome/chrome_dll.gypi b/chrome/chrome_dll.gypi
index d900130..c99fc08 100644
--- a/chrome/chrome_dll.gypi
+++ b/chrome/chrome_dll.gypi
@@ -184,6 +184,7 @@
'app/nibs/AutoFillDialog.xib',
'app/nibs/BookmarkAllTabs.xib',
'app/nibs/BookmarkBar.xib',
+ 'app/nibs/BookmarkBarFolderWindow.xib',
'app/nibs/BookmarkBubble.xib',
'app/nibs/BookmarkEditor.xib',
'app/nibs/BookmarkManager.xib',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index c1ba1c6..0390631 100755
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -581,6 +581,9 @@
'browser/cocoa/bookmark_all_tabs_controller_unittest.mm',
'browser/cocoa/bookmark_bar_bridge_unittest.mm',
'browser/cocoa/bookmark_bar_controller_unittest.mm',
+ 'browser/cocoa/bookmark_bar_folder_controller_unittest.mm',
+ 'browser/cocoa/bookmark_bar_folder_view_unittest.mm',
+ 'browser/cocoa/bookmark_bar_folder_window_unittest.mm',
'browser/cocoa/bookmark_bar_toolbar_view_unittest.mm',
'browser/cocoa/bookmark_bar_view_unittest.mm',
'browser/cocoa/bookmark_bubble_controller_unittest.mm',