PyObjC」タグアーカイブ

PyObjC Exif

よし休日だ。
今日こそ、今度こそは五条川でカワセミを見つけて撮影してやるぞ!
と昼前に遊歩道へ行って十分後。

こんなにあっさり。
わざわざ八田川まで通っていた日々は何だったんだろう。
まあいい、次は狩りの瞬間を撮りたいな。

ところで。

Fedora の場合 Exif 情報を得るには GExiv2 を使えばいい。
macOS の場合はどうすればいいんだろう、ちゃちゃっと検索。

@macosx: NSImage Exif (metadata)

CGImageSource なんてものが SDK にあるみたい。
連想配列を使うのなら PyObjC で書くとスゲェ簡単だぞ。
ImageIO SDK の Objective-c Bridge は以下にあった。

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/Quartz

ということでエラー処理を省いて PyObjC で書き換えてみた。
メソッドの場合は引数の数だけアンダーバーが必要なのを忘れずに。

#!/usr/bin/env python3

from AppKit import *
from Quartz.ImageIO import *
import sys

def get_exif_dict(filename):
    url = NSURL.fileURLWithPath_(filename)
    source = CGImageSourceCreateWithURL(url, None)
    data = CGImageSourceCopyPropertiesAtIndex(source, 0, None)
    return data #['{Exif}']

d = get_exif_dict(sys.argv[1])
print(d)

以上。

macos_exif

dict の中に更に {ExifAux} 等の dict があって細かく情報が得られる。
CFDictionaryRef も Python の dict 同様に添字でアクセスできる。
こんなに楽チンなのに PyObjC って全然流行らないのは何故だろう。

レンズの名前まで記録しているのね、こういう情報が残っていると助かる。
野鳥撮影は楽しいよ、プログラミングの楽しさになんか似ている。

macOS Get UTI

Apple 関連で開発をしていると UTI を調べる必要がある場合が多々ある。

Uniform Type Identifier – Wikipedia

Uniform Type Identifier Concepts

検索をしていたら素敵なページを見つけた。

Mac や iOS でファイルの種類を表す識別子 Uniform Type Identifiers を拡張子から調べる(Swiftで1行で出来る) – niwatakoのはてなブログ

てゆーか JXA でも PyObjC でもできる。
しかし困ったことに JXA では CFString が NSString にキャストできない。

objective c – JXA: Accessing CFString constants from CoreServices – Stack Overflow

上記を見つけてようやく解決。
console.log って C 言語の char[] を出力できる、初めて知った。
UTF16LE に変換は不要、CJK 文字列でも問題ないようです。

#!/usr/bin/osascript -l JavaScript

let jp = $('スズキ GSX250R').UTF8String;
console.log(jp);
//=> スズキ GSX250R

// ex: ft=js.jxa

ということで JXA にて簡単に調べるコマンドを作ってみる。

#!/usr/bin/osascript -l JavaScript

ObjC.import('CoreServices');

function run(argv) {
    for (let ext of argv) {
        let uti = $.UTTypeCreatePreferredIdentifierForTag(
            $.kUTTagClassFilenameExtension, $(ext), null);
        let s = $.CFStringGetCStringPtr(uti, 0);
        console.log(`${ext}: ${s}`);
    }
}

// ex: ft=js.jxa

getuti.js

せっかくなので基底タイプも調べたいぞ。
JXA で得る方法が解らなかったので PyObjC で書いてみる。
PyObjC は CFDictionary や CFString も Python の型と等価なので超簡単。
他の言語を使うのが馬鹿馬鹿しくなってしまうので注意が必要。

#!/usr/bin/env python3

import sys, CoreServices

for ext in sys.argv[1:]:
    uti = CoreServices.UTTypeCreatePreferredIdentifierForTag(
        CoreServices.kUTTagClassFilenameExtension, ext, None)
    arr = CoreServices.UTTypeCopyDeclaration(uti)['UTTypeConformsTo']
    con = ','.join(arr)
    print(f'{ext}: {uti} [{con}]')

# ex: ft=py

@PyObjC

getuti という拡張子の無い名前で +x のパーミッションを付けパスの通った場所へ。

getuti

JXA でやりたかったけどまだまだ修行が足りない。

NSView Auto Layout

NSView のレイアウト方式いろいろ – ひ?to?り?go?と

こんなの見つけた。
windowDidResize に頼らず NSView を引き延ばす手段がこんなにあったんだ。

autoresizingMask の指定
addConstraints でアンカーの指定
layout のオーバーライド

一つづつ作って試すの面倒だから NSView の上に NSView を置いて。
上記三つを全部 PyObjC でやってみた。

#!/usr/bin/env python3

from AppKit import *

RECT = ((0, 0), (300, 100))
wins = []

class TopView(NSView):
    def initWithFrame_(self, rect):
        objc.super(TopView, self).initWithFrame_(rect)
        return self

    def drawRect_(self, rect):
        # 全体にバッテンを描く
        NSColor.darkGrayColor().set()
        path = NSBezierPath.bezierPath()
        path.setLineWidth_(10)
        path.moveToPoint_((0, 0))
        path.lineToPoint_(rect.size) # タプルなのでこれでいい
        path.moveToPoint_((0, rect.size.height))
        path.lineToPoint_((rect.size.width, 0))
        path.stroke()

class SecondView(NSView):
    def initWithFrame_(self, rect):
        objc.super(SecondView, self).initWithFrame_(rect)
        self.v = TopView.alloc().initWithFrame_(RECT)
        self.addSubview_(self.v)
        return self

    def layout(self):
        # Override
        objc.super(SecondView, self).layout()
        self.v.setFrameSize_(self.frame().size)

class ThirdView(NSView):
    def initWithFrame_(self, rect):
        objc.super(ThirdView, self).initWithFrame_(rect)
        self.v = SecondView.alloc().initWithFrame_(RECT)
        self.addSubview_(self.v)
        #
        # addConstraints
        #
        self.v.setTranslatesAutoresizingMaskIntoConstraints_(False)
        self.addConstraints_([
            self.v.leftAnchor().constraintEqualToAnchor_constant_(self.leftAnchor(), 0),
            self.v.rightAnchor().constraintEqualToAnchor_constant_(self.rightAnchor(), 0),
            self.v.topAnchor().constraintEqualToAnchor_constant_(self.topAnchor(), 0),
            self.v.bottomAnchor().constraintEqualToAnchor_constant_(self.bottomAnchor(), 0)
        ])
        return self

class MyWindow(NSWindow):
    def init(self):
        objc.super(MyWindow, self).initWithContentRect_styleMask_backing_defer_(
            RECT,
            NSTitledWindowMask | NSClosableWindowMask |
            NSResizableWindowMask | NSMiniaturizableWindowMask,
            NSBackingStoreBuffered, False)
        self.center()
        self.setTitle_('Auto Resize')
        #self.setDelegate_(self)
        # View
        self.v = ThirdView.alloc().initWithFrame_(RECT)
        self.contentView().addSubview_(self.v)
        #
        # autoresizingMask
        #
        self.v.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable) # 2+16
        #
        return self

    #def windowDidResize_(self, sender):
    #    self.v.setFrameSize_(self.contentView().frame().size)

class AppDelegate(NSObject):
    def applicationDidFinishLaunching_(self, notification):
        window = MyWindow.new()
        window.makeKeyAndOrderFront_(window)
        wins.append(window)

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

NSApplication.sharedApplication()
NSApp.setMainMenu_(AppMenu.new())
NSApp.setDelegate_(AppDelegate.new())
NSApp.activateIgnoringOtherApps_(True)
NSApp.run()

なるほど、全部 PyObjC からでも使えますね。
でも結局 windowDidResize が一番扱いやすいような。。。。。

NSPopUpButton, NSComboBox

NSComboBox を使ってみたけど何か変だ。
システム環境設定のコンボボックスと形が違う。
タップでプルダウンも矢印の所しか反応しないし何コレ?

Views and Controls | Apple Developer Documentation
どうやらこれは NSPopUpButton というコントロールのようだ。
GTK+ みたいに Gallery が欲しいんですけど。
Widget Gallery: GTK+ 3 Reference Manual

NSComboBox が NSTextField のサブクラス。
NSPopUpButton が NSButton のサブクラス。

items = ['YAMAHA', 'SUZUKI']

class MyView(NSView):
    def initWithFrame_(self, rect):
        objc.super(MyView, self).initWithFrame_(rect)
        #
        # NSComboBox @ NSTextField init method
        cbox = NSComboBox.labelWithString_('Select')
        w, h = cbox.frameSize() # Get Control Height
        cbox.setFrame_(((10, 10), (200, h)))
        cbox.addItemsWithObjectValues_(items)
        self.addSubview_(cbox)
        #
        # NSPopUpButton
        pop = NSPopUpButton.alloc().initWithFrame_pullsDown_(((10, 40), (200, h)), False)
        pop.addItemsWithTitles_(items)
        self.addSubview_(pop)
        #
        return self

未選択状態が必要な場合は NSComboBox を利用。
みたいな使い分けでいいのかな?
どこをタップしてもいい NSPopUpButton はタッチ操作向けだよな。
もう少し弄ってみる。

pythonhosted

あれ? https://pythonhosted.org/pyobjc/index.html が消えている。

PyObjC Document はコッチに移動(?)したようだ。
Introduction ? PyObjC – the Python to Objective-C bridge
以前からあったのかどうかは知らない、基本 Fedora 屋なので。

というわけで NSTextField Tips 追加。
NSTextField – L’Isola di Niente
短いけど実用には充分にしたつもり。

しかし textDidChange: は何故デリゲートではなくメソッドなんだよ。
NSButton に至っては target, action 指定だし、この統一感の無さが macOS の中身。
macOS の洗練された見た目と統一された操作性はこの糞みたいな Cocoa で作られている。
GTK+ も GTK3 にならなかったらこんな感じになっていたんだろうな。

次は NSTextView にしようと思ったけど。
NSTextView – AppKit | Apple Developer Documentation
Rich Text のことばかりみたいだなぁ、ヤル気しねぇ。
NSSlider, NSComboBox あたりにしよう、のんびりと。