diff options
author | avi@chromium.org <avi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-07 18:34:55 +0000 |
---|---|---|
committer | avi@chromium.org <avi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-07 18:34:55 +0000 |
commit | 630559915695886f8239fc0748aad03c9596bb9e (patch) | |
tree | 60737a2f1983ec8bd5b678a473721efa142f8d72 | |
parent | e91a617485ddf9fa6fd151a777f012a1ab5f43eb (diff) | |
download | chromium_src-630559915695886f8239fc0748aad03c9596bb9e.zip chromium_src-630559915695886f8239fc0748aad03c9596bb9e.tar.gz chromium_src-630559915695886f8239fc0748aad03c9596bb9e.tar.bz2 |
Implement back/forward toolbar menus on Mac (bug 13203).
Note: The drop-down menu is actually a drag-down (activating on
click-hold or on drag), working much like Safari's (and other Apple
apps, such as Dictionary). This can be changed to a pop-down if that's
what's desired.
TODO: Show keyboard shortcut for "Show Full History".
Patch by viettrungluu.
BUG=http://crbug.com/13203
TEST=Navigate around, check out and use the menus; do so in multiple tabs and
windows.
Review URL: http://codereview.chromium.org/160496
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@22740 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/app/nibs/Toolbar.xib | 73 | ||||
-rw-r--r-- | chrome/browser/cocoa/back_forward_menu_controller.h | 42 | ||||
-rw-r--r-- | chrome/browser/cocoa/back_forward_menu_controller.mm | 100 | ||||
-rw-r--r-- | chrome/browser/cocoa/browser_window_controller.mm | 1 | ||||
-rw-r--r-- | chrome/browser/cocoa/clickhold_button_cell.h | 45 | ||||
-rw-r--r-- | chrome/browser/cocoa/clickhold_button_cell.mm | 136 | ||||
-rw-r--r-- | chrome/browser/cocoa/delayedmenu_button.h | 30 | ||||
-rw-r--r-- | chrome/browser/cocoa/delayedmenu_button.mm | 129 | ||||
-rw-r--r-- | chrome/browser/cocoa/toolbar_button_cell.h | 4 | ||||
-rw-r--r-- | chrome/browser/cocoa/toolbar_controller.h | 15 | ||||
-rw-r--r-- | chrome/browser/cocoa/toolbar_controller.mm | 13 | ||||
-rw-r--r-- | chrome/browser/cocoa/toolbar_controller_unittest.mm | 1 | ||||
-rw-r--r-- | chrome/chrome.gyp | 6 |
13 files changed, 547 insertions, 48 deletions
diff --git a/chrome/app/nibs/Toolbar.xib b/chrome/app/nibs/Toolbar.xib index e77045d..ad59b58 100644 --- a/chrome/app/nibs/Toolbar.xib +++ b/chrome/app/nibs/Toolbar.xib @@ -8,9 +8,9 @@ <string key="IBDocument.HIToolboxVersion">353.00</string> <object class="NSMutableArray" key="IBDocument.EditedObjectIDs"> <bool key="EncodedWithXMLCoder">YES</bool> - <integer value="102"/> - <integer value="4"/> <integer value="88"/> + <integer value="4"/> + <integer value="102"/> </object> <object class="NSArray" key="IBDocument.PluginDependencies"> <bool key="EncodedWithXMLCoder">YES</bool> @@ -1560,6 +1560,7 @@ <string>14.IBPluginDependency</string> <string>15.CustomClassName</string> <string>15.IBPluginDependency</string> + <string>2.CustomClassName</string> <string>2.IBPluginDependency</string> <string>3.IBPluginDependency</string> <string>32.IBPluginDependency</string> @@ -1605,6 +1606,7 @@ <string>67.IBPluginDependency</string> <string>68.IBPluginDependency</string> <string>69.IBPluginDependency</string> + <string>7.CustomClassName</string> <string>7.IBPluginDependency</string> <string>70.IBPluginDependency</string> <string>8.IBPluginDependency</string> @@ -1624,10 +1626,10 @@ <string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string> - <string>{{287, 51}, {607, 39}}</string> + <string>{{287, 52}, {607, 39}}</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string> <boolean value="YES"/> - <string>ToolbarButtonCell</string> + <string>ClickHoldButtonCell</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string> @@ -1644,8 +1646,9 @@ <string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>ToolbarButtonCell</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string> - <string>ToolbarButtonCell</string> + <string>ClickHoldButtonCell</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>DelayedMenuButton</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string> @@ -1691,6 +1694,7 @@ <string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>DelayedMenuButton</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string> @@ -1792,11 +1796,23 @@ </object> </object> <object class="IBPartialClassDescription"> - <string key="className">ChromeUILocalizer</string> - <string key="superclassName">GTMUILocalizer</string> + <string key="className">ClickHoldButtonCell</string> + <string key="superclassName">GradientButtonCell</string> + <object class="NSMutableDictionary" key="outlets"> + <string key="NS.key.0">clickHoldTarget_</string> + <string key="NS.object.0">id</string> + </object> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBProjectSource</string> + <string key="minorKey">browser/cocoa/clickhold_button_cell.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">DelayedMenuButton</string> + <string key="superclassName">NSButton</string> <object class="IBClassDescriptionSource" key="sourceIdentifier"> <string key="majorKey">IBProjectSource</string> - <string key="minorKey">browser/cocoa/ui_localizer.h</string> + <string key="minorKey">browser/cocoa/delayedmenu_button.h</string> </object> </object> <object class="IBPartialClassDescription"> @@ -1812,29 +1828,6 @@ </object> </object> <object class="IBPartialClassDescription"> - <string key="className">GTMUILocalizer</string> - <string key="superclassName">NSObject</string> - <object class="NSMutableDictionary" key="outlets"> - <bool key="EncodedWithXMLCoder">YES</bool> - <object class="NSMutableArray" key="dict.sortedKeys"> - <bool key="EncodedWithXMLCoder">YES</bool> - <string>otherObjectToLocalize_</string> - <string>owner_</string> - <string>yetAnotherObjectToLocalize_</string> - </object> - <object class="NSMutableArray" key="dict.values"> - <bool key="EncodedWithXMLCoder">YES</bool> - <string>id</string> - <string>id</string> - <string>id</string> - </object> - </object> - <object class="IBClassDescriptionSource" key="sourceIdentifier"> - <string key="majorKey">IBProjectSource</string> - <string key="minorKey">../third_party/GTM/AppKit/GTMUILocalizer.h</string> - </object> - </object> - <object class="IBPartialClassDescription"> <string key="className">GradientButtonCell</string> <string key="superclassName">NSButtonCell</string> <object class="IBClassDescriptionSource" key="sourceIdentifier"> @@ -1950,7 +1943,7 @@ </object> <object class="IBPartialClassDescription"> <string key="className">ToolbarButtonCell</string> - <string key="superclassName">GradientButtonCell</string> + <string key="superclassName">ClickHoldButtonCell</string> <object class="IBClassDescriptionSource" key="sourceIdentifier"> <string key="majorKey">IBProjectSource</string> <string key="minorKey">browser/cocoa/toolbar_button_cell.h</string> @@ -1958,7 +1951,7 @@ </object> <object class="IBPartialClassDescription"> <string key="className">ToolbarButtonCell</string> - <string key="superclassName">GradientButtonCell</string> + <string key="superclassName">ClickHoldButtonCell</string> <object class="IBClassDescriptionSource" key="sourceIdentifier"> <string key="majorKey">IBUserSource</string> <string key="minorKey"/> @@ -1993,21 +1986,23 @@ <string>pageButton_</string> <string>pageMenu_</string> <string>reloadButton_</string> + <string>resizeDelegate_</string> <string>starButton_</string> <string>wrenchButton_</string> <string>wrenchMenu_</string> </object> <object class="NSMutableArray" key="dict.values"> <bool key="EncodedWithXMLCoder">YES</bool> - <string>NSButton</string> + <string>DelayedMenuButton</string> <string>id</string> - <string>NSButton</string> + <string>DelayedMenuButton</string> <string>NSButton</string> <string>NSButton</string> <string>AutocompleteTextField</string> <string>NSButton</string> <string>NSMenu</string> <string>NSButton</string> + <string>id</string> <string>NSButton</string> <string>NSButton</string> <string>NSMenu</string> @@ -2019,14 +2014,6 @@ </object> </object> <object class="IBPartialClassDescription"> - <string key="className">ToolbarLocalizer</string> - <string key="superclassName">ChromeUILocalizer</string> - <object class="IBClassDescriptionSource" key="sourceIdentifier"> - <string key="majorKey">IBProjectSource</string> - <string key="minorKey">../xcodebuild/chrome.build/DerivedSources/Debug/xib_localizers/toolbar_localizer.h</string> - </object> - </object> - <object class="IBPartialClassDescription"> <string key="className">ToolbarView</string> <string key="superclassName">BackgroundGradientView</string> <object class="IBClassDescriptionSource" key="sourceIdentifier"> diff --git a/chrome/browser/cocoa/back_forward_menu_controller.h b/chrome/browser/cocoa/back_forward_menu_controller.h new file mode 100644 index 0000000..1f0336a9 --- /dev/null +++ b/chrome/browser/cocoa/back_forward_menu_controller.h @@ -0,0 +1,42 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_COCOA_BACK_FORWARD_MENU_CONTROLLER_H_ +#define CHROME_BROWSER_COCOA_BACK_FORWARD_MENU_CONTROLLER_H_ + +#import <Cocoa/Cocoa.h> + +#include "base/scoped_nsobject.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/back_forward_menu_model.h" + +@class DelayedMenuButton; + +typedef BackForwardMenuModel::ModelType BackForwardMenuType; +const BackForwardMenuType BACK_FORWARD_MENU_TYPE_BACK = + BackForwardMenuModel::BACKWARD_MENU; +const BackForwardMenuType BACK_FORWARD_MENU_TYPE_FORWARD = + BackForwardMenuModel::FORWARD_MENU; + +// A class that manages the back/forward menu (and delayed-menu button, and +// model). + +@interface BackForwardMenuController : NSObject { + @private + BackForwardMenuType type_; + DelayedMenuButton* button_; // Weak; comes from nib. + scoped_ptr<BackForwardMenuModel> model_; + scoped_nsobject<NSMenu> menu_; +} + +// Type (back or forwards); can only be set on initialization. +@property(readonly, nonatomic) BackForwardMenuType type; + +- (id)initWithBrowser:(Browser*)browser + modelType:(BackForwardMenuType)type + button:(DelayedMenuButton*)button; + +@end // @interface BackForwardMenuController + +#endif // CHROME_BROWSER_COCOA_BACK_FORWARD_MENU_CONTROLLER_H_ diff --git a/chrome/browser/cocoa/back_forward_menu_controller.mm b/chrome/browser/cocoa/back_forward_menu_controller.mm new file mode 100644 index 0000000..a826df5 --- /dev/null +++ b/chrome/browser/cocoa/back_forward_menu_controller.mm @@ -0,0 +1,100 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "chrome/browser/cocoa/back_forward_menu_controller.h" + +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "base/sys_string_conversions.h" +#include "chrome/browser/back_forward_menu_model.h" +#import "chrome/browser/cocoa/delayedmenu_button.h" +#include "skia/ext/skia_utils_mac.h" + +using base::SysUTF16ToNSString; +using gfx::SkBitmapToNSImage; + +@implementation BackForwardMenuController + +// Accessors and mutators: + +@synthesize type = type_; + +// Own methods: + +- (id)initWithBrowser:(Browser*)browser + modelType:(BackForwardMenuType)type + button:(DelayedMenuButton*)button { + if ((self = [super init])) { + type_ = type; + button_ = button; + model_.reset(new BackForwardMenuModel(browser, type_)); + DCHECK(model_.get()); + menu_.reset([[NSMenu alloc] initWithTitle:@""]); + DCHECK(menu_.get()); + [menu_ setDelegate:self]; + + [button_ setMenu:menu_]; + [button_ setMenuEnabled:YES]; + } + return self; +} + +// Methods as delegate: + +// Called by menu_ just before tracking begins. +//TODO(viettrungluu@gmail.com): do anything for chapter stops (see model)? +- (void)menuNeedsUpdate:(NSMenu*)menu { + DCHECK(menu == menu_); + + // Remove old menu items (backwards order is as good as any). + for (NSInteger i = [menu_ numberOfItems]; i > 0; i--) + [menu_ removeItemAtIndex:(i-1)]; + + // 0-th item must be blank. (This is because we use a pulldown list, for which + // Cocoa uses the 0-th item as "title" in the button.) + [menu_ insertItemWithTitle:@"" + action:nil + keyEquivalent:@"" + atIndex:0]; + for (int menuID = 1; menuID <= model_->GetTotalItemCount(); menuID++) { + if (model_->IsSeparator(menuID)) { + [menu_ insertItem:[NSMenuItem separatorItem] + atIndex:menuID]; + } else { + // Create a menu item with the right label. + NSMenuItem* menuItem = [[NSMenuItem alloc] + initWithTitle:SysUTF16ToNSString(model_->GetItemLabel(menuID)) + action:nil + keyEquivalent:@""]; + [menuItem autorelease]; + + // Only enable it if it's supposed to do something. + [menuItem setEnabled:(model_->ItemHasCommand(menuID) ? YES : NO)]; + + // Icon (if it has one). + if (model_->ItemHasIcon(menuID)) + [menuItem setImage:SkBitmapToNSImage(model_->GetItemIcon(menuID))]; + + // This will make it call our |-executeMenuItem:| method. We store the + // |menuID| (or |menu_id|) in the tag. + [menuItem setTag:menuID]; + [menuItem setTarget:self]; + [menuItem setAction:@selector(executeMenuItem:)]; + + // Put it in the menu! + [menu_ insertItem:menuItem + atIndex:menuID]; + } + } +} + +// Action methods: + +- (void)executeMenuItem:(id)sender { + DCHECK([sender isKindOfClass:[NSMenuItem class]]); + int menuID = [sender tag]; + model_->ExecuteCommandById(menuID); +} + +@end // @implementation BackForwardMenuController diff --git a/chrome/browser/cocoa/browser_window_controller.mm b/chrome/browser/cocoa/browser_window_controller.mm index bb073ec..5155a72 100644 --- a/chrome/browser/cocoa/browser_window_controller.mm +++ b/chrome/browser/cocoa/browser_window_controller.mm @@ -174,6 +174,7 @@ willPositionSheet:(NSWindow*)sheet initWithModel:browser->toolbar_model() commands:browser->command_updater() profile:browser->profile() + browser:browser resizeDelegate:self bookmarkDelegate:self]); // If we are a pop-up, we have a titlebar and no toolbar. diff --git a/chrome/browser/cocoa/clickhold_button_cell.h b/chrome/browser/cocoa/clickhold_button_cell.h new file mode 100644 index 0000000..28dde69 --- /dev/null +++ b/chrome/browser/cocoa/clickhold_button_cell.h @@ -0,0 +1,45 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_COCOA_CLICKHOLD_BUTTON_CELL_H_ +#define CHROME_BROWSER_COCOA_CLICKHOLD_BUTTON_CELL_H_ + +#import <Cocoa/Cocoa.h> + +#include "base/scoped_nsobject.h" +#import "chrome/browser/cocoa/gradient_button_cell.h" + +// A button cell that implements "click hold" behavior after a specified +// delay. If -setClickHoldTimeout: is never called, this behaves like a normal +// button. + +@interface ClickHoldButtonCell : GradientButtonCell { + @private + BOOL enableClickHold_; + NSTimeInterval clickHoldTimeout_; + id clickHoldTarget_; // Weak. + SEL clickHoldAction_; + BOOL trackOnlyInRect_; + BOOL activateOnDrag_; +} + +// Enable click-hold? +@property(assign, nonatomic) BOOL enableClickHold; + +// Timeout is in seconds (at least 0.01, at most 3600). +@property(assign, nonatomic) NSTimeInterval clickHoldTimeout; + +// Track only in the frame rectangle? +@property(assign, nonatomic) BOOL trackOnlyInRect; + +// Activate (click-hold) immediately on drag? +@property(assign, nonatomic) BOOL activateOnDrag; + +// Defines what to do when click-held (as per usual action/target). +@property(assign, nonatomic) id clickHoldTarget; +@property(assign, nonatomic) SEL clickHoldAction; + +@end // @interface ClickHoldButtonCell + +#endif // CHROME_BROWSER_COCOA_CLICKHOLD_BUTTON_CELL_H_ diff --git a/chrome/browser/cocoa/clickhold_button_cell.mm b/chrome/browser/cocoa/clickhold_button_cell.mm new file mode 100644 index 0000000..c65971c --- /dev/null +++ b/chrome/browser/cocoa/clickhold_button_cell.mm @@ -0,0 +1,136 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "chrome/browser/cocoa/clickhold_button_cell.h" + +#include "base/logging.h" + +// Minimum and maximum click-hold timeout. +static const NSTimeInterval kMinTimeout = 0.01; +static const NSTimeInterval kMaxTimeout = 3600.0; + +@implementation ClickHoldButtonCell + +// Overrides: + ++ (BOOL)prefersTrackingUntilMouseUp { + return NO; +} + +- (BOOL)startTrackingAt:(NSPoint)startPoint + inView:(NSView*)controlView { + return enableClickHold_ ? + YES : + [super startTrackingAt:startPoint + inView:controlView]; +} + +- (BOOL)continueTracking:(NSPoint)lastPoint + at:(NSPoint)currentPoint + inView:(NSView*)controlView { + return enableClickHold_ ? + YES : + [super continueTracking:lastPoint + at:currentPoint + inView:controlView]; +} + +- (BOOL)trackMouse:(NSEvent*)originalEvent + inRect:(NSRect)cellFrame + ofView:(NSView*)controlView + untilMouseUp:(BOOL)untilMouseUp { + if (!enableClickHold_) { + return [super trackMouse:originalEvent + inRect:cellFrame + ofView:controlView + untilMouseUp:untilMouseUp]; + } + + // If doing click-hold, track the mouse ourselves. + NSPoint currPoint = [controlView convertPoint:[originalEvent locationInWindow] + fromView:nil]; + NSPoint lastPoint = currPoint; + NSTimeInterval timeout = + MAX(MIN(clickHoldTimeout_, kMaxTimeout), kMinTimeout); + NSDate* clickHoldBailTime = [NSDate dateWithTimeIntervalSinceNow:timeout]; + + if (![self startTrackingAt:currPoint inView:controlView]) + return NO; + + enum { + kContinueTrack, kStopClickHold, kStopMouseUp, kStopLeftRect, kStopNoContinue + } state = kContinueTrack; + do { + NSEvent* event = [NSApp nextEventMatchingMask:(NSLeftMouseDraggedMask | + NSLeftMouseUpMask) + untilDate:clickHoldBailTime + inMode:NSEventTrackingRunLoopMode + dequeue:YES]; + currPoint = [controlView convertPoint:[event locationInWindow] + fromView:nil]; + + // Time-out or drag. + if (!event || (activateOnDrag_ && ([event type] == NSLeftMouseDragged))) { + state = kStopClickHold; + + // Mouse up. + } else if ([event type] == NSLeftMouseUp) { + state = kStopMouseUp; + + // Stop tracking if mouse left frame rectangle (if requested to do so). + } else if (trackOnlyInRect_ && ![controlView mouse:currPoint + inRect:cellFrame]) { + state = kStopLeftRect; + + // Stop tracking if instructed to. + } else if (![self continueTracking:lastPoint + at:currPoint + inView:controlView]) { + state = kStopNoContinue; + } + + lastPoint = currPoint; + } while (state == kContinueTrack); + + [self stopTracking:lastPoint + at:lastPoint + inView:controlView + mouseIsUp:NO]; + + switch (state) { + case kStopClickHold: + if (clickHoldAction_) { + [static_cast<NSControl*>(controlView) sendAction:clickHoldAction_ + to:clickHoldTarget_]; + } + return YES; + + case kStopMouseUp: + if ([self action]) { + [static_cast<NSControl*>(controlView) sendAction:[self action] + to:[self target]]; + } + return YES; + + case kStopLeftRect: + case kStopNoContinue: + return NO; + + default: + NOTREACHED() << "Unknown terminating state!"; + } + + return NO; +} + +// Accessors and mutators: + +@synthesize enableClickHold = enableClickHold_; +@synthesize clickHoldTimeout = clickHoldTimeout_; +@synthesize trackOnlyInRect = trackOnlyInRect_; +@synthesize activateOnDrag = activateOnDrag_; +@synthesize clickHoldTarget = clickHoldTarget_; +@synthesize clickHoldAction = clickHoldAction_; + +@end // @implementation ClickHoldButtonCell diff --git a/chrome/browser/cocoa/delayedmenu_button.h b/chrome/browser/cocoa/delayedmenu_button.h new file mode 100644 index 0000000..28c1977 --- /dev/null +++ b/chrome/browser/cocoa/delayedmenu_button.h @@ -0,0 +1,30 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_COCOA_DELAYEDMENU_BUTTON_H_ +#define CHROME_BROWSER_COCOA_DELAYEDMENU_BUTTON_H_ + +#import <Cocoa/Cocoa.h> + +#include "base/scoped_nsobject.h" + +@interface DelayedMenuButton : NSButton { + NSMenu* menu_; // Strong (retained). + BOOL menuEnabled_; +} + +// The menu to display. Note that it should have no (i.e., a blank) title and +// that the 0-th entry should be blank (and won't be displayed). (This is +// because we use a pulldown list, for which Cocoa uses the 0-th item as "title" +// in the button. This might change if we ever switch to a pop-up. Our direct +// use of the given NSMenu object means that the one can set and use NSMenu's +// delegate as usual.) +@property(retain, nonatomic) NSMenu* menu; + +// Is the menu enabled? (If not, don't act like a click-hold button.) +@property(assign, nonatomic) BOOL menuEnabled; + +@end // @interface DelayedMenuButton + +#endif // CHROME_BROWSER_COCOA_DELAYEDMENU_BUTTON_H_ diff --git a/chrome/browser/cocoa/delayedmenu_button.mm b/chrome/browser/cocoa/delayedmenu_button.mm new file mode 100644 index 0000000..9eabf67 --- /dev/null +++ b/chrome/browser/cocoa/delayedmenu_button.mm @@ -0,0 +1,129 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "chrome/browser/cocoa/delayedmenu_button.h" + +#include "base/logging.h" +#include "base/scoped_nsobject.h" +#import "chrome/browser/cocoa/clickhold_button_cell.h" + +@interface DelayedMenuButton (Private) + +- (void)resetToDefaults; +- (void)menuAction:(id)sender; + +@end // @interface DelayedMenuButton (Private) + +@implementation DelayedMenuButton + +// Overrides: + ++ (Class)cellClass { + return [ClickHoldButtonCell class]; +} + +- (id)init { + if ((self = [super init])) + [self resetToDefaults]; + return self; +} + +- (id)initWithCoder:(NSCoder*)decoder { + if ((self = [super initWithCoder:decoder])) + [self resetToDefaults]; + return self; +} + +- (id)initWithFrame:(NSRect)frameRect { + if ((self = [super initWithFrame:frameRect])) + [self resetToDefaults]; + return self; +} + +- (void)dealloc { + [menu_ release]; + [super dealloc]; +} + +- (void)awakeFromNib { + [self resetToDefaults]; +} + +// Accessors and mutators: + +@synthesize menu = menu_; + +// Don't synthesize for menuEnabled_; its mutator must do other things. +- (void)setMenuEnabled:(BOOL)enabled { + menuEnabled_ = enabled; + [[self cell] setEnableClickHold:menuEnabled_]; +} + +- (BOOL)menuEnabled { + return menuEnabled_; +} + +@end // @implementation DelayedMenuButton + +@implementation DelayedMenuButton (Private) + +- (void)resetToDefaults { + id cell = [self cell]; + DCHECK([cell isKindOfClass:[ClickHoldButtonCell class]]); + [self setEnabled:NO]; // Make the controller put in a menu and + // enable it explicitly. This also takes + // care of |[cell setEnableClickHold:]|. + [cell setClickHoldTimeout:0.25]; // Random guess at Cocoa-ish value. + [cell setTrackOnlyInRect:NO]; + [cell setActivateOnDrag:YES]; + [cell setClickHoldAction:@selector(menuAction:)]; + [cell setClickHoldTarget:self]; +} + +- (void)menuAction:(id)sender { + // We shouldn't get here unless the menu is enabled. + DCHECK(menuEnabled_); + + // If we don't have a menu (in which case the person using this control is + // being bad), just wait for a mouse up. + if (!menu_) { + LOG(WARNING) << "No menu available."; + [NSApp nextEventMatchingMask:NSLeftMouseUpMask + untilDate:[NSDate distantFuture] + inMode:NSEventTrackingRunLoopMode + dequeue:YES]; + return; + } + + // FIXME(viettrungluu@gmail.com): Don't ask me. I don't know what's going on. + // But it yields unquestionably right results (even when the menu has to flip + // upwards because you've stupidly dragged the top of the window to the bottom + // of the screen) -- this demonstrates that the y-coordinate (in our + // superview's coordinates) is right. The x-coordinate (in our coordinates) is + // right since the menu appears horizontally in the right place (more or + // less). The |- 2.0| factor is an inexplicable fudge to make it approximately + // line up. If someone figures out what's going on, please fix this. + NSRect frame = [self frame]; + frame.origin.x = [self convertPoint:frame.origin + fromView:[self superview]].x - 2.0; + + // Make our pop-up button cell and set things up. This is, as of 10.5, the + // official Apple-recommended hack. Later, perhaps |-[NSMenu + // popUpMenuPositioningItem:atLocation:inView:]| may be a better option. + // However, using a pulldown has the benefit that Cocoa automatically places + // the menu correctly even when we're at the edge of the screen (including + // "dragging upwards" when the button is close to the bottom of the screen). + scoped_nsobject<NSPopUpButtonCell> popUpCell( + [[NSPopUpButtonCell alloc] initTextCell:@"" + pullsDown:YES]); + DCHECK(popUpCell.get()); + [popUpCell setMenu:menu_]; + [popUpCell selectItem:nil]; + [popUpCell attachPopUpWithFrame:frame + inView:self]; + [popUpCell performClickWithFrame:frame + inView:self]; +} + +@end // @implementation DelayedMenuButton (Private) diff --git a/chrome/browser/cocoa/toolbar_button_cell.h b/chrome/browser/cocoa/toolbar_button_cell.h index da12036..c382ff7 100644 --- a/chrome/browser/cocoa/toolbar_button_cell.h +++ b/chrome/browser/cocoa/toolbar_button_cell.h @@ -6,7 +6,7 @@ #define CHROME_BROWSER_COCOA_TOOLBAR_BUTTON_CELL_H_ #import <Cocoa/Cocoa.h> -#import "chrome/browser/cocoa/gradient_button_cell.h" +#import "chrome/browser/cocoa/clickhold_button_cell.h" // A button cell for the toolbar. @@ -20,7 +20,7 @@ // TODO(jrg): If no differences come up, remove this file and use // the base class explicitly for both the toolbar and bookmark bar. -@interface ToolbarButtonCell : GradientButtonCell { +@interface ToolbarButtonCell : ClickHoldButtonCell { } @end diff --git a/chrome/browser/cocoa/toolbar_controller.h b/chrome/browser/cocoa/toolbar_controller.h index 0e33048..c5135d3 100644 --- a/chrome/browser/cocoa/toolbar_controller.h +++ b/chrome/browser/cocoa/toolbar_controller.h @@ -11,11 +11,15 @@ #include "base/scoped_nsobject.h" #import "chrome/browser/cocoa/command_observer_bridge.h" #import "chrome/browser/cocoa/bookmark_bar_controller.h" +#import "chrome/browser/cocoa/delayedmenu_button.h" #import "chrome/browser/cocoa/view_resizer.h" #include "chrome/common/pref_member.h" @class AutocompleteTextField; @class AutocompleteTextFieldEditor; +@class DelayedMenuButton; +@class BackForwardMenuController; +class Browser; class CommandUpdater; class LocationBar; class LocationBarViewMac; @@ -38,12 +42,15 @@ class ToolbarView; ToolbarModel* toolbarModel_; // weak, one per window CommandUpdater* commands_; // weak, one per window Profile* profile_; // weak, one per window + Browser* browser_; // weak, one per window scoped_ptr<CommandObserverBridge> commandObserver_; scoped_ptr<LocationBarViewMac> locationBarView_; scoped_nsobject<AutocompleteTextFieldEditor> autocompleteTextFieldEditor_; scoped_nsobject<BookmarkBarController> bookmarkBarController_; id<ViewResizer> resizeDelegate_; // weak id<BookmarkURLOpener> bookmarkBarDelegate_; // weak + scoped_nsobject<BackForwardMenuController> backMenuController_; + scoped_nsobject<BackForwardMenuController> forwardMenuController_; // Used for monitoring the optional toolbar button prefs. scoped_ptr<ToolbarControllerInternal::PrefObserverBridge> prefObserver_; @@ -61,8 +68,8 @@ class ToolbarView; // The ordering is important for unit tests. If new items are added or the // ordering is changed, make sure to update |-toolbarViews| and the // corresponding enum in the unit tests. - IBOutlet NSButton* backButton_; - IBOutlet NSButton* forwardButton_; + IBOutlet DelayedMenuButton* backButton_; + IBOutlet DelayedMenuButton* forwardButton_; IBOutlet NSButton* reloadButton_; IBOutlet NSButton* homeButton_; IBOutlet NSButton* starButton_; @@ -73,10 +80,12 @@ class ToolbarView; } // Initialize the toolbar and register for command updates. The profile is -// needed for initializing the location bar. +// needed for initializing the location bar. The browser is needed for +// initializing the back/forward menus. - (id)initWithModel:(ToolbarModel*)model commands:(CommandUpdater*)commands profile:(Profile*)profile + browser:(Browser*)browser resizeDelegate:(id<ViewResizer>)resizeDelegate bookmarkDelegate:(id<BookmarkURLOpener>)delegate; diff --git a/chrome/browser/cocoa/toolbar_controller.mm b/chrome/browser/cocoa/toolbar_controller.mm index c999764..acddc8b 100644 --- a/chrome/browser/cocoa/toolbar_controller.mm +++ b/chrome/browser/cocoa/toolbar_controller.mm @@ -9,6 +9,7 @@ #include "chrome/app/chrome_dll_resource.h" #import "chrome/browser/cocoa/autocomplete_text_field.h" #import "chrome/browser/cocoa/autocomplete_text_field_editor.h" +#import "chrome/browser/cocoa/back_forward_menu_controller.h" #import "chrome/browser/cocoa/gradient_button_cell.h" #import "chrome/browser/cocoa/location_bar_view_mac.h" #include "chrome/browser/cocoa/nsimage_cache.h" @@ -60,6 +61,7 @@ class PrefObserverBridge : public NotificationObserver { - (id)initWithModel:(ToolbarModel*)model commands:(CommandUpdater*)commands profile:(Profile*)profile + browser:(Browser*)browser resizeDelegate:(id<ViewResizer>)resizeDelegate bookmarkDelegate:(id<BookmarkURLOpener>)delegate { DCHECK(model && commands && profile); @@ -68,6 +70,7 @@ class PrefObserverBridge : public NotificationObserver { toolbarModel_ = model; commands_ = commands; profile_ = profile; + browser_ = browser; resizeDelegate_ = resizeDelegate; bookmarkBarDelegate_ = delegate; hasToolbar_ = YES; @@ -123,6 +126,16 @@ class PrefObserverBridge : public NotificationObserver { // position and resize properties don't need to be set. [[self view] addSubview:[bookmarkBarController_ view]]; + // Create the controllers for the back/forward menus. + backMenuController_.reset([[BackForwardMenuController alloc] + initWithBrowser:browser_ + modelType:BACK_FORWARD_MENU_TYPE_BACK + button:backButton_]); + forwardMenuController_.reset([[BackForwardMenuController alloc] + initWithBrowser:browser_ + modelType:BACK_FORWARD_MENU_TYPE_FORWARD + button:forwardButton_]); + // 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 diff --git a/chrome/browser/cocoa/toolbar_controller_unittest.mm b/chrome/browser/cocoa/toolbar_controller_unittest.mm index a82212b..aa053bc 100644 --- a/chrome/browser/cocoa/toolbar_controller_unittest.mm +++ b/chrome/browser/cocoa/toolbar_controller_unittest.mm @@ -38,6 +38,7 @@ class ToolbarControllerTest : public testing::Test { [[ToolbarController alloc] initWithModel:browser->toolbar_model() commands:browser->command_updater() profile:helper_.profile() + browser:browser resizeDelegate:resizeDelegate_.get() bookmarkDelegate:nil]); EXPECT_TRUE([bar_ view]); diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index aa1d275..80d4ea6 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -752,6 +752,8 @@ 'browser/cocoa/autocomplete_text_field_editor.mm', 'browser/cocoa/autoseparating_menu.h', 'browser/cocoa/autoseparating_menu.m', + 'browser/cocoa/back_forward_menu_controller.h', + 'browser/cocoa/back_forward_menu_controller.mm', 'browser/cocoa/background_gradient_view.h', 'browser/cocoa/background_gradient_view.mm', 'browser/cocoa/base_view.h', @@ -782,11 +784,15 @@ 'browser/cocoa/browser_window_controller.mm', 'browser/cocoa/clear_browsing_data_controller.h', 'browser/cocoa/clear_browsing_data_controller.mm', + 'browser/cocoa/clickhold_button_cell.h', + 'browser/cocoa/clickhold_button_cell.mm', 'browser/cocoa/cocoa_test_helper.h', 'browser/cocoa/command_observer_bridge.h', 'browser/cocoa/command_observer_bridge.mm', 'browser/cocoa/custom_home_pages_model.h', 'browser/cocoa/custom_home_pages_model.mm', + 'browser/cocoa/delayedmenu_button.h', + 'browser/cocoa/delayedmenu_button.mm', 'browser/cocoa/download_item_cell.h', 'browser/cocoa/download_item_cell.mm', 'browser/cocoa/download_item_controller.h', |