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