Fix mouse warp with 2x displays
[chromium-blink-merge.git] / remoting / host / disconnect_window_mac.mm
blob623c3af49f81c7f5b1c38f955f5de37bed5b51f9
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.
5 #import <Cocoa/Cocoa.h>
7 #import "remoting/host/disconnect_window_mac.h"
9 #include "base/bind.h"
10 #include "base/compiler_specific.h"
11 #include "base/i18n/rtl.h"
12 #include "base/memory/weak_ptr.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/sys_string_conversions.h"
15 #include "remoting/base/string_resources.h"
16 #include "remoting/host/client_session_control.h"
17 #include "remoting/host/host_window.h"
18 #include "ui/base/l10n/l10n_util_mac.h"
20 @interface DisconnectWindowController()
21 - (BOOL)isRToL;
22 - (void)Hide;
23 @end
25 const int kMaximumConnectedNameWidthInPixels = 600;
27 namespace remoting {
29 class DisconnectWindowMac : public HostWindow {
30  public:
31   DisconnectWindowMac();
32   ~DisconnectWindowMac() override;
34   // HostWindow overrides.
35   void Start(const base::WeakPtr<ClientSessionControl>& client_session_control)
36       override;
38  private:
39   DisconnectWindowController* window_controller_;
41   DISALLOW_COPY_AND_ASSIGN(DisconnectWindowMac);
44 DisconnectWindowMac::DisconnectWindowMac()
45     : window_controller_(nil) {
48 DisconnectWindowMac::~DisconnectWindowMac() {
49   DCHECK(CalledOnValidThread());
51   // DisconnectWindowController is responsible for releasing itself in its
52   // windowWillClose: method.
53   [window_controller_ Hide];
54   window_controller_ = nil;
57 void DisconnectWindowMac::Start(
58     const base::WeakPtr<ClientSessionControl>& client_session_control) {
59   DCHECK(CalledOnValidThread());
60   DCHECK(client_session_control);
61   DCHECK(window_controller_ == nil);
63   // Create the window.
64   base::Closure disconnect_callback =
65       base::Bind(&ClientSessionControl::DisconnectSession,
66                  client_session_control);
67   std::string client_jid = client_session_control->client_jid();
68   std::string username = client_jid.substr(0, client_jid.find('/'));
69   window_controller_ =
70       [[DisconnectWindowController alloc] initWithCallback:disconnect_callback
71                                                   username:username];
72   [window_controller_ showWindow:nil];
75 // static
76 scoped_ptr<HostWindow> HostWindow::CreateDisconnectWindow() {
77   return make_scoped_ptr(new DisconnectWindowMac());
80 }  // namespace remoting
82 @implementation DisconnectWindowController
83 - (id)initWithCallback:(const base::Closure&)disconnect_callback
84               username:(const std::string&)username {
85   self = [super initWithWindowNibName:@"disconnect_window"];
86   if (self) {
87     disconnect_callback_ = disconnect_callback;
88     username_ = base::UTF8ToUTF16(username);
89   }
90   return self;
93 - (void)dealloc {
94   [super dealloc];
97 - (IBAction)stopSharing:(id)sender {
98   if (!disconnect_callback_.is_null()) {
99     disconnect_callback_.Run();
100   }
103 - (BOOL)isRToL {
104   return base::i18n::IsRTL();
107 - (void)Hide {
108   disconnect_callback_.Reset();
109   [self close];
112 - (void)windowDidLoad {
113   [connectedToField_ setStringValue:l10n_util::GetNSStringF(IDS_MESSAGE_SHARED,
114                                                             username_)];
115   [disconnectButton_ setTitle:l10n_util::GetNSString(IDS_STOP_SHARING_BUTTON)];
117   // Resize the window dynamically based on the content.
118   CGFloat oldConnectedWidth = NSWidth([connectedToField_ bounds]);
119   [connectedToField_ sizeToFit];
120   NSRect connectedToFrame = [connectedToField_ frame];
121   CGFloat newConnectedWidth = NSWidth(connectedToFrame);
123   // Set a max width for the connected to text field.
124   if (newConnectedWidth > kMaximumConnectedNameWidthInPixels) {
125     newConnectedWidth = kMaximumConnectedNameWidthInPixels;
126     connectedToFrame.size.width = newConnectedWidth;
127     [connectedToField_ setFrame:connectedToFrame];
128   }
130   CGFloat oldDisconnectWidth = NSWidth([disconnectButton_ bounds]);
131   [disconnectButton_ sizeToFit];
132   NSRect disconnectFrame = [disconnectButton_ frame];
133   CGFloat newDisconnectWidth = NSWidth(disconnectFrame);
135   // Move the disconnect button appropriately.
136   disconnectFrame.origin.x += newConnectedWidth - oldConnectedWidth;
137   [disconnectButton_ setFrame:disconnectFrame];
139   // Then resize the window appropriately
140   NSWindow *window = [self window];
141   NSRect windowFrame = [window frame];
142   windowFrame.size.width += (newConnectedWidth - oldConnectedWidth +
143                              newDisconnectWidth - oldDisconnectWidth);
144   [window setFrame:windowFrame display:NO];
146   if ([self isRToL]) {
147     // Handle right to left case
148     CGFloat buttonInset = NSWidth(windowFrame) - NSMaxX(disconnectFrame);
149     CGFloat buttonTextSpacing
150         = NSMinX(disconnectFrame) - NSMaxX(connectedToFrame);
151     disconnectFrame.origin.x = buttonInset;
152     connectedToFrame.origin.x = NSMaxX(disconnectFrame) + buttonTextSpacing;
153     [connectedToField_ setFrame:connectedToFrame];
154     [disconnectButton_ setFrame:disconnectFrame];
155   }
157   // Center the window at the bottom of the screen, above the dock (if present).
158   NSRect desktopRect = [[NSScreen mainScreen] visibleFrame];
159   NSRect windowRect = [[self window] frame];
160   CGFloat x = (NSWidth(desktopRect) - NSWidth(windowRect)) / 2;
161   CGFloat y = NSMinY(desktopRect);
162   [[self window] setFrameOrigin:NSMakePoint(x, y)];
165 - (void)windowWillClose:(NSNotification*)notification {
166   [self stopSharing:self];
167   [self autorelease];
170 @end
173 @interface DisconnectWindow()
174 - (BOOL)isRToL;
175 @end
177 @implementation DisconnectWindow
179 - (id)initWithContentRect:(NSRect)contentRect
180                 styleMask:(NSUInteger)aStyle
181                   backing:(NSBackingStoreType)bufferingType
182                   defer:(BOOL)flag {
183   // Pass NSBorderlessWindowMask for the styleMask to remove the title bar.
184   self = [super initWithContentRect:contentRect
185                           styleMask:NSBorderlessWindowMask
186                             backing:bufferingType
187                               defer:flag];
189   if (self) {
190     // Set window to be clear and non-opaque so we can see through it.
191     [self setBackgroundColor:[NSColor clearColor]];
192     [self setOpaque:NO];
193     [self setMovableByWindowBackground:YES];
195     // Pull the window up to Status Level so that it always displays.
196     [self setLevel:NSStatusWindowLevel];
197   }
198   return self;
201 - (BOOL)isRToL {
202   DCHECK([[self windowController] respondsToSelector:@selector(isRToL)]);
203   return [[self windowController] isRToL];
206 @end
209 @interface DisconnectView()
210 - (BOOL)isRToL;
211 @end
213 @implementation DisconnectView
215 - (BOOL)isRToL {
216   DCHECK([[self window] isKindOfClass:[DisconnectWindow class]]);
217   return [static_cast<DisconnectWindow*>([self window]) isRToL];
220 - (void)drawRect:(NSRect)rect {
221   // All magic numbers taken from screen shots provided by UX.
222   NSRect bounds = NSInsetRect([self bounds], 1, 1);
224   NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:bounds
225                                                        xRadius:5
226                                                        yRadius:5];
227   NSColor *gray = [NSColor colorWithCalibratedWhite:0.91 alpha:1.0];
228   [gray setFill];
229   [path fill];
230   [path setLineWidth:4];
231   NSColor *green = [NSColor colorWithCalibratedRed:0.13
232                                              green:0.69
233                                               blue:0.11
234                                              alpha:1.0];
235   [green setStroke];
236   [path stroke];
239   // Draw drag handle on proper side
240   const CGFloat kHeight = 21.0;
241   const CGFloat kBaseInset = 12.0;
242   const CGFloat kDragHandleWidth = 5.0;
244   NSColor *dark = [NSColor colorWithCalibratedWhite:0.70 alpha:1.0];
245   NSColor *light = [NSColor colorWithCalibratedWhite:0.97 alpha:1.0];
247   // Turn off aliasing so it's nice and crisp.
248   NSGraphicsContext *context = [NSGraphicsContext currentContext];
249   BOOL alias = [context shouldAntialias];
250   [context setShouldAntialias:NO];
252   // Handle bidirectional locales properly.
253   CGFloat inset = [self isRToL] ? NSMaxX(bounds) - kBaseInset - kDragHandleWidth
254                                 : kBaseInset;
256   NSPoint top = NSMakePoint(inset, NSMidY(bounds) - kHeight / 2.0);
257   NSPoint bottom = NSMakePoint(inset, top.y + kHeight);
259   path = [NSBezierPath bezierPath];
260   [path moveToPoint:top];
261   [path lineToPoint:bottom];
262   [dark setStroke];
263   [path stroke];
265   top.x += 1;
266   bottom.x += 1;
267   path = [NSBezierPath bezierPath];
268   [path moveToPoint:top];
269   [path lineToPoint:bottom];
270   [light setStroke];
271   [path stroke];
273   top.x += 2;
274   bottom.x += 2;
275   path = [NSBezierPath bezierPath];
276   [path moveToPoint:top];
277   [path lineToPoint:bottom];
278   [dark setStroke];
279   [path stroke];
281   top.x += 1;
282   bottom.x += 1;
283   path = [NSBezierPath bezierPath];
284   [path moveToPoint:top];
285   [path lineToPoint:bottom];
286   [light setStroke];
287   [path stroke];
289   [context setShouldAntialias:alias];
292 @end