1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef CHROME_BROWSER_UI_COCOA_BOOKMARKS_BOOKMARK_BAR_CONTROLLER_H_ 6 #define CHROME_BROWSER_UI_COCOA_BOOKMARKS_BOOKMARK_BAR_CONTROLLER_H_ 7 8 #import <Cocoa/Cocoa.h> 9 #include <map> 10 11 #import "base/mac/cocoa_protocols.h" 12 #include "base/mac/scoped_nsobject.h" 13 #include "base/memory/scoped_ptr.h" 14 #include "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_bridge.h" 15 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_constants.h" 16 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_state.h" 17 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_toolbar_view.h" 18 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_button.h" 19 #include "chrome/browser/ui/cocoa/tabs/tab_strip_model_observer_bridge.h" 20 #include "ui/base/window_open_disposition.h" 21 22 @class BookmarkBarController; 23 @class BookmarkBarFolderController; 24 @class BookmarkBarView; 25 @class BookmarkButtonCell; 26 @class BookmarkFolderTarget; 27 @class BookmarkContextMenuCocoaController; 28 class BookmarkModel; 29 class BookmarkNode; 30 class Browser; 31 class GURL; 32 namespace ui { 33 class ThemeProvider; 34 } 35 36 namespace bookmarks { 37 38 // Magic numbers from Cole 39 // TODO(jrg): create an objc-friendly version of bookmark_bar_constants.h? 40 41 // Used as a maximum width for buttons on the bar. 42 const CGFloat kDefaultBookmarkWidth = 150.0; 43 44 // Horizontal frame inset for buttons in the bookmark bar. 45 const CGFloat kBookmarkHorizontalPadding = 1.0; 46 47 // Vertical frame inset for buttons in the bookmark bar. 48 const CGFloat kBookmarkVerticalPadding = 2.0; 49 50 // Left margin before the first button in the bookmark bar. 51 const CGFloat kBookmarkLeftMargin = 2.0; 52 53 // Right margin before the last button in the bookmark bar. 54 const CGFloat kBookmarkRightMargin = 2.0; 55 56 // Used as a min/max width for buttons on menus (not on the bar). 57 const CGFloat kBookmarkMenuButtonMinimumWidth = 100.0; 58 const CGFloat kBookmarkMenuButtonMaximumWidth = 485.0; 59 60 // The minimum separation between a folder menu and the edge of the screen. 61 // If the menu gets closer to the edge of the screen (either right or left) 62 // then it is pops up in the opposite direction. 63 // (See -[BookmarkBarFolderController childFolderWindowLeftForWidth:]). 64 const CGFloat kBookmarkHorizontalScreenPadding = 8.0; 65 66 // Our NSScrollView is supposed to be just barely big enough to fit its 67 // contentView. It is actually a hair too small. 68 // This turns on horizontal scrolling which, although slight, is awkward. 69 // Make sure our window (and NSScrollView) are wider than its documentView 70 // by at least this much. 71 const CGFloat kScrollViewContentWidthMargin = 2; 72 73 // Make subfolder menus overlap their parent menu a bit to give a better 74 // perception of a menuing system. 75 const CGFloat kBookmarkMenuOverlap = 2.0; 76 77 // When constraining a scrolling bookmark bar folder window to the 78 // screen, shrink the "constrain" by this much vertically. Currently 79 // this is 0.0 to avoid a problem with tracking areas leaving the 80 // window, but should probably be 8.0 or something. 81 const CGFloat kScrollWindowVerticalMargin = 6.0; 82 83 // How far to offset a folder menu from the top of the bookmark bar. This 84 // is set just above the bar so that it become distinctive when drawn. 85 const CGFloat kBookmarkBarMenuOffset = 2.0; 86 87 // How far to offset a folder menu's left edge horizontally in relation to 88 // the left edge of the button from which it springs. Because of drawing 89 // differences, simply aligning the |frame| of each does not render the 90 // pproper result, so we have to offset. 91 const CGFloat kBookmarkBarButtonOffset = 2.0; 92 93 // Delay before opening a subfolder (and closing the previous one) 94 // when hovering over a folder button. 95 const NSTimeInterval kHoverOpenDelay = 0.3; 96 97 // Delay on hover before a submenu opens when dragging. 98 // Experimentally a drag hover open delay needs to be bigger than a 99 // normal (non-drag) menu hover open such as used in the bookmark folder. 100 // TODO(jrg): confirm feel of this constant with ui-team. 101 // http://crbug.com/36276 102 const NSTimeInterval kDragHoverOpenDelay = 0.7; 103 104 // Notes on use of kDragHoverCloseDelay in 105 // -[BookmarkBarFolderController draggingEntered:]. 106 // 107 // We have an implicit delay on stop-hover-open before a submenu 108 // closes. This cannot be zero since it's nice to move the mouse in a 109 // direct line from "current position" to "position of item in 110 // submenu". However, by doing so, it's possible to overlap a 111 // different button on the current menu. Example: 112 // 113 // Folder1 114 // Folder2 ---> Sub1 115 // Folder3 Sub2 116 // Sub3 117 // 118 // If you hover over the F in Folder2 to open the sub, and then want to 119 // select Sub3, a direct line movement of the mouse may cross over 120 // Folder3. Without this delay, that'll cause Sub to be closed before 121 // you get there, since a "hover over" of Folder3 gets activated. 122 // It's subtle but without the delay it feels broken. 123 // 124 // This is only really a problem with vertical menu --> vertical menu 125 // movement; the bookmark bar (horizontal menu, sort of) seems fine, 126 // perhaps because mouse move direction is purely vertical so there is 127 // no opportunity for overlap. 128 const NSTimeInterval kDragHoverCloseDelay = 0.4; 129 130 } // namespace bookmarks 131 132 // The interface for the bookmark bar controller's delegate. Currently, the 133 // delegate is the BWC and is responsible for ensuring that the toolbar is 134 // displayed correctly (as specified by |-getDesiredToolbarHeightCompression| 135 // and |-toolbarDividerOpacity|) at the beginning and at the end of an animation 136 // (or after a state change). 137 @protocol BookmarkBarControllerDelegate 138 139 // Sent when the state has changed (after any animation), but before the final 140 // display update. 141 - (void)bookmarkBar:(BookmarkBarController*)controller 142 didChangeFromState:(BookmarkBar::State)oldState 143 toState:(BookmarkBar::State)newState; 144 145 // Sent before the animation begins. 146 - (void)bookmarkBar:(BookmarkBarController*)controller 147 willAnimateFromState:(BookmarkBar::State)oldState 148 toState:(BookmarkBar::State)newState; 149 150 @end 151 152 // A controller for the bookmark bar in the browser window. Handles showing 153 // and hiding based on the preference in the given profile. 154 @interface BookmarkBarController : 155 NSViewController<BookmarkBarState, 156 BookmarkBarToolbarViewController, 157 BookmarkButtonDelegate, 158 BookmarkButtonControllerProtocol, 159 NSDraggingDestination> { 160 @private 161 // The state of the bookmark bar. If an animation is running, this is set to 162 // the "destination" and |lastState_| is set to the "original" state. 163 BookmarkBar::State currentState_; 164 165 // The "original" state of the bookmark bar if an animation is running. 166 BookmarkBar::State lastState_; 167 168 // YES if an animation is running. 169 BOOL isAnimationRunning_; 170 171 Browser* browser_; // weak; owned by its window 172 BookmarkModel* bookmarkModel_; // weak; part of the profile owned by the 173 // top-level Browser object. 174 175 // Our initial view width, which is applied in awakeFromNib. 176 CGFloat initialWidth_; 177 178 // BookmarkNodes have a 64bit id. NSMenuItems have a 32bit tag used 179 // to represent the bookmark node they refer to. This map provides 180 // a mapping from one to the other, so we can properly identify the 181 // node from the item. When adding items in, we start with seedId_. 182 int32 seedId_; 183 std::map<int32,int64> menuTagMap_; 184 185 // Our bookmark buttons, ordered from L-->R. 186 base::scoped_nsobject<NSMutableArray> buttons_; 187 188 // The folder image so we can use one copy for all buttons 189 base::scoped_nsobject<NSImage> folderImage_; 190 191 // The default image, so we can use one copy for all buttons. 192 base::scoped_nsobject<NSImage> defaultImage_; 193 194 // If the bar is disabled, we hide it and ignore show/hide commands. 195 // Set when using fullscreen mode. 196 BOOL barIsEnabled_; 197 198 // Bridge from Chrome-style C++ notifications (e.g. derived from 199 // BookmarkModelObserver) 200 scoped_ptr<BookmarkBarBridge> bridge_; 201 202 // Delegate that is informed about state changes in the bookmark bar. 203 id<BookmarkBarControllerDelegate> delegate_; // weak 204 205 // Delegate that can resize us. 206 id<ViewResizer> resizeDelegate_; // weak 207 208 // Logic for dealing with a click on a bookmark folder button. 209 base::scoped_nsobject<BookmarkFolderTarget> folderTarget_; 210 211 // A controller for a pop-up bookmark folder window (custom menu). 212 // This is not a scoped_nsobject because it owns itself (when its 213 // window closes the controller gets autoreleased). 214 BookmarkBarFolderController* folderController_; 215 216 // The event tap that allows monitoring of all events, to properly close with 217 // a click outside the bounds of the window. 218 id exitEventTap_; 219 220 IBOutlet BookmarkBarView* buttonView_; // Contains 'no items' text fields. 221 IBOutlet BookmarkButton* offTheSideButton_; // aka the chevron. 222 223 NSRect originalNoItemsRect_; // Original, pre-resized field rect. 224 NSRect originalImportBookmarksRect_; // Original, pre-resized field rect. 225 226 // "Other bookmarks" button on the right side. 227 base::scoped_nsobject<BookmarkButton> otherBookmarksButton_; 228 229 // "Apps" button to the right of "Other bookmarks". 230 base::scoped_nsobject<BookmarkButton> appsPageShortcutButton_; 231 232 // When doing a drag, this is folder button "hovered over" which we 233 // may want to open after a short delay. There are cases where a 234 // mouse-enter can open a folder (e.g. if the menus are "active") 235 // but that doesn't use this variable or need a delay so "hover" is 236 // the wrong term. 237 base::scoped_nsobject<BookmarkButton> hoverButton_; 238 239 // We save the view width when we add bookmark buttons. This lets 240 // us avoid a rebuild until we've grown the window bigger than our 241 // initial build. 242 CGFloat savedFrameWidth_; 243 244 // The number of buttons we display in the bookmark bar. This does 245 // not include the "off the side" chevron or the "Other Bookmarks" 246 // button. We use this number to determine if we need to display 247 // the chevron, and to know what to place in the chevron's menu. 248 // Since we create everything before doing layout we can't be sure 249 // that all bookmark buttons we create will be visible. Thus, 250 // [buttons_ count] isn't a definitive check. 251 int displayedButtonCount_; 252 253 // A state flag which tracks when the bar's folder menus should be shown. 254 // An initial click in any of the folder buttons turns this on and 255 // one of the following will turn it off: another click in the button, 256 // the window losing focus, a click somewhere other than in the bar 257 // or a folder menu. 258 BOOL showFolderMenus_; 259 260 // If YES then state changes (for example, from hidden to shown) are animated. 261 // This is turned off for unit tests. 262 BOOL stateAnimationsEnabled_; 263 264 // If YES then changes inside the bookmark bar (for example, removing a 265 // bookmark) are animated. This is turned off for unit tests. 266 BOOL innerContentAnimationsEnabled_; 267 268 // YES if there is a possible drop about to happen in the bar. 269 BOOL hasInsertionPos_; 270 271 // The x point on the bar where the left edge of the new item will end 272 // up if it is dropped. 273 CGFloat insertionPos_; 274 275 // Controller responsible for all bookmark context menus. 276 base::scoped_nsobject<BookmarkContextMenuCocoaController> 277 contextMenuController_; 278 } 279 280 @property(readonly, nonatomic) BookmarkBar::State currentState; 281 @property(readonly, nonatomic) BookmarkBar::State lastState; 282 @property(readonly, nonatomic) BOOL isAnimationRunning; 283 @property(assign, nonatomic) id<BookmarkBarControllerDelegate> delegate; 284 @property(assign, nonatomic) BOOL stateAnimationsEnabled; 285 @property(assign, nonatomic) BOOL innerContentAnimationsEnabled; 286 287 // Initializes the bookmark bar controller with the given browser 288 // profile and delegates. 289 - (id)initWithBrowser:(Browser*)browser 290 initialWidth:(CGFloat)initialWidth 291 delegate:(id<BookmarkBarControllerDelegate>)delegate 292 resizeDelegate:(id<ViewResizer>)resizeDelegate; 293 294 // The Browser corresponding to this BookmarkBarController. 295 - (Browser*)browser; 296 297 // The controller for all bookmark bar context menus. 298 - (BookmarkContextMenuCocoaController*)menuController; 299 300 // Updates the bookmark bar (from its current, possibly in-transition) state to 301 // the new state. 302 - (void)updateState:(BookmarkBar::State)newState 303 changeType:(BookmarkBar::AnimateChangeType)changeType; 304 305 // Update the visible state of the bookmark bar. 306 - (void)updateVisibility; 307 308 // Update the visible state of the bookmark bar. 309 - (void)updateAppsPageShortcutButtonVisibility; 310 311 // Hides or shows the bookmark bar depending on the current state. 312 - (void)updateHiddenState; 313 314 // Turn on or off the bookmark bar and prevent or reallow its appearance. On 315 // disable, toggle off if shown. On enable, show only if needed. App and popup 316 // windows do not show a bookmark bar. 317 - (void)setBookmarkBarEnabled:(BOOL)enabled; 318 319 // Returns the amount by which the toolbar above should be compressed. 320 - (CGFloat)getDesiredToolbarHeightCompression; 321 322 // Gets the appropriate opacity for the toolbar's divider; 0 means that it 323 // shouldn't be shown. 324 - (CGFloat)toolbarDividerOpacity; 325 326 // Updates the sizes and positions of the subviews. 327 // TODO(viettrungluu): I'm not convinced this should be public, but I currently 328 // need it for animations. Try not to propagate its use. 329 - (void)layoutSubviews; 330 331 // Called by our view when it is moved to a window. 332 - (void)viewDidMoveToWindow; 333 334 // Provide a favicon for a bookmark node. May return nil. 335 - (NSImage*)faviconForNode:(const BookmarkNode*)node; 336 337 // Used for situations where the bookmark bar folder menus should no longer 338 // be actively popping up. Called when the window loses focus, a click has 339 // occured outside the menus or a bookmark has been activated. (Note that this 340 // differs from the behavior of the -[BookmarkButtonControllerProtocol 341 // closeAllBookmarkFolders] method in that the latter does not terminate menu 342 // tracking since it may be being called in response to actions (such as 343 // dragging) where a 'stale' menu presentation should first be collapsed before 344 // presenting a new menu.) 345 - (void)closeFolderAndStopTrackingMenus; 346 347 // Checks if operations such as edit or delete are allowed. 348 - (BOOL)canEditBookmark:(const BookmarkNode*)node; 349 350 // Checks if bookmark editing is enabled at all. 351 - (BOOL)canEditBookmarks; 352 353 // Actions for manipulating bookmarks. 354 // Open a normal bookmark or folder from a button, ... 355 - (IBAction)openBookmark:(id)sender; 356 - (IBAction)openBookmarkFolderFromButton:(id)sender; 357 // From the "off the side" button, ... 358 - (IBAction)openOffTheSideFolderFromButton:(id)sender; 359 // Import bookmarks from another browser. 360 - (IBAction)importBookmarks:(id)sender; 361 @end 362 363 // Redirects from BookmarkBarBridge, the C++ object which glues us to 364 // the rest of Chromium. Internal to BookmarkBarController. 365 @interface BookmarkBarController(BridgeRedirect) 366 - (void)loaded:(BookmarkModel*)model; 367 - (void)beingDeleted:(BookmarkModel*)model; 368 - (void)nodeAdded:(BookmarkModel*)model 369 parent:(const BookmarkNode*)oldParent index:(int)index; 370 - (void)nodeChanged:(BookmarkModel*)model 371 node:(const BookmarkNode*)node; 372 - (void)nodeMoved:(BookmarkModel*)model 373 oldParent:(const BookmarkNode*)oldParent oldIndex:(int)oldIndex 374 newParent:(const BookmarkNode*)newParent newIndex:(int)newIndex; 375 - (void)nodeRemoved:(BookmarkModel*)model 376 parent:(const BookmarkNode*)oldParent index:(int)index; 377 - (void)nodeFaviconLoaded:(BookmarkModel*)model 378 node:(const BookmarkNode*)node; 379 - (void)nodeChildrenReordered:(BookmarkModel*)model 380 node:(const BookmarkNode*)node; 381 @end 382 383 // These APIs should only be used by unit tests (or used internally). 384 @interface BookmarkBarController(InternalOrTestingAPI) 385 - (void)openBookmarkFolder:(id)sender; 386 - (BookmarkBarView*)buttonView; 387 - (NSMutableArray*)buttons; 388 - (NSButton*)offTheSideButton; 389 - (NSButton*)appsPageShortcutButton; 390 - (BOOL)offTheSideButtonIsHidden; 391 - (BOOL)appsPageShortcutButtonIsHidden; 392 - (BookmarkButton*)otherBookmarksButton; 393 - (BookmarkBarFolderController*)folderController; 394 - (id)folderTarget; 395 - (int)displayedButtonCount; 396 - (void)openURL:(GURL)url disposition:(WindowOpenDisposition)disposition; 397 - (void)clearBookmarkBar; 398 - (BookmarkButtonCell*)cellForBookmarkNode:(const BookmarkNode*)node; 399 - (BookmarkButtonCell*)cellForCustomButtonWithText:(NSString*)text 400 image:(NSImage*)image; 401 - (NSRect)frameForBookmarkButtonFromCell:(NSCell*)cell xOffset:(int*)xOffset; 402 - (void)checkForBookmarkButtonGrowth:(NSButton*)button; 403 - (void)frameDidChange; 404 - (int64)nodeIdFromMenuTag:(int32)tag; 405 - (int32)menuTagFromNodeId:(int64)menuid; 406 - (void)updateTheme:(ui::ThemeProvider*)themeProvider; 407 - (BookmarkButton*)buttonForDroppingOnAtPoint:(NSPoint)point; 408 - (BOOL)isEventAnExitEvent:(NSEvent*)event; 409 - (BOOL)shrinkOrHideView:(NSView*)view forMaxX:(CGFloat)maxViewX; 410 - (void)unhighlightBookmark:(const BookmarkNode*)node; 411 412 // The following are for testing purposes only and are not used internally. 413 - (NSMenu *)menuForFolderNode:(const BookmarkNode*)node; 414 @end 415 416 #endif // CHROME_BROWSER_UI_COCOA_BOOKMARKS_BOOKMARK_BAR_CONTROLLER_H_ 417