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()

前回のに追加。

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