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 37namespace views { 38namespace { 39 40NSInteger 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 52NSRect 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 65gfx::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 76NativeWidgetMac::NativeWidgetMac(internal::NativeWidgetDelegate* delegate) 77 : delegate_(delegate), 78 bridge_(new BridgedNativeWidget(this)), 79 ownership_(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) { 80} 81 82NativeWidgetMac::~NativeWidgetMac() { 83 if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) 84 delete delegate_; 85 else 86 CloseNow(); 87} 88 89void 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 102void 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 126NonClientFrameView* NativeWidgetMac::CreateNonClientFrameView() { 127 return new NativeFrameView(GetWidget()); 128} 129 130bool NativeWidgetMac::ShouldUseNativeFrame() const { 131 return true; 132} 133 134bool NativeWidgetMac::ShouldWindowContentsBeTransparent() const { 135 NOTIMPLEMENTED(); 136 return false; 137} 138 139void NativeWidgetMac::FrameTypeChanged() { 140 NOTIMPLEMENTED(); 141} 142 143Widget* NativeWidgetMac::GetWidget() { 144 return delegate_->AsWidget(); 145} 146 147const Widget* NativeWidgetMac::GetWidget() const { 148 return delegate_->AsWidget(); 149} 150 151gfx::NativeView NativeWidgetMac::GetNativeView() const { 152 // Returns a BridgedContentView, unless there is no views::RootView set. 153 return [GetNativeWindow() contentView]; 154} 155 156gfx::NativeWindow NativeWidgetMac::GetNativeWindow() const { 157 return bridge_ ? bridge_->ns_window() : nil; 158} 159 160Widget* NativeWidgetMac::GetTopLevelWidget() { 161 NativeWidgetPrivate* native_widget = GetTopLevelNativeWidget(GetNativeView()); 162 return native_widget ? native_widget->GetWidget() : NULL; 163} 164 165const ui::Compositor* NativeWidgetMac::GetCompositor() const { 166 NOTIMPLEMENTED(); 167 return NULL; 168} 169 170ui::Compositor* NativeWidgetMac::GetCompositor() { 171 NOTIMPLEMENTED(); 172 return NULL; 173} 174 175ui::Layer* NativeWidgetMac::GetLayer() { 176 NOTIMPLEMENTED(); 177 return NULL; 178} 179 180void NativeWidgetMac::ReorderNativeViews() { 181 if (bridge_) 182 bridge_->SetRootView(GetWidget()->GetRootView()); 183} 184 185void NativeWidgetMac::ViewRemoved(View* view) { 186 NOTIMPLEMENTED(); 187} 188 189void NativeWidgetMac::SetNativeWindowProperty(const char* name, void* value) { 190 NOTIMPLEMENTED(); 191} 192 193void* NativeWidgetMac::GetNativeWindowProperty(const char* name) const { 194 NOTIMPLEMENTED(); 195 return NULL; 196} 197 198TooltipManager* NativeWidgetMac::GetTooltipManager() const { 199 NOTIMPLEMENTED(); 200 return NULL; 201} 202 203void NativeWidgetMac::SetCapture() { 204 NOTIMPLEMENTED(); 205} 206 207void NativeWidgetMac::ReleaseCapture() { 208 NOTIMPLEMENTED(); 209} 210 211bool NativeWidgetMac::HasCapture() const { 212 NOTIMPLEMENTED(); 213 return false; 214} 215 216InputMethod* NativeWidgetMac::CreateInputMethod() { 217 return bridge_ ? bridge_->CreateInputMethod() : NULL; 218} 219 220internal::InputMethodDelegate* NativeWidgetMac::GetInputMethodDelegate() { 221 return bridge_.get(); 222} 223 224ui::InputMethod* NativeWidgetMac::GetHostInputMethod() { 225 return bridge_ ? bridge_->GetHostInputMethod() : NULL; 226} 227 228void 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 236void NativeWidgetMac::GetWindowPlacement(gfx::Rect* bounds, 237 ui::WindowShowState* maximized) const { 238 NOTIMPLEMENTED(); 239} 240 241bool 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 252void NativeWidgetMac::SetWindowIcons(const gfx::ImageSkia& window_icon, 253 const gfx::ImageSkia& app_icon) { 254 NOTIMPLEMENTED(); 255} 256 257void NativeWidgetMac::InitModalType(ui::ModalType modal_type) { 258 NOTIMPLEMENTED(); 259} 260 261gfx::Rect NativeWidgetMac::GetWindowBoundsInScreen() const { 262 return gfx::ScreenRectFromNSRect([GetNativeWindow() frame]); 263} 264 265gfx::Rect NativeWidgetMac::GetClientAreaBoundsInScreen() const { 266 NSWindow* window = GetNativeWindow(); 267 return gfx::ScreenRectFromNSRect( 268 [window contentRectForFrameRect:[window frame]]); 269} 270 271gfx::Rect NativeWidgetMac::GetRestoredBounds() const { 272 NOTIMPLEMENTED(); 273 return gfx::Rect(); 274} 275 276void NativeWidgetMac::SetBounds(const gfx::Rect& bounds) { 277 [GetNativeWindow() setFrame:gfx::ScreenRectToNSRect(bounds) 278 display:YES 279 animate:NO]; 280} 281 282void 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 288void NativeWidgetMac::StackAbove(gfx::NativeView native_view) { 289 NOTIMPLEMENTED(); 290} 291 292void NativeWidgetMac::StackAtTop() { 293 NOTIMPLEMENTED(); 294} 295 296void NativeWidgetMac::StackBelow(gfx::NativeView native_view) { 297 NOTIMPLEMENTED(); 298} 299 300void NativeWidgetMac::SetShape(gfx::NativeRegion shape) { 301 NOTIMPLEMENTED(); 302} 303 304void 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 314void NativeWidgetMac::CloseNow() { 315 // Reset |bridge_| to NULL before destroying it. 316 scoped_ptr<BridgedNativeWidget> bridge(bridge_.Pass()); 317} 318 319void NativeWidgetMac::Show() { 320 ShowWithWindowState(ui::SHOW_STATE_NORMAL); 321} 322 323void NativeWidgetMac::Hide() { 324 NOTIMPLEMENTED(); 325} 326 327void NativeWidgetMac::ShowMaximizedWithBounds( 328 const gfx::Rect& restored_bounds) { 329 NOTIMPLEMENTED(); 330} 331 332void 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 355bool NativeWidgetMac::IsVisible() const { 356 return [GetNativeWindow() isVisible]; 357} 358 359void NativeWidgetMac::Activate() { 360 [GetNativeWindow() makeKeyAndOrderFront:nil]; 361 [NSApp activateIgnoringOtherApps:YES]; 362} 363 364void NativeWidgetMac::Deactivate() { 365 NOTIMPLEMENTED(); 366} 367 368bool 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 394void NativeWidgetMac::SetAlwaysOnTop(bool always_on_top) { 395 NOTIMPLEMENTED(); 396} 397 398bool NativeWidgetMac::IsAlwaysOnTop() const { 399 NOTIMPLEMENTED(); 400 return false; 401} 402 403void NativeWidgetMac::SetVisibleOnAllWorkspaces(bool always_visible) { 404 NOTIMPLEMENTED(); 405} 406 407void NativeWidgetMac::Maximize() { 408 NOTIMPLEMENTED(); 409} 410 411void NativeWidgetMac::Minimize() { 412 NOTIMPLEMENTED(); 413} 414 415bool NativeWidgetMac::IsMaximized() const { 416 NOTIMPLEMENTED(); 417 return false; 418} 419 420bool NativeWidgetMac::IsMinimized() const { 421 NOTIMPLEMENTED(); 422 return false; 423} 424 425void NativeWidgetMac::Restore() { 426 NOTIMPLEMENTED(); 427} 428 429void NativeWidgetMac::SetFullscreen(bool fullscreen) { 430 NOTIMPLEMENTED(); 431} 432 433bool NativeWidgetMac::IsFullscreen() const { 434 NOTIMPLEMENTED(); 435 return false; 436} 437 438void NativeWidgetMac::SetOpacity(unsigned char opacity) { 439 NOTIMPLEMENTED(); 440} 441 442void NativeWidgetMac::SetUseDragFrame(bool use_drag_frame) { 443 NOTIMPLEMENTED(); 444} 445 446void NativeWidgetMac::FlashFrame(bool flash_frame) { 447 NOTIMPLEMENTED(); 448} 449 450void 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 458void 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 464void NativeWidgetMac::SetCursor(gfx::NativeCursor cursor) { 465 NOTIMPLEMENTED(); 466} 467 468bool NativeWidgetMac::IsMouseEventsEnabled() const { 469 NOTIMPLEMENTED(); 470 return true; 471} 472 473void NativeWidgetMac::ClearNativeFocus() { 474 NOTIMPLEMENTED(); 475} 476 477gfx::Rect NativeWidgetMac::GetWorkAreaBoundsInScreen() const { 478 NOTIMPLEMENTED(); 479 return gfx::Rect(); 480} 481 482Widget::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 490void NativeWidgetMac::EndMoveLoop() { 491 NOTIMPLEMENTED(); 492} 493 494void NativeWidgetMac::SetVisibilityChangedAnimationsEnabled(bool value) { 495 NOTIMPLEMENTED(); 496} 497 498ui::NativeTheme* NativeWidgetMac::GetNativeTheme() const { 499 return ui::NativeTheme::instance(); 500} 501 502void NativeWidgetMac::OnRootViewLayout() { 503 NOTIMPLEMENTED(); 504} 505 506bool NativeWidgetMac::IsTranslucentWindowOpacitySupported() const { 507 return false; 508} 509 510void NativeWidgetMac::OnSizeConstraintsChanged() { 511 NOTIMPLEMENTED(); 512} 513 514void NativeWidgetMac::RepostNativeEvent(gfx::NativeEvent native_event) { 515 NOTIMPLEMENTED(); 516} 517 518//////////////////////////////////////////////////////////////////////////////// 519// Widget, public: 520 521bool Widget::ConvertRect(const Widget* source, 522 const Widget* target, 523 gfx::Rect* rect) { 524 return false; 525} 526 527namespace internal { 528 529//////////////////////////////////////////////////////////////////////////////// 530// internal::NativeWidgetPrivate, public: 531 532// static 533NativeWidgetPrivate* NativeWidgetPrivate::CreateNativeWidget( 534 internal::NativeWidgetDelegate* delegate) { 535 return new NativeWidgetMac(delegate); 536} 537 538// static 539NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeView( 540 gfx::NativeView native_view) { 541 return GetNativeWidgetForNativeWindow([native_view window]); 542} 543 544// static 545NativeWidgetPrivate* 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 557NativeWidgetPrivate* 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 573void 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 589void NativeWidgetPrivate::GetAllOwnedWidgets(gfx::NativeView native_view, 590 Widget::Widgets* owned) { 591 NOTIMPLEMENTED(); 592} 593 594// static 595void NativeWidgetPrivate::ReparentNativeView(gfx::NativeView native_view, 596 gfx::NativeView new_parent) { 597 NOTIMPLEMENTED(); 598} 599 600// static 601bool NativeWidgetPrivate::IsMouseButtonDown() { 602 return [NSEvent pressedMouseButtons] != 0; 603} 604 605// static 606gfx::FontList NativeWidgetPrivate::GetWindowTitleFontList() { 607 NOTIMPLEMENTED(); 608 return gfx::FontList(); 609} 610 611} // namespace internal 612} // namespace views 613