1 // Copyright 2014 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 #include "ui/views/widget/native_widget_mac.h" 6 7 #import <Cocoa/Cocoa.h> 8 9 #include "base/mac/foundation_util.h" 10 #include "base/mac/scoped_nsobject.h" 11 #include "base/strings/sys_string_conversions.h" 12 #include "ui/gfx/font_list.h" 13 #import "ui/gfx/mac/coordinate_conversion.h" 14 #include "ui/native_theme/native_theme.h" 15 #import "ui/views/cocoa/bridged_content_view.h" 16 #import "ui/views/cocoa/bridged_native_widget.h" 17 #import "ui/views/cocoa/views_nswindow_delegate.h" 18 #include "ui/views/window/native_frame_view.h" 19 20 @interface NativeWidgetMacNSWindow : NSWindow 21 @end 22 23 @implementation NativeWidgetMacNSWindow 24 25 // Override canBecome{Key,Main}Window to always return YES, otherwise Windows 26 // with a styleMask of NSBorderlessWindowMask default to NO. 27 - (BOOL)canBecomeKeyWindow { 28 return YES; 29 } 30 31 - (BOOL)canBecomeMainWindow { 32 return YES; 33 } 34 35 @end 36 37 namespace views { 38 namespace { 39 40 NSInteger StyleMaskForParams(const Widget::InitParams& params) { 41 // TODO(tapted): Determine better masks when there are use cases for it. 42 if (params.remove_standard_frame) 43 return NSBorderlessWindowMask; 44 45 if (params.type == Widget::InitParams::TYPE_WINDOW) { 46 return NSTitledWindowMask | NSClosableWindowMask | 47 NSMiniaturizableWindowMask | NSResizableWindowMask; 48 } 49 return NSBorderlessWindowMask; 50 } 51 52 NSRect ValidateContentRect(NSRect content_rect) { 53 // A contentRect with zero width or height is a banned practice in Chrome, due 54 // to unpredictable OSX treatment. For now, silently give a minimum dimension. 55 // TODO(tapted): Add a DCHECK, or add emulation logic (e.g. to auto-hide). 56 if (NSWidth(content_rect) == 0) 57 content_rect.size.width = 1; 58 59 if (NSHeight(content_rect) == 0) 60 content_rect.size.height = 1; 61 62 return content_rect; 63 } 64 65 gfx::Size WindowSizeForClientAreaSize(NSWindow* window, const gfx::Size& size) { 66 NSRect content_rect = NSMakeRect(0, 0, size.width(), size.height()); 67 NSRect frame_rect = [window frameRectForContentRect:content_rect]; 68 return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect)); 69 } 70 71 } // namespace 72 73 //////////////////////////////////////////////////////////////////////////////// 74 // NativeWidgetMac, public: 75 76 NativeWidgetMac::NativeWidgetMac(internal::NativeWidgetDelegate* delegate) 77 : delegate_(delegate), 78 bridge_(new BridgedNativeWidget(this)), 79 ownership_(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) { 80 } 81 82 NativeWidgetMac::~NativeWidgetMac() { 83 if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) 84 delete delegate_; 85 else 86 CloseNow(); 87 } 88 89 void NativeWidgetMac::OnWindowWillClose() { 90 delegate_->OnNativeWidgetDestroying(); 91 // Note: If closed via CloseNow(), |bridge_| will already be reset. If closed 92 // by the user, or via Close() and a RunLoop, this will reset it. 93 bridge_.reset(); 94 delegate_->OnNativeWidgetDestroyed(); 95 if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) 96 delete this; 97 } 98 99 //////////////////////////////////////////////////////////////////////////////// 100 // NativeWidgetMac, internal::NativeWidgetPrivate implementation: 101 102 void NativeWidgetMac::InitNativeWidget(const Widget::InitParams& params) { 103 ownership_ = params.ownership; 104 105 NSInteger style_mask = StyleMaskForParams(params); 106 NSRect content_rect = ValidateContentRect( 107 [NSWindow contentRectForFrameRect:gfx::ScreenRectToNSRect(params.bounds) 108 styleMask:style_mask]); 109 110 base::scoped_nsobject<NSWindow> window([[NativeWidgetMacNSWindow alloc] 111 initWithContentRect:content_rect 112 styleMask:style_mask 113 backing:NSBackingStoreBuffered 114 defer:YES]); 115 [window setReleasedWhenClosed:NO]; // Owned by scoped_nsobject. 116 bridge_->Init(window, params); 117 118 delegate_->OnNativeWidgetCreated(true); 119 120 bridge_->SetFocusManager(GetWidget()->GetFocusManager()); 121 122 DCHECK(GetWidget()->GetRootView()); 123 bridge_->SetRootView(GetWidget()->GetRootView()); 124 } 125 126 NonClientFrameView* NativeWidgetMac::CreateNonClientFrameView() { 127 return new NativeFrameView(GetWidget()); 128 } 129 130 bool NativeWidgetMac::ShouldUseNativeFrame() const { 131 return true; 132 } 133 134 bool NativeWidgetMac::ShouldWindowContentsBeTransparent() const { 135 NOTIMPLEMENTED(); 136 return false; 137 } 138 139 void NativeWidgetMac::FrameTypeChanged() { 140 NOTIMPLEMENTED(); 141 } 142 143 Widget* NativeWidgetMac::GetWidget() { 144 return delegate_->AsWidget(); 145 } 146 147 const Widget* NativeWidgetMac::GetWidget() const { 148 return delegate_->AsWidget(); 149 } 150 151 gfx::NativeView NativeWidgetMac::GetNativeView() const { 152 // Returns a BridgedContentView, unless there is no views::RootView set. 153 return [GetNativeWindow() contentView]; 154 } 155 156 gfx::NativeWindow NativeWidgetMac::GetNativeWindow() const { 157 return bridge_ ? bridge_->ns_window() : nil; 158 } 159 160 Widget* NativeWidgetMac::GetTopLevelWidget() { 161 NativeWidgetPrivate* native_widget = GetTopLevelNativeWidget(GetNativeView()); 162 return native_widget ? native_widget->GetWidget() : NULL; 163 } 164 165 const ui::Compositor* NativeWidgetMac::GetCompositor() const { 166 NOTIMPLEMENTED(); 167 return NULL; 168 } 169 170 ui::Compositor* NativeWidgetMac::GetCompositor() { 171 NOTIMPLEMENTED(); 172 return NULL; 173 } 174 175 ui::Layer* NativeWidgetMac::GetLayer() { 176 NOTIMPLEMENTED(); 177 return NULL; 178 } 179 180 void NativeWidgetMac::ReorderNativeViews() { 181 if (bridge_) 182 bridge_->SetRootView(GetWidget()->GetRootView()); 183 } 184 185 void NativeWidgetMac::ViewRemoved(View* view) { 186 NOTIMPLEMENTED(); 187 } 188 189 void NativeWidgetMac::SetNativeWindowProperty(const char* name, void* value) { 190 NOTIMPLEMENTED(); 191 } 192 193 void* NativeWidgetMac::GetNativeWindowProperty(const char* name) const { 194 NOTIMPLEMENTED(); 195 return NULL; 196 } 197 198 TooltipManager* NativeWidgetMac::GetTooltipManager() const { 199 NOTIMPLEMENTED(); 200 return NULL; 201 } 202 203 void NativeWidgetMac::SetCapture() { 204 NOTIMPLEMENTED(); 205 } 206 207 void NativeWidgetMac::ReleaseCapture() { 208 NOTIMPLEMENTED(); 209 } 210 211 bool NativeWidgetMac::HasCapture() const { 212 NOTIMPLEMENTED(); 213 return false; 214 } 215 216 InputMethod* NativeWidgetMac::CreateInputMethod() { 217 return bridge_ ? bridge_->CreateInputMethod() : NULL; 218 } 219 220 internal::InputMethodDelegate* NativeWidgetMac::GetInputMethodDelegate() { 221 return bridge_.get(); 222 } 223 224 ui::InputMethod* NativeWidgetMac::GetHostInputMethod() { 225 return bridge_ ? bridge_->GetHostInputMethod() : NULL; 226 } 227 228 void NativeWidgetMac::CenterWindow(const gfx::Size& size) { 229 SetSize(WindowSizeForClientAreaSize(GetNativeWindow(), size)); 230 // Note that this is not the precise center of screen, but it is the standard 231 // location for windows like dialogs to appear on screen for Mac. 232 // TODO(tapted): If there is a parent window, center in that instead. 233 [GetNativeWindow() center]; 234 } 235 236 void NativeWidgetMac::GetWindowPlacement(gfx::Rect* bounds, 237 ui::WindowShowState* maximized) const { 238 NOTIMPLEMENTED(); 239 } 240 241 bool NativeWidgetMac::SetWindowTitle(const base::string16& title) { 242 NSWindow* window = GetNativeWindow(); 243 NSString* current_title = [window title]; 244 NSString* new_title = SysUTF16ToNSString(title); 245 if ([current_title isEqualToString:new_title]) 246 return false; 247 248 [window setTitle:new_title]; 249 return true; 250 } 251 252 void NativeWidgetMac::SetWindowIcons(const gfx::ImageSkia& window_icon, 253 const gfx::ImageSkia& app_icon) { 254 NOTIMPLEMENTED(); 255 } 256 257 void NativeWidgetMac::InitModalType(ui::ModalType modal_type) { 258 NOTIMPLEMENTED(); 259 } 260 261 gfx::Rect NativeWidgetMac::GetWindowBoundsInScreen() const { 262 return gfx::ScreenRectFromNSRect([GetNativeWindow() frame]); 263 } 264 265 gfx::Rect NativeWidgetMac::GetClientAreaBoundsInScreen() const { 266 NSWindow* window = GetNativeWindow(); 267 return gfx::ScreenRectFromNSRect( 268 [window contentRectForFrameRect:[window frame]]); 269 } 270 271 gfx::Rect NativeWidgetMac::GetRestoredBounds() const { 272 NOTIMPLEMENTED(); 273 return gfx::Rect(); 274 } 275 276 void NativeWidgetMac::SetBounds(const gfx::Rect& bounds) { 277 [GetNativeWindow() setFrame:gfx::ScreenRectToNSRect(bounds) 278 display:YES 279 animate:NO]; 280 } 281 282 void NativeWidgetMac::SetSize(const gfx::Size& size) { 283 // Ensure the top-left corner stays in-place (rather than the bottom-left, 284 // which -[NSWindow setContentSize:] would do). 285 SetBounds(gfx::Rect(GetWindowBoundsInScreen().origin(), size)); 286 } 287 288 void NativeWidgetMac::StackAbove(gfx::NativeView native_view) { 289 NOTIMPLEMENTED(); 290 } 291 292 void NativeWidgetMac::StackAtTop() { 293 NOTIMPLEMENTED(); 294 } 295 296 void NativeWidgetMac::StackBelow(gfx::NativeView native_view) { 297 NOTIMPLEMENTED(); 298 } 299 300 void NativeWidgetMac::SetShape(gfx::NativeRegion shape) { 301 NOTIMPLEMENTED(); 302 } 303 304 void NativeWidgetMac::Close() { 305 NSWindow* window = GetNativeWindow(); 306 // Calling performClose: will momentarily highlight the close button, but 307 // AppKit will reject it if there is no close button. 308 SEL close_selector = ([window styleMask] & NSClosableWindowMask) 309 ? @selector(performClose:) 310 : @selector(close); 311 [window performSelector:close_selector withObject:nil afterDelay:0]; 312 } 313 314 void NativeWidgetMac::CloseNow() { 315 // Reset |bridge_| to NULL before destroying it. 316 scoped_ptr<BridgedNativeWidget> bridge(bridge_.Pass()); 317 } 318 319 void NativeWidgetMac::Show() { 320 ShowWithWindowState(ui::SHOW_STATE_NORMAL); 321 } 322 323 void NativeWidgetMac::Hide() { 324 NOTIMPLEMENTED(); 325 } 326 327 void NativeWidgetMac::ShowMaximizedWithBounds( 328 const gfx::Rect& restored_bounds) { 329 NOTIMPLEMENTED(); 330 } 331 332 void NativeWidgetMac::ShowWithWindowState(ui::WindowShowState state) { 333 switch (state) { 334 case ui::SHOW_STATE_DEFAULT: 335 case ui::SHOW_STATE_NORMAL: 336 case ui::SHOW_STATE_INACTIVE: 337 break; 338 case ui::SHOW_STATE_MINIMIZED: 339 case ui::SHOW_STATE_MAXIMIZED: 340 case ui::SHOW_STATE_FULLSCREEN: 341 NOTIMPLEMENTED(); 342 break; 343 case ui::SHOW_STATE_END: 344 NOTREACHED(); 345 break; 346 } 347 if (state == ui::SHOW_STATE_INACTIVE) { 348 if (!IsVisible()) 349 [GetNativeWindow() orderBack:nil]; 350 } else { 351 Activate(); 352 } 353 } 354 355 bool NativeWidgetMac::IsVisible() const { 356 return [GetNativeWindow() isVisible]; 357 } 358 359 void NativeWidgetMac::Activate() { 360 [GetNativeWindow() makeKeyAndOrderFront:nil]; 361 [NSApp activateIgnoringOtherApps:YES]; 362 } 363 364 void NativeWidgetMac::Deactivate() { 365 NOTIMPLEMENTED(); 366 } 367 368 bool NativeWidgetMac::IsActive() const { 369 // To behave like ::GetActiveWindow on Windows, IsActive() must return the 370 // "active" window attached to the calling application. NSWindow provides 371 // -isKeyWindow and -isMainWindow, but these are system-wide and update 372 // asynchronously. A window can not be main or key on Mac without the 373 // application being active. 374 // Here, define the active window as the frontmost visible window in the 375 // application. 376 // Note that this might not be the keyWindow, even when Chrome is active. 377 // Also note that -[NSApplication orderedWindows] excludes panels and other 378 // "unscriptable" windows, but includes invisible windows. 379 if (!IsVisible()) 380 return false; 381 382 NSWindow* window = GetNativeWindow(); 383 for (NSWindow* other_window in [NSApp orderedWindows]) { 384 if ([window isEqual:other_window]) 385 return true; 386 387 if ([other_window isVisible]) 388 return false; 389 } 390 391 return false; 392 } 393 394 void NativeWidgetMac::SetAlwaysOnTop(bool always_on_top) { 395 NOTIMPLEMENTED(); 396 } 397 398 bool NativeWidgetMac::IsAlwaysOnTop() const { 399 NOTIMPLEMENTED(); 400 return false; 401 } 402 403 void NativeWidgetMac::SetVisibleOnAllWorkspaces(bool always_visible) { 404 NOTIMPLEMENTED(); 405 } 406 407 void NativeWidgetMac::Maximize() { 408 NOTIMPLEMENTED(); 409 } 410 411 void NativeWidgetMac::Minimize() { 412 NOTIMPLEMENTED(); 413 } 414 415 bool NativeWidgetMac::IsMaximized() const { 416 NOTIMPLEMENTED(); 417 return false; 418 } 419 420 bool NativeWidgetMac::IsMinimized() const { 421 NOTIMPLEMENTED(); 422 return false; 423 } 424 425 void NativeWidgetMac::Restore() { 426 NOTIMPLEMENTED(); 427 } 428 429 void NativeWidgetMac::SetFullscreen(bool fullscreen) { 430 NOTIMPLEMENTED(); 431 } 432 433 bool NativeWidgetMac::IsFullscreen() const { 434 NOTIMPLEMENTED(); 435 return false; 436 } 437 438 void NativeWidgetMac::SetOpacity(unsigned char opacity) { 439 NOTIMPLEMENTED(); 440 } 441 442 void NativeWidgetMac::SetUseDragFrame(bool use_drag_frame) { 443 NOTIMPLEMENTED(); 444 } 445 446 void NativeWidgetMac::FlashFrame(bool flash_frame) { 447 NOTIMPLEMENTED(); 448 } 449 450 void NativeWidgetMac::RunShellDrag(View* view, 451 const ui::OSExchangeData& data, 452 const gfx::Point& location, 453 int operation, 454 ui::DragDropTypes::DragEventSource source) { 455 NOTIMPLEMENTED(); 456 } 457 458 void NativeWidgetMac::SchedulePaintInRect(const gfx::Rect& rect) { 459 // TODO(tapted): This should use setNeedsDisplayInRect:, once the coordinate 460 // system of |rect| has been converted. 461 [GetNativeView() setNeedsDisplay:YES]; 462 } 463 464 void NativeWidgetMac::SetCursor(gfx::NativeCursor cursor) { 465 NOTIMPLEMENTED(); 466 } 467 468 bool NativeWidgetMac::IsMouseEventsEnabled() const { 469 NOTIMPLEMENTED(); 470 return true; 471 } 472 473 void NativeWidgetMac::ClearNativeFocus() { 474 NOTIMPLEMENTED(); 475 } 476 477 gfx::Rect NativeWidgetMac::GetWorkAreaBoundsInScreen() const { 478 NOTIMPLEMENTED(); 479 return gfx::Rect(); 480 } 481 482 Widget::MoveLoopResult NativeWidgetMac::RunMoveLoop( 483 const gfx::Vector2d& drag_offset, 484 Widget::MoveLoopSource source, 485 Widget::MoveLoopEscapeBehavior escape_behavior) { 486 NOTIMPLEMENTED(); 487 return Widget::MOVE_LOOP_CANCELED; 488 } 489 490 void NativeWidgetMac::EndMoveLoop() { 491 NOTIMPLEMENTED(); 492 } 493 494 void NativeWidgetMac::SetVisibilityChangedAnimationsEnabled(bool value) { 495 NOTIMPLEMENTED(); 496 } 497 498 ui::NativeTheme* NativeWidgetMac::GetNativeTheme() const { 499 return ui::NativeTheme::instance(); 500 } 501 502 void NativeWidgetMac::OnRootViewLayout() { 503 NOTIMPLEMENTED(); 504 } 505 506 bool NativeWidgetMac::IsTranslucentWindowOpacitySupported() const { 507 return false; 508 } 509 510 void NativeWidgetMac::OnSizeConstraintsChanged() { 511 NOTIMPLEMENTED(); 512 } 513 514 void NativeWidgetMac::RepostNativeEvent(gfx::NativeEvent native_event) { 515 NOTIMPLEMENTED(); 516 } 517 518 //////////////////////////////////////////////////////////////////////////////// 519 // Widget, public: 520 521 bool Widget::ConvertRect(const Widget* source, 522 const Widget* target, 523 gfx::Rect* rect) { 524 return false; 525 } 526 527 namespace internal { 528 529 //////////////////////////////////////////////////////////////////////////////// 530 // internal::NativeWidgetPrivate, public: 531 532 // static 533 NativeWidgetPrivate* NativeWidgetPrivate::CreateNativeWidget( 534 internal::NativeWidgetDelegate* delegate) { 535 return new NativeWidgetMac(delegate); 536 } 537 538 // static 539 NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeView( 540 gfx::NativeView native_view) { 541 return GetNativeWidgetForNativeWindow([native_view window]); 542 } 543 544 // static 545 NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeWindow( 546 gfx::NativeWindow native_window) { 547 id<NSWindowDelegate> window_delegate = [native_window delegate]; 548 if ([window_delegate respondsToSelector:@selector(nativeWidgetMac)]) { 549 ViewsNSWindowDelegate* delegate = 550 base::mac::ObjCCastStrict<ViewsNSWindowDelegate>(window_delegate); 551 return [delegate nativeWidgetMac]; 552 } 553 return NULL; // Not created by NativeWidgetMac. 554 } 555 556 // static 557 NativeWidgetPrivate* NativeWidgetPrivate::GetTopLevelNativeWidget( 558 gfx::NativeView native_view) { 559 NativeWidgetPrivate* native_widget = 560 GetNativeWidgetForNativeView(native_view); 561 if (!native_widget) 562 return NULL; 563 564 for (NativeWidgetPrivate* parent; 565 (parent = GetNativeWidgetForNativeWindow( 566 [native_widget->GetNativeWindow() parentWindow])); 567 native_widget = parent) { 568 } 569 return native_widget; 570 } 571 572 // static 573 void NativeWidgetPrivate::GetAllChildWidgets(gfx::NativeView native_view, 574 Widget::Widgets* children) { 575 NativeWidgetPrivate* native_widget = 576 GetNativeWidgetForNativeView(native_view); 577 if (!native_widget) 578 return; 579 580 // Code expects widget for |native_view| to be added to |children|. 581 if (native_widget->GetWidget()) 582 children->insert(native_widget->GetWidget()); 583 584 for (NSWindow* child_window : [native_widget->GetNativeWindow() childWindows]) 585 GetAllChildWidgets([child_window contentView], children); 586 } 587 588 // static 589 void NativeWidgetPrivate::GetAllOwnedWidgets(gfx::NativeView native_view, 590 Widget::Widgets* owned) { 591 NOTIMPLEMENTED(); 592 } 593 594 // static 595 void NativeWidgetPrivate::ReparentNativeView(gfx::NativeView native_view, 596 gfx::NativeView new_parent) { 597 NOTIMPLEMENTED(); 598 } 599 600 // static 601 bool NativeWidgetPrivate::IsMouseButtonDown() { 602 return [NSEvent pressedMouseButtons] != 0; 603 } 604 605 // static 606 gfx::FontList NativeWidgetPrivate::GetWindowTitleFontList() { 607 NOTIMPLEMENTED(); 608 return gfx::FontList(); 609 } 610 611 } // namespace internal 612 } // namespace views 613