blob: 95632375fb89e8e158ee6e949f12e4c745d6923b [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ui/views/cocoa/bridged_native_widget.h"
#include "base/logging.h"
#include "base/mac/mac_util.h"
#import "base/mac/sdk_forward_declarations.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/ime/input_method_factory.h"
#include "ui/base/ui_base_switches_util.h"
#import "ui/gfx/mac/coordinate_conversion.h"
#import "ui/views/cocoa/bridged_content_view.h"
#import "ui/views/cocoa/views_nswindow_delegate.h"
#include "ui/views/widget/native_widget_mac.h"
#include "ui/views/ime/input_method_bridge.h"
#include "ui/views/ime/null_input_method.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
namespace views {
BridgedNativeWidget::BridgedNativeWidget(NativeWidgetMac* parent)
: native_widget_mac_(parent),
focus_manager_(NULL),
parent_(nullptr),
target_fullscreen_state_(false),
in_fullscreen_transition_(false) {
DCHECK(parent);
window_delegate_.reset(
[[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]);
}
BridgedNativeWidget::~BridgedNativeWidget() {
RemoveOrDestroyChildren();
DCHECK(child_windows_.empty());
SetFocusManager(NULL);
SetRootView(NULL);
if ([window_ delegate]) {
// If the delegate is still set, it means OnWindowWillClose has not been
// called and the window is still open. Calling -[NSWindow close] will
// synchronously call OnWindowWillClose and notify NativeWidgetMac.
[window_ close];
}
DCHECK(![window_ delegate]);
}
void BridgedNativeWidget::Init(base::scoped_nsobject<NSWindow> window,
const Widget::InitParams& params) {
DCHECK(!window_);
window_.swap(window);
[window_ setDelegate:window_delegate_];
// Validate the window's initial state, otherwise the bridge's initial
// tracking state will be incorrect.
DCHECK(![window_ isVisible]);
DCHECK_EQ(0u, [window_ styleMask] & NSFullScreenWindowMask);
if (params.parent) {
// Disallow creating child windows of views not currently in an NSWindow.
CHECK([params.parent window]);
BridgedNativeWidget* parent =
NativeWidgetMac::GetBridgeForNativeWindow([params.parent window]);
// The parent could be an NSWindow without an associated Widget. That could
// work by observing NSWindowWillCloseNotification, but for now it's not
// supported, and there might not be a use-case for that.
CHECK(parent);
parent_ = parent;
parent->child_windows_.push_back(this);
}
}
void BridgedNativeWidget::SetFocusManager(FocusManager* focus_manager) {
if (focus_manager_ == focus_manager)
return;
if (focus_manager_)
focus_manager_->RemoveFocusChangeListener(this);
if (focus_manager)
focus_manager->AddFocusChangeListener(this);
focus_manager_ = focus_manager;
}
void BridgedNativeWidget::SetBounds(const gfx::Rect& new_bounds) {
[window_ setFrame:gfx::ScreenRectToNSRect(new_bounds)
display:YES
animate:NO];
}
void BridgedNativeWidget::SetRootView(views::View* view) {
if (view == [bridged_view_ hostedView])
return;
[bridged_view_ clearView];
bridged_view_.reset();
// Note that there can still be references to the old |bridged_view_|
// floating around in Cocoa libraries at this point. However, references to
// the old views::View will be gone, so any method calls will become no-ops.
if (view) {
bridged_view_.reset([[BridgedContentView alloc] initWithView:view]);
// Objective C initializers can return nil. However, if |view| is non-NULL
// this should be treated as an error and caught early.
CHECK(bridged_view_);
}
[window_ setContentView:bridged_view_];
}
void BridgedNativeWidget::OnWindowWillClose() {
if (parent_)
parent_->RemoveChildWindow(this);
[window_ setDelegate:nil];
native_widget_mac_->OnWindowWillClose();
}
void BridgedNativeWidget::OnFullscreenTransitionStart(
bool target_fullscreen_state) {
DCHECK_NE(target_fullscreen_state, target_fullscreen_state_);
target_fullscreen_state_ = target_fullscreen_state;
in_fullscreen_transition_ = true;
// If going into fullscreen, store an answer for GetRestoredBounds().
if (target_fullscreen_state)
bounds_before_fullscreen_ = gfx::ScreenRectFromNSRect([window_ frame]);
}
void BridgedNativeWidget::OnFullscreenTransitionComplete(
bool actual_fullscreen_state) {
in_fullscreen_transition_ = false;
if (target_fullscreen_state_ == actual_fullscreen_state)
return;
// First update to reflect reality so that OnTargetFullscreenStateChanged()
// expects the change.
target_fullscreen_state_ = actual_fullscreen_state;
ToggleDesiredFullscreenState();
// Usually ToggleDesiredFullscreenState() sets |in_fullscreen_transition_| via
// OnFullscreenTransitionStart(). When it does not, it means Cocoa ignored the
// toggleFullScreen: request. This can occur when the fullscreen transition
// fails and Cocoa is *about* to send windowDidFailToEnterFullScreen:.
// Annoyingly, for this case, Cocoa first sends windowDidExitFullScreen:.
if (in_fullscreen_transition_)
DCHECK_NE(target_fullscreen_state_, actual_fullscreen_state);
}
void BridgedNativeWidget::ToggleDesiredFullscreenState() {
if (base::mac::IsOSSnowLeopard()) {
NOTIMPLEMENTED();
return; // TODO(tapted): Implement this for Snow Leopard.
}
// If there is currently an animation into or out of fullscreen, then AppKit
// emits the string "not in fullscreen state" to stdio and does nothing. For
// this case, schedule a transition back into the desired state when the
// animation completes.
if (in_fullscreen_transition_) {
target_fullscreen_state_ = !target_fullscreen_state_;
return;
}
// Since fullscreen requests are ignored if the collection behavior does not
// allow it, save the collection behavior and restore it after.
NSWindowCollectionBehavior behavior = [window_ collectionBehavior];
[window_ setCollectionBehavior:behavior |
NSWindowCollectionBehaviorFullScreenPrimary];
[window_ toggleFullScreen:nil];
[window_ setCollectionBehavior:behavior];
}
void BridgedNativeWidget::OnSizeChanged() {
NSSize new_size = [window_ frame].size;
native_widget_mac_->GetWidget()->OnNativeWidgetSizeChanged(
gfx::Size(new_size.width, new_size.height));
// TODO(tapted): If there's a layer, resize it here.
}
InputMethod* BridgedNativeWidget::CreateInputMethod() {
if (switches::IsTextInputFocusManagerEnabled())
return new NullInputMethod();
return new InputMethodBridge(this, GetHostInputMethod(), true);
}
ui::InputMethod* BridgedNativeWidget::GetHostInputMethod() {
if (!input_method_) {
// Delegate is NULL because Mac IME does not need DispatchKeyEventPostIME
// callbacks.
input_method_ = ui::CreateInputMethod(NULL, nil);
}
return input_method_.get();
}
gfx::Rect BridgedNativeWidget::GetRestoredBounds() const {
if (target_fullscreen_state_ || in_fullscreen_transition_)
return bounds_before_fullscreen_;
return gfx::ScreenRectFromNSRect([window_ frame]);
}
////////////////////////////////////////////////////////////////////////////////
// BridgedNativeWidget, internal::InputMethodDelegate:
void BridgedNativeWidget::DispatchKeyEventPostIME(const ui::KeyEvent& key) {
// Mac key events don't go through this, but some unit tests that use
// MockInputMethod do.
DCHECK(focus_manager_);
native_widget_mac_->GetWidget()->OnKeyEvent(const_cast<ui::KeyEvent*>(&key));
if (!key.handled())
focus_manager_->OnKeyEvent(key);
}
////////////////////////////////////////////////////////////////////////////////
// BridgedNativeWidget, FocusChangeListener:
void BridgedNativeWidget::OnWillChangeFocus(View* focused_before,
View* focused_now) {
}
void BridgedNativeWidget::OnDidChangeFocus(View* focused_before,
View* focused_now) {
ui::TextInputClient* input_client =
focused_now ? focused_now->GetTextInputClient() : NULL;
[bridged_view_ setTextInputClient:input_client];
}
////////////////////////////////////////////////////////////////////////////////
// BridgedNativeWidget, private:
void BridgedNativeWidget::RemoveOrDestroyChildren() {
// TODO(tapted): Implement unowned child windows if required.
while (!child_windows_.empty()) {
// The NSWindow can only be destroyed after -[NSWindow close] is complete.
// Retain the window, otherwise the reference count can reach zero when the
// child calls back into RemoveChildWindow() via its OnWindowWillClose().
base::scoped_nsobject<NSWindow> child(
[child_windows_.back()->ns_window() retain]);
[child close];
}
}
void BridgedNativeWidget::RemoveChildWindow(BridgedNativeWidget* child) {
auto location = std::find(
child_windows_.begin(), child_windows_.end(), child);
DCHECK(location != child_windows_.end());
child_windows_.erase(location);
child->parent_ = nullptr;
}
} // namespace views