NSWindowを最前面に表示する - Keynote, PowerPointのプレゼンモードに対応

# 概要
- 自分のアプリを最前面に表示するための調査・実装です。

# 参考
- [NSWindow\.Level](https://developer.apple.com/documentation/appkit/nswindow/level)
- [NSWindowLevel Enum](https://docs.microsoft.com/en-us/dotnet/api/appkit.nswindowlevel?view=xamarin-mac-sdk-14)
    - NSWindowLevelの数値定義
- [Swift4でのクラス名取得方法まとめ \- Qiita](https://qiita.com/yosshi4486/items/f7916352d22d0e4d03ee)
    - `windowNibName`で使用
- [他アプリのフルスクリーン上にウインドウを表示する](https://qiita.com/a_jike/items/7eb5240b5ecb9a0c4fd7)

# GitHub

[Qiita\_NSWIndowLevelSample](https://github.com/pommdau/Qiita_NSWIndowLevelSample)

# 各LevelのWindowを表示する
- 最前面ということで、まず`NSWindowLevel`を考えます。
- テストのため、`Swift`で表向きに定義されているwindowLevelでウィンドウをまとめて表示します。

![-w628](https://i.imgur.com/YRH23xE.jpg)

# PowerPointで最前面

- `Power Point`や`Google Slide`のプレゼンテーションモードでは、`WindowdowLevel`はそのままでフルスクリーンとなっています。
- `NSPanel`に対して、下記の通り設定を行います。
- また`NSWindowLevel`は`.normal`よりも大きければOKですね。

```swift
// フルスクリーン時に表示するための設定
myWindowController.myWindow.styleMask.insert(.nonactivatingPanel)
myWindowController.myWindow.collectionBehavior.insert(.canJoinAllSpaces)
myWindowController.myWindow.collectionBehavior.insert(.fullScreenAuxiliary)
```

# Keynoteで最前面
- 一方Keynoteのプレゼンテーションモードで最前面表示をするには、WindowLevel`.popUpMenu`以上にする必要があります。
    - PowerPointと目的は同じですが手段が違うということですね。
- 下記が実際に試してみた所
    - `.popUpMenu`と`.screenSaver`のみが表示されていますね。
    
![](https://i.imgur.com/SzE4b6H.jpg)

# 実装

- ほぼ[他アプリのフルスクリーン上にウインドウを表示する](https://qiita.com/a_jike/items/7eb5240b5ecb9a0c4fd7)の実装の通りです。
    - `NSPanel`を使うのがミソです。

```swift:MyWindowController.swift
import Cocoa

class MyWindowController: NSWindowController {
    
    @IBOutlet weak var myWindow: NSPanel!
    @IBOutlet weak var windowLevelLabel: NSTextField!
    
    override var windowNibName : String? {
        return String(describing: type(of: self))
    }
    
    override func windowDidLoad() {
        super.windowDidLoad()
        
        // フルスクリーン時に表示するための設定
        self.myWindow.styleMask.insert(.nonactivatingPanel)
        self.myWindow.collectionBehavior.insert(.canJoinAllSpaces)
        self.myWindow.collectionBehavior.insert(.fullScreenAuxiliary)
    }
}

```

```swift:AppDelegate.swift
import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
    
    @IBOutlet weak var window: NSWindow!
    var myWindowControllerList = [MyWindowController]()
    
    struct WindowControllerPropertyKey {
        static let nameKey        = "name"
        static let windowLevelKey = "windowLevel"
    }
    
    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Insert code here to initialize your application
        
        var windowSettings:[Dictionary<String, Any>] = []

        // [NSWindow\.Level](https://developer.apple.com/documentation/appkit/nswindow/level)
        // [NSWindowLevel Enum](https://docs.microsoft.com/en-us/dotnet/api/appkit.nswindowlevel?view=xamarin-mac-sdk-14)
        // 上表の通り、数字の順で間違い無さそう。下に行くほど最前面
//        windowSettings.append([WindowControllerPropertyKey.nameKey        : ".normal",
//                               WindowControllerPropertyKey.windowLevelKey : NSWindow.Level.normal])
        
        // 以下3つは同じ優先度
        windowSettings.append([WindowControllerPropertyKey.nameKey        : ".submenu",
                               WindowControllerPropertyKey.windowLevelKey : NSWindow.Level.submenu])
        windowSettings.append([WindowControllerPropertyKey.nameKey        : ".tornOffMenu",
                               WindowControllerPropertyKey.windowLevelKey : NSWindow.Level.tornOffMenu])
        windowSettings.append([WindowControllerPropertyKey.nameKey        : ".floating",
                               WindowControllerPropertyKey.windowLevelKey : NSWindow.Level.floating])
        
        windowSettings.append([WindowControllerPropertyKey.nameKey        : ".modalPanel",
                               WindowControllerPropertyKey.windowLevelKey : NSWindow.Level.modalPanel])
        windowSettings.append([WindowControllerPropertyKey.nameKey        : ".mainMenu",
                               WindowControllerPropertyKey.windowLevelKey : NSWindow.Level.mainMenu])
        windowSettings.append([WindowControllerPropertyKey.nameKey        : ".statusBar",
                               WindowControllerPropertyKey.windowLevelKey : NSWindow.Level.statusBar])
        windowSettings.append([WindowControllerPropertyKey.nameKey        : ".popUpMenu",
                               WindowControllerPropertyKey.windowLevelKey : NSWindow.Level.popUpMenu])
        windowSettings.append([WindowControllerPropertyKey.nameKey        : ".screenSaver",
                               WindowControllerPropertyKey.windowLevelKey : NSWindow.Level.screenSaver])
        
        for windowSetting:Dictionary<String, Any> in windowSettings {
            let myWindowController = MyWindowController()

            myWindowController.showWindow(self)
            myWindowController.myWindow.title               = windowSetting[WindowControllerPropertyKey.nameKey] as! String
            myWindowController.windowLevelLabel.stringValue = windowSetting[WindowControllerPropertyKey.nameKey] as! String
            myWindowController.myWindow.level               = windowSetting[WindowControllerPropertyKey.windowLevelKey] as! NSWindow.Level
            
            myWindowControllerList.append(myWindowController)
        }
    }
    
    func applicationWillTerminate(_ aNotification: Notification) {
        // Insert code here to tear down your application
    }
    

}
```

# 補足
## NSColorPanelをKeynoteのプレゼンモードで表示する
### 概要
- `NSColorPanel`のWindowLevelを一旦設定しても、再度`NSColorWell`を押下して表示した際に、`.floating`に戻ってしまっています（なぜだろう？）
- そこで`order(_:relativeTo:)`で表示前にアクションを挟み込むことで対応しました。

### 参考
- [How to get notified when NSWindow opens?](https://stackoverflow.com/questions/20453965/how-to-get-notified-when-nswindow-opens)
- [order\(\_:relativeTo:\)](https://developer.apple.com/documentation/appkit/nswindow/1419672-order)

### 実装

```swift
import Cocoa

extension NSColorPanel {    
    // [How to get notified when NSWindow opens?](https://stackoverflow.com/questions/20453965/how-to-get-notified-when-nswindow-opens)
    open override func order(_ place: NSWindow.OrderingMode, relativeTo otherWin: Int) {
        super.order(place, relativeTo: otherWin)
        NSColorPanel.shared.level = .screenSaver
    }
}
```