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