PyObjC」タグアーカイブ

cancelOperation

OS Xアプリケーションにおける環境設定ウィンドウの作り方 ? Genji App Blog

esc でウインドウを閉じるには cancelOperation を処理するだけなのか。
でも書いてあるとおり macOS でこの動作は一時的なウインドウのみ。

オリジナルは Eye of GNOME に合わせて esc で終了できるようにしているけど。
macOS 版では本体を閉じる設定は無しにしたほうがいいようだ。
設定ウインドウやサムネイルウインドウに適用することに。

それから、macOS ってスピンボタンが全然使われていないよね。
NSStepper というものが一応あるんだけど、どのアプリも使っていない。
macOS っぽいものにしたいのでサムネイルサイズ設定は NSSlider にする。
80-200 の 20px ステップで問題無いだろう。

それから、macOS アプリの設定はどれも全体設定になっている。
つまりグローバル変数にする必要がある。
どうせなら NSApp にくっつけときたいよね、ということで。

class ComipoliApplication(NSApplication):
    # global variable
    init_frame = None
    esc_close = False
    is_pdf = False
    thumbnail_height = 120
    is_unrar = False
    is_7za = False

def main():
    #NSApplication.sharedApplication()
    ComipoliApplication.sharedApplication()

サブクラスにしちゃえです。

それと macOS でマウスを使っている人はいない、と思う。
マウスカーソルを変更してクリックでページめくりもいらないな。

設定は二つだけになってもーた。
そんなこんなで、こいつでやりたかった機能は全部付いた。
PyObjC の参考にでも使ってね。
comipoli_pyobjc_3.tar.gz

後は app 化。
py2app ってのでイケるらしいけど分割ファイルでの手段が見付からない。
たいした行数ではないので一つのソースにまとめてもいいんだけど。

PyObjC NSWindow Cascade Position

macOS では二つ目以降のウインドウは規則的にズレて表示される。
でも NSWindow は NSRect を指定して作成する、どうやっているのか。

cascadeTopLeftFromPoint: – Cocoa API解説(macOS/iOS)

そういうことか、解説ありがとう。
window を作った後に自分で移動するのね。

objc.super(MyWindow, self).initWithContentRect_styleMask_backing_defer_(
    frame,
    NSTitledWindowMask |
    NSClosableWindowMask |
    NSResizableWindowMask |
    NSMiniaturizableWindowMask,
    NSBackingStoreBuffered,
    False)
# Cascade Position
if len(NSApp.windows()) > 1:
    point = NSApp.keyWindow().cascadeTopLeftFromPoint_(NSZeroPoint)
    self.setFrameTopLeftPoint_(point)

で。

うん macOS アプリっぽくなった。
次は設定ダイアログの位置決めをどうするかだ。
小ネタでした。

PyObjC NSAttributedString

NSCollectionView は色々カスタムできるようなのでとやってみたけど。。。。。
いくら探してもチュートリアル Interface Builder しか出てこない。
しかもボコボコ落ちる、無意味なクラッシュレポートを Apple に沢山送ったよ。
もうあきらめて普通の NSView として使うことにするよ。

ということで。
NSView に文字列を表示したい。

[Objective-C]NSAttributedStringの背景色あたりの話|杏z 学習帳(旧)

NSMutableAttributedString というトンデモネェものがあるようだ。
筆者の用途では継承元の NSAttributedString で十分そう。

てか UIColor なの?
PyObjC には UIKit は無いぞ、あってどうするという話だが。
NSColor で当然のようにイケた、これは Apple の解説が悪いな。

comipoli オリジナルのサムネイル文字列は赤、背景色は 66ffff で 50% 半透明。
これを再現するには。

colorWithSRGBRed:green:blue:alpha: – NSColor | Apple Developer Documentation

最大値を 1.0 にして RGBA 指定でイケるのね。
やってみる。

class ItemView(NSView):
    def init(self):
        objc.super(ItemView, self).init()
        self.image = None
        self.num = 0
        return self

    def drawRect_(self, rect):
        NSColor.blackColor().set()
        NSRectFill(rect)
        if (self.image):
            self.image.drawInRect_(rect)
            bg_color = NSColor.colorWithSRGBRed_green_blue_alpha_(0.4, 1.0, 1.0, 0.5)
            text = NSAttributedString.alloc().initWithString_attributes_(
                '{}'.format(self.num + 1),
                {
                    NSForegroundColorAttributeName: NSColor.redColor(),
                    NSBackgroundColorAttributeName: bg_color
                })
            x = rect.size.width / 2
            text.drawAtPoint_(NSMakePoint(x, 0))

オリジナル

よし完成、といいたいけどまだ超不安定。

PyObjC Background Thread

サムネイル画像を作るバックグラウンド処理。

Objective-C で NSThread および GCD で非同期処理した結果を UI に反映する処理を書いてみた – 凹みTips

こんなアホみたく簡単にバックグラウンドスレッドが動かせるのかい!
performSelectorInBackground:withObject:
を使うだけ、GCD は Python なので関係ない。

それから GUI の常識、関数を抜けるまで画像は表示されない。
普通に書くとすべての画像の読み込みが終わってから一気に表示される。
順次表示するには for 文の中で一つ読み終わる毎に関数を抜ける必要がある。

EZ-NET: UI 関連の機能はメインスレッドで実行すること : Objective-C プログラミング

バックグラウンドスレッドの中で
performSelectorOnMainThread:withObject:waitUntilDone:
を使い UniilDone を True にすればメインスレッドの関数を抜けることができる。

Objective-c スゲェ。
みんな大好き yield を使おうと思ったけど必要ないぞ。

残念なのは NSCollectionView.content が NSArray だった。
python の配列のような可変長ではないので丸ごと配列を入れ替えすることに。

#!/usr/bin/env python3

import objc, os, re
from AppKit import *

PATH = '/Users/sasakima-nao/Pictures/nae'

class ItemView(NSView):
    def init(self):
        objc.super(ItemView, self).init()
        self.image = None
        return self

    def drawRect_(self, rect):
        self.image.drawInRect_(rect)

class CollectionViewItem(NSCollectionViewItem):
    def init(self):
        objc.super(CollectionViewItem, self).init()
        self.setView_(ItemView.alloc().initWithFrame_(NSMakeRect(0, 0, 100, 100)))
        return self

    def setRepresentedObject_(self, rep):
        objc.super(CollectionViewItem, self).setRepresentedObject_(rep)
        if rep != None:
            self.view().image = rep['pic']

class ComipoliCollectionView(NSCollectionView):
    def init(self):
        objc.super(ComipoliCollectionView, self).init()
        self.pixs = []
        self.setItemPrototype_(CollectionViewItem.new())
        self.performSelectorInBackground_withObject_(self.readImage_, PATH)
        return self

    def readImage_(self, path):
        l = os.listdir(path)
        for s in l:
            if re.search(r'\.(jpg|jpeg|png|gif)$', s, re.I):
                f = '{0}/{1}'.format(PATH, s)
                image = NSImage.alloc().initWithContentsOfFile_(f)
                self.performSelectorOnMainThread_withObject_waitUntilDone_(self.addImage_, image, True)

    def addImage_(self, image):
        self.pixs.append({'pic': image})
        self.setContent_(self.pixs)

class ComipoliWindow(NSWindow):
    def init(self):
        frame = NSMakeRect(100, 400, 400, 600)
        objc.super(ComipoliWindow, self).initWithContentRect_styleMask_backing_defer_(
            frame,
            NSTitledWindowMask |
            NSClosableWindowMask |
            NSResizableWindowMask |
            NSMiniaturizableWindowMask,
            NSBackingStoreBuffered,
            False)
        self.setTitle_('thumbnail')
        self.setDelegate_(self)
        # view
        self.canvas = ComipoliCollectionView.new()
        self.canvas.setFrameSize_(frame.size)
        self.contentView().addSubview_(self.canvas)
        return self

    def windowDidResize_(self, sender):
        aw = self.contentView().frame().size.width
        ah = self.contentView().frame().size.height
        size = NSMakeSize(aw, ah)
        self.canvas.setFrameSize_(size)

class ComipoliMenu(NSMenu):
    def init(self):
        objc.super(ComipoliMenu, self).init()
        item_app  = NSMenuItem.new()
        self.addItem_(item_app)
        menu_app = NSMenu.new()
        item_app.setSubmenu_(menu_app)
        item_quit = NSMenuItem.new()
        item_quit.initWithTitle_action_keyEquivalent_('Quit', 'terminate:', 'q')
        menu_app.addItem_(item_quit)
        return self

NSApplication.sharedApplication()
window = ComipoliWindow.new()
window.makeKeyAndOrderFront_(window)
NSApp.setMainMenu_(ComipoliMenu.new())
NSApp.activateIgnoringOtherApps_(True)
NSApp.run()

前回のに追加。

ウインドウが表示された後に順次画像が表示されていくのが確認できる。

PyObjC NSCollectionView

JXA で後回しにし続けていた画像サムネイル表示をそろそろ作りたい。

NSCollectionView – AppKit | Apple Developer Documentation

どうやらコレを使えばイケるらしい。
色々遠回りしたけど上手くいったコードをいきなり。

#!/usr/bin/env python3

import objc, os, re
from AppKit import *

PATH = '/Users/sasakima-nao/Pictures/nae'

class ItemView(NSView):
    def init(self):
        objc.super(ItemView, self).init()
        self.image = None
        return self

    def drawRect_(self, rect):
        self.image.drawInRect_(rect)

class CollectionViewItem(NSCollectionViewItem):
    def init(self):
        objc.super(CollectionViewItem, self).init()
        self.setView_(ItemView.alloc().initWithFrame_(NSMakeRect(0, 0, 100, 100)))
        return self

    def setRepresentedObject_(self, rep):
        objc.super(CollectionViewItem, self).setRepresentedObject_(rep)
        if rep != None:
            self.view().image = rep['pic']

class ComipoliCollectionView(NSCollectionView):
    def init(self):
        objc.super(ComipoliCollectionView, self).init()
        a = []
        l = os.listdir(PATH)
        for s in l:
            if re.search(r'\.(jpg|png)$', s, re.I):
                f = '{0}/{1}'.format(PATH, s)
                image = NSImage.alloc().initWithContentsOfFile_(f)
                a.append({'pic': image})
        self.setItemPrototype_(CollectionViewItem.new())
        self.setContent_(a)
        return self


class ComipoliWindow(NSWindow):
    def init(self):
        frame = NSMakeRect(100, 400, 400, 300)
        objc.super(ComipoliWindow, self).initWithContentRect_styleMask_backing_defer_(
            frame,
            NSTitledWindowMask |
            NSClosableWindowMask |
            NSResizableWindowMask |
            NSMiniaturizableWindowMask,
            NSBackingStoreBuffered,
            False)
        self.setTitle_('thumbnail')
        self.setDelegate_(self)
        # view
        self.canvas = ComipoliCollectionView.new()
        self.canvas.setFrameSize_(frame.size)
        self.contentView().addSubview_(self.canvas)
        return self

    def windowDidResize_(self, sender):
        aw = self.contentView().frame().size.width
        ah = self.contentView().frame().size.height
        size = NSMakeSize(aw, ah)
        self.canvas.setFrameSize_(size)

class ComipoliMenu(NSMenu):
    def init(self):
        objc.super(ComipoliMenu, self).init()
        item_app  = NSMenuItem.new()
        self.addItem_(item_app)
        menu_app = NSMenu.new()
        item_app.setSubmenu_(menu_app)
        item_quit = NSMenuItem.new()
        item_quit.initWithTitle_action_keyEquivalent_('Quit', 'terminate:', 'q')
        menu_app.addItem_(item_quit)
        return self

NSApplication.sharedApplication()
window = ComipoliWindow.new()
window.makeKeyAndOrderFront_(window)
NSApp.setMainMenu_(ComipoliMenu.new())
NSApp.activateIgnoringOtherApps_(True)
NSApp.run()

NSCollectionView, NSCollectionViewItem, NSView が最小限必要。
表示したい NSImage は適当なキーで辞書にいれて配列に突っ込む。
辞書毎に setRepresentedObject_ が呼ばれるので取り出す。
他にも手段があるようだけど、一つの方法として。

後選択可能にしたりとかもやらないと。
画像や PDF のリサイズやバックグラウンド処理をやらないと。
苗ちゃんの画像が古いのは実はもうやっていないからだとか。