diff options
author | andybons@chromium.org <andybons@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-10 02:00:10 +0000 |
---|---|---|
committer | andybons@chromium.org <andybons@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-10 02:00:10 +0000 |
commit | 4810f7d1b27ea0bc9b4020ba85cd60b8ed1789f6 (patch) | |
tree | 921fd786b8002ca760521ba60accd42053772e0e | |
parent | c6839877bdbf53f97fec94d6a9c046a7974c1ce1 (diff) | |
download | chromium_src-4810f7d1b27ea0bc9b4020ba85cd60b8ed1789f6.zip chromium_src-4810f7d1b27ea0bc9b4020ba85cd60b8ed1789f6.tar.gz chromium_src-4810f7d1b27ea0bc9b4020ba85cd60b8ed1789f6.tar.bz2 |
[Mac] More progress towards resizing the Browser Actions container.
o Icons fade out as they are moved off screen by resizing the window.
o Dragging the resizer actually resizes the container.
o Known issues include:
+ no chevron.
+ no updating of other windows if the size of the container changes.
+ the cursor is a bit wonky in updating its state.
o Fixes a slew of janky UI bugs relating to having a lot of extensions installed.
BUG=26990,29838
TEST=none
Review URL: http://codereview.chromium.org/657038
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@41114 0039d316-1c4b-4281-b951-d872f2087c98
8 files changed, 529 insertions, 145 deletions
diff --git a/chrome/browser/cocoa/browser_window_controller.mm b/chrome/browser/cocoa/browser_window_controller.mm index ef46709..21f8da4 100644 --- a/chrome/browser/cocoa/browser_window_controller.mm +++ b/chrome/browser/cocoa/browser_window_controller.mm @@ -254,10 +254,6 @@ hasLocationBar:[self hasLocationBar]]; [[[self window] contentView] addSubview:[toolbarController_ view]]; - // This must be done after the view is added to the window since it relies - // on the window bounds to determine whether to show buttons or not. - [toolbarController_ createBrowserActionButtons]; - // Create a sub-controller for the bookmark bar. bookmarkBarController_.reset( [[BookmarkBarController alloc] @@ -301,6 +297,10 @@ name:NSApplicationDidUnhideNotification object:nil]; + // This must be done after the view is added to the window since it relies + // on the window bounds to determine whether to show buttons or not. + [toolbarController_ createBrowserActionButtons]; + // We are done initializing now. initializing_ = NO; } diff --git a/chrome/browser/cocoa/extension_installed_bubble_controller.mm b/chrome/browser/cocoa/extension_installed_bubble_controller.mm index 677e55c..968413b 100644 --- a/chrome/browser/cocoa/extension_installed_bubble_controller.mm +++ b/chrome/browser/cocoa/extension_installed_bubble_controller.mm @@ -174,9 +174,14 @@ class ExtensionLoadedNotificationObserver : public NotificationObserver { NSRect boundsRect = [[[button window] contentView] convertRect:[button frame] fromView:[button superview]]; - arrowPoint = - NSMakePoint(NSMinX(boundsRect) + NSWidth([button frame]) / 2, - NSMinY(boundsRect)); + CGFloat xPos = NSMinX(boundsRect) + NSWidth([button frame]) / 2; + // If the button is hidden, display the button at the edge of the Browser + // Actions container. + // TODO(andybons): Make it point to the chevron once it's implemented. + if ([button alphaValue] == 0.0) + xPos = NSMaxX([[button superview] frame]); + + arrowPoint = NSMakePoint(xPos, NSMinY(boundsRect)); break; } case extension_installed_bubble::kPageAction: { diff --git a/chrome/browser/cocoa/extensions/browser_actions_container_view.h b/chrome/browser/cocoa/extensions/browser_actions_container_view.h index 8eaf6cd..6a50b8b 100644 --- a/chrome/browser/cocoa/extensions/browser_actions_container_view.h +++ b/chrome/browser/cocoa/extensions/browser_actions_container_view.h @@ -2,18 +2,76 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#ifndef CHROME_BROWSER_COCOA_EXTENSIONS_BROWSER_ACTIONS_CONTAINER_VIEW_ +#define CHROME_BROWSER_COCOA_EXTENSIONS_BROWSER_ACTIONS_CONTAINER_VIEW_ + #import <Cocoa/Cocoa.h> @class BrowserActionButton; +// Sent when a user-initiated drag to resize the container is initiated. +extern const NSString* kBrowserActionGrippyDragStartedNotification; + +// Sent when a user-initiated drag is resizing the container. +extern const NSString* kBrowserActionGrippyDraggingNotification; + +// Sent when a user-initiated drag to resize the container has finished. +extern const NSString* kBrowserActionGrippyDragFinishedNotification; + +// The view that encompasses the Browser Action buttons in the toolbar and +// provides mechanisms for resizing. @interface BrowserActionsContainerView : NSView { + @private + // The frame encompasing the grippy used for resizing the container. + NSRect grippyRect_; + + // Used to cache the original position within the container that initiated the + // drag. + NSPoint initialDragPoint_; + + // Used to cache the previous x-pos of the frame rect for resizing purposes. + CGFloat lastXPos_; + // Whether there is a border to the right of the last Browser Action. BOOL rightBorderShown_; + + // Whether the container is currently being resized by the user. + BOOL userIsResizing_; + + // Whether the user is allowed to drag the grippy to the left. NO if all + // extensions are shown or the location bar has hit its minimum width (handled + // within toolbar_controller.mm). + BOOL canDragLeft_; + + // Whether the user is allowed to drag the grippy to the right. NO if all + // extensions are hidden. + BOOL canDragRight_; + + // When the left grippy is pinned, resizing the window has no effect on its + // position. This prevents it from overlapping with other elements as well + // as letting the container expand when the window is going from super small + // to large. + BOOL grippyPinned_; } +// Resizes the container to the given ideal width, adjusting the |lastXPos_| so +// that |resizeDeltaX| is accurate. +- (void)resizeToWidth:(CGFloat)width animate:(BOOL)animate; + // Returns the (visible) button at the given index in the view's hierarchy. - (BrowserActionButton*)buttonAtIndex:(NSUInteger)index; +// Returns the change in the x-pos of the frame rect during resizing. Meant to +// be queried when a NSViewFrameDidChangeNotification is fired to determine +// placement of surrounding elements. +- (CGFloat)resizeDeltaX; + +@property(nonatomic) BOOL canDragLeft; +@property(nonatomic) BOOL canDragRight; +@property(nonatomic) BOOL grippyPinned; +@property(readonly, nonatomic) BOOL userIsResizing; @property(nonatomic) BOOL rightBorderShown; @end + +#endif // CHROME_BROWSER_COCOA_EXTENSIONS_BROWSER_ACTIONS_CONTAINER_VIEW_ diff --git a/chrome/browser/cocoa/extensions/browser_actions_container_view.mm b/chrome/browser/cocoa/extensions/browser_actions_container_view.mm index 71e5b98..ce78490 100644 --- a/chrome/browser/cocoa/extensions/browser_actions_container_view.mm +++ b/chrome/browser/cocoa/extensions/browser_actions_container_view.mm @@ -2,40 +2,64 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <algorithm> + #import "base/scoped_nsobject.h" #import "chrome/browser/cocoa/extensions/browser_action_button.h" #import "chrome/browser/cocoa/extensions/browser_actions_container_view.h" +extern const NSString* kBrowserActionGrippyDragStartedNotification = + @"BrowserActionGrippyDragStartedNotification"; +extern const NSString* kBrowserActionGrippyDraggingNotification = + @"BrowserActionGrippyDraggingNotification"; +extern const NSString* kBrowserActionGrippyDragFinishedNotification = + @"BrowserActionGrippyDragFinishedNotification"; + namespace { - const CGFloat kGrippyLowerPadding = 4.0; - const CGFloat kGrippyUpperPadding = 8.0; - const CGFloat kRightBorderXOffset = -1.0; - const CGFloat kRightBorderWidth = 1.0; - const CGFloat kRightBorderGrayscale = 0.5; - const CGFloat kUpperPadding = 9.0; - const CGFloat kLowerPadding = 5.0; +const CGFloat kAnimationDuration = 0.1; +const CGFloat kGrippyLowerPadding = 4.0; +const CGFloat kGrippyUpperPadding = 8.0; +const CGFloat kGrippyWidth = 10.0; +const CGFloat kLowerPadding = 5.0; +const CGFloat kMinimumContainerWidth = 5.0; +const CGFloat kRightBorderXOffset = -1.0; +const CGFloat kRightBorderWidth = 1.0; +const CGFloat kRightBorderGrayscale = 0.5; +const CGFloat kUpperPadding = 9.0; } // namespace @interface BrowserActionsContainerView(Private) -- (void)drawLeftGrippers; +- (NSCursor*)appropriateCursorForGrippy; +- (void)drawGrippy; @end @implementation BrowserActionsContainerView +@synthesize canDragLeft = canDragLeft_; +@synthesize canDragRight = canDragRight_; +@synthesize grippyPinned = grippyPinned_; +@synthesize userIsResizing = userIsResizing_; @synthesize rightBorderShown = rightBorderShown_; +- (id)initWithFrame:(NSRect)frameRect { + if ((self = [super initWithFrame:frameRect])) { + grippyRect_ = NSMakeRect(0.0, 0.0, kGrippyWidth, NSHeight([self bounds])); + } + return self; +} + - (void)drawRect:(NSRect)dirtyRect { - NSRect bounds = [self bounds]; if (rightBorderShown_) { + NSRect bounds = [self bounds]; NSColor* middleColor = [NSColor colorWithCalibratedWhite:kRightBorderGrayscale alpha:1.0]; NSColor* endPointColor = [NSColor colorWithCalibratedWhite:kRightBorderGrayscale alpha:0.0]; - NSGradient* borderGradient = [[[NSGradient alloc] + scoped_nsobject<NSGradient> borderGradient([[NSGradient alloc] initWithColorsAndLocations:endPointColor, (CGFloat)0.0, middleColor, (CGFloat)0.5, endPointColor, (CGFloat)1.0, - nil] autorelease]; + nil]); CGFloat xPos = bounds.origin.x + bounds.size.width - kRightBorderWidth + kRightBorderXOffset; NSRect borderRect = NSMakeRect(xPos, kLowerPadding, kRightBorderWidth, @@ -43,10 +67,12 @@ namespace { [borderGradient drawInRect:borderRect angle:90.0]; } - [self drawLeftGrippers]; + [self drawGrippy]; } -- (void)drawLeftGrippers { +// Draws the area that the user can use to resize the container. Currently, two +// vertical "grip" bars. +- (void)drawGrippy { NSRect grippyRect = NSMakeRect(0.0, kLowerPadding + kGrippyLowerPadding, 1.0, [self bounds].size.height - kUpperPadding - kGrippyUpperPadding); [[NSColor colorWithCalibratedWhite:0.7 alpha:0.5] set]; @@ -67,8 +93,122 @@ namespace { NSRectFill(grippyRect); } +- (void)resizeToWidth:(CGFloat)width animate:(BOOL)animate { + width = std::max(width, kMinimumContainerWidth); + NSRect frame = [self frame]; + lastXPos_ = frame.origin.x; + CGFloat dX = frame.size.width - width; + frame.size.width = width; + NSRect newFrame = NSOffsetRect(frame, dX, 0); + if (animate) { + [NSAnimationContext beginGrouping]; + [[NSAnimationContext currentContext] setDuration:kAnimationDuration]; + [[self animator] setFrame:newFrame]; + [NSAnimationContext endGrouping]; + } else { + // TODO(andybons): Worry about animations already in progress in this case. + [self setFrame:newFrame]; + } + [self setNeedsDisplay:YES]; +} + - (BrowserActionButton*)buttonAtIndex:(NSUInteger)index { return [[self subviews] objectAtIndex:index]; } +// Returns the cursor to display over the grippy hover region depending on the +// current drag state. +- (NSCursor*)appropriateCursorForGrippy { + NSCursor* retVal; + if (!canDragLeft_ && !canDragRight_) { + retVal = [NSCursor arrowCursor]; + } else if (!canDragLeft_) { + retVal = [NSCursor resizeRightCursor]; + } else if (!canDragRight_) { + retVal = [NSCursor resizeLeftCursor]; + } else { + retVal = [NSCursor resizeLeftRightCursor]; + } + return retVal; +} + +- (void)resetCursorRects { + [self discardCursorRects]; + [self addCursorRect:grippyRect_ cursor:[self appropriateCursorForGrippy]]; +} + +- (BOOL)acceptsFirstResponder { + return YES; +} + +- (void)mouseDown:(NSEvent*)theEvent { + initialDragPoint_ = [self convertPoint:[theEvent locationInWindow] + fromView:nil]; + if (!NSMouseInRect(initialDragPoint_, grippyRect_, [self isFlipped])) + return; + + lastXPos_ = [self frame].origin.x; + userIsResizing_ = YES; + [[NSNotificationCenter defaultCenter] + postNotificationName:kBrowserActionGrippyDragStartedNotification + object:self]; + // TODO(andybons): The cursor does not stick once moved outside of the + // toolbar. Investigate further. http://crbug.com/36698 + while (1) { + // This inner run loop catches and removes mouse up and drag events from the + // default event queue and dispatches them to the appropriate custom + // handlers. This is to prevent the cursor from changing (or any other side- + // effects of dragging the mouse around the app). + theEvent = + [NSApp nextEventMatchingMask:NSLeftMouseUpMask | NSLeftMouseDraggedMask + untilDate:[NSDate distantFuture] + inMode:NSDefaultRunLoopMode dequeue:YES]; + + NSEventType type = [theEvent type]; + if (type == NSLeftMouseDragged) { + [self mouseDragged:theEvent]; + } else if (type == NSLeftMouseUp) { + [self mouseUp:theEvent]; + break; + } + } +} + +- (void)mouseUp:(NSEvent*)theEvent { + userIsResizing_ = NO; + [[NSNotificationCenter defaultCenter] + postNotificationName:kBrowserActionGrippyDragFinishedNotification + object:self]; +} + +- (void)mouseDragged:(NSEvent*)theEvent { + if (!userIsResizing_) + return; + + NSPoint location = [self convertPoint:[theEvent locationInWindow] + fromView:nil]; + CGFloat dX = [theEvent deltaX]; + CGFloat withDelta = location.x - dX; + canDragRight_ = withDelta >= initialDragPoint_.x; + + if ((dX < 0.0 && !canDragLeft_) || (dX > 0.0 && !canDragRight_)) + return; + + NSRect containerFrame = [self frame]; + containerFrame.origin.x += dX; + containerFrame.size.width -= dX; + [self setFrame:containerFrame]; + [self setNeedsDisplay:YES]; + + [[NSNotificationCenter defaultCenter] + postNotificationName:kBrowserActionGrippyDraggingNotification + object:self]; + + lastXPos_ += dX; +} + +- (CGFloat)resizeDeltaX { + return [self frame].origin.x - lastXPos_; +} + @end diff --git a/chrome/browser/cocoa/extensions/browser_actions_controller.h b/chrome/browser/cocoa/extensions/browser_actions_controller.h index 969464b..4ff528a 100644 --- a/chrome/browser/cocoa/extensions/browser_actions_controller.h +++ b/chrome/browser/cocoa/extensions/browser_actions_controller.h @@ -17,12 +17,17 @@ class Extension; @class ExtensionPopupController; class ExtensionToolbarModel; class ExtensionsServiceObserverBridge; +class PrefService; class Profile; +// The padding between browser action buttons. extern const CGFloat kBrowserActionButtonPadding; -extern NSString* const kBrowserActionsChangedNotification; +// Sent when the visibility of the Browser Actions changes. +extern const NSString* kBrowserActionVisibilityChangedNotification; +// Handles state and provides an interface for controlling the Browser Actions +// container within the Toolbar. @interface BrowserActionsController : NSObject { @private // Reference to the current browser. Weak. @@ -44,9 +49,6 @@ extern NSString* const kBrowserActionsChangedNotification; // buttons present in the container view. The ID is a string unique to each // extension. scoped_nsobject<NSMutableDictionary> buttons_; - - // The order of the BrowserActionButton objects within the dictionary. - scoped_nsobject<NSMutableArray> buttonOrder_; } @property(readonly, nonatomic) BrowserActionsContainerView* containerView; @@ -56,14 +58,6 @@ extern NSString* const kBrowserActionsChangedNotification; - (id)initWithBrowser:(Browser*)browser containerView:(BrowserActionsContainerView*)container; -// Creates and appends any existing browser action buttons present within the -// extensions service to the toolbar. -- (void)createButtons; - -// Returns the ideal (not current) width to fit all visible extensions and other -// UI elements in the container nicely. -- (CGFloat)idealContainerWidth; - // Update the display of all buttons. - (void)update; @@ -75,16 +69,28 @@ extern NSString* const kBrowserActionsChangedNotification; // container. - (NSUInteger)visibleButtonCount; +// Resizes the container to fit all the visible buttons and other elements +// (grippy and overflow button). +- (void)resizeContainerWithAnimation:(BOOL)animate; + // Executes the action designated by the extension. - (void)browserActionClicked:(BrowserActionButton*)sender; // Returns the NSView for the action button associated with an extension. - (NSView*)browserActionViewForExtension:(Extension*)extension; +// Returns the saved width preference as specified by the user. If none is +// specified, then zero is returned, indicating that the width has never been +// set. +- (CGFloat)savedWidth; + +// Registers the user preferences used by this class. ++ (void)registerUserPrefs:(PrefService*)prefs; + @end // @interface BrowserActionsController @interface BrowserActionsController(TestingAPI) -- (NSButton*)buttonWithIndex:(int)index; +- (NSButton*)buttonWithIndex:(NSUInteger)index; @end #endif // CHROME_BROWSER_COCOA_EXTENSIONS_BROWSER_ACTIONS_CONTROLLER_H_ diff --git a/chrome/browser/cocoa/extensions/browser_actions_controller.mm b/chrome/browser/cocoa/extensions/browser_actions_controller.mm index 49b6f8d..2294c19 100644 --- a/chrome/browser/cocoa/extensions/browser_actions_controller.mm +++ b/chrome/browser/cocoa/extensions/browser_actions_controller.mm @@ -8,9 +8,10 @@ #include "base/sys_string_conversions.h" #include "chrome/browser/browser.h" -#include "chrome/browser/cocoa/extensions/browser_action_button.h" -#include "chrome/browser/cocoa/extensions/browser_actions_container_view.h" -#include "chrome/browser/cocoa/extensions/extension_popup_controller.h" +#include "chrome/browser/pref_service.h" +#import "chrome/browser/cocoa/extensions/browser_action_button.h" +#import "chrome/browser/cocoa/extensions/browser_actions_container_view.h" +#import "chrome/browser/cocoa/extensions/extension_popup_controller.h" #include "chrome/browser/extensions/extension_browser_event_router.h" #include "chrome/browser/extensions/extension_toolbar_model.h" #include "chrome/browser/extensions/extensions_service.h" @@ -18,22 +19,31 @@ #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/common/notification_observer.h" #include "chrome/common/notification_registrar.h" +#include "chrome/common/pref_names.h" -// The padding between browser action buttons. extern const CGFloat kBrowserActionButtonPadding = 3; +extern const NSString* kBrowserActionVisibilityChangedNotification = + @"BrowserActionVisibilityChangedNotification"; + namespace { +const CGFloat kAnimationDuration = 0.2; const CGFloat kContainerPadding = 2.0; const CGFloat kGrippyXOffset = 8.0; +const CGFloat kButtonOpacityLeadPadding = 5.0; } // namespace -NSString* const kBrowserActionsChangedNotification = @"BrowserActionsChanged"; - @interface BrowserActionsController(Private) +- (void)createButtons; - (void)createActionButtonForExtension:(Extension*)extension - withIndex:(int)index; + withIndex:(NSUInteger)index; - (void)removeActionButtonForExtension:(Extension*)extension; +- (CGFloat)containerWidthWithButtonCount:(NSUInteger)buttonCount; - (void)repositionActionButtons; +- (void)updateButtonOpacityAndDragAbilities; +- (void)containerFrameChanged; +- (void)containerDragging; +- (void)containerDragFinished; - (int)currentTabId; - (bool)shouldDisplayBrowserAction:(Extension*)extension; @end @@ -69,10 +79,12 @@ class ExtensionsServiceObserverBridge : public NotificationObserver, // ExtensionToolbarModel::Observer implementation. void BrowserActionAdded(Extension* extension, int index) { [owner_ createActionButtonForExtension:extension withIndex:index]; + [owner_ resizeContainerWithAnimation:NO]; } void BrowserActionRemoved(Extension* extension) { [owner_ removeActionButtonForExtension:extension]; + [owner_ resizeContainerWithAnimation:NO]; } private: @@ -97,6 +109,10 @@ class ExtensionsServiceObserverBridge : public NotificationObserver, browser_ = browser; profile_ = browser->profile(); + if (!profile_->GetPrefs()->FindPreference( + prefs::kBrowserActionContainerWidth)) + [BrowserActionsController registerUserPrefs:profile_->GetPrefs()]; + observer_.reset(new ExtensionsServiceObserverBridge(self, profile_)); ExtensionsService* extensionsService = profile_->GetExtensionsService(); // |extensionsService| can be NULL in Incognito. @@ -107,9 +123,27 @@ class ExtensionsServiceObserverBridge : public NotificationObserver, containerView_ = container; [containerView_ setHidden:YES]; + [containerView_ setCanDragLeft:YES]; + [containerView_ setCanDragRight:YES]; + [containerView_ setPostsFrameChangedNotifications:YES]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(containerFrameChanged) + name:NSViewFrameDidChangeNotification + object:containerView_]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(containerDragging) + name:kBrowserActionGrippyDraggingNotification + object:containerView_]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(containerDragFinished) + name:kBrowserActionGrippyDragFinishedNotification + object:containerView_]; buttons_.reset([[NSMutableDictionary alloc] init]); - buttonOrder_.reset([[NSMutableArray alloc] init]); + [self createButtons]; } return self; @@ -135,24 +169,24 @@ class ExtensionsServiceObserverBridge : public NotificationObserver, if (!toolbarModel_) return; - int i = 0; + NSUInteger i = 0; for (ExtensionList::iterator iter = toolbarModel_->begin(); iter != toolbarModel_->end(); ++iter) { [self createActionButtonForExtension:*iter withIndex:i++]; } -} -- (CGFloat)idealContainerWidth { - NSUInteger buttonCount = [self visibleButtonCount]; - if (buttonCount == 0) - return 0.0; + CGFloat width = [self savedWidth]; + // The width will never be 0 (due to the container's minimum size restriction) + // except when no width has been saved. In this case, set the width to be as + // if all buttons are shown. + if (width == 0) + width = [self containerWidthWithButtonCount:[self buttonCount]]; - return kGrippyXOffset + kContainerPadding + (buttonCount * - (kBrowserActionWidth + kBrowserActionButtonPadding)); + [containerView_ resizeToWidth:width animate:NO]; } - (void)createActionButtonForExtension:(Extension*)extension - withIndex:(int)index { + withIndex:(NSUInteger)index { if (!extension->browser_action()) return; @@ -171,13 +205,14 @@ class ExtensionsServiceObserverBridge : public NotificationObserver, [newButton setTarget:self]; [newButton setAction:@selector(browserActionClicked:)]; NSString* buttonKey = base::SysUTF8ToNSString(extension->id()); + if (!buttonKey) + return; [buttons_ setObject:newButton forKey:buttonKey]; - [buttonOrder_ insertObject:newButton atIndex:index]; [containerView_ addSubview:newButton]; - [self repositionActionButtons]; + if (index >= [self visibleButtonCount]) + [newButton setAlphaValue:0.0]; - [[NSNotificationCenter defaultCenter] - postNotificationName:kBrowserActionsChangedNotification object:self]; + [self repositionActionButtons]; [containerView_ setNeedsDisplay:YES]; } @@ -186,6 +221,8 @@ class ExtensionsServiceObserverBridge : public NotificationObserver, return; NSString* buttonKey = base::SysUTF8ToNSString(extension->id()); + if (!buttonKey) + return; BrowserActionButton* button = [buttons_ objectForKey:buttonKey]; if (!button) { @@ -194,27 +231,106 @@ class ExtensionsServiceObserverBridge : public NotificationObserver, } [button removeFromSuperview]; [buttons_ removeObjectForKey:buttonKey]; - [buttonOrder_ removeObject:button]; if ([buttons_ count] == 0) { // No more buttons? Hide the container. [containerView_ setHidden:YES]; } else { [self repositionActionButtons]; } - [[NSNotificationCenter defaultCenter] - postNotificationName:kBrowserActionsChangedNotification object:self]; [containerView_ setNeedsDisplay:YES]; } - (void)repositionActionButtons { - for (NSUInteger i = 0; i < [buttonOrder_ count]; ++i) { + NSUInteger i = 0; + for (ExtensionList::iterator iter = toolbarModel_->begin(); + iter != toolbarModel_->end(); ++iter) { CGFloat xOffset = kGrippyXOffset + (i * (kBrowserActionWidth + kBrowserActionButtonPadding)); - BrowserActionButton* button = [buttonOrder_ objectAtIndex:i]; + NSString* extensionId = base::SysUTF8ToNSString((*iter)->id()); + DCHECK(extensionId); + if (!extensionId) + continue; + BrowserActionButton* button = [buttons_ objectForKey:extensionId]; NSRect buttonFrame = [button frame]; buttonFrame.origin.x = xOffset; [button setFrame:buttonFrame]; + ++i; + } +} + +- (CGFloat)containerWidthWithButtonCount:(NSUInteger)buttonCount { + CGFloat width = 0.0; + if (buttonCount > 0) { + width = kGrippyXOffset + kContainerPadding + + (buttonCount * (kBrowserActionWidth + kBrowserActionButtonPadding)); + } + return width; +} + +// Resizes the container given the number of visible buttons in the container, +// taking into account the size of the grippy. Also updates the persistent +// width preference. +- (void)resizeContainerWithAnimation:(BOOL)animate { + CGFloat width = + [self containerWidthWithButtonCount:[self visibleButtonCount]]; + [containerView_ resizeToWidth:width animate:animate]; + profile_->GetPrefs()->SetReal(prefs::kBrowserActionContainerWidth, + NSWidth([containerView_ frame])); + + [[NSNotificationCenter defaultCenter] + postNotificationName:kBrowserActionVisibilityChangedNotification + object:self]; +} + +- (void)updateButtonOpacityAndDragAbilities { + for (BrowserActionButton* button in [buttons_ allValues]) { + NSRect buttonFrame = [button frame]; + buttonFrame.origin.x += kButtonOpacityLeadPadding; + if (NSContainsRect([containerView_ bounds], buttonFrame)) { + if ([button alphaValue] != 1.0) + [button setAlphaValue:1.0]; + + continue; + } + CGFloat intersectionWidth = + NSWidth(NSIntersectionRect([containerView_ bounds], buttonFrame)); + CGFloat alpha = std::max(0.0f, + (intersectionWidth - kButtonOpacityLeadPadding) / NSWidth(buttonFrame)); + [button setAlphaValue:alpha]; + [button setNeedsDisplay:YES]; } + + // Updates the drag direction constraint variables based on relevant metrics. + [containerView_ setCanDragLeft: + ([self visibleButtonCount] != [self buttonCount])]; + [[containerView_ window] invalidateCursorRectsForView:containerView_]; +} + +- (void)containerFrameChanged { + [self updateButtonOpacityAndDragAbilities]; +} + +- (void)containerDragging { + [[NSNotificationCenter defaultCenter] + postNotificationName:kBrowserActionGrippyDraggingNotification + object:self]; +} + +// Handles when a user initiated drag to resize the container has finished. +- (void)containerDragFinished { + for (BrowserActionButton* button in [buttons_ allValues]) { + NSRect buttonFrame = [button frame]; + if (NSContainsRect([containerView_ bounds], buttonFrame)) + continue; + + CGFloat intersectionWidth = + NSWidth(NSIntersectionRect([containerView_ bounds], buttonFrame)); + if (([containerView_ grippyPinned] && intersectionWidth > 0) || + (intersectionWidth <= (NSWidth(buttonFrame) / 2))) + [button setAlphaValue:0.0]; + } + + [self resizeContainerWithAnimation:NO]; } - (NSUInteger)buttonCount { @@ -224,7 +340,7 @@ class ExtensionsServiceObserverBridge : public NotificationObserver, - (NSUInteger)visibleButtonCount { int count = 0; for (BrowserActionButton* button in [buttons_ allValues]) { - if (![button isHidden]) + if ([button alphaValue] > 0.0) ++count; } return count; @@ -275,7 +391,7 @@ class ExtensionsServiceObserverBridge : public NotificationObserver, } - (NSView*)browserActionViewForExtension:(Extension*)extension { - for (BrowserActionButton* button in buttonOrder_.get()) { + for (BrowserActionButton* button in [buttons_ allValues]) { if ([button extension] == extension) return button; } @@ -283,8 +399,16 @@ class ExtensionsServiceObserverBridge : public NotificationObserver, return nil; } -- (NSButton*)buttonWithIndex:(int)index { - return [buttonOrder_ objectAtIndex:(NSUInteger)index]; +- (NSButton*)buttonWithIndex:(NSUInteger)index { + NSUInteger i = 0; + for (ExtensionList::iterator iter = toolbarModel_->begin(); + iter != toolbarModel_->end(); ++iter) { + if (i == index) + return [buttons_ objectForKey:base::SysUTF8ToNSString((*iter)->id())]; + + ++i; + } + return nil; } - (bool)shouldDisplayBrowserAction:(Extension*)extension { @@ -293,4 +417,12 @@ class ExtensionsServiceObserverBridge : public NotificationObserver, IsIncognitoEnabled(extension->id())); } +- (CGFloat)savedWidth { + return profile_->GetPrefs()->GetReal(prefs::kBrowserActionContainerWidth); +} + ++ (void)registerUserPrefs:(PrefService*)prefs { + prefs->RegisterRealPref(prefs::kBrowserActionContainerWidth, 0); +} + @end diff --git a/chrome/browser/cocoa/toolbar_controller.h b/chrome/browser/cocoa/toolbar_controller.h index efce7e1..3c5086b 100644 --- a/chrome/browser/cocoa/toolbar_controller.h +++ b/chrome/browser/cocoa/toolbar_controller.h @@ -76,6 +76,7 @@ class ToolbarModel; BooleanPrefMember showPageOptionButtons_; BOOL hasToolbar_; // If NO, we may have only the location bar. BOOL hasLocationBar_; // If |hasToolbar_| is YES, this must also be YES. + BOOL locationBarAtMinSize_; // If the location bar is at the minimum size. // We have an extra retain in the locationBar_. // See comments in awakeFromNib for more info. diff --git a/chrome/browser/cocoa/toolbar_controller.mm b/chrome/browser/cocoa/toolbar_controller.mm index f7a441c..53e6b3a 100644 --- a/chrome/browser/cocoa/toolbar_controller.mm +++ b/chrome/browser/cocoa/toolbar_controller.mm @@ -60,21 +60,25 @@ NSString* const kWrenchButtonImageName = @"menu_chrome_Template.pdf"; // Height of the toolbar in pixels when the bookmark bar is closed. const CGFloat kBaseToolbarHeight = 36.0; -// The threshold width in pixels between the reload button (the home button is -// optional) and the right side of the window for use in determining whether to -// show or hide Browser Action buttons depending on window size. -const CGFloat kHideBrowserActionThresholdWidth = 300.0; +// The distance from the 'Go' button to the Browser Actions container in pixels. +const CGFloat kBrowserActionsContainerLeftPadding = 5.0; + +// The minimum width of the location bar in pixels. +const CGFloat kMinimumLocationBarWidth = 100.0; } // namespace @interface ToolbarController(Private) - (void)addAccessibilityDescriptions; -- (void)windowResized; - (void)initCommandStatus:(CommandUpdater*)commands; - (void)prefChanged:(std::wstring*)prefName; - (BackgroundGradientView*)backgroundGradientView; -- (void)showOrHideBrowserActionButtons; -- (void)browserActionsChanged; +- (void)toolbarFrameChanged; +- (void)pinGoButtonToLeftOfBrowserActionsContainer; +- (void)maintainMinimumLocationBarWidth; +- (void)adjustBrowserActionsContainerForNewWindow; +- (void)browserActionsContainerDragged; +- (void)browserActionsVisibilityChanged; - (void)adjustLocationAndGoPositionsBy:(CGFloat)dX; @end @@ -217,11 +221,6 @@ class PrefObserverBridge : public NotificationObserver { commands_, toolbarModel_, profile_, browser_)); [locationBar_ setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]]; - [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(windowResized) - name:NSWindowDidResizeNotification - object:[[self view] window]]; // Register pref observers for the optional home and page/options buttons // and then add them to the toolbar based on those prefs. prefObserver_.reset(new ToolbarControllerInternal::PrefObserverBridge(self)); @@ -241,17 +240,7 @@ class PrefObserverBridge : public NotificationObserver { initWithBrowser:browser_ modelType:BACK_FORWARD_MENU_TYPE_FORWARD button:forwardButton_]); - browserActionsController_.reset([[BrowserActionsController alloc] - initWithBrowser:browser_ - containerView:browserActionsContainerView_]); - // When new browser actions are added/removed, the container view for them is - // resized, necessitating the probable resizing of surrounding elements - // handled by this controller. - [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(browserActionsChanged) - name:kBrowserActionsChangedNotification - object:browserActionsController_]; + // For a popup window, the toolbar is really just a location bar // (see override for [ToolbarController view], below). When going // fullscreen, we remove the toolbar controller's view from the view @@ -269,11 +258,21 @@ class PrefObserverBridge : public NotificationObserver { NSTrackingActiveAlways owner:self userInfo:nil]); - [[self view] addTrackingArea:trackingArea_.get()]; + NSView* toolbarView = [self view]; + [toolbarView addTrackingArea:trackingArea_.get()]; // We want a dynamic tooltip on the go button, so tell the go button to ask // us for the tooltip. [goButton_ addToolTipRect:[goButton_ bounds] owner:self userData:nil]; + + // If the user has any Browser Actions installed, the container view for them + // may have to be resized depending on the width of the toolbar frame. + [toolbarView setPostsFrameChangedNotifications:YES]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(toolbarFrameChanged) + name:NSViewFrameDidChangeNotification + object:toolbarView]; } - (void)addAccessibilityDescriptions { @@ -316,12 +315,6 @@ class PrefObserverBridge : public NotificationObserver { forAttribute:NSAccessibilityDescriptionAttribute]; } -- (void)windowResized { - // Some Browser Action buttons may have to be hidden or shown depending on the - // window's size. - [self showOrHideBrowserActionButtons]; -} - - (void)mouseExited:(NSEvent*)theEvent { [[hoveredButton_ cell] setMouseInside:NO animate:YES]; [hoveredButton_ release]; @@ -599,72 +592,121 @@ class PrefObserverBridge : public NotificationObserver { } - (void)createBrowserActionButtons { - [browserActionsController_ createButtons]; - [self showOrHideBrowserActionButtons]; + if (browserActionsController_.get() == nil) { + browserActionsController_.reset([[BrowserActionsController alloc] + initWithBrowser:browser_ + containerView:browserActionsContainerView_]); + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(browserActionsContainerDragged) + name:kBrowserActionGrippyDraggingNotification + object:browserActionsController_]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(browserActionsVisibilityChanged) + name:kBrowserActionVisibilityChangedNotification + object:browserActionsController_]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(adjustBrowserActionsContainerForNewWindow) + name:NSWindowDidBecomeKeyNotification + object:[[self view] window]]; + } + + CGFloat dX = NSWidth([browserActionsContainerView_ frame]) * -1; + [self adjustLocationAndGoPositionsBy:dX]; BOOL rightBorderShown = !([pageButton_ isHidden] && [wrenchButton_ isHidden]); [browserActionsContainerView_ setRightBorderShown:rightBorderShown]; } -- (void)showOrHideBrowserActionButtons { - // TODO(andybons): This is ugly as sin and hard to follow. Fix it up. +- (void)adjustBrowserActionsContainerForNewWindow { + [self toolbarFrameChanged]; + [[NSNotificationCenter defaultCenter] + removeObserver:self + name:NSWindowDidBecomeKeyNotification + object:[[self view] window]]; +} - int buttonCount = [browserActionsController_ buttonCount]; - if (buttonCount == 0 || !hasToolbar_) - return; +- (void)browserActionsContainerDragged { + CGFloat locationBarWidth = NSWidth([locationBar_ frame]); + locationBarAtMinSize_ = locationBarWidth <= kMinimumLocationBarWidth; + [browserActionsContainerView_ setCanDragLeft:!locationBarAtMinSize_]; + [browserActionsContainerView_ setGrippyPinned:locationBarAtMinSize_]; - CGFloat curWidth = NSWidth([[[self view] window] frame]); - NSRect reloadFrame = [reloadButton_ frame]; - // Calculate the width between the reload button and the end of the frame and - // subtract the threshold width, which represents the space that the - // (optional) home button, omnibar, go button and page/wrench buttons take up - // when no Browser Actions are displayed. This is to prevent the Browser - // Action buttons from pushing the elements to the left of them too far over. - CGFloat availableWidth = std::max(0.0f, - curWidth - reloadFrame.origin.x + NSWidth(reloadFrame) - - kHideBrowserActionThresholdWidth); - // How many Browser Action buttons can we safely display without overflow? - int numAvailableSlots = availableWidth / - (kBrowserActionWidth + kBrowserActionButtonPadding); - int visibleCount = [browserActionsController_ visibleButtonCount]; - - // |delta| is the number of buttons that should be shown or hidden based on - // the number of available slots and the number of visible buttons. - int delta = numAvailableSlots - visibleCount; - BOOL hide = delta < 0; - if (hide) { - delta *= -1; - } else if (visibleCount == buttonCount) { - // The number of available slots is greater than the number of displayed - // buttons then all buttons are already displayed. - return; - } - int arrayOffset = hide ? -1 : 0; - - while (delta > 0) { - visibleCount = [browserActionsController_ visibleButtonCount]; - if (visibleCount == buttonCount && !hide) - return; - BrowserActionButton* button = [[browserActionsContainerView_ subviews] - objectAtIndex:visibleCount + arrayOffset]; - [button setHidden:hide]; - [self browserActionsChanged]; - --delta; - } + [self adjustLocationAndGoPositionsBy: + [browserActionsContainerView_ resizeDeltaX]]; } -- (void)browserActionsChanged { - CGFloat width = [browserActionsController_ idealContainerWidth]; +- (void)browserActionsVisibilityChanged { + [self pinGoButtonToLeftOfBrowserActionsContainer]; +} + +- (void)pinGoButtonToLeftOfBrowserActionsContainer { + NSRect goFrame = [goButton_ frame]; NSRect containerFrame = [browserActionsContainerView_ frame]; - CGFloat dX = containerFrame.size.width - width; - containerFrame.size.width = width; + CGFloat leftPadding = containerFrame.origin.x - + (goFrame.origin.x + NSWidth(goFrame)); + if (leftPadding != kBrowserActionsContainerLeftPadding) { + CGFloat dX = leftPadding - kBrowserActionsContainerLeftPadding; + [self adjustLocationAndGoPositionsBy:dX]; + } +} - [browserActionsContainerView_ setFrame:NSOffsetRect(containerFrame, dX, 0)]; - [self adjustLocationAndGoPositionsBy:dX]; +- (void)maintainMinimumLocationBarWidth { + CGFloat locationBarWidth = NSWidth([locationBar_ frame]); + locationBarAtMinSize_ = locationBarWidth <= kMinimumLocationBarWidth; + if (locationBarAtMinSize_) { + CGFloat dX = kMinimumLocationBarWidth - locationBarWidth; + [self adjustLocationAndGoPositionsBy:dX]; + } +} + +- (void)toolbarFrameChanged { + [self maintainMinimumLocationBarWidth]; + + if (locationBarAtMinSize_) { + // Once the grippy is pinned, leave it until it is explicity un-pinned. + [browserActionsContainerView_ setGrippyPinned:YES]; + NSRect containerFrame = [browserActionsContainerView_ frame]; + // Determine how much the container needs to move in case it's overlapping + // with the location bar. + CGFloat dX = ([goButton_ frame].origin.x + NSWidth([goButton_ frame])) - + containerFrame.origin.x + kBrowserActionsContainerLeftPadding; + containerFrame = NSOffsetRect(containerFrame, dX, 0); + containerFrame.size.width -= dX; + [browserActionsContainerView_ setFrame:containerFrame]; + } else if (!locationBarAtMinSize_ && + [browserActionsContainerView_ grippyPinned]) { + // Expand out the container until it hits the saved size, then unpin the + // grippy. + // Add 0.1 pixel so that it doesn't hit the minimum width codepath above. + CGFloat dX = NSWidth([locationBar_ frame]) - + (kMinimumLocationBarWidth + 0.1); + NSRect containerFrame = [browserActionsContainerView_ frame]; + containerFrame = NSOffsetRect(containerFrame, -dX, 0); + containerFrame.size.width += dX; + CGFloat savedContainerWidth = [browserActionsController_ savedWidth]; + if (NSWidth(containerFrame) >= savedContainerWidth) { + containerFrame = NSOffsetRect(containerFrame, + NSWidth(containerFrame) - savedContainerWidth, 0); + containerFrame.size.width = savedContainerWidth; + [browserActionsContainerView_ setGrippyPinned:NO]; + } + [browserActionsContainerView_ setFrame:containerFrame]; + [self pinGoButtonToLeftOfBrowserActionsContainer]; + } } - (void)adjustLocationAndGoPositionsBy:(CGFloat)dX { - [goButton_ setFrame:NSOffsetRect([goButton_ frame], dX, 0)]; + // Ensure that the 'Go' button is in its proper place. + NSRect goFrame = [goButton_ frame]; NSRect locationFrame = [locationBar_ frame]; + CGFloat rightDelta = (locationFrame.origin.x + NSWidth(locationFrame)) - + goFrame.origin.x; + if (rightDelta != 0.0) + [goButton_ setFrame:NSOffsetRect(goFrame, rightDelta, 0)]; + + [goButton_ setFrame:NSOffsetRect([goButton_ frame], dX, 0)]; locationFrame.size.width += dX; [locationBar_ setFrame:locationFrame]; } |