summaryrefslogtreecommitdiffstats
path: root/chrome/browser
diff options
context:
space:
mode:
authorjrg@chromium.org <jrg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-03-24 22:06:06 +0000
committerjrg@chromium.org <jrg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-03-24 22:06:06 +0000
commitc422c9199cfaff70b58abdb12d8d0e5b6bcd8101 (patch)
tree2be9a86945b18e9a00f3b225dbc352e0b8a5aad6 /chrome/browser
parent594f4ff352685bb83b71d1402264bc2cf2116e67 (diff)
downloadchromium_src-c422c9199cfaff70b58abdb12d8d0e5b6bcd8101.zip
chromium_src-c422c9199cfaff70b58abdb12d8d0e5b6bcd8101.tar.gz
chromium_src-c422c9199cfaff70b58abdb12d8d0e5b6bcd8101.tar.bz2
Cmd-click in bookmark subfolders will open all.
Refactor code for better sharing. BUG=http://crbug.com/26380 (cmd-click in subfolders now opens all) BUG=http://crbug.com/35966 (code dup reduced) TEST=\ Cmd-click a folder in the bar --> open all Cmd-click a folder in a SUBfolder of the bar --> open all Review URL: http://codereview.chromium.org/1134008 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@42546 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
-rw-r--r--chrome/browser/cocoa/bookmark_bar_controller.h5
-rw-r--r--chrome/browser/cocoa/bookmark_bar_controller.mm69
-rw-r--r--chrome/browser/cocoa/bookmark_bar_controller_unittest.mm12
-rw-r--r--chrome/browser/cocoa/bookmark_bar_folder_controller.h5
-rw-r--r--chrome/browser/cocoa/bookmark_bar_folder_controller.mm56
-rw-r--r--chrome/browser/cocoa/bookmark_bar_folder_controller_unittest.mm33
-rw-r--r--chrome/browser/cocoa/bookmark_bar_folder_view_unittest.mm11
-rw-r--r--chrome/browser/cocoa/bookmark_button.h13
-rw-r--r--chrome/browser/cocoa/bookmark_folder_target.h31
-rw-r--r--chrome/browser/cocoa/bookmark_folder_target.mm68
-rw-r--r--chrome/browser/cocoa/bookmark_folder_target_unittest.mm92
11 files changed, 331 insertions, 64 deletions
diff --git a/chrome/browser/cocoa/bookmark_bar_controller.h b/chrome/browser/cocoa/bookmark_bar_controller.h
index 5dff09a..dfc51d4 100644
--- a/chrome/browser/cocoa/bookmark_bar_controller.h
+++ b/chrome/browser/cocoa/bookmark_bar_controller.h
@@ -22,6 +22,7 @@
@class BookmarkBarFolderController;
@class BookmarkBarView;
@class BookmarkButton;
+@class BookmarkFolderTarget;
class BookmarkModel;
@class BookmarkMenu;
class BookmarkNode;
@@ -166,6 +167,9 @@ willAnimateFromState:(bookmarks::VisualState)oldState
// Delegate that can resize us.
id<ViewResizer> resizeDelegate_; // weak
+ // Logic for dealing with a click on a bookmark folder button.
+ scoped_nsobject<BookmarkFolderTarget> folderTarget_;
+
// A controller for a pop-up bookmark folder window (custom menu).
// This is not a scoped_nsobject because it owns itself (when its
// window closes the controller gets autoreleased).
@@ -320,6 +324,7 @@ willAnimateFromState:(bookmarks::VisualState)oldState
- (BookmarkBarFolderController*)folderController;
- (BookmarkButton*)buttonForDroppingOnAtPoint:(NSPoint)point;
- (BOOL)isEventAClickOutside:(NSEvent*)event;
+- (id)folderTarget;
@end
// The (internal) |NSPasteboard| type string for bookmark button drags, used for
diff --git a/chrome/browser/cocoa/bookmark_bar_controller.mm b/chrome/browser/cocoa/bookmark_bar_controller.mm
index 60b1b26..163dfd4 100644
--- a/chrome/browser/cocoa/bookmark_bar_controller.mm
+++ b/chrome/browser/cocoa/bookmark_bar_controller.mm
@@ -22,6 +22,7 @@
#import "chrome/browser/cocoa/bookmark_button.h"
#import "chrome/browser/cocoa/bookmark_button_cell.h"
#import "chrome/browser/cocoa/bookmark_editor_controller.h"
+#import "chrome/browser/cocoa/bookmark_folder_target.h"
#import "chrome/browser/cocoa/bookmark_menu.h"
#import "chrome/browser/cocoa/bookmark_menu_cocoa_controller.h"
#import "chrome/browser/cocoa/bookmark_name_folder_controller.h"
@@ -208,6 +209,7 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
buttons_.reset([[NSMutableArray alloc] init]);
delegate_ = delegate;
resizeDelegate_ = resizeDelegate;
+ folderTarget_.reset([[BookmarkFolderTarget alloc] initWithController:self]);
ResourceBundle& rb = ResourceBundle::GetSharedInstance();
folderImage_.reset([rb.GetNSImageNamed(IDR_BOOKMARK_BAR_FOLDER) retain]);
@@ -455,7 +457,7 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
return;
// Else open a new one if it makes sense to do so.
if ([sender bookmarkNode]->is_folder())
- [self openBookmarkFolderFromButton:sender];
+ [folderTarget_ openBookmarkFolderFromButton:sender];
}
// BookmarkButtonDelegate protocol implementation.
@@ -526,6 +528,12 @@ const NSTimeInterval kBookmarkBarAnimationDuration = 0.12;
return NO;
}
+// Exposed for testing.
+- (id)folderTarget {
+ return folderTarget_.get();
+}
+
+
// Keep the "no items" label centered in response to a frame size change.
- (void)centerNoItemsLabel {
// Note that this computation is done in the parent's coordinate system,
@@ -851,6 +859,8 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
hoverButton_.reset();
}
hoverButton_.reset([button retain]);
+ DCHECK([[hoverButton_ target]
+ respondsToSelector:@selector(openBookmarkFolderFromButton:)]);
[[hoverButton_ target]
performSelector:@selector(openBookmarkFolderFromButton:)
withObject:hoverButton_
@@ -1069,6 +1079,11 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
}
}
+// Redirect to our logic shared with BookmarkBarFolderController.
+- (IBAction)openBookmarkFolderFromButton:(id)sender {
+ [folderTarget_ openBookmarkFolderFromButton:sender];
+}
+
// Recursively add the given bookmark node and all its children to
// menu, one menu item per node.
- (void)addNode:(const BookmarkNode*)child toMenu:(NSMenu*)menu {
@@ -1132,54 +1147,20 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
return menu;
}
-// This IBAction is called when the user clicks (mouseUp, really) on a
-// "folder" bookmark button. (In this context, "Click" does not
-// include right-click to open a context menu which follows a
-// different path). Scenarios when folder X is clicked:
-// *Predicate* *Action*
-// (nothing) Open Folder X
-// Folder X open Close folder X
-// Folder Y open Close Y, open X
-// Cmd-click Open All with proper disposition
-//
-// Note complication in which a click-drag engages drag and drop, not
-// a click-to-open. Thus the path to get here is a little twisted.
-- (IBAction)openBookmarkFolderFromButton:(id)sender {
- DCHECK(sender);
- BOOL same = false;
-
- if (folderController_) {
- // closeAllBookmarkFolders sets folderController_ to nil
- // so we need the SAME check to happen first.
- same = ([folderController_ parentButton] == sender);
+// Add a new folder controller as triggered by the given folder button.
+- (void)addNewFolderControllerWithParentButton:(BookmarkButton*)parentButton {
+ DCHECK(!folderController_);
+ if (folderController_)
[self closeAllBookmarkFolders];
- }
-
- // Watch out for a modifier click. For example, command-click
- // should open all.
- //
- // NOTE: we cannot use [[sender cell] mouseDownFlags] because we
- // thwart the normal mouse click mechanism to make buttons
- // draggable. Thus we must use [NSApp currentEvent].
- DCHECK([sender bookmarkNode]->is_folder());
- WindowOpenDisposition disposition =
- event_utils::WindowOpenDispositionFromNSEvent([NSApp currentEvent]);
- if (disposition == NEW_BACKGROUND_TAB) {
- [self openBookmarkNodesRecursive:[sender bookmarkNode]
- disposition:disposition];
- return;
- }
-
- // If click on same folder, close it and be done.
- // Else we clicked on a different folder so more work to do.
- if (same)
- return;
// Folder controller, like many window controllers, owns itself.
folderController_ = [[BookmarkBarFolderController alloc]
- initWithParentButton:sender
- parentController:self];
+ initWithParentButton:parentButton
+ parentController:self];
[folderController_ showWindow:self];
+
+ // Only BookmarkBarController has this; the
+ // BookmarkBarFolderController does not.
[self watchForClickOutside:YES];
}
diff --git a/chrome/browser/cocoa/bookmark_bar_controller_unittest.mm b/chrome/browser/cocoa/bookmark_bar_controller_unittest.mm
index 96f4365..ae1c35e 100644
--- a/chrome/browser/cocoa/bookmark_bar_controller_unittest.mm
+++ b/chrome/browser/cocoa/bookmark_bar_controller_unittest.mm
@@ -24,6 +24,18 @@
#include "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
+// Add a redirect to make testing easier.
+@interface BookmarkBarController(MakeTestingEasier)
+- (IBAction)openBookmarkFolderFromButton:(id)sender;
+@end
+
+@implementation BookmarkBarController(MakeTestingEasier)
+- (IBAction)openBookmarkFolderFromButton:(id)sender {
+ [[self folderTarget] openBookmarkFolderFromButton:sender];
+}
+@end
+
+
// Just like a BookmarkBarController but openURL: is stubbed out.
@interface BookmarkBarControllerNoOpen : BookmarkBarController {
@public
diff --git a/chrome/browser/cocoa/bookmark_bar_folder_controller.h b/chrome/browser/cocoa/bookmark_bar_folder_controller.h
index 86d53f7..201a5ea 100644
--- a/chrome/browser/cocoa/bookmark_bar_folder_controller.h
+++ b/chrome/browser/cocoa/bookmark_bar_folder_controller.h
@@ -8,6 +8,7 @@
#import "chrome/browser/cocoa/bookmark_button.h"
@class BookmarkBarFolderView;
+@class BookmarkFolderTarget;
// A controller for the pop-up windows from bookmark folder buttons
// which look sort of like menus.
@@ -62,6 +63,9 @@
// not necessarily fired yet).
scoped_nsobject<BookmarkButton> hoverButton_;
+ // Logic for dealing with a click on a bookmark folder button.
+ scoped_nsobject<BookmarkFolderTarget> folderTarget_;
+
// A controller for a pop-up bookmark folder window (custom menu).
// We (self) are the parentController_ for our folderController_.
// This is not a scoped_nsobject because it owns itself (when its
@@ -98,5 +102,6 @@
- (NSPoint)windowTopLeft;
- (NSArray*)buttons;
- (BookmarkBarFolderController*)folderController;
+- (id)folderTarget;
@end
diff --git a/chrome/browser/cocoa/bookmark_bar_folder_controller.mm b/chrome/browser/cocoa/bookmark_bar_folder_controller.mm
index ffa190b4..0257613 100644
--- a/chrome/browser/cocoa/bookmark_bar_folder_controller.mm
+++ b/chrome/browser/cocoa/bookmark_bar_folder_controller.mm
@@ -11,7 +11,10 @@
#import "chrome/browser/cocoa/bookmark_bar_controller.h" // namespace bookmarks
#import "chrome/browser/cocoa/bookmark_bar_folder_view.h"
#import "chrome/browser/cocoa/bookmark_button_cell.h"
+#import "chrome/browser/cocoa/bookmark_folder_target.h"
#import "chrome/browser/cocoa/browser_window_controller.h"
+#import "chrome/browser/cocoa/event_utils.h"
+
namespace {
@@ -31,7 +34,6 @@ const CGFloat kBookmarkBarFolderScrollAmount =
- (void)removeScrollTracking;
- (void)endScroll;
- (void)addScrollTimerWithDelta:(CGFloat)delta;
-- (IBAction)openBookmarkFolderFromButton:(id)sender;
@end
@@ -46,6 +48,7 @@ const CGFloat kBookmarkBarFolderScrollAmount =
parentButton_.reset([button retain]);
parentController_.reset([controller retain]);
buttons_.reset([[NSMutableArray alloc] init]);
+ folderTarget_.reset([[BookmarkFolderTarget alloc] initWithController:self]);
// Register for theme changes.
NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
@@ -104,6 +107,11 @@ const CGFloat kBookmarkBarFolderScrollAmount =
return [parentController_ cellForBookmarkNode:child];
}
+// Redirect to our logic shared with BookmarkBarController.
+- (IBAction)openBookmarkFolderFromButton:(id)sender {
+ [folderTarget_ openBookmarkFolderFromButton:sender];
+}
+
// Create a bookmark button for the given node using frame.
//
// If |node| is NULL this is an "(empty)" button.
@@ -159,11 +167,14 @@ const CGFloat kBookmarkBarFolderScrollAmount =
return mainView_;
}
-// Exposed for testing.
- (BookmarkBarFolderController*)folderController {
return folderController_;
}
+- (id)folderTarget {
+ return folderTarget_.get();
+}
+
// Compute and return the top left point of our window (screen
// coordinates). The top left is positioned in a manner similar to
// cascading menus.
@@ -532,7 +543,6 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)info {
draggingExited_ = NO;
NSPoint currentLocation = [info draggingLocation];
-
BookmarkButton* button = [self buttonForDroppingOnAtPoint:currentLocation];
if ([button isFolder]) {
if (hoverButton_ == button) {
@@ -540,6 +550,9 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
}
if (hoverButton_) {
// Oops, another one triggered or open.
+ DCHECK(
+ [[hoverButton_ target]
+ respondsToSelector:@selector(closeBookmarkFolderOnHoverButton:)]);
[NSObject cancelPreviousPerformRequestsWithTarget:[hoverButton_
target]];
[self performSelector:@selector(closeBookmarkFolderOnHoverButton:)
@@ -547,6 +560,8 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
afterDelay:bookmarks::kDragHoverCloseDelay];
}
hoverButton_.reset([button retain]);
+ DCHECK([[hoverButton_ target]
+ respondsToSelector:@selector(openBookmarkFolderFromButton:)]);
[[hoverButton_ target]
performSelector:@selector(openBookmarkFolderFromButton:)
withObject:hoverButton_
@@ -556,7 +571,11 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
// If we get here we may no longer have a hover button.
if (!button) {
if (hoverButton_) {
- [NSObject cancelPreviousPerformRequestsWithTarget:[hoverButton_ target]];
+ [NSObject cancelPreviousPerformRequestsWithTarget:[hoverButton_
+ target]];
+ DCHECK(
+ [[hoverButton_ target]
+ respondsToSelector:@selector(closeBookmarkFolderOnHoverButton:)]);
[self performSelector:@selector(closeBookmarkFolderOnHoverButton:)
withObject:hoverButton_
afterDelay:bookmarks::kDragHoverCloseDelay];
@@ -738,7 +757,7 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
// Open a new one if meaningful.
if ([sender isFolder])
- [self openBookmarkFolderFromButton:sender];
+ [folderTarget_ openBookmarkFolderFromButton:sender];
}
// Called from BookmarkButton.
@@ -772,22 +791,27 @@ static BOOL ValueInRangeInclusive(CGFloat low, CGFloat value, CGFloat high) {
}
- (IBAction)openBookmark:(id)sender {
- // Carent controller closes it all...
+ // Parent controller closes it all...
[parentController_ openBookmark:sender];
}
-// Unlike the bookmark_bar_controller, we do not watch for click outside.
-- (IBAction)openBookmarkFolderFromButton:(id)sender {
- if (folderController_) {
- // If the same we have nothing to do.
- if ([folderController_ parentButton] == sender)
- return;
+// Flow up the chain until someone (the top level controller) knows
+// what to do.
+- (void)openBookmarkNodesRecursive:(const BookmarkNode*)node
+ disposition:(WindowOpenDisposition)disposition {
+ [parentController_ openBookmarkNodesRecursive:node
+ disposition:disposition];
+}
- [self closeBookmarkFolder:sender];
- }
+// Add a new folder controller as triggered by the given folder button.
+- (void)addNewFolderControllerWithParentButton:(BookmarkButton*)parentButton {
+ if (folderController_)
+ [self closeAllBookmarkFolders];
+
+ // Folder controller, like many window controllers, owns itself.
folderController_ = [[BookmarkBarFolderController alloc]
- initWithParentButton:sender
- parentController:self];
+ initWithParentButton:parentButton
+ parentController:self];
[folderController_ showWindow:self];
}
diff --git a/chrome/browser/cocoa/bookmark_bar_folder_controller_unittest.mm b/chrome/browser/cocoa/bookmark_bar_folder_controller_unittest.mm
index 7d00a23..844e090 100644
--- a/chrome/browser/cocoa/bookmark_bar_folder_controller_unittest.mm
+++ b/chrome/browser/cocoa/bookmark_bar_folder_controller_unittest.mm
@@ -13,6 +13,18 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
+// Add a redirect to make testing easier.
+@interface BookmarkBarFolderController(MakeTestingEasier)
+- (IBAction)openBookmarkFolderFromButton:(id)sender;
+@end
+
+@implementation BookmarkBarFolderController(MakeTestingEasier)
+- (IBAction)openBookmarkFolderFromButton:(id)sender {
+ [[self folderTarget] openBookmarkFolderFromButton:sender];
+}
+@end
+
+
@interface BookmarkBarFolderControllerPong : BookmarkBarFolderController {
BOOL childFolderWillShow_;
BOOL childFolderWillClose_;
@@ -32,6 +44,13 @@
- (void)childFolderWillClose:(id<BookmarkButtonControllerProtocol>)child {
childFolderWillClose_ = YES;
}
+
+// We don't have a real BookmarkBarController as our parent root so
+// we fake this one out.
+- (void)closeAllBookmarkFolders {
+ [self closeBookmarkFolder:self];
+}
+
@end
class BookmarkBarFolderControllerTest : public CocoaTest {
@@ -50,7 +69,10 @@ class BookmarkBarFolderControllerTest : public CocoaTest {
L"sibbling group");
const BookmarkNode* folderB = model->AddGroup(folderA,
folderA->GetChildCount(),
- L"subgroup");
+ L"subgroup 1");
+ model->AddGroup(folderA,
+ folderA->GetChildCount(),
+ L"subgroup 2");
model->AddURL(folderA, folderA->GetChildCount(), L"title a",
GURL("http://www.google.com/a"));
longTitleNode_ = model->AddURL(
@@ -209,17 +231,20 @@ TEST_F(BookmarkBarFolderControllerTest, OpenFolder) {
EXPECT_TRUE(bbfc.get());
EXPECT_FALSE([bbfc folderController]);
- [bbfc openBookmarkFolderFromButton:[[bbfc buttons] objectAtIndex:0]];
+ BookmarkButton* button = [[bbfc buttons] objectAtIndex:0];
+ [bbfc openBookmarkFolderFromButton:button];
id controller = [bbfc folderController];
EXPECT_TRUE(controller);
+ EXPECT_EQ([controller parentButton], button);
- // Open the same one --> no change.
+ // Click the same one --> it gets closed.
[bbfc openBookmarkFolderFromButton:[[bbfc buttons] objectAtIndex:0]];
- EXPECT_EQ(controller, [bbfc folderController]);
+ EXPECT_FALSE([bbfc folderController]);
// Open a new one --> change.
[bbfc openBookmarkFolderFromButton:[[bbfc buttons] objectAtIndex:1]];
EXPECT_NE(controller, [bbfc folderController]);
+ EXPECT_NE([[bbfc folderController] parentButton], button);
// Close it --> all gone!
[bbfc closeBookmarkFolder:nil];
diff --git a/chrome/browser/cocoa/bookmark_bar_folder_view_unittest.mm b/chrome/browser/cocoa/bookmark_bar_folder_view_unittest.mm
index 088c0c8..2cf339e 100644
--- a/chrome/browser/cocoa/bookmark_bar_folder_view_unittest.mm
+++ b/chrome/browser/cocoa/bookmark_bar_folder_view_unittest.mm
@@ -103,6 +103,17 @@
- (void)childFolderWillClose:(id<BookmarkButtonControllerProtocol>)child {
}
+- (void)openBookmarkNodesRecursive:(const BookmarkNode*)node
+ disposition:(WindowOpenDisposition)disposition {
+}
+
+- (void)addNewFolderControllerWithParentButton:(BookmarkButton*)parentButton {
+}
+
+- (BookmarkBarFolderController*)folderController {
+ return nil;
+}
+
@end
namespace {
diff --git a/chrome/browser/cocoa/bookmark_button.h b/chrome/browser/cocoa/bookmark_button.h
index 3fea094..5050315 100644
--- a/chrome/browser/cocoa/bookmark_button.h
+++ b/chrome/browser/cocoa/bookmark_button.h
@@ -4,7 +4,9 @@
#import <Cocoa/Cocoa.h>
#import "chrome/browser/cocoa/draggable_button.h"
+#include "webkit/glue/window_open_disposition.h"
+@class BookmarkBarFolderController;
@class BookmarkButton;
class BookmarkModel;
class BookmarkNode;
@@ -103,6 +105,17 @@ class ThemeProvider;
// Called just before a child folder closes.
- (void)childFolderWillClose:(id<BookmarkButtonControllerProtocol>)child;
+// Return a controller's folder controller for a subfolder, or nil.
+- (BookmarkBarFolderController*)folderController;
+
+// Recursively open all bookmarks from this folder using the given disposition.
+- (void)openBookmarkNodesRecursive:(const BookmarkNode*)node
+ disposition:(WindowOpenDisposition)disposition;
+
+// Add a new folder controller as triggered by the given folder button.
+// If there is a current folder controller, close it.
+- (void)addNewFolderControllerWithParentButton:(BookmarkButton*)parentButton;
+
@end // @protocol BookmarkButtonControllerProtocol
diff --git a/chrome/browser/cocoa/bookmark_folder_target.h b/chrome/browser/cocoa/bookmark_folder_target.h
new file mode 100644
index 0000000..c5482d9
--- /dev/null
+++ b/chrome/browser/cocoa/bookmark_folder_target.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2010 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_BOOKMARK_FOLDER_TARGET_CONTROLLER_H_
+#define CHROME_BROWSER_COCOA_BOOKMARK_FOLDER_TARGET_CONTROLLER_H_
+
+#import <Cocoa/Cocoa.h>
+
+@protocol BookmarkButtonControllerProtocol;
+
+// Target (in the target/action sense) of a bookmark folder button.
+// Since ObjC doesn't have multiple inheritance we use has-a instead
+// of is-a to share behavior between the BookmarkBarFolderController
+// (NSWindowController) and the BookmarkBarController
+// (NSViewController).
+//
+// This class is unit tested in the context of a BookmarkBarController.
+@interface BookmarkFolderTarget : NSObject {
+ // The owner of the bookmark folder button
+ id<BookmarkButtonControllerProtocol> controller_; // weak
+}
+
+- (id)initWithController:(id<BookmarkButtonControllerProtocol>)controller;
+
+// Main IBAction for a button click.
+- (IBAction)openBookmarkFolderFromButton:(id)sender;
+
+@end
+
+#endif // CHROME_BROWSER_COCOA_BOOKMARK_FOLDER_TARGET_CONTROLLER_H_
diff --git a/chrome/browser/cocoa/bookmark_folder_target.mm b/chrome/browser/cocoa/bookmark_folder_target.mm
new file mode 100644
index 0000000..54a868e
--- /dev/null
+++ b/chrome/browser/cocoa/bookmark_folder_target.mm
@@ -0,0 +1,68 @@
+// Copyright (c) 2010 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/bookmark_folder_target.h"
+
+#include "base/logging.h"
+#include "chrome/browser/bookmarks/bookmark_model.h"
+#import "chrome/browser/cocoa/bookmark_bar_folder_controller.h"
+#import "chrome/browser/cocoa/bookmark_button.h"
+#import "chrome/browser/cocoa/event_utils.h"
+
+@implementation BookmarkFolderTarget
+
+- (id)initWithController:(id<BookmarkButtonControllerProtocol>)controller {
+ if ((self = [super init])) {
+ controller_ = controller;
+ }
+ return self;
+}
+
+// This IBAction is called when the user clicks (mouseUp, really) on a
+// "folder" bookmark button. (In this context, "Click" does not
+// include right-click to open a context menu which follows a
+// different path). Scenarios when folder X is clicked:
+// *Predicate* *Action*
+// (nothing) Open Folder X
+// Folder X open Close folder X
+// Folder Y open Close Y, open X
+// Cmd-click Open All with proper disposition
+//
+// Note complication in which a click-drag engages drag and drop, not
+// a click-to-open. Thus the path to get here is a little twisted.
+- (IBAction)openBookmarkFolderFromButton:(id)sender {
+ DCHECK(sender);
+ BOOL same = false;
+
+ if ([controller_ folderController]) {
+ // closeAllBookmarkFolders sets folderController_ to nil
+ // so we need the SAME check to happen first.
+ same = ([[controller_ folderController] parentButton] == sender);
+ [controller_ closeAllBookmarkFolders];
+ }
+
+ // Watch out for a modifier click. For example, command-click
+ // should open all.
+ //
+ // NOTE: we cannot use [[sender cell] mouseDownFlags] because we
+ // thwart the normal mouse click mechanism to make buttons
+ // draggable. Thus we must use [NSApp currentEvent].
+ DCHECK([sender bookmarkNode]->is_folder());
+ WindowOpenDisposition disposition =
+ event_utils::WindowOpenDispositionFromNSEvent([NSApp currentEvent]);
+ if (disposition == NEW_BACKGROUND_TAB) {
+ [controller_ openBookmarkNodesRecursive:[sender bookmarkNode]
+ disposition:disposition];
+ return;
+ }
+
+ // If click on same folder, close it and be done.
+ // Else we clicked on a different folder so more work to do.
+ if (same)
+ return;
+
+ [controller_ addNewFolderControllerWithParentButton:sender];
+}
+
+@end
diff --git a/chrome/browser/cocoa/bookmark_folder_target_unittest.mm b/chrome/browser/cocoa/bookmark_folder_target_unittest.mm
new file mode 100644
index 0000000..179892e
--- /dev/null
+++ b/chrome/browser/cocoa/bookmark_folder_target_unittest.mm
@@ -0,0 +1,92 @@
+// Copyright (c) 2010 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/bookmark_bar_controller.h"
+#import "chrome/browser/cocoa/bookmark_bar_folder_controller.h"
+#import "chrome/browser/cocoa/bookmark_folder_target.h"
+#include "chrome/browser/cocoa/bookmark_button.h"
+#include "chrome/browser/cocoa/browser_test_helper.h"
+#include "chrome/browser/cocoa/cocoa_test_helper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+#import "third_party/ocmock/OCMock/OCMock.h"
+
+
+class BookmarkFolderTargetTest : public CocoaTest {
+ public:
+ virtual void SetUp() {
+ CocoaTest::SetUp();
+ BookmarkModel* model = helper_.profile()->GetBookmarkModel();
+ bmbNode_ = model->GetBookmarkBarNode();
+ }
+
+ BrowserTestHelper helper_;
+ const BookmarkNode* bmbNode_;
+};
+
+TEST_F(BookmarkFolderTargetTest, StartWithNothing) {
+ // Need a fake "button" which has a bookmark node.
+ id sender = [OCMockObject mockForClass:[BookmarkButton class]];
+ [[[sender stub] andReturnValue:OCMOCK_VALUE(bmbNode_)] bookmarkNode];
+
+ // Fake controller
+ id controller = [OCMockObject mockForClass:[BookmarkBarFolderController
+ class]];
+ // No current folder
+ [[[controller stub] andReturn:nil] folderController];
+
+ // Make sure we get an addNew
+ [[controller expect] addNewFolderControllerWithParentButton:sender];
+
+ scoped_nsobject<BookmarkFolderTarget> target(
+ [[BookmarkFolderTarget alloc] initWithController:controller]);
+
+ [target openBookmarkFolderFromButton:sender];
+ [controller verify];
+}
+
+TEST_F(BookmarkFolderTargetTest, ReopenSame) {
+ // Need a fake "button" which has a bookmark node.
+ id sender = [OCMockObject mockForClass:[BookmarkButton class]];
+ [[[sender stub] andReturnValue:OCMOCK_VALUE(bmbNode_)] bookmarkNode];
+
+ // Fake controller
+ id controller = [OCMockObject mockForClass:[BookmarkBarFolderController
+ class]];
+ // YES a current folder. Self-mock that as well, so "same" will be true.
+ [[[controller stub] andReturn:controller] folderController];
+ [[[controller stub] andReturn:sender] parentButton];
+
+ // Make sure we close all and do NOT create a new one.
+ [[controller expect] closeAllBookmarkFolders];
+
+ scoped_nsobject<BookmarkFolderTarget> target(
+ [[BookmarkFolderTarget alloc] initWithController:controller]);
+
+ [target openBookmarkFolderFromButton:sender];
+ [controller verify];
+}
+
+TEST_F(BookmarkFolderTargetTest, ReopenNotSame) {
+ // Need a fake "button" which has a bookmark node.
+ id sender = [OCMockObject mockForClass:[BookmarkButton class]];
+ [[[sender stub] andReturnValue:OCMOCK_VALUE(bmbNode_)] bookmarkNode];
+
+ // Fake controller
+ id controller = [OCMockObject mockForClass:[BookmarkBarFolderController
+ class]];
+ // YES a current folder but NOT same.
+ [[[controller stub] andReturn:controller] folderController];
+ [[[controller stub] andReturn:nil] parentButton];
+
+ // Make sure we close all AND create a new one.
+ [[controller expect] closeAllBookmarkFolders];
+ [[controller expect] addNewFolderControllerWithParentButton:sender];
+
+ scoped_nsobject<BookmarkFolderTarget> target(
+ [[BookmarkFolderTarget alloc] initWithController:controller]);
+
+ [target openBookmarkFolderFromButton:sender];
+ [controller verify];
+}