macOS: Use sheet window as modal window

This commit is contained in:
Cheng Zhao 2016-06-20 14:49:24 +09:00
parent 1866dbe608
commit e33e4be257
8 changed files with 81 additions and 112 deletions

View file

@ -76,8 +76,7 @@ v8::Local<v8::Value> ToBuffer(v8::Isolate* isolate, void* val, int size) {
} // namespace } // namespace
Window::Window(v8::Isolate* isolate, const mate::Dictionary& options) Window::Window(v8::Isolate* isolate, const mate::Dictionary& options) {
: is_modal_(false) {
// Use options.webPreferences to create WebContents. // Use options.webPreferences to create WebContents.
mate::Dictionary web_preferences = mate::Dictionary::CreateEmpty(isolate); mate::Dictionary web_preferences = mate::Dictionary::CreateEmpty(isolate);
options.Get(options::kWebPreferences, &web_preferences); options.Get(options::kWebPreferences, &web_preferences);
@ -312,6 +311,10 @@ void Window::Show() {
} }
void Window::ShowInactive() { void Window::ShowInactive() {
// This method doesn't make sense for modal window..
if (IsModal())
return;
window_->ShowInactive(); window_->ShowInactive();
} }
@ -696,6 +699,11 @@ void Window::SetAspectRatio(double aspect_ratio, mate::Arguments* args) {
void Window::SetParentWindow(v8::Local<v8::Value> value, void Window::SetParentWindow(v8::Local<v8::Value> value,
mate::Arguments* args) { mate::Arguments* args) {
if (IsModal()) {
args->ThrowError("Can not be called for modal window");
return;
}
mate::Handle<Window> parent; mate::Handle<Window> parent;
if (value->IsNull()) { if (value->IsNull()) {
RemoveFromParentChildWindows(); RemoveFromParentChildWindows();
@ -721,43 +729,8 @@ std::vector<v8::Local<v8::Object>> Window::GetChildWindows() const {
return child_windows_.Values(isolate()); return child_windows_.Values(isolate());
} }
void Window::SetModal(bool modal, mate::Arguments* args) {
if (parent_window_.IsEmpty()) {
args->ThrowError("setModal can only be called for child window");
return;
}
mate::Handle<Window> parent;
if (!mate::ConvertFromV8(isolate(), GetParentWindow(), &parent)) {
args->ThrowError("Invalid parent window"); // should never happen
return;
}
if (modal == is_modal_)
return;
if (modal)
parent->Disable();
else
parent->Enable();
window_->SetModal(modal);
is_modal_ = modal;
}
bool Window::IsModal() const { bool Window::IsModal() const {
return is_modal_; return window_->is_modal();
}
void Window::BeginSheet(mate::Handle<Window> sheet, mate::Arguments* args) {
if (sheet->IsVisible()) {
args->ThrowError("Sheet window must not be visible");
return;
}
window_->BeginSheet(sheet->window_.get());
}
void Window::EndSheet(mate::Handle<Window> sheet) {
window_->EndSheet(sheet->window_.get());
} }
v8::Local<v8::Value> Window::GetNativeWindowHandle() { v8::Local<v8::Value> Window::GetNativeWindowHandle() {
@ -794,10 +767,8 @@ void Window::RemoveFromParentChildWindows() {
return; return;
parent->child_windows_.Remove(ID()); parent->child_windows_.Remove(ID());
if (IsModal()) { if (IsModal())
parent->Enable(); parent->Enable();
is_modal_ = false;
}
} }
// static // static
@ -828,10 +799,7 @@ void Window::BuildPrototype(v8::Isolate* isolate,
.SetMethod("setParentWindow", &Window::SetParentWindow) .SetMethod("setParentWindow", &Window::SetParentWindow)
.SetMethod("getParentWindow", &Window::GetParentWindow) .SetMethod("getParentWindow", &Window::GetParentWindow)
.SetMethod("getChildWindows", &Window::GetChildWindows) .SetMethod("getChildWindows", &Window::GetChildWindows)
.SetMethod("setModal", &Window::SetModal)
.SetMethod("isModal", &Window::IsModal) .SetMethod("isModal", &Window::IsModal)
.SetMethod("beginSheet", &Window::BeginSheet)
.SetMethod("endSheet", &Window::EndSheet)
.SetMethod("getNativeWindowHandle", &Window::GetNativeWindowHandle) .SetMethod("getNativeWindowHandle", &Window::GetNativeWindowHandle)
.SetMethod("getBounds", &Window::GetBounds) .SetMethod("getBounds", &Window::GetBounds)
.SetMethod("setBounds", &Window::SetBounds) .SetMethod("setBounds", &Window::SetBounds)

View file

@ -170,10 +170,7 @@ class Window : public mate::TrackableObject<Window>,
void SetParentWindow(v8::Local<v8::Value> value, mate::Arguments* args); void SetParentWindow(v8::Local<v8::Value> value, mate::Arguments* args);
v8::Local<v8::Value> GetParentWindow() const; v8::Local<v8::Value> GetParentWindow() const;
std::vector<v8::Local<v8::Object>> GetChildWindows() const; std::vector<v8::Local<v8::Object>> GetChildWindows() const;
void SetModal(bool modal, mate::Arguments* args);
bool IsModal() const; bool IsModal() const;
void BeginSheet(mate::Handle<Window> sheet, mate::Arguments* args);
void EndSheet(mate::Handle<Window> sheet);
v8::Local<v8::Value> GetNativeWindowHandle(); v8::Local<v8::Value> GetNativeWindowHandle();
#if defined(OS_WIN) #if defined(OS_WIN)
@ -209,9 +206,6 @@ class Window : public mate::TrackableObject<Window>,
v8::Global<v8::Value> parent_window_; v8::Global<v8::Value> parent_window_;
KeyWeakMap<int> child_windows_; KeyWeakMap<int> child_windows_;
// Is current window modal.
bool is_modal_;
api::WebContents* api_web_contents_; api::WebContents* api_web_contents_;
std::unique_ptr<NativeWindow> window_; std::unique_ptr<NativeWindow> window_;

View file

@ -46,7 +46,8 @@ namespace atom {
NativeWindow::NativeWindow( NativeWindow::NativeWindow(
brightray::InspectableWebContents* inspectable_web_contents, brightray::InspectableWebContents* inspectable_web_contents,
const mate::Dictionary& options) const mate::Dictionary& options,
NativeWindow* parent)
: content::WebContentsObserver(inspectable_web_contents->GetWebContents()), : content::WebContentsObserver(inspectable_web_contents->GetWebContents()),
has_frame_(true), has_frame_(true),
transparent_(false), transparent_(false),
@ -57,12 +58,17 @@ NativeWindow::NativeWindow(
sheet_offset_y_(0.0), sheet_offset_y_(0.0),
aspect_ratio_(0.0), aspect_ratio_(0.0),
disable_count_(0), disable_count_(0),
parent_(parent),
is_modal_(false),
inspectable_web_contents_(inspectable_web_contents), inspectable_web_contents_(inspectable_web_contents),
weak_factory_(this) { weak_factory_(this) {
options.Get(options::kFrame, &has_frame_); options.Get(options::kFrame, &has_frame_);
options.Get(options::kTransparent, &transparent_); options.Get(options::kTransparent, &transparent_);
options.Get(options::kEnableLargerThanScreen, &enable_larger_than_screen_); options.Get(options::kEnableLargerThanScreen, &enable_larger_than_screen_);
if (parent)
options.Get("modal", &is_modal_);
// Tell the content module to initialize renderer widget with transparent // Tell the content module to initialize renderer widget with transparent
// mode. // mode.
ui::GpuSwitchingManager::SetTransparent(transparent_); ui::GpuSwitchingManager::SetTransparent(transparent_);
@ -305,10 +311,8 @@ bool NativeWindow::HasModalDialog() {
return has_dialog_attached_; return has_dialog_attached_;
} }
void NativeWindow::BeginSheet(NativeWindow* sheet) { void NativeWindow::SetParentWindow(NativeWindow* parent) {
} parent_ = parent;
void NativeWindow::EndSheet(NativeWindow* sheet) {
} }
void NativeWindow::FocusOnWebView() { void NativeWindow::FocusOnWebView() {

View file

@ -100,7 +100,7 @@ class NativeWindow : public base::SupportsUserData,
virtual bool IsVisible() = 0; virtual bool IsVisible() = 0;
virtual void Disable(); virtual void Disable();
virtual void Enable(); virtual void Enable();
virtual void SetEnabled(bool enable) = 0; // should not be used virtual void SetEnabled(bool enable) = 0; // internal API, should not be used
virtual bool IsEnabled() = 0; virtual bool IsEnabled() = 0;
virtual void Maximize() = 0; virtual void Maximize() = 0;
virtual void Unmaximize() = 0; virtual void Unmaximize() = 0;
@ -163,10 +163,7 @@ class NativeWindow : public base::SupportsUserData,
virtual void SetFocusable(bool focusable); virtual void SetFocusable(bool focusable);
virtual void SetMenu(ui::MenuModel* menu); virtual void SetMenu(ui::MenuModel* menu);
virtual bool HasModalDialog(); virtual bool HasModalDialog();
virtual void SetParentWindow(NativeWindow* parent) = 0; virtual void SetParentWindow(NativeWindow* parent);
virtual void BeginSheet(NativeWindow* sheet);
virtual void EndSheet(NativeWindow* sheet);
virtual void SetModal(bool modal) = 0;
virtual gfx::NativeWindow GetNativeWindow() = 0; virtual gfx::NativeWindow GetNativeWindow() = 0;
virtual gfx::AcceleratedWidget GetAcceleratedWidget() = 0; virtual gfx::AcceleratedWidget GetAcceleratedWidget() = 0;
@ -264,9 +261,13 @@ class NativeWindow : public base::SupportsUserData,
has_dialog_attached_ = has_dialog_attached; has_dialog_attached_ = has_dialog_attached;
} }
NativeWindow* parent() const { return parent_; }
bool is_modal() const { return is_modal_; }
protected: protected:
NativeWindow(brightray::InspectableWebContents* inspectable_web_contents, NativeWindow(brightray::InspectableWebContents* inspectable_web_contents,
const mate::Dictionary& options); const mate::Dictionary& options,
NativeWindow* parent);
// Convert draggable regions in raw format to SkRegion format. Caller is // Convert draggable regions in raw format to SkRegion format. Caller is
// responsible for deleting the returned SkRegion instance. // responsible for deleting the returned SkRegion instance.
@ -341,6 +342,12 @@ class NativeWindow : public base::SupportsUserData,
// How many times the Disable has been called. // How many times the Disable has been called.
int disable_count_; int disable_count_;
// The parent window, it is guaranteed to be valid during this window's life.
NativeWindow* parent_;
// Is this a modal window.
bool is_modal_;
// The page this window is viewing. // The page this window is viewing.
brightray::InspectableWebContents* inspectable_web_contents_; brightray::InspectableWebContents* inspectable_web_contents_;

View file

@ -82,9 +82,6 @@ class NativeWindowMac : public NativeWindow {
void SetIgnoreMouseEvents(bool ignore) override; void SetIgnoreMouseEvents(bool ignore) override;
bool HasModalDialog() override; bool HasModalDialog() override;
void SetParentWindow(NativeWindow* parent) override; void SetParentWindow(NativeWindow* parent) override;
void BeginSheet(NativeWindow* sheet) override;
void EndSheet(NativeWindow* sheet) override;
void SetModal(bool modal) override;
gfx::NativeWindow GetNativeWindow() override; gfx::NativeWindow GetNativeWindow() override;
gfx::AcceleratedWidget GetAcceleratedWidget() override; gfx::AcceleratedWidget GetAcceleratedWidget() override;
void SetProgressBar(double progress) override; void SetProgressBar(double progress) override;
@ -151,9 +148,6 @@ class NativeWindowMac : public NativeWindow {
// The "titleBarStyle" option. // The "titleBarStyle" option.
TitleBarStyle title_bar_style_; TitleBarStyle title_bar_style_;
// Whether to hide the native toolbar under fullscreen mode.
bool should_hide_native_toolbar_in_fullscreen_;
DISALLOW_COPY_AND_ASSIGN(NativeWindowMac); DISALLOW_COPY_AND_ASSIGN(NativeWindowMac);
}; };

View file

@ -455,7 +455,7 @@ NativeWindowMac::NativeWindowMac(
brightray::InspectableWebContents* web_contents, brightray::InspectableWebContents* web_contents,
const mate::Dictionary& options, const mate::Dictionary& options,
NativeWindow* parent) NativeWindow* parent)
: NativeWindow(web_contents, options), : NativeWindow(web_contents, options, parent),
is_kiosk_(false), is_kiosk_(false),
attention_request_id_(0), attention_request_id_(0),
title_bar_style_(NORMAL) { title_bar_style_(NORMAL) {
@ -527,7 +527,8 @@ NativeWindowMac::NativeWindowMac(
window_delegate_.reset([[AtomNSWindowDelegate alloc] initWithShell:this]); window_delegate_.reset([[AtomNSWindowDelegate alloc] initWithShell:this]);
[window_ setDelegate:window_delegate_]; [window_ setDelegate:window_delegate_];
if (parent) { // Only use native parent window for non-modal windows.
if (parent && !is_modal()) {
SetParentWindow(parent); SetParentWindow(parent);
} }
@ -625,6 +626,12 @@ NativeWindowMac::~NativeWindowMac() {
} }
void NativeWindowMac::Close() { void NativeWindowMac::Close() {
// When this is a sheet showing, performClose won't work.
if (is_modal() && parent() && IsVisible()) {
CloseImmediately();
return;
}
if (!IsClosable()) { if (!IsClosable()) {
WindowList::WindowCloseCancelled(this); WindowList::WindowCloseCancelled(this);
return; return;
@ -654,6 +661,12 @@ bool NativeWindowMac::IsFocused() {
} }
void NativeWindowMac::Show() { void NativeWindowMac::Show() {
if (is_modal() && parent()) {
[parent()->GetNativeWindow() beginSheet:window_
completionHandler:^(NSModalResponse) {}];
return;
}
// This method is supposed to put focus on window, however if the app does not // This method is supposed to put focus on window, however if the app does not
// have focus then "makeKeyAndOrderFront" will only show the window. // have focus then "makeKeyAndOrderFront" will only show the window.
[NSApp activateIgnoringOtherApps:YES]; [NSApp activateIgnoringOtherApps:YES];
@ -666,6 +679,12 @@ void NativeWindowMac::ShowInactive() {
} }
void NativeWindowMac::Hide() { void NativeWindowMac::Hide() {
if (is_modal() && parent()) {
[window_ orderOut:nil];
[parent()->GetNativeWindow() endSheet:window_];
return;
}
[window_ orderOut:nil]; [window_ orderOut:nil];
} }
@ -679,7 +698,7 @@ void NativeWindowMac::SetEnabled(bool enable) {
} }
bool NativeWindowMac::IsEnabled() { bool NativeWindowMac::IsEnabled() {
return ![window_ disableMouseEvents]; return [window_ attachedSheet] == nil;
} }
void NativeWindowMac::Maximize() { void NativeWindowMac::Maximize() {
@ -951,6 +970,11 @@ bool NativeWindowMac::HasModalDialog() {
} }
void NativeWindowMac::SetParentWindow(NativeWindow* parent) { void NativeWindowMac::SetParentWindow(NativeWindow* parent) {
if (is_modal())
return;
NativeWindow::SetParentWindow(parent);
// Remove current parent window. // Remove current parent window.
if ([window_ parentWindow]) if ([window_ parentWindow])
[[window_ parentWindow] removeChildWindow:window_]; [[window_ parentWindow] removeChildWindow:window_];
@ -960,21 +984,6 @@ void NativeWindowMac::SetParentWindow(NativeWindow* parent) {
[parent->GetNativeWindow() addChildWindow:window_ ordered:NSWindowAbove]; [parent->GetNativeWindow() addChildWindow:window_ ordered:NSWindowAbove];
} }
void NativeWindowMac::BeginSheet(NativeWindow* sheet) {
[window_ beginSheet:sheet->GetNativeWindow()
completionHandler:^(NSModalResponse) {
}];
}
void NativeWindowMac::EndSheet(NativeWindow* sheet) {
sheet->Hide();
[window_ endSheet:sheet->GetNativeWindow()];
sheet->CloseImmediately();
}
void NativeWindowMac::SetModal(bool modal) {
}
gfx::NativeWindow NativeWindowMac::GetNativeWindow() { gfx::NativeWindow NativeWindowMac::GetNativeWindow() {
return window_; return window_;
} }

View file

@ -806,6 +806,8 @@ void NativeWindowViews::SetMenu(ui::MenuModel* menu_model) {
} }
void NativeWindowViews::SetParentWindow(NativeWindow* parent) { void NativeWindowViews::SetParentWindow(NativeWindow* parent) {
NativeWindow::SetParentWindow(parent);
#if defined(USE_X11) #if defined(USE_X11)
XDisplay* xdisplay = gfx::GetXDisplay(); XDisplay* xdisplay = gfx::GetXDisplay();
XSetTransientForHint( XSetTransientForHint(
@ -830,6 +832,7 @@ void NativeWindowViews::SetParentWindow(NativeWindow* parent) {
} }
void NativeWindowViews::SetModal(bool modal) { void NativeWindowViews::SetModal(bool modal) {
NativeWindow::SetModal(modal);
#if defined(USE_X11) #if defined(USE_X11)
SetWindowType(GetAcceleratedWidget(), modal ? "dialog" : "normal"); SetWindowType(GetAcceleratedWidget(), modal ? "dialog" : "normal");
Show(); Show();

View file

@ -902,10 +902,18 @@ describe('browser-window module', function () {
}) })
}) })
describe('win.setModal(modal)', function () { describe('modal option', function () {
// The isEnabled API is not reliable on macOS.
if (process.platform === 'darwin') return
beforeEach(function () {
if (c != null) c.destroy()
c = new BrowserWindow({show: false, parent: w, modal: true})
})
it('disables parent window', function () { it('disables parent window', function () {
assert.equal(w.isEnabled(), true) assert.equal(w.isEnabled(), true)
c.setModal(true) c.show()
assert.equal(w.isEnabled(), false) assert.equal(w.isEnabled(), false)
}) })
@ -914,38 +922,20 @@ describe('browser-window module', function () {
assert.equal(w.isEnabled(), true) assert.equal(w.isEnabled(), true)
done() done()
}) })
c.setModal(true) c.show()
c.close() c.close()
}) })
it('enables parent window when setting not modal', function () {
assert.equal(w.isEnabled(), true)
c.setModal(true)
assert.equal(w.isEnabled(), false)
c.setModal(false)
assert.equal(w.isEnabled(), true)
})
it('enables parent window when removing parent', function () {
if (process.platform !== 'darwin') return
assert.equal(w.isEnabled(), true)
c.setModal(true)
assert.equal(w.isEnabled(), false)
c.setParentWindow(null)
assert.equal(w.isEnabled(), true)
})
it('disables parent window recursively', function () { it('disables parent window recursively', function () {
let c2 = new BrowserWindow({show: false, parent: w}) let c2 = new BrowserWindow({show: false, parent: w, modal: true})
c.setModal(true) c.show()
c2.setModal(true)
assert.equal(w.isEnabled(), false) assert.equal(w.isEnabled(), false)
c.setModal(false) c2.show()
assert.equal(w.isEnabled(), false)
c.destroy()
assert.equal(w.isEnabled(), false) assert.equal(w.isEnabled(), false)
c2.setModal(false)
assert.equal(w.isEnabled(), true)
c2.destroy() c2.destroy()
assert.equal(w.isEnabled(), true)
}) })
}) })
}) })