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;
29class BookmarkModel;
30@class BookmarkMenu;
31class BookmarkNode;
32class Browser;
33class GURL;
34
35namespace 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.
41const CGFloat kDefaultBookmarkWidth = 150.0;
42
43// Horizontal frame inset for buttons in the bookmark bar.
44const CGFloat kBookmarkHorizontalPadding = 1.0;
45
46// Vertical frame inset for buttons in the bookmark bar.
47const CGFloat kBookmarkVerticalPadding = 2.0;
48
49// Used as a min/max width for buttons on menus (not on the bar).
50const CGFloat kBookmarkMenuButtonMinimumWidth = 100.0;
51const 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:]).
57const 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.
64const CGFloat kScrollViewContentWidthMargin = 2;
65
66// Make subfolder menus overlap their parent menu a bit to give a better
67// perception of a menuing system.
68const 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.
74const 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.
78const 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.
84const CGFloat kBookmarkBarButtonOffset = 2.0;
85
86// Delay before opening a subfolder (and closing the previous one)
87// when hovering over a folder button.
88const 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
95const 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.
121const 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
140willAnimateFromState:(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