Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / base_bubble_controller.mm
1 // Copyright (c) 2012 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/bundle_locations.h"
9 #include "base/mac/mac_util.h"
10 #include "base/mac/scoped_nsobject.h"
11 #include "base/strings/string_util.h"
12 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
13 #import "chrome/browser/ui/cocoa/info_bubble_view.h"
14 #import "chrome/browser/ui/cocoa/tabs/tab_strip_model_observer_bridge.h"
15 #include "grit/generated_resources.h"
16 #include "ui/base/l10n/l10n_util.h"
17
18 @interface BaseBubbleController (Private)
19 - (void)registerForNotifications;
20 - (void)updateOriginFromAnchor;
21 - (void)activateTabWithContents:(content::WebContents*)newContents
22                previousContents:(content::WebContents*)oldContents
23                         atIndex:(NSInteger)index
24                          reason:(int)reason;
25 - (void)recordAnchorOffset;
26 - (void)parentWindowDidResize:(NSNotification*)notification;
27 - (void)parentWindowWillClose:(NSNotification*)notification;
28 - (void)closeCleanup;
29 @end
30
31 @implementation BaseBubbleController
32
33 @synthesize parentWindow = parentWindow_;
34 @synthesize anchorPoint = anchor_;
35 @synthesize bubble = bubble_;
36 @synthesize shouldOpenAsKeyWindow = shouldOpenAsKeyWindow_;
37 @synthesize shouldCloseOnResignKey = shouldCloseOnResignKey_;
38
39 - (id)initWithWindowNibPath:(NSString*)nibPath
40                parentWindow:(NSWindow*)parentWindow
41                  anchoredAt:(NSPoint)anchoredAt {
42   nibPath = [base::mac::FrameworkBundle() pathForResource:nibPath
43                                                    ofType:@"nib"];
44   if ((self = [super initWithWindowNibPath:nibPath owner:self])) {
45     parentWindow_ = parentWindow;
46     anchor_ = anchoredAt;
47     shouldOpenAsKeyWindow_ = YES;
48     shouldCloseOnResignKey_ = YES;
49     [self registerForNotifications];
50   }
51   return self;
52 }
53
54 - (id)initWithWindowNibPath:(NSString*)nibPath
55              relativeToView:(NSView*)view
56                      offset:(NSPoint)offset {
57   DCHECK([view window]);
58   NSWindow* window = [view window];
59   NSRect bounds = [view convertRect:[view bounds] toView:nil];
60   NSPoint anchor = NSMakePoint(NSMinX(bounds) + offset.x,
61                                NSMinY(bounds) + offset.y);
62   anchor = [window convertBaseToScreen:anchor];
63   return [self initWithWindowNibPath:nibPath
64                         parentWindow:window
65                           anchoredAt:anchor];
66 }
67
68 - (id)initWithWindow:(NSWindow*)theWindow
69         parentWindow:(NSWindow*)parentWindow
70           anchoredAt:(NSPoint)anchoredAt {
71   DCHECK(theWindow);
72   if ((self = [super initWithWindow:theWindow])) {
73     parentWindow_ = parentWindow;
74     anchor_ = anchoredAt;
75     shouldOpenAsKeyWindow_ = YES;
76     shouldCloseOnResignKey_ = YES;
77
78     DCHECK(![[self window] delegate]);
79     [theWindow setDelegate:self];
80
81     base::scoped_nsobject<InfoBubbleView> contentView(
82         [[InfoBubbleView alloc] initWithFrame:NSZeroRect]);
83     [theWindow setContentView:contentView.get()];
84     bubble_ = contentView.get();
85
86     [self registerForNotifications];
87     [self awakeFromNib];
88   }
89   return self;
90 }
91
92 - (void)awakeFromNib {
93   // Check all connections have been made in Interface Builder.
94   DCHECK([self window]);
95   DCHECK(bubble_);
96   DCHECK_EQ(self, [[self window] delegate]);
97
98   BrowserWindowController* bwc =
99       [BrowserWindowController browserWindowControllerForWindow:parentWindow_];
100   if (bwc) {
101     TabStripController* tabStripController = [bwc tabStripController];
102     TabStripModel* tabStripModel = [tabStripController tabStripModel];
103     tabStripObserverBridge_.reset(new TabStripModelObserverBridge(tabStripModel,
104                                                                   self));
105   }
106
107   [bubble_ setArrowLocation:info_bubble::kTopRight];
108 }
109
110 - (void)dealloc {
111   [[NSNotificationCenter defaultCenter] removeObserver:self];
112   [super dealloc];
113 }
114
115 - (void)registerForNotifications {
116   NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
117   // Watch to see if the parent window closes, and if so, close this one.
118   [center addObserver:self
119              selector:@selector(parentWindowWillClose:)
120                  name:NSWindowWillCloseNotification
121                object:parentWindow_];
122   // Watch for parent window's resizing, to ensure this one is always
123   // anchored correctly.
124   [center addObserver:self
125              selector:@selector(parentWindowDidResize:)
126                  name:NSWindowDidResizeNotification
127                object:parentWindow_];
128 }
129
130 - (void)setAnchorPoint:(NSPoint)anchor {
131   anchor_ = anchor;
132   [self updateOriginFromAnchor];
133 }
134
135 - (void)recordAnchorOffset {
136   // The offset of the anchor from the parent's upper-left-hand corner is kept
137   // to ensure the bubble stays anchored correctly if the parent is resized.
138   anchorOffset_ = NSMakePoint(NSMinX([parentWindow_ frame]),
139                               NSMaxY([parentWindow_ frame]));
140   anchorOffset_.x -= anchor_.x;
141   anchorOffset_.y -= anchor_.y;
142 }
143
144 - (NSBox*)separatorWithFrame:(NSRect)frame {
145   frame.size.height = 1.0;
146   base::scoped_nsobject<NSBox> spacer([[NSBox alloc] initWithFrame:frame]);
147   [spacer setBoxType:NSBoxSeparator];
148   [spacer setBorderType:NSLineBorder];
149   [spacer setAlphaValue:0.2];
150   return [spacer.release() autorelease];
151 }
152
153 - (void)parentWindowDidResize:(NSNotification*)notification {
154   DCHECK_EQ(parentWindow_, [notification object]);
155   NSPoint newOrigin = NSMakePoint(NSMinX([parentWindow_ frame]),
156                                   NSMaxY([parentWindow_ frame]));
157   newOrigin.x -= anchorOffset_.x;
158   newOrigin.y -= anchorOffset_.y;
159   [self setAnchorPoint:newOrigin];
160 }
161
162 - (void)parentWindowWillClose:(NSNotification*)notification {
163   parentWindow_ = nil;
164   [self close];
165 }
166
167 - (void)closeCleanup {
168   if (eventTap_) {
169     [NSEvent removeMonitor:eventTap_];
170     eventTap_ = nil;
171   }
172   if (resignationObserver_) {
173     [[NSNotificationCenter defaultCenter]
174         removeObserver:resignationObserver_
175                   name:NSWindowDidResignKeyNotification
176                 object:nil];
177     resignationObserver_ = nil;
178   }
179
180   tabStripObserverBridge_.reset();
181
182   NSWindow* window = [self window];
183   [[window parentWindow] removeChildWindow:window];
184 }
185
186 - (void)windowWillClose:(NSNotification*)notification {
187   [self closeCleanup];
188   [[NSNotificationCenter defaultCenter] removeObserver:self];
189   [self autorelease];
190 }
191
192 // We want this to be a child of a browser window.  addChildWindow:
193 // (called from this function) will bring the window on-screen;
194 // unfortunately, [NSWindowController showWindow:] will also bring it
195 // on-screen (but will cause unexpected changes to the window's
196 // position).  We cannot have an addChildWindow: and a subsequent
197 // showWindow:. Thus, we have our own version.
198 - (void)showWindow:(id)sender {
199   NSWindow* window = [self window];  // Completes nib load.
200   [self updateOriginFromAnchor];
201   [parentWindow_ addChildWindow:window ordered:NSWindowAbove];
202   if (shouldOpenAsKeyWindow_)
203     [window makeKeyAndOrderFront:self];
204   else
205     [window orderFront:nil];
206   [self registerKeyStateEventTap];
207   [self recordAnchorOffset];
208 }
209
210 - (void)close {
211   [self closeCleanup];
212   [super close];
213 }
214
215 // The controller is the delegate of the window so it receives did resign key
216 // notifications. When key is resigned mirror Windows behavior and close the
217 // window.
218 - (void)windowDidResignKey:(NSNotification*)notification {
219   NSWindow* window = [self window];
220   DCHECK_EQ([notification object], window);
221   if ([window isVisible] && [self shouldCloseOnResignKey]) {
222     // If the window isn't visible, it is already closed, and this notification
223     // has been sent as part of the closing operation, so no need to close.
224     [self close];
225   }
226 }
227
228 // Since the bubble shares first responder with its parent window, set
229 // event handlers to dismiss the bubble when it would normally lose key
230 // state.
231 - (void)registerKeyStateEventTap {
232   // Parent key state sharing is only avaiable on 10.7+.
233   if (!base::mac::IsOSLionOrLater())
234     return;
235
236   NSWindow* window = self.window;
237   NSNotification* note =
238       [NSNotification notificationWithName:NSWindowDidResignKeyNotification
239                                     object:window];
240
241   // The eventTap_ catches clicks within the application that are outside the
242   // window.
243   eventTap_ = [NSEvent
244       addLocalMonitorForEventsMatchingMask:NSLeftMouseDownMask
245       handler:^NSEvent* (NSEvent* event) {
246           if (event.window != window) {
247             // Call via the runloop because this block is called in the
248             // middle of event dispatch.
249             [self performSelector:@selector(windowDidResignKey:)
250                        withObject:note
251                        afterDelay:0];
252           }
253           return event;
254       }];
255
256   // The resignationObserver_ watches for when a window resigns key state,
257   // meaning the key window has changed and the bubble should be dismissed.
258   NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
259   resignationObserver_ =
260       [center addObserverForName:NSWindowDidResignKeyNotification
261                           object:nil
262                            queue:[NSOperationQueue mainQueue]
263                       usingBlock:^(NSNotification* notif) {
264                           [self windowDidResignKey:note];
265                       }];
266 }
267
268 // By implementing this, ESC causes the window to go away.
269 - (IBAction)cancel:(id)sender {
270   // This is not a "real" cancel as potential changes to the radio group are not
271   // undone. That's ok.
272   [self close];
273 }
274
275 // Takes the |anchor_| point and adjusts the window's origin accordingly.
276 - (void)updateOriginFromAnchor {
277   NSWindow* window = [self window];
278   NSPoint origin = anchor_;
279
280   switch ([bubble_ alignment]) {
281     case info_bubble::kAlignArrowToAnchor: {
282       NSSize offsets = NSMakeSize(info_bubble::kBubbleArrowXOffset +
283                                   info_bubble::kBubbleArrowWidth / 2.0, 0);
284       offsets = [[parentWindow_ contentView] convertSize:offsets toView:nil];
285       switch ([bubble_ arrowLocation]) {
286         case info_bubble::kTopRight:
287           origin.x -= NSWidth([window frame]) - offsets.width;
288           break;
289         case info_bubble::kTopLeft:
290           origin.x -= offsets.width;
291           break;
292         case info_bubble::kTopCenter:
293           origin.x -= NSWidth([window frame]) / 2.0;
294           break;
295         case info_bubble::kNoArrow:
296           NOTREACHED();
297           break;
298       }
299       break;
300     }
301
302     case info_bubble::kAlignEdgeToAnchorEdge:
303       // If the arrow is to the right then move the origin so that the right
304       // edge aligns with the anchor. If the arrow is to the left then there's
305       // nothing to do because the left edge is already aligned with the left
306       // edge of the anchor.
307       if ([bubble_ arrowLocation] == info_bubble::kTopRight) {
308         origin.x -= NSWidth([window frame]);
309       }
310       break;
311
312     case info_bubble::kAlignRightEdgeToAnchorEdge:
313       origin.x -= NSWidth([window frame]);
314       break;
315
316     case info_bubble::kAlignLeftEdgeToAnchorEdge:
317       // Nothing to do.
318       break;
319
320     default:
321       NOTREACHED();
322   }
323
324   origin.y -= NSHeight([window frame]);
325   [window setFrameOrigin:origin];
326 }
327
328 - (void)activateTabWithContents:(content::WebContents*)newContents
329                previousContents:(content::WebContents*)oldContents
330                         atIndex:(NSInteger)index
331                          reason:(int)reason {
332   // The user switched tabs; close.
333   [self close];
334 }
335
336 @end  // BaseBubbleController