Paepoi

Paepoi » PyObjC Tips » NSApplication

NSApplication

最終更新日 2024.08.25

NSApp を作る
AppKit.NSApplication.sharedApplication()
を呼び出すと AppKit.NSApp というグローバル変数に NSApplication のインスタンスが入ります。

#!/usr/bin/env python3

import AppKit, objc

class AppMenu(AppKit.NSMenu):
    def init(self):
        objc.super(AppMenu, self).init()
        item_app  = AppKit.NSMenuItem.new()
        self.addItem_(item_app)
        menu_app = AppKit.NSMenu.new()
        item_app.setSubmenu_(menu_app)
        # command+Q で閉じるメニュー
        item_quit = AppKit.NSMenuItem.new()
        item_quit.initWithTitle_action_keyEquivalent_('Quit App', 'terminate:', 'q')
        menu_app.addItem_(item_quit)
        return self

# NSApp を作る
AppKit.NSApplication.sharedApplication()
# command+Q で終了するメニューを入れる
AppKit.NSApp.setMainMenu_(AppMenu.new())
# メインループを回す
AppKit.NSApp.run()
ウインドウも何も無いアプリケーション。

.NET や GtkApplication 又は TApplication 等を知っている人はアレッと思うかも。
つまり Cocoa ではウインドウが無いアプリケーションのインスタンスが作れます。
というか既存の Cocoa アプリも全部そうなっています、そういう文化です。

NSApplication は NSWindow を参照カウンタで管理なんかしません。
同じインスタンスで動いている NSWindow を keyWindow 等で見つけてくれるだけです。
でも終了時には全部まとめて破棄してくれる頼もしい存在です。

閉じるボタンで終了するサンプルコードばかり見つかるので最初にコレを書いてみました。
macOS アプリは command+Q で終了しないとおかしいですよね。

NSWindow を作る
AppKit の一般的な動作をするウインドウを書いてみます。

#!/usr/bin/env python3

import AppKit, objc

class MyWindow(AppKit.NSWindow):
    def init(self):
        objc.super(MyWindow, self).initWithContentRect_styleMask_backing_defer_(
            AppKit.NSMakeRect(0, 0, 320, 240),
            AppKit.NSTitledWindowMask |
            AppKit.NSClosableWindowMask |
            AppKit.NSResizableWindowMask |
            AppKit.NSMiniaturizableWindowMask,
            AppKit.NSBackingStoreBuffered, False)
        # すでに Window がある場合はズラして配置
        if len(AppKit.NSApp.windows()) == 1:
            self.center()
        else:
            point = AppKit.NSApp.keyWindow().cascadeTopLeftFromPoint_(AppKit.NSZeroPoint)
            self.setFrameTopLeftPoint_(point)
        # title はプロパティなので
        self.setTitle_('日本語')
        # 自身をデリゲートに
        self.setDelegate_(self)
        # 自分を戻す
        return self

    def windowWillClose_(self, sender):
        # オーバーライド、閉じるアクションでココに来る
        print('Close!')

class AppDelegate(AppKit.NSObject):
    # ガベージコレクション回避用
    wins = []

    def applicationDidFinishLaunching_(self, notification):
        '''
            オーバーライド、ウインドウは基本ココで作る
        '''
        window = MyWindow.new()
        window.makeKeyAndOrderFront_(window)
        # ガベージコレクションされないようにどこかに保存する
        self.wins.append(window)
        # アクティブ化
        AppKit.NSApp.activateIgnoringOtherApps_(True)

    def applicationSupportsSecureRestorableState_(self, app):
        '''
            セキュリティのためにこれを書けと怒られるので
        '''
        return True

    def newWindow_(self, notification):
        '''
            自作メソッドの場合でもメニューではアンダーバーをコロンにする
        '''
        window = MyWindow.new()
        window.makeKeyAndOrderFront_(window)
        self.wins.append(window)

    def closeWindow_(self, notification):
        '''
            command+w
        '''
        AppKit.NSApp.keyWindow().close()

class AppMenu(AppKit.NSMenu):
    def init(self):
        objc.super(AppMenu, self).init()
        item_app  = AppKit.NSMenuItem.new()
        self.addItem_(item_app)
        menu_app = AppKit.NSMenu.new()
        item_app.setSubmenu_(menu_app)
        # new menu
        item_new = AppKit.NSMenuItem.new()
        item_new.initWithTitle_action_keyEquivalent_('New Window', 'newWindow:', 'n')
        menu_app.addItem_(item_new)
        # close menu
        item_close = AppKit.NSMenuItem.new()
        item_close.initWithTitle_action_keyEquivalent_('Close Window', 'closeWindow:', 'w')
        menu_app.addItem_(item_close)
        # separator
        menu_app.addItem_(AppKit.NSMenuItem.separatorItem())
        # quit menu
        item_quit = AppKit.NSMenuItem.new()
        item_quit.initWithTitle_action_keyEquivalent_('Quit App', 'terminate:', 'q')
        menu_app.addItem_(item_quit)
        return self

AppKit.NSApplication.sharedApplication()
AppKit.NSApp.setMainMenu_(AppMenu.new())
AppKit.NSApp.setDelegate_(AppDelegate.new())
AppKit.NSApp.run()
先ほどの NSApp にデリゲートを追加してウインドウを作るメニューを追加。
メソッド名はアンダーバーを使いますがメニューを作る時はコロンで指定なので注意。
デリゲートは NSApplicationDelegate を継承する必要は無い、ただの class でいい。

command+N で新規ウインドウ、command+W でウインドウを閉じる。
という macOS 標準ショートカットはこんな感じで実装します。
terminate は予約されているので上書きしないように注意しよう。
NSApp.keyWindow() で現在キー操作を受け付けるウインドウが得られます。

NSWindow を作ったらどこかに保存する。
先ほど書いたように NSApp は NSWindow を参照カウンタで管理なんかしません。
つまり自力で保持しないと Python のガベージコレクションに破棄されてしまいます。

NSWindow は close() メソッドを呼ぶ、又は閉じるボタンで自動的に破棄される。
その時リストで保持した NSWindow は何もしなくていい。
ポインタアドレスが残るだけなので無視する、へたに破棄すると二重破棄でエラー。

継承した NSWindow はこんな感じで作ります。
必要に応じて NSRect と **mask を指定、他の引数は現在固定値です。
Interface Builder の nib から作る方法もあるけどココでは解説しません。
ズラして配置はどうせ必要になるから書いたけど説明は省きます。

NSWindowDelegate は自身を指定する。
別の class を作ってもいいけど無駄に class が多くなるより管理しやすい。
windowWillClose_ は閉じるボタンや command+W で届くメッセージのハンドラです。
stdout に Close! が表示されるのを確認ください。

applicationSupportsSecureRestorableState は書かないと警告が出ます。
素直に True を戻すコードを入れておきましょう。

Copyright(C) sasakima-nao All rights reserved 2002 --- 2024.