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