summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorandybons@chromium.org <andybons@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-03-10 02:00:10 +0000
committerandybons@chromium.org <andybons@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-03-10 02:00:10 +0000
commit4810f7d1b27ea0bc9b4020ba85cd60b8ed1789f6 (patch)
tree921fd786b8002ca760521ba60accd42053772e0e
parentc6839877bdbf53f97fec94d6a9c046a7974c1ce1 (diff)
downloadchromium_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
-rw-r--r--chrome/browser/cocoa/browser_window_controller.mm8
-rw-r--r--chrome/browser/cocoa/extension_installed_bubble_controller.mm11
-rw-r--r--chrome/browser/cocoa/extensions/browser_actions_container_view.h58
-rw-r--r--chrome/browser/cocoa/extensions/browser_actions_container_view.mm166
-rw-r--r--chrome/browser/cocoa/extensions/browser_actions_controller.h32
-rw-r--r--chrome/browser/cocoa/extensions/browser_actions_controller.mm192
-rw-r--r--chrome/browser/cocoa/toolbar_controller.h1
-rw-r--r--chrome/browser/cocoa/toolbar_controller.mm206
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];
}