base_bubble_controller.mm revision 72a454cd3513ac24fbdd0e0cb9ad70b86a99b801
1// Copyright (c) 2010 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#import "chrome/browser/ui/cocoa/base_bubble_controller.h"
6
7#include "base/logging.h"
8#include "base/mac/mac_util.h"
9#include "base/scoped_nsobject.h"
10#include "base/string_util.h"
11#import "chrome/browser/ui/cocoa/info_bubble_view.h"
12#include "chrome/common/notification_observer.h"
13#include "chrome/common/notification_registrar.h"
14#include "chrome/common/notification_service.h"
15#include "chrome/common/notification_type.h"
16#include "grit/generated_resources.h"
17#include "ui/base/l10n/l10n_util.h"
18
19@interface BaseBubbleController (Private)
20- (void)updateOriginFromAnchor;
21@end
22
23namespace BaseBubbleControllerInternal {
24
25// This bridge listens for notifications so that the bubble closes when a user
26// switches tabs (including by opening a new one).
27class Bridge : public NotificationObserver {
28 public:
29  explicit Bridge(BaseBubbleController* controller) : controller_(controller) {
30    registrar_.Add(this, NotificationType::TAB_CONTENTS_HIDDEN,
31        NotificationService::AllSources());
32  }
33
34  // NotificationObserver:
35  virtual void Observe(NotificationType type,
36                       const NotificationSource& source,
37                       const NotificationDetails& details) {
38    [controller_ close];
39  }
40
41 private:
42  BaseBubbleController* controller_;  // Weak, owns this.
43  NotificationRegistrar registrar_;
44};
45
46}  // namespace BaseBubbleControllerInternal
47
48@implementation BaseBubbleController
49
50@synthesize parentWindow = parentWindow_;
51@synthesize anchorPoint = anchor_;
52@synthesize bubble = bubble_;
53
54- (id)initWithWindowNibPath:(NSString*)nibPath
55               parentWindow:(NSWindow*)parentWindow
56                 anchoredAt:(NSPoint)anchoredAt {
57  nibPath = [base::mac::MainAppBundle() pathForResource:nibPath
58                                                ofType:@"nib"];
59  if ((self = [super initWithWindowNibPath:nibPath owner:self])) {
60    parentWindow_ = parentWindow;
61    anchor_ = anchoredAt;
62
63    // Watch to see if the parent window closes, and if so, close this one.
64    NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
65    [center addObserver:self
66               selector:@selector(parentWindowWillClose:)
67                   name:NSWindowWillCloseNotification
68                 object:parentWindow_];
69  }
70  return self;
71}
72
73- (id)initWithWindowNibPath:(NSString*)nibPath
74             relativeToView:(NSView*)view
75                     offset:(NSPoint)offset {
76  DCHECK([view window]);
77  NSWindow* window = [view window];
78  NSRect bounds = [view convertRect:[view bounds] toView:nil];
79  NSPoint anchor = NSMakePoint(NSMinX(bounds) + offset.x,
80                               NSMinY(bounds) + offset.y);
81  anchor = [window convertBaseToScreen:anchor];
82  return [self initWithWindowNibPath:nibPath
83                        parentWindow:window
84                          anchoredAt:anchor];
85}
86
87- (id)initWithWindow:(NSWindow*)theWindow
88        parentWindow:(NSWindow*)parentWindow
89          anchoredAt:(NSPoint)anchoredAt {
90  DCHECK(theWindow);
91  if ((self = [super initWithWindow:theWindow])) {
92    parentWindow_ = parentWindow;
93    anchor_ = anchoredAt;
94
95    DCHECK(![[self window] delegate]);
96    [theWindow setDelegate:self];
97
98    scoped_nsobject<InfoBubbleView> contentView(
99        [[InfoBubbleView alloc] initWithFrame:NSMakeRect(0, 0, 0, 0)]);
100    [theWindow setContentView:contentView.get()];
101    bubble_ = contentView.get();
102
103    // Watch to see if the parent window closes, and if so, close this one.
104    NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
105    [center addObserver:self
106               selector:@selector(parentWindowWillClose:)
107                   name:NSWindowWillCloseNotification
108                 object:parentWindow_];
109
110    [self awakeFromNib];
111  }
112  return self;
113}
114
115- (void)awakeFromNib {
116  // Check all connections have been made in Interface Builder.
117  DCHECK([self window]);
118  DCHECK(bubble_);
119  DCHECK_EQ(self, [[self window] delegate]);
120
121  base_bridge_.reset(new BaseBubbleControllerInternal::Bridge(self));
122
123  [bubble_ setArrowLocation:info_bubble::kTopRight];
124}
125
126- (void)dealloc {
127  [[NSNotificationCenter defaultCenter] removeObserver:self];
128  [super dealloc];
129}
130
131- (void)setAnchorPoint:(NSPoint)anchor {
132  anchor_ = anchor;
133  [self updateOriginFromAnchor];
134}
135
136- (void)parentWindowWillClose:(NSNotification*)notification {
137  [self close];
138}
139
140- (void)windowWillClose:(NSNotification*)notification {
141  // We caught a close so we don't need to watch for the parent closing.
142  [[NSNotificationCenter defaultCenter] removeObserver:self];
143  [self autorelease];
144}
145
146// We want this to be a child of a browser window.  addChildWindow:
147// (called from this function) will bring the window on-screen;
148// unfortunately, [NSWindowController showWindow:] will also bring it
149// on-screen (but will cause unexpected changes to the window's
150// position).  We cannot have an addChildWindow: and a subsequent
151// showWindow:. Thus, we have our own version.
152- (void)showWindow:(id)sender {
153  NSWindow* window = [self window];  // completes nib load
154  [self updateOriginFromAnchor];
155  [parentWindow_ addChildWindow:window ordered:NSWindowAbove];
156  [window makeKeyAndOrderFront:self];
157}
158
159- (void)close {
160  [parentWindow_ removeChildWindow:[self window]];
161  [super close];
162}
163
164// The controller is the delegate of the window so it receives did resign key
165// notifications. When key is resigned mirror Windows behavior and close the
166// window.
167- (void)windowDidResignKey:(NSNotification*)notification {
168  NSWindow* window = [self window];
169  DCHECK_EQ([notification object], window);
170  if ([window isVisible]) {
171    // If the window isn't visible, it is already closed, and this notification
172    // has been sent as part of the closing operation, so no need to close.
173    [self close];
174  }
175}
176
177// By implementing this, ESC causes the window to go away.
178- (IBAction)cancel:(id)sender {
179  // This is not a "real" cancel as potential changes to the radio group are not
180  // undone. That's ok.
181  [self close];
182}
183
184// Takes the |anchor_| point and adjusts the window's origin accordingly.
185- (void)updateOriginFromAnchor {
186  NSWindow* window = [self window];
187  NSPoint origin = anchor_;
188  NSSize offsets = NSMakeSize(info_bubble::kBubbleArrowXOffset +
189                              info_bubble::kBubbleArrowWidth / 2.0, 0);
190  offsets = [[parentWindow_ contentView] convertSize:offsets toView:nil];
191  if ([bubble_ arrowLocation] == info_bubble::kTopRight) {
192    origin.x -= NSWidth([window frame]) - offsets.width;
193  } else {
194    origin.x -= offsets.width;
195  }
196  origin.y -= NSHeight([window frame]);
197  [window setFrameOrigin:origin];
198}
199
200@end  // BaseBubbleController
201