summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui/cocoa/base_bubble_controller.mm
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/ui/cocoa/base_bubble_controller.mm')
-rw-r--r--chrome/browser/ui/cocoa/base_bubble_controller.mm200
1 files changed, 200 insertions, 0 deletions
diff --git a/chrome/browser/ui/cocoa/base_bubble_controller.mm b/chrome/browser/ui/cocoa/base_bubble_controller.mm
new file mode 100644
index 0000000..3c1e9d2
--- /dev/null
+++ b/chrome/browser/ui/cocoa/base_bubble_controller.mm
@@ -0,0 +1,200 @@
+// 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/ui/cocoa/base_bubble_controller.h"
+
+#include "app/l10n_util.h"
+#include "base/logging.h"
+#include "base/mac_util.h"
+#include "base/scoped_nsobject.h"
+#include "base/string_util.h"
+#import "chrome/browser/ui/cocoa/info_bubble_view.h"
+#include "chrome/common/notification_observer.h"
+#include "chrome/common/notification_registrar.h"
+#include "chrome/common/notification_service.h"
+#include "chrome/common/notification_type.h"
+#include "grit/generated_resources.h"
+
+@interface BaseBubbleController (Private)
+- (void)updateOriginFromAnchor;
+@end
+
+namespace BaseBubbleControllerInternal {
+
+// This bridge listens for notifications so that the bubble closes when a user
+// switches tabs (including by opening a new one).
+class Bridge : public NotificationObserver {
+ public:
+ explicit Bridge(BaseBubbleController* controller) : controller_(controller) {
+ registrar_.Add(this, NotificationType::TAB_CONTENTS_HIDDEN,
+ NotificationService::AllSources());
+ }
+
+ // NotificationObserver:
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ [controller_ close];
+ }
+
+ private:
+ BaseBubbleController* controller_; // Weak, owns this.
+ NotificationRegistrar registrar_;
+};
+
+} // namespace BaseBubbleControllerInternal
+
+@implementation BaseBubbleController
+
+@synthesize parentWindow = parentWindow_;
+@synthesize anchorPoint = anchor_;
+@synthesize bubble = bubble_;
+
+- (id)initWithWindowNibPath:(NSString*)nibPath
+ parentWindow:(NSWindow*)parentWindow
+ anchoredAt:(NSPoint)anchoredAt {
+ nibPath = [mac_util::MainAppBundle() pathForResource:nibPath
+ ofType:@"nib"];
+ if ((self = [super initWithWindowNibPath:nibPath owner:self])) {
+ parentWindow_ = parentWindow;
+ anchor_ = anchoredAt;
+
+ // Watch to see if the parent window closes, and if so, close this one.
+ NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
+ [center addObserver:self
+ selector:@selector(parentWindowWillClose:)
+ name:NSWindowWillCloseNotification
+ object:parentWindow_];
+ }
+ return self;
+}
+
+- (id)initWithWindowNibPath:(NSString*)nibPath
+ relativeToView:(NSView*)view
+ offset:(NSPoint)offset {
+ DCHECK([view window]);
+ NSWindow* window = [view window];
+ NSRect bounds = [view convertRect:[view bounds] toView:nil];
+ NSPoint anchor = NSMakePoint(NSMinX(bounds) + offset.x,
+ NSMinY(bounds) + offset.y);
+ anchor = [window convertBaseToScreen:anchor];
+ return [self initWithWindowNibPath:nibPath
+ parentWindow:window
+ anchoredAt:anchor];
+}
+
+- (id)initWithWindow:(NSWindow*)theWindow
+ parentWindow:(NSWindow*)parentWindow
+ anchoredAt:(NSPoint)anchoredAt {
+ DCHECK(theWindow);
+ if ((self = [super initWithWindow:theWindow])) {
+ parentWindow_ = parentWindow;
+ anchor_ = anchoredAt;
+
+ DCHECK(![[self window] delegate]);
+ [theWindow setDelegate:self];
+
+ scoped_nsobject<InfoBubbleView> contentView(
+ [[InfoBubbleView alloc] initWithFrame:NSMakeRect(0, 0, 0, 0)]);
+ [theWindow setContentView:contentView.get()];
+ bubble_ = contentView.get();
+
+ // Watch to see if the parent window closes, and if so, close this one.
+ NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
+ [center addObserver:self
+ selector:@selector(parentWindowWillClose:)
+ name:NSWindowWillCloseNotification
+ object:parentWindow_];
+
+ [self awakeFromNib];
+ }
+ return self;
+}
+
+- (void)awakeFromNib {
+ // Check all connections have been made in Interface Builder.
+ DCHECK([self window]);
+ DCHECK(bubble_);
+ DCHECK_EQ(self, [[self window] delegate]);
+
+ base_bridge_.reset(new BaseBubbleControllerInternal::Bridge(self));
+
+ [bubble_ setArrowLocation:info_bubble::kTopRight];
+}
+
+- (void)dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [super dealloc];
+}
+
+- (void)setAnchorPoint:(NSPoint)anchor {
+ anchor_ = anchor;
+ [self updateOriginFromAnchor];
+}
+
+- (void)parentWindowWillClose:(NSNotification*)notification {
+ [self close];
+}
+
+- (void)windowWillClose:(NSNotification*)notification {
+ // We caught a close so we don't need to watch for the parent closing.
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [self autorelease];
+}
+
+// We want this to be a child of a browser window. addChildWindow:
+// (called from this function) will bring the window on-screen;
+// unfortunately, [NSWindowController showWindow:] will also bring it
+// on-screen (but will cause unexpected changes to the window's
+// position). We cannot have an addChildWindow: and a subsequent
+// showWindow:. Thus, we have our own version.
+- (void)showWindow:(id)sender {
+ NSWindow* window = [self window]; // completes nib load
+ [self updateOriginFromAnchor];
+ [parentWindow_ addChildWindow:window ordered:NSWindowAbove];
+ [window makeKeyAndOrderFront:self];
+}
+
+- (void)close {
+ [parentWindow_ removeChildWindow:[self window]];
+ [super close];
+}
+
+// The controller is the delegate of the window so it receives did resign key
+// notifications. When key is resigned mirror Windows behavior and close the
+// window.
+- (void)windowDidResignKey:(NSNotification*)notification {
+ NSWindow* window = [self window];
+ DCHECK_EQ([notification object], window);
+ if ([window isVisible]) {
+ // If the window isn't visible, it is already closed, and this notification
+ // has been sent as part of the closing operation, so no need to close.
+ [self close];
+ }
+}
+
+// By implementing this, ESC causes the window to go away.
+- (IBAction)cancel:(id)sender {
+ // This is not a "real" cancel as potential changes to the radio group are not
+ // undone. That's ok.
+ [self close];
+}
+
+// Takes the |anchor_| point and adjusts the window's origin accordingly.
+- (void)updateOriginFromAnchor {
+ NSWindow* window = [self window];
+ NSPoint origin = anchor_;
+ NSSize offsets = NSMakeSize(info_bubble::kBubbleArrowXOffset +
+ info_bubble::kBubbleArrowWidth / 2.0, 0);
+ offsets = [[parentWindow_ contentView] convertSize:offsets toView:nil];
+ if ([bubble_ arrowLocation] == info_bubble::kTopRight) {
+ origin.x -= NSWidth([window frame]) - offsets.width;
+ } else {
+ origin.x -= offsets.width;
+ }
+ origin.y -= NSHeight([window frame]);
+ [window setFrameOrigin:origin];
+}
+
+@end // BaseBubbleController