summaryrefslogtreecommitdiffstats
path: root/chrome/browser/cocoa
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/cocoa')
-rw-r--r--chrome/browser/cocoa/bookmark_bar_controller.mm4
-rw-r--r--chrome/browser/cocoa/bookmark_bubble_controller.h98
-rw-r--r--chrome/browser/cocoa/bookmark_bubble_controller.mm215
-rw-r--r--chrome/browser/cocoa/bookmark_bubble_controller_unittest.mm213
-rw-r--r--chrome/browser/cocoa/bookmark_bubble_view.h11
-rw-r--r--chrome/browser/cocoa/bookmark_bubble_view.mm58
-rw-r--r--chrome/browser/cocoa/bookmark_bubble_view_unittest.mm36
-rw-r--r--chrome/browser/cocoa/bookmark_bubble_window.h11
-rw-r--r--chrome/browser/cocoa/bookmark_bubble_window.mm33
-rw-r--r--chrome/browser/cocoa/bookmark_bubble_window_unittest.mm27
-rw-r--r--chrome/browser/cocoa/bookmark_editor_controller.h3
-rw-r--r--chrome/browser/cocoa/bookmark_editor_controller.mm9
-rw-r--r--chrome/browser/cocoa/bookmark_name_folder_controller.h2
-rw-r--r--chrome/browser/cocoa/bookmark_name_folder_controller.mm2
-rw-r--r--chrome/browser/cocoa/browser_window_cocoa.mm3
-rw-r--r--chrome/browser/cocoa/browser_window_controller.h10
-rw-r--r--chrome/browser/cocoa/browser_window_controller.mm49
-rw-r--r--chrome/browser/cocoa/browser_window_controller_unittest.mm11
-rw-r--r--chrome/browser/cocoa/toolbar_controller.h4
-rw-r--r--chrome/browser/cocoa/toolbar_controller.mm6
-rw-r--r--chrome/browser/cocoa/toolbar_controller_unittest.mm9
21 files changed, 802 insertions, 12 deletions
diff --git a/chrome/browser/cocoa/bookmark_bar_controller.mm b/chrome/browser/cocoa/bookmark_bar_controller.mm
index 1f64cf4..75e3441 100644
--- a/chrome/browser/cocoa/bookmark_bar_controller.mm
+++ b/chrome/browser/cocoa/bookmark_bar_controller.mm
@@ -411,9 +411,9 @@ const CGFloat kBookmarkHorizontalPadding = 1.0;
initWithParentWindow:[[self view] window]
profile:profile_
node:node];
- [controller runModal];
+ [controller runAsModalSheet];
- // runModal will run the window as a sheet. The
+ // runAsModalSheet will run the window as a sheet. The
// BookmarkNameFolderController will release itself when the sheet
// ends.
}
diff --git a/chrome/browser/cocoa/bookmark_bubble_controller.h b/chrome/browser/cocoa/bookmark_bubble_controller.h
new file mode 100644
index 0000000..0760fa0
--- /dev/null
+++ b/chrome/browser/cocoa/bookmark_bubble_controller.h
@@ -0,0 +1,98 @@
+// 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 <Cocoa/Cocoa.h>
+#include "base/scoped_nsobject.h"
+
+class BookmarkModel;
+class BookmarkNode;
+@class BookmarkBubbleController;
+
+// Protocol for a BookmarkBubbleController's (BBC's) delegate.
+@protocol BookmarkBubbleControllerDelegate
+
+// The bubble asks the delegate to perform an edit when needed.
+- (void)editBookmarkNode:(const BookmarkNode*)node;
+
+// The bubble tells its delegate when it's done and can be deallocated.
+- (void)doneWithBubbleController:(BookmarkBubbleController*)controller;
+
+@end
+
+// Controller for the bookmark bubble. The bookmark bubble is a
+// bubble that pops up when clicking on the STAR next to the URL to
+// add or remove it as a bookmark. This bubble allows for editing of
+// the bookmark in various ways (name, folder, etc.)
+//
+// The bubble is stored in a nib as a view, not as a window, so we can
+// make it an actual bubble. There is no nib-rific way to encode a
+// NSBorderlessWindowMask NSWindow, and the style of an NSWindow can't
+// be set other than init time. To deal, we create the NSWindow
+// programatically, but encode the view in a nib. Thus,
+// BookmarkBubbleController is an NSViewController, not an
+// NSWindowController.
+@interface BookmarkBubbleController : NSViewController {
+ @private
+ // Unexpected for this controller, perhaps, but our window does NOT
+ // come from a nib.
+ scoped_nsobject<NSWindow> window_;
+
+ id<BookmarkBubbleControllerDelegate> delegate_; // weak like other delegates
+ NSWindow* parentWindow_; // weak
+ NSPoint topLeftForBubble_;
+
+ // Both weak; owned by the current browser's profile
+ BookmarkModel* model_;
+ const BookmarkNode* node_;
+
+ // A mapping from titles to nodes so we only have to walk this once.
+ scoped_nsobject<NSMutableArray> titleMapping_;
+
+ BOOL alreadyBookmarked_;
+ scoped_nsobject<NSString> chooseAnotherFolder_;
+
+ IBOutlet NSTextField* bigTitle_; // "Bookmark" or "Bookmark Added!"
+ IBOutlet NSTextField* nameTextField_;
+ IBOutlet NSComboBox* folderComboBox_;
+}
+
+// |node| is the bookmark node we edit in this bubble.
+// |alreadyBookmarked| tells us if the node was bookmarked before the
+// user clicked on the star. (if NO, this is a brand new bookmark).
+// The owner of this object is responsible for showing the bubble if
+// it desires it to be visible on the screen. It is not shown by the
+// init routine. Closing of the window happens implicitly on dealloc.
+- (id)initWithDelegate:(id<BookmarkBubbleControllerDelegate>)delegate
+ parentWindow:(NSWindow*)parentWindow
+ topLeftForBubble:(NSPoint)topLeftForBubble
+ model:(BookmarkModel*)model
+ node:(const BookmarkNode*)node
+ alreadyBookmarked:(BOOL)alreadyBookmarked;
+
+- (void)showWindow;
+
+// Actions for buttons in the dialog.
+- (IBAction)edit:(id)sender;
+- (IBAction)close:(id)sender;
+- (IBAction)remove:(id)sender;
+
+@end
+
+
+// Exposed only for unit testing.
+@interface BookmarkBubbleController(ExposedForUnitTesting)
+- (NSWindow*)createBubbleWindow;
+- (void)fillInFolderList;
+- (BOOL)windowHasBeenClosed;
+- (void)addFolderNodes:(const BookmarkNode*)parent toComboBox:(NSComboBox*)box;
+- (void)updateBookmarkNode;
+- (void)setTitle:(NSString *)title parentFolder:(NSString*)folder;
+- (NSString*)chooseAnotherFolderString;
+@end
+
+// Also private but I need to declare them specially for @synthesize to work.
+@interface BookmarkBubbleController ()
+@property (readonly) id delegate;
+@property (readonly) NSComboBox* folderComboBox;
+@end
diff --git a/chrome/browser/cocoa/bookmark_bubble_controller.mm b/chrome/browser/cocoa/bookmark_bubble_controller.mm
new file mode 100644
index 0000000..7e9b1e2
--- /dev/null
+++ b/chrome/browser/cocoa/bookmark_bubble_controller.mm
@@ -0,0 +1,215 @@
+// 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.
+
+#include "app/l10n_util_mac.h"
+#include "base/mac_util.h"
+#include "base/sys_string_conversions.h"
+#include "chrome/browser/bookmarks/bookmark_model.h"
+#import "chrome/browser/cocoa/bookmark_bubble_controller.h"
+#import "chrome/browser/cocoa/bookmark_bubble_window.h"
+#include "grit/generated_resources.h"
+
+
+@interface BookmarkBubbleController(PrivateAPI)
+- (void)closeWindow;
+@end
+
+@implementation BookmarkBubbleController
+
+@synthesize delegate = delegate_;
+@synthesize folderComboBox = folderComboBox_;
+
+- (id)initWithDelegate:(id<BookmarkBubbleControllerDelegate>)delegate
+ parentWindow:(NSWindow*)parentWindow
+ topLeftForBubble:(NSPoint)topLeftForBubble
+ model:(BookmarkModel*)model
+ node:(const BookmarkNode*)node
+ alreadyBookmarked:(BOOL)alreadyBookmarked {
+ if ((self = [super initWithNibName:@"BookmarkBubble"
+ bundle:mac_util::MainAppBundle()])) {
+ // all these are weak...
+ delegate_ = delegate;
+ parentWindow_ = parentWindow;
+ topLeftForBubble_ = topLeftForBubble;
+ model_ = model;
+ node_ = node;
+ alreadyBookmarked_ = alreadyBookmarked;
+ // But this is strong.
+ titleMapping_.reset([[NSMutableDictionary alloc] init]);
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [self closeWindow];
+ [super dealloc];
+}
+
+- (void)showWindow {
+ [self view]; // force nib load and window_ allocation
+ [window_ makeKeyAndOrderFront:self];
+}
+
+// Actually close the window. Do nothing else.
+- (void)closeWindow {
+ [parentWindow_ removeChildWindow:window_];
+ [window_ close];
+}
+
+- (void)awakeFromNib {
+ window_.reset([self createBubbleWindow]);
+ [parentWindow_ addChildWindow:window_ ordered:NSWindowAbove];
+
+ // Fill in inital values for text, controls, ...
+
+ // Default is IDS_BOOMARK_BUBBLE_PAGE_BOOKMARK; "Bookmark".
+ // If adding for the 1st time the string becomes "Bookmark Added!"
+ if (!alreadyBookmarked_) {
+ NSString* title =
+ l10n_util::GetNSString(IDS_BOOMARK_BUBBLE_PAGE_BOOKMARKED);
+ [bigTitle_ setStringValue:title];
+ }
+
+ [self fillInFolderList];
+}
+
+- (IBAction)edit:(id)sender {
+ [self updateBookmarkNode];
+ [self closeWindow];
+ [delegate_ editBookmarkNode:node_];
+ [delegate_ doneWithBubbleController:self];
+}
+
+- (IBAction)close:(id)sender {
+ if (node_) {
+ // no node_ if the bookmark was just removed
+ [self updateBookmarkNode];
+ }
+ [self closeWindow];
+ [delegate_ doneWithBubbleController:self];
+}
+
+// By implementing this, ESC causes the window to go away.
+- (IBAction)cancel:(id)sender {
+ [self close:sender];
+}
+
+- (IBAction)remove:(id)sender {
+ model_->SetURLStarred(node_->GetURL(), node_->GetTitle(), false);
+ node_ = NULL; // no longer valid
+ [self close:self];
+}
+
+// We are the delegate of the combo box so we can tell when "choose
+// another folder" was picked.
+- (void)comboBoxSelectionDidChange:(NSNotification*)notification {
+ NSString* selected = [folderComboBox_ objectValueOfSelectedItem];
+ if ([selected isEqual:chooseAnotherFolder_.get()]) {
+ [self edit:self];
+ }
+}
+
+// We are the delegate of our own window so we know when we lose key.
+// When we lose key status we close, mirroring Windows behaivor.
+- (void)windowDidResignKey:(NSNotification*)notification {
+ if ([window_ isVisible])
+ [self close:self];
+}
+
+@end // BookmarkBubbleController
+
+
+@implementation BookmarkBubbleController(ExposedForUnitTesting)
+
+// Create and return a retained NSWindow for this bubble.
+- (NSWindow*)createBubbleWindow {
+ NSRect contentRect = [[self view] frame];
+ NSPoint origin = topLeftForBubble_;
+ origin.y -= contentRect.size.height; // since it'll be our bottom-left
+ contentRect.origin = origin;
+ // Now convert to global coordinates since it'll be used for a window.
+ contentRect.origin = [parentWindow_ convertBaseToScreen:contentRect.origin];
+ NSWindow* window = [[BookmarkBubbleWindow alloc]
+ initWithContentRect:contentRect];
+ [window setDelegate:self];
+ [window setContentView:[self view]];
+ return window;
+}
+
+// Fill in all information related to the folder combo box.
+//
+// TODO(jrg): make sure nested folders that have the same name are
+// handled properly.
+// http://crbug.com/19408
+- (void)fillInFolderList {
+ [nameTextField_ setStringValue:base::SysWideToNSString(node_->GetTitle())];
+ [self addFolderNodes:model_->root_node() toComboBox:folderComboBox_];
+
+ // Add "Choose another folder...". Remember it for later to compare against.
+ chooseAnotherFolder_.reset(
+ [l10n_util::GetNSString(IDS_BOOMARK_BUBBLE_CHOOSER_ANOTHER_FOLDER)
+ retain]);
+ [folderComboBox_ addItemWithObjectValue:chooseAnotherFolder_.get()];
+
+ // Finally, select the current parent.
+ NSString* parentTitle = base::SysWideToNSString(
+ node_->GetParent()->GetTitle());
+ [folderComboBox_ selectItemWithObjectValue:parentTitle];
+}
+
+- (BOOL)windowHasBeenClosed {
+ return ![window_ isVisible];
+}
+
+// For the given folder node, walk the tree and add folder names to
+// the given combo box.
+//
+// TODO(jrg): no distinction is made among folders with the same name.
+- (void)addFolderNodes:(const BookmarkNode*)parent toComboBox:(NSComboBox*)box {
+ NSString* title = base::SysWideToNSString(parent->GetTitle());
+ if ([title length]) { // no title if root
+ [box addItemWithObjectValue:title];
+ [titleMapping_ setValue:[NSValue valueWithPointer:parent] forKey:title];
+ }
+ for (int i = 0; i < parent->GetChildCount(); i++) {
+ const BookmarkNode* child = parent->GetChild(i);
+ if (child->is_folder())
+ [self addFolderNodes:child toComboBox:box];
+ }
+}
+
+// Look at the dialog; if the user has changed anything, update the
+// bookmark node to reflect this.
+- (void)updateBookmarkNode {
+ // First the title...
+ NSString* oldTitle = base::SysWideToNSString(node_->GetTitle());
+ NSString* newTitle = [nameTextField_ stringValue];
+ if (![oldTitle isEqual:newTitle]) {
+ model_->SetTitle(node_, base::SysNSStringToWide(newTitle));
+ }
+ // Then the parent folder.
+ NSString* oldParentTitle = base::SysWideToNSString(
+ node_->GetParent()->GetTitle());
+ NSString* newParentTitle = [folderComboBox_ objectValueOfSelectedItem];
+ if (![oldParentTitle isEqual:newParentTitle]) {
+ const BookmarkNode* newParent = static_cast<const BookmarkNode*>(
+ [[titleMapping_ objectForKey:newParentTitle] pointerValue]);
+ if (newParent) {
+ // newParent should only ever possibly be NULL in a unit test.
+ int index = newParent->GetChildCount();
+ model_->Move(node_, newParent, index);
+ }
+ }
+}
+
+- (void)setTitle:(NSString*)title parentFolder:(NSString*)folder {
+ [nameTextField_ setStringValue:title];
+ [folderComboBox_ selectItemWithObjectValue:folder];
+}
+
+- (NSString*)chooseAnotherFolderString {
+ return chooseAnotherFolder_.get();
+}
+
+@end // implementation BookmarkBubbleController(ExposedForUnitTesting)
diff --git a/chrome/browser/cocoa/bookmark_bubble_controller_unittest.mm b/chrome/browser/cocoa/bookmark_bubble_controller_unittest.mm
new file mode 100644
index 0000000..2e741c0
--- /dev/null
+++ b/chrome/browser/cocoa/bookmark_bubble_controller_unittest.mm
@@ -0,0 +1,213 @@
+// 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 <Cocoa/Cocoa.h>
+
+#include "base/basictypes.h"
+#include "base/scoped_nsobject.h"
+#import "chrome/browser/cocoa/bookmark_bubble_controller.h"
+#include "chrome/browser/cocoa/browser_test_helper.h"
+#import "chrome/browser/cocoa/cocoa_test_helper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+@interface BBDelegate : NSObject<BookmarkBubbleControllerDelegate> {
+ NSWindow* window_; // weak
+ int edits_;
+ int dones_;
+}
+@property (readonly) int edits;
+@property (readonly) int dones;
+@property (readonly) NSWindow* window;
+@end
+
+@implementation BBDelegate
+
+@synthesize edits = edits_;
+@synthesize window = window_;
+@synthesize dones = dones_;
+
+- (NSPoint)topLeftForBubble {
+ return NSMakePoint(10, 300);
+}
+
+- (void)editBookmarkNode:(const BookmarkNode*)node {
+ edits_++;
+}
+
+- (void)doneWithBubbleController:(BookmarkBubbleController*)controller {
+ dones_++;
+}
+
+- (void)clear {
+ edits_ = 0;
+ dones_ = 0;
+}
+
+@end
+
+namespace {
+
+class BookmarkBubbleControllerTest : public PlatformTest {
+ public:
+ CocoaTestHelper cocoa_helper_; // Inits Cocoa, creates window, etc...
+ BrowserTestHelper helper_;
+ scoped_nsobject<BBDelegate> delegate_;
+ scoped_nsobject<BookmarkBubbleController> controller_;
+
+ BookmarkBubbleControllerTest() {
+ delegate_.reset([[BBDelegate alloc] init]);
+ }
+
+ // Returns a controller but ownership not transferred.
+ // Only one of these will be valid at a time.
+ BookmarkBubbleController* ControllerForNode(const BookmarkNode* node) {
+ controller_.reset([[BookmarkBubbleController alloc]
+ initWithDelegate:delegate_.get()
+ parentWindow:cocoa_helper_.window()
+ topLeftForBubble:[delegate_ topLeftForBubble]
+ model:helper_.profile()->GetBookmarkModel()
+ node:node
+ alreadyBookmarked:YES]);
+ [controller_ view]; // force nib load
+ return controller_.get();
+ }
+
+ BookmarkModel* GetBookmarkModel() {
+ return helper_.profile()->GetBookmarkModel();
+ }
+};
+
+// Confirm basics about the bubble window (e.g. that it is inside the
+// parent window)
+TEST_F(BookmarkBubbleControllerTest, TestBubbleWindow) {
+ BookmarkModel* model = GetBookmarkModel();
+ const BookmarkNode* node = model->AddURL(model->GetBookmarkBarNode(),
+ 0,
+ L"Bookie markie title",
+ GURL("http://www.google.com"));
+ BookmarkBubbleController* controller = ControllerForNode(node);
+ EXPECT_TRUE(controller);
+ NSWindow* window = [controller createBubbleWindow];
+ EXPECT_TRUE(window);
+ EXPECT_TRUE(NSContainsRect([cocoa_helper_.window() frame],
+ [window frame]));
+}
+
+// Confirm population of folder list
+TEST_F(BookmarkBubbleControllerTest, TestFillInFolder) {
+ // Create some folders, including a nested folder
+ BookmarkModel* model = GetBookmarkModel();
+ const BookmarkNode* node1 = model->AddGroup(model->GetBookmarkBarNode(),
+ 0, L"one");
+ const BookmarkNode* node2 = model->AddGroup(model->GetBookmarkBarNode(),
+ 1, L"two");
+ const BookmarkNode* node3 = model->AddGroup(model->GetBookmarkBarNode(),
+ 2, L"three");
+ const BookmarkNode* node4 = model->AddGroup(node2,
+ 0, L"sub");
+ model->AddURL(node1, 0, L"title1", GURL("http://www.google.com"));
+ model->AddURL(node3, 0, L"title2", GURL("http://www.google.com"));
+ model->AddURL(node4, 0, L"title3", GURL("http://www.google.com/reader"));
+
+ BookmarkBubbleController* controller = ControllerForNode(node4);
+ EXPECT_TRUE(controller);
+
+ NSArray* items = [[controller folderComboBox] objectValues];
+ EXPECT_TRUE([items containsObject:@"one"]);
+ EXPECT_TRUE([items containsObject:@"two"]);
+ EXPECT_TRUE([items containsObject:@"three"]);
+ EXPECT_TRUE([items containsObject:@"sub"]);
+ EXPECT_FALSE([items containsObject:@"title1"]);
+ EXPECT_FALSE([items containsObject:@"title2"]);
+}
+
+// Click on edit; bubble gets closed.
+TEST_F(BookmarkBubbleControllerTest, TestSimpleActions) {
+ BookmarkModel* model = GetBookmarkModel();
+ const BookmarkNode* node = model->AddURL(model->GetBookmarkBarNode(),
+ 0,
+ L"Bookie markie title",
+ GURL("http://www.google.com"));
+ BookmarkBubbleController* controller = ControllerForNode(node);
+ EXPECT_TRUE(controller);
+
+ EXPECT_EQ([delegate_ edits], 0);
+ EXPECT_EQ([delegate_ dones], 0);
+ EXPECT_FALSE([controller windowHasBeenClosed]);
+ [controller edit:controller];
+ EXPECT_EQ([delegate_ edits], 1);
+ EXPECT_EQ([delegate_ dones], 1);
+ EXPECT_TRUE([controller windowHasBeenClosed]);
+
+ [delegate_ clear];
+ EXPECT_EQ([delegate_ edits], 0);
+ EXPECT_EQ([delegate_ dones], 0);
+
+ controller = ControllerForNode(node);
+ EXPECT_TRUE(controller);
+ EXPECT_FALSE([controller windowHasBeenClosed]);
+ [controller close:controller];
+ EXPECT_EQ([delegate_ edits], 0);
+ EXPECT_EQ([delegate_ dones], 1);
+ EXPECT_TRUE([controller windowHasBeenClosed]);
+}
+
+// User changes title and parent folder in the UI
+TEST_F(BookmarkBubbleControllerTest, TestUserEdit) {
+ BookmarkModel* model = GetBookmarkModel();
+ const BookmarkNode* node = model->AddURL(model->GetBookmarkBarNode(),
+ 0,
+ L"short-title",
+ GURL("http://www.google.com"));
+ model->AddGroup(model->GetBookmarkBarNode(), 0, L"grandma");
+ model->AddGroup(model->GetBookmarkBarNode(), 0, L"grandpa");
+ BookmarkBubbleController* controller = ControllerForNode(node);
+ EXPECT_TRUE(controller);
+
+ // simulate a user edit
+ [controller setTitle:@"oops" parentFolder:@"grandma"];
+ [controller edit:controller];
+
+ // Make sure bookmark has changed
+ EXPECT_EQ(node->GetTitle(), L"oops");
+ EXPECT_EQ(node->GetParent()->GetTitle(), L"grandma");
+}
+
+// Click the "remove" button
+TEST_F(BookmarkBubbleControllerTest, TestRemove) {
+ BookmarkModel* model = GetBookmarkModel();
+ GURL gurl("http://www.google.com");
+ const BookmarkNode* node = model->AddURL(model->GetBookmarkBarNode(),
+ 0,
+ L"Bookie markie title",
+ gurl);
+ BookmarkBubbleController* controller = ControllerForNode(node);
+ EXPECT_TRUE(controller);
+ EXPECT_TRUE(model->IsBookmarked(gurl));
+
+ [controller remove:controller];
+ EXPECT_FALSE(model->IsBookmarked(gurl));
+ EXPECT_TRUE([controller windowHasBeenClosed]);
+ EXPECT_EQ([delegate_ dones], 1);
+}
+
+// Confirm picking "choose another folder" caused edit: to be called.
+TEST_F(BookmarkBubbleControllerTest, ComboSelectionChanged) {
+ BookmarkModel* model = GetBookmarkModel();
+ GURL gurl("http://www.google.com");
+ const BookmarkNode* node = model->AddURL(model->GetBookmarkBarNode(),
+ 0, L"super-title",
+ gurl);
+ BookmarkBubbleController* controller = ControllerForNode(node);
+ EXPECT_TRUE(controller);
+
+ NSString* chooseAnotherFolder = [controller chooseAnotherFolderString];
+ EXPECT_EQ([delegate_ edits], 0);
+ [controller setTitle:@"DOH!" parentFolder:chooseAnotherFolder];
+ EXPECT_EQ([delegate_ edits], 1);
+}
+
+
+} // namespace
diff --git a/chrome/browser/cocoa/bookmark_bubble_view.h b/chrome/browser/cocoa/bookmark_bubble_view.h
new file mode 100644
index 0000000..1e811a8
--- /dev/null
+++ b/chrome/browser/cocoa/bookmark_bubble_view.h
@@ -0,0 +1,11 @@
+// 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 <Cocoa/Cocoa.h>
+
+// Content view for a Bookmark Bubble opened by clicking on the star
+// toolbar button. This is where nonrectangular drawing happens.
+@interface BookmarkBubbleView : NSView
+@end
+
diff --git a/chrome/browser/cocoa/bookmark_bubble_view.mm b/chrome/browser/cocoa/bookmark_bubble_view.mm
new file mode 100644
index 0000000..b4c5c7e56
--- /dev/null
+++ b/chrome/browser/cocoa/bookmark_bubble_view.mm
@@ -0,0 +1,58 @@
+// 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/bookmark_bubble_view.h"
+#import "third_party/GTM/AppKit/GTMTheme.h"
+
+namespace {
+// TODO(jrg): confirm constants with UI dudes
+const CGFloat kBubbleCornerRadius = 8.0;
+const CGFloat kBubbleArrowXOffset = 10.0;
+const CGFloat kBubbleArrowWidth = 15.0;
+const CGFloat kBubbleArrowHeight = 8.0;
+const CGFloat kBubbleBorderLineWidth = 1.0;
+}
+
+@implementation BookmarkBubbleView
+
+- (void)drawRect:(NSRect)rect {
+ // Make room for the border to be seen.
+ NSRect bounds = [self bounds];
+ bounds.size.height -= kBubbleArrowHeight;
+ bounds = NSInsetRect(bounds,
+ kBubbleBorderLineWidth/2.0,
+ kBubbleBorderLineWidth/2.0);
+
+ NSBezierPath* bezier = [NSBezierPath bezierPath];
+ rect.size.height -= kBubbleArrowHeight;
+
+ // Start with a rounded rectangle.
+ [bezier appendBezierPathWithRoundedRect:bounds
+ xRadius:kBubbleCornerRadius
+ yRadius:kBubbleCornerRadius];
+
+ // Add the bubble arrow (pointed at the star).
+ NSPoint arrowStart = NSMakePoint(NSMinX(bounds), NSMaxY(bounds));
+ arrowStart.x += kBubbleArrowXOffset;
+ [bezier moveToPoint:NSMakePoint(arrowStart.x, arrowStart.y)];
+ [bezier lineToPoint:NSMakePoint(arrowStart.x + kBubbleArrowWidth/2.0,
+ arrowStart.y + kBubbleArrowHeight)];
+ [bezier lineToPoint:NSMakePoint(arrowStart.x + kBubbleArrowWidth,
+ arrowStart.y)];
+ [bezier closePath];
+
+ // Draw the outline...
+ [[NSColor blackColor] set];
+ [bezier setLineWidth:kBubbleBorderLineWidth];
+ [bezier stroke];
+
+ // Then fill the inside.
+ GTMTheme *theme = [GTMTheme defaultTheme];
+ NSGradient *gradient = [theme gradientForStyle:GTMThemeStyleToolBar
+ state:NO];
+ [gradient drawInBezierPath:bezier angle:0.0];
+}
+
+@end
+
diff --git a/chrome/browser/cocoa/bookmark_bubble_view_unittest.mm b/chrome/browser/cocoa/bookmark_bubble_view_unittest.mm
new file mode 100644
index 0000000..9744397
--- /dev/null
+++ b/chrome/browser/cocoa/bookmark_bubble_view_unittest.mm
@@ -0,0 +1,36 @@
+// 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 <Cocoa/Cocoa.h>
+
+#include "base/scoped_nsobject.h"
+#import "chrome/browser/cocoa/bookmark_bubble_view.h"
+#import "chrome/browser/cocoa/cocoa_test_helper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace {
+
+class BookmarkBubbleViewTest : public PlatformTest {
+ public:
+ BookmarkBubbleViewTest() {
+ NSRect frame = NSMakeRect(0, 0, 100, 30);
+ view_.reset([[BookmarkBubbleView alloc] initWithFrame:frame]);
+ [cocoa_helper_.contentView() addSubview:view_.get()];
+ }
+
+ CocoaTestHelper cocoa_helper_; // Inits Cocoa, creates window, etc...
+ scoped_nsobject<BookmarkBubbleView> view_;
+};
+
+// Test drawing and an add/remove from the view hierarchy to ensure
+// nothing leaks or crashes.
+TEST_F(BookmarkBubbleViewTest, AddRemoveDisplay) {
+ [view_ display];
+ EXPECT_EQ(cocoa_helper_.contentView(), [view_ superview]);
+ [view_.get() removeFromSuperview];
+ EXPECT_FALSE([view_ superview]);
+}
+
+} // namespace
diff --git a/chrome/browser/cocoa/bookmark_bubble_window.h b/chrome/browser/cocoa/bookmark_bubble_window.h
new file mode 100644
index 0000000..45e2f3b
--- /dev/null
+++ b/chrome/browser/cocoa/bookmark_bubble_window.h
@@ -0,0 +1,11 @@
+// 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 <Cocoa/Cocoa.h>
+
+// Window for the bookmark bubble that comes up when you click on "STAR".
+@interface BookmarkBubbleWindow : NSWindow
+- (id)initWithContentRect:(NSRect)contentRect;
+@end
+
diff --git a/chrome/browser/cocoa/bookmark_bubble_window.mm b/chrome/browser/cocoa/bookmark_bubble_window.mm
new file mode 100644
index 0000000..126b30e
--- /dev/null
+++ b/chrome/browser/cocoa/bookmark_bubble_window.mm
@@ -0,0 +1,33 @@
+// 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/bookmark_bubble_window.h"
+
+@implementation BookmarkBubbleWindow
+
+- (id)initWithContentRect:(NSRect)contentRect {
+ if ((self = [super initWithContentRect:contentRect
+ styleMask:NSBorderlessWindowMask
+ backing:NSBackingStoreBuffered
+ defer:YES])) {
+ [self setReleasedWhenClosed:NO];
+ [self setBackgroundColor:[NSColor clearColor]];
+ [self setExcludedFromWindowsMenu:YES];
+ [self setAlphaValue:1.0];
+ [self setOpaque:NO];
+ }
+ return self;
+}
+
+// According to
+// http://www.cocoabuilder.com/archive/message/cocoa/2006/6/19/165953,
+// NSBorderlessWindowMask windows cannot become key or main. In our
+// case, however, we don't want all of that behavior. (As an example,
+// our bubble has buttons!)
+
+- (BOOL)canBecomeKeyWindow {
+ return YES;
+}
+
+@end
diff --git a/chrome/browser/cocoa/bookmark_bubble_window_unittest.mm b/chrome/browser/cocoa/bookmark_bubble_window_unittest.mm
new file mode 100644
index 0000000..08baec4
--- /dev/null
+++ b/chrome/browser/cocoa/bookmark_bubble_window_unittest.mm
@@ -0,0 +1,27 @@
+// 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.
+
+#include "base/scoped_ptr.h"
+#include "chrome/browser/cocoa/cocoa_test_helper.h"
+#include "chrome/browser/cocoa/bookmark_bubble_window.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class BookmarkBubbleWindowTest : public testing::Test {
+ public:
+ CocoaTestHelper cocoa_helper_;
+};
+
+TEST_F(BookmarkBubbleWindowTest, Basics) {
+ scoped_nsobject<BookmarkBubbleWindow> window_;
+ window_.reset([[BookmarkBubbleWindow alloc]
+ initWithContentRect:NSMakeRect(0,0,10,10)]);
+
+ EXPECT_TRUE([window_ canBecomeKeyWindow]);
+ EXPECT_FALSE([window_ canBecomeMainWindow]);
+
+ EXPECT_TRUE([window_ isExcludedFromWindowsMenu]);
+ EXPECT_FALSE([window_ isReleasedWhenClosed]);
+}
+
+
diff --git a/chrome/browser/cocoa/bookmark_editor_controller.h b/chrome/browser/cocoa/bookmark_editor_controller.h
index 742fbd1..4c80161 100644
--- a/chrome/browser/cocoa/bookmark_editor_controller.h
+++ b/chrome/browser/cocoa/bookmark_editor_controller.h
@@ -39,6 +39,9 @@
configuration:(BookmarkEditor::Configuration)configuration
handler:(BookmarkEditor::Handler*)handler;
+// Run the bookmark editor as a modal sheet. Does not block.
+- (void)runAsModalSheet;
+
// Actions for the buttons at the bottom of the window.
- (IBAction)newFolder:(id)sender;
- (IBAction)cancel:(id)sender;
diff --git a/chrome/browser/cocoa/bookmark_editor_controller.mm b/chrome/browser/cocoa/bookmark_editor_controller.mm
index 4ba0ab39..79cd1dc 100644
--- a/chrome/browser/cocoa/bookmark_editor_controller.mm
+++ b/chrome/browser/cocoa/bookmark_editor_controller.mm
@@ -10,11 +10,6 @@
#include "chrome/browser/profile.h"
#import "chrome/browser/cocoa/bookmark_editor_controller.h"
-@interface BookmarkEditorController(Private)
-// Run the bookmark editor as a modal sheet. Does not block.
-- (void)runModal;
-@end
-
// static; implemented for each platform.
void BookmarkEditor::Show(gfx::NativeView parent_hwnd,
Profile* profile,
@@ -30,7 +25,7 @@ void BookmarkEditor::Show(gfx::NativeView parent_hwnd,
node:node
configuration:configuration
handler:handler];
- [controller runModal];
+ [controller runAsModalSheet];
}
@@ -107,7 +102,7 @@ void BookmarkEditor::Show(gfx::NativeView parent_hwnd,
*/
// TODO(jrg): consider NSModalSession.
-- (void)runModal {
+- (void)runAsModalSheet {
[NSApp beginSheet:[self window]
modalForWindow:parentWindow_
modalDelegate:self
diff --git a/chrome/browser/cocoa/bookmark_name_folder_controller.h b/chrome/browser/cocoa/bookmark_name_folder_controller.h
index 64c3183..764a04a 100644
--- a/chrome/browser/cocoa/bookmark_name_folder_controller.h
+++ b/chrome/browser/cocoa/bookmark_name_folder_controller.h
@@ -26,7 +26,7 @@
- (id)initWithParentWindow:(NSWindow*)window
profile:(Profile*)profile
node:(const BookmarkNode*)node;
-- (void)runModal;
+- (void)runAsModalSheet;
- (IBAction)cancel:(id)sender;
- (IBAction)ok:(id)sender;
@end
diff --git a/chrome/browser/cocoa/bookmark_name_folder_controller.mm b/chrome/browser/cocoa/bookmark_name_folder_controller.mm
index fa24ac5..859e8e7 100644
--- a/chrome/browser/cocoa/bookmark_name_folder_controller.mm
+++ b/chrome/browser/cocoa/bookmark_name_folder_controller.mm
@@ -39,7 +39,7 @@
}
// TODO(jrg): consider NSModalSession.
-- (void)runModal {
+- (void)runAsModalSheet {
[NSApp beginSheet:[self window]
modalForWindow:parentWindow_
modalDelegate:self
diff --git a/chrome/browser/cocoa/browser_window_cocoa.mm b/chrome/browser/cocoa/browser_window_cocoa.mm
index 91c6a0f..ff1d813 100644
--- a/chrome/browser/cocoa/browser_window_cocoa.mm
+++ b/chrome/browser/cocoa/browser_window_cocoa.mm
@@ -202,7 +202,8 @@ void BrowserWindowCocoa::ShowBookmarkManager() {
void BrowserWindowCocoa::ShowBookmarkBubble(const GURL& url,
bool already_bookmarked) {
- NOTIMPLEMENTED();
+ [controller_ showBookmarkBubbleForURL:url
+ alreadyBookmarked:(already_bookmarked ? YES : NO)];
}
bool BrowserWindowCocoa::IsDownloadShelfVisible() const {
diff --git a/chrome/browser/cocoa/browser_window_controller.h b/chrome/browser/cocoa/browser_window_controller.h
index 6713044..eb34d94 100644
--- a/chrome/browser/cocoa/browser_window_controller.h
+++ b/chrome/browser/cocoa/browser_window_controller.h
@@ -16,6 +16,7 @@
#include "base/scoped_ptr.h"
#import "chrome/browser/cocoa/tab_window_controller.h"
#import "chrome/browser/cocoa/bookmark_bar_controller.h"
+#import "chrome/browser/cocoa/bookmark_bubble_controller.h"
#import "chrome/browser/cocoa/view_resizer.h"
#import "third_party/GTM/AppKit/GTMTheme.h"
@@ -40,6 +41,7 @@ class TabStripModelObserverBridge;
@interface BrowserWindowController :
TabWindowController<NSUserInterfaceValidations,
BookmarkURLOpener,
+ BookmarkBubbleControllerDelegate,
ViewResizer,
GTMThemeDelegate> {
@private
@@ -63,6 +65,7 @@ class TabStripModelObserverBridge;
scoped_nsobject<InfoBarContainerController> infoBarContainerController_;
scoped_ptr<StatusBubble> statusBubble_;
scoped_nsobject<DownloadShelfController> downloadShelfController_;
+ scoped_nsobject<BookmarkBubbleController> bookmarkBubbleController_;
scoped_nsobject<GTMTheme> theme_;
BOOL ownsBrowser_; // Only ever NO when testing
BOOL fullscreen_;
@@ -139,6 +142,10 @@ class TabStripModelObserverBridge;
// Delegate method for the status bubble to query about its vertical offset.
- (float)verticalOffsetForStatusBubble;
+// Show the bookmark bubble (e.g. user just clicked on the STAR)
+- (void)showBookmarkBubbleForURL:(const GURL&)url
+ alreadyBookmarked:(BOOL)alreadyBookmarked;
+
// Returns the (lazily created) window sheet controller of this window. Used
// for the per-tab sheets.
- (GTMWindowSheetController*)sheetController;
@@ -165,6 +172,9 @@ class TabStripModelObserverBridge;
// Return an autoreleased NSWindow suitable for fullscreen use.
- (NSWindow*)fullscreenWindow;
+// Return a point suitable for the topLeft for a bookmark bubble.
+- (NSPoint)topLeftForBubble;
+
@end // BrowserWindowController(TestingAPI)
#endif // CHROME_BROWSER_COCOA_BROWSER_WINDOW_CONTROLLER_H_
diff --git a/chrome/browser/cocoa/browser_window_controller.mm b/chrome/browser/cocoa/browser_window_controller.mm
index 5918cc4..01cd173 100644
--- a/chrome/browser/cocoa/browser_window_controller.mm
+++ b/chrome/browser/cocoa/browser_window_controller.mm
@@ -9,6 +9,7 @@
#import "base/scoped_nsobject.h"
#include "base/sys_string_conversions.h"
#include "chrome/app/chrome_dll_resource.h" // IDC_*
+#include "chrome/browser/bookmarks/bookmark_editor.h"
#include "chrome/browser/browser.h"
#include "chrome/browser/browser_list.h"
#include "chrome/browser/browser_process.h"
@@ -20,6 +21,7 @@
#include "chrome/browser/tab_contents/tab_contents_view.h"
#include "chrome/browser/tabs/tab_strip_model.h"
#import "chrome/browser/cocoa/bookmark_bar_controller.h"
+#import "chrome/browser/cocoa/bookmark_editor_controller.h"
#import "chrome/browser/cocoa/browser_window_cocoa.h"
#import "chrome/browser/cocoa/browser_window_controller.h"
#import "chrome/browser/cocoa/download_shelf_controller.h"
@@ -933,6 +935,53 @@ willPositionSheet:(NSWindow*)sheet
return theme_ ? theme_ : [GTMTheme defaultTheme];
}
+- (NSPoint)topLeftForBubble {
+ NSRect rect = [toolbarController_ starButtonInWindowCoordinates];
+ NSPoint p = NSMakePoint(NSMinX(rect), NSMinY(rect)); // bottom left
+ return p;
+}
+
+// Show the bookmark bubble (e.g. user just clicked on the STAR).
+- (void)showBookmarkBubbleForURL:(const GURL&)url
+ alreadyBookmarked:(BOOL)alreadyBookmarked {
+ BookmarkModel* model = browser_->profile()->GetBookmarkModel();
+ const BookmarkNode* node = model->GetMostRecentlyAddedNodeForURL(url);
+
+ // Bring up the bubble. But clicking on STAR while the bubble is
+ // open should make it go away.
+ if (bookmarkBubbleController_.get()) {
+ [self doneWithBubbleController:bookmarkBubbleController_.get()];
+ } else {
+ bookmarkBubbleController_.reset([[BookmarkBubbleController alloc]
+ initWithDelegate:self
+ parentWindow:[self window]
+ topLeftForBubble:[self topLeftForBubble]
+ model:model
+ node:node
+ alreadyBookmarked:alreadyBookmarked]);
+ [bookmarkBubbleController_ showWindow];
+ }
+}
+
+// Implement BookmarkBubbleControllerDelegate
+- (void)editBookmarkNode:(const BookmarkNode*)node {
+ // A BookmarkEditorController is a sheet that owns itself, and
+ // deallocates itself when closed.
+ [[[BookmarkEditorController alloc]
+ initWithParentWindow:[self window]
+ profile:browser_->profile()
+ parent:node->GetParent()
+ node:node
+ configuration:BookmarkEditor::SHOW_TREE
+ handler:NULL]
+ runAsModalSheet];
+}
+
+// Implement BookmarkBubbleControllerDelegate
+- (void)doneWithBubbleController:(BookmarkBubbleController*)controller {
+ bookmarkBubbleController_.reset(nil);
+}
+
@end
@implementation BrowserWindowController (Private)
diff --git a/chrome/browser/cocoa/browser_window_controller_unittest.mm b/chrome/browser/cocoa/browser_window_controller_unittest.mm
index 4b40a67..743c90c 100644
--- a/chrome/browser/cocoa/browser_window_controller_unittest.mm
+++ b/chrome/browser/cocoa/browser_window_controller_unittest.mm
@@ -257,4 +257,15 @@ TEST_F(BrowserWindowControllerTest, TestResizeViews) {
EXPECT_TRUE(NSEqualRects([toolbar frame], NSMakeRect(0, 561, 800, 39)));
}
+TEST_F(BrowserWindowControllerTest, TestTopLeftForBubble) {
+ NSPoint p = [controller_ topLeftForBubble];
+ NSRect all = [[controller_ window] frame];
+
+ // As a sanity check make sure the point is vaguely in the top left
+ // of the window.
+ EXPECT_GT(p.y, all.origin.y + (all.size.height/2));
+ EXPECT_LT(p.x, all.origin.x + (all.size.width/2));
+}
+
+
/* TODO(???): test other methods of BrowserWindowController */
diff --git a/chrome/browser/cocoa/toolbar_controller.h b/chrome/browser/cocoa/toolbar_controller.h
index c5135d3..31aafc4 100644
--- a/chrome/browser/cocoa/toolbar_controller.h
+++ b/chrome/browser/cocoa/toolbar_controller.h
@@ -128,6 +128,10 @@ class ToolbarView;
- (IBAction)showPageMenu:(id)sender;
- (IBAction)showWrenchMenu:(id)sender;
+// The bookmark bubble (when you click the star) needs to know where to go.
+// Somewhere near the star button seems like a good start.
+- (NSRect)starButtonInWindowCoordinates;
+
@end
// A set of private methods used by tests, in the absence of "friends" in ObjC.
diff --git a/chrome/browser/cocoa/toolbar_controller.mm b/chrome/browser/cocoa/toolbar_controller.mm
index 04c7e5e0..1bcf8a4 100644
--- a/chrome/browser/cocoa/toolbar_controller.mm
+++ b/chrome/browser/cocoa/toolbar_controller.mm
@@ -373,4 +373,10 @@ class PrefObserverBridge : public NotificationObserver {
forView:wrenchButton_];
}
+- (NSRect)starButtonInWindowCoordinates {
+ return [[[starButton_ window] contentView] convertRect:[starButton_ bounds]
+ fromView:starButton_];
+}
+
+
@end
diff --git a/chrome/browser/cocoa/toolbar_controller_unittest.mm b/chrome/browser/cocoa/toolbar_controller_unittest.mm
index aa0257193..091bddc 100644
--- a/chrome/browser/cocoa/toolbar_controller_unittest.mm
+++ b/chrome/browser/cocoa/toolbar_controller_unittest.mm
@@ -248,4 +248,13 @@ TEST_F(ToolbarControllerTest, BookmarkBarIsFullWidth) {
EXPECT_TRUE([bookmarkBarView isDescendantOf:[bar_ view]]);
}
+TEST_F(ToolbarControllerTest, StarButtonInWindowCoordinates) {
+ NSRect star = [bar_ starButtonInWindowCoordinates];
+ NSRect all = [[[bar_ view] window] frame];
+
+ // Make sure the star is completely inside the window rect
+ EXPECT_TRUE(NSContainsRect(all, star));
+}
+
+
} // namespace