Programming」カテゴリーアーカイブ

GtkFileLauncher

Gtk.FileLauncher

Gtk 4.10 にこんな class が追加されていた。
いやえっと、ファイルからアプリを起動するなら gio コマンドでよくね?

#!/usr/bin/env python3

import gi, subprocess
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, Adw, Gio

PICFILE = './test.jpg'

class Win(Gtk.ApplicationWindow):
    '''
        Default App Launcher: Sample Code
    '''
    def __init__(self, a):
        # Set Adwaita Style
        manager = Adw.StyleManager.get_default()
        manager.set_color_scheme(Adw.ColorScheme.DEFAULT)
        # init
        Gtk.ApplicationWindow.__init__(self, application=a)
        # button
        button = Gtk.Button(label='Launch test.jpg')
        button.connect('clicked', self.on_button_clicked)
        self.set_child(button)

    def on_button_clicked(self, button):
        subprocess.run(['gio', 'open', PICFILE])

app = Gtk.Application()
app.connect('activate', lambda a: Win(a).present())
app.run()

みたいに。

GtkUriLauncher もある、gio コマンドは URI でもイケるんだが。
とはいえ、わざわざ追加したということは何かあるんだろうなって。
とっとと試してみる、使い方は GtkFileDialog と同じみたいね。

#!/usr/bin/env python3

import gi
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, Adw, Gio

PICFILE = './test.jpg'

class Win(Gtk.ApplicationWindow):
    '''
        GtkFileLauncher: Sample Code
    '''
    def __init__(self, a):
        # Set Adwaita Style
        manager = Adw.StyleManager.get_default()
        manager.set_color_scheme(Adw.ColorScheme.DEFAULT)
        # init
        Gtk.ApplicationWindow.__init__(self, application=a)
        # button
        button = Gtk.Button(label='Launch test.jpg')
        button.connect('clicked', self.on_button_clicked)
        self.set_child(button)

    def on_button_clicked(self, button):
        f = Gio.File.new_for_path(PICFILE)
        # GtkFileLauncher
        launcher = Gtk.FileLauncher(file=f)
        launcher.launch(self, None, self.on_launch_finish)

    def on_launch_finish(self, launcher, res):
        try:
            result = launcher.launch_finish(res)
            if result:
                print('Success')
            else:
                print('Failed')
        except Exception as e:
            print(f'Launch Error: {e}')

app = Gtk.Application()
app.connect('activate', lambda a: Win(a).present())
app.run()

launch

そういうことか!

デフォルトアプリではなく何で開くかを選択できる、ということだった。
てか GtkAppChooserDialog の後継でした、名前がまぎらわしいわ!
デフォルトアプリ起動なら上のコードほうが簡単です、と一言。
ということで。

hibari

今日が囀り続けるキビタキを三十分追いかけ、とうとう一枚も撮れず。
ヤケクソで午後はキジを探したら、ものすごく遠くにしか出てこないし。
あきらめて帰ろうと思ったところにヒバリが現れてくれました。
ヒバリのトサカって正面から見るとこんな感じなんだね。

Gtk.FileDialog

Gtk.FileDialog

GtkDialog 関連が廃止予定なので GTK+ 4.8 の間に置き換え。
と早めにしようとしたら GtkFileDialog が 4.10 にならないと用意されない。
ということでのんびり 4.10 を待っていたのは筆者だけではないと思う。

FileChooserDialog は GtkDialog ベース。
GtkDialog は GtkWindow ベース、更に GtkWidget ベース更に…
継承の仕組みとはいえ限られた用途な部品とは思えない作りだった。
GtkFileDialog は GObject からの単一継承と解りやすくなった。

いやそんな細かいことはどうでもいいですよね。
とっととサンプルコード。

#!/usr/bin/env python3

import gi
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, Adw, Gio

WRITE_TEXT = 'Python 文字列は UTF-8 に変換する'.encode('utf8')

class Win(Gtk.ApplicationWindow):
    '''
        GtkFileDialog: Sample Code
    '''
    def __init__(self, a):
        # Set Adwaita Style
        manager = Adw.StyleManager.get_default()
        manager.set_color_scheme(Adw.ColorScheme.DEFAULT)
        # init
        Gtk.ApplicationWindow.__init__(self, application=a)
        # Button
        button_open = Gtk.Button(label='_Open', use_underline=True)
        button_open.connect('clicked', self.on_button_open_clicked)
        button_save = Gtk.Button(label='_Save', use_underline=True)
        button_save.connect('clicked', self.on_button_save_clicked)
        button_old = Gtk.Button(label='_GtkFileChooserDialog', use_underline=True)
        button_old.connect('clicked', self.on_button_old_clicked)
        # box
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        vbox.append(button_open)
        vbox.append(button_save)
        vbox.append(button_old)
        self.set_child(vbox)

    def on_button_open_clicked(self, button):
        dlg = Gtk.FileDialog()
        dlg.open(self, None, self.on_open_dlg_callback)

    def on_open_dlg_callback(self, dlg, res):
        try:
            f = dlg.open_finish(res)
            print(f.get_path())
        except Exception as e:
            print('Cancel')

    def on_button_save_clicked(self, button):
        dlg = Gtk.FileDialog()
        dlg.save(self, None, self.on_save_dlg_callback)

    def on_save_dlg_callback(self, dlg, res):
        try:
            f = dlg.save_finish(res)
            stream = f.replace(None, False, Gio.FileCreateFlags.NONE)
            stream.write(WRITE_TEXT)
            stream.close()
        except Exception as e:
            print('Cancel')

    def on_button_old_clicked(self, button):
        dlg = Gtk.FileChooserDialog(title='Open', action=Gtk.FileChooserAction.OPEN, modal=True)
        dlg.add_buttons('_Cancel', Gtk.ResponseType.CANCEL, '_Open', Gtk.ResponseType.ACCEPT)
        dlg.set_transient_for(self)
        dlg.connect('response', self.on_open_dlg_response)
        # CSS CLASS
        dlg.get_widget_for_response(Gtk.ResponseType.ACCEPT).add_css_class('suggested-action')
        # Gtk.Widget.show() is deprecated
        dlg.set_visible(True)

    def on_open_dlg_response(self, dlg, response_id):
        if response_id == Gtk.ResponseType.ACCEPT:
            f = dlg.get_file()
            print(f.get_path())
        dlg.destroy()

app = Gtk.Application()
app.connect('activate', lambda a: Win(a).present())
app.run()

filedialog

GtkFileChooserDialog は GTK+ 4.10 でもエラーにはなりません。
AppKit|UIKit 同様に廃止予定にして頃合で削除するみたい。

open と save の振り分けはプロパティではなく関数になりました。
複数選択の指定も関数になったけど説明はいらないよね。
シグナルではなくコールバック関数にて処理だけどあまり変わらない。
GtkDialog とは違い自前で破棄する必要は無くなったようです。

open file

ACCEPT ボタンは CSS 指定せずとも勝手に青くなります。
GtkFileFilter は default-filter プロパティで指定可能。
後は公式を見れば使い方は解ると思う。

ついでに気がつく、Gtk.Widget.show|hide も廃止なんだね。
visible プロパティと被っていたし整理したのかな。

ところで WordPress のアップロードがこの FileDialog になった。
複数選択ができないんですけど、一個づつアップロードするのメンドイ。

ということで我が Comipoli も更新しようと思ったのですが。
svg アイコンが摘要されない、128×128 強制になったのだろうか?
現在 Boxy SVG というアプリと格闘中、ということで。

kiji

昨日今日と夏鳥を求めて歩き回って成果はゼロ。
キジなら何故か簡単に見つかってこんなカッコイイ写真が撮れるのに。
筆者は今年も夏鳥とは相性が悪いのか。。。。。

macOS: Get UTI (Ventura)

macOS 13 Ventura で以下の関数が使えなくなった件。

# Deprecated
UTTypeCreatePreferredIdentifierForTag

UTI を調べる方法にて検索するとメッチャ見つかります。
でももう使えません、別の方法を探す。

UTType | Apple Developer Documentation

UTType クラスに typeWithFilenameExtension メソッドがある。
+ だから static method ですね、これ使えるかも。
以下 PyObjC です、他言語の人は変換してね。

#!/usr/bin/env python3

'''
    PyObjC @ Get UTI
'''

import sys, UniformTypeIdentifiers

for filename in sys.argv[1:]:
    n = filename.rfind('.') + 1
    if n > 0:
        ext = filename[n:]
        uti = UniformTypeIdentifiers.UTType.typeWithFilenameExtension_(ext)
        print(f'{ext}: {uti}')


''' Deprecated

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

get uti

よしデジカメ RAW ファイルでも問題なく UTI が得られるぞと。
本サイトのほうも書き換えしておきます。
しかし以前貼ったスクリプトで WebP 化したけど文字がチト見づらいな。
これも要改良かな、ということで。

aoji

今日はアオジが撮れました。

Escape

INI ファイルの読み書きページを更新しました。
INI ファイルの読み書き – Paepoi

セクションを見つける正規表現に最初戸惑った。

EXP = r'^\[\w+\]$'

でイケると思ったけど文字列に半角スペースがあると認識できない。
半角スペースを使うなで済ませようとも思ったけど。

EXP = r'^\[[^\]]+\]$'

そうだ「閉じブラケット以外の文字列なら何でもいい」にすれば!
こんなアホな思いつきに対応できる正規表現ってやはり面白い。

とほほの正規表現入門 – とほほのWWW入門
ところで、とほほさんで見た「ブラケット内は記号の意味を失う」なんですが。

#! /usr/bin/env python3

import re

# [..] にマッチさせる正規表現
EXP = r'^\[[^\]]+\]$'
# EXP = r'^\[[^]]+\]$' # Python OK

a = ['[test]', '[test2]', '[test3] ', ' [test4]', '[test 5]']

for s in a:
    if re.search(EXP, s):
        print(s)

Python

#! /usr/bin/env php

<?php

// [..] にマッチさせる正規表現
$EXP = '/^\[[^\]]+\]$/';
// $EXP = '/^\[[^]]+\]$/'; # PHP OK

$a = ['[test]', '[test2]', '[test3] ', ' [test4]', '[test 5]'];

foreach ($a as $s) {
    if (preg_match($EXP, $s))
        echo $s.PHP_EOL;
}
?>

PHP

#! /usr/bin/gjs

// [..] にマッチさせる正規表現
const EXP = /^\[[^\]]+\]$/;
//const EXP = /^\[[^]]+\]$/; // Gjs NO

let a = ['[test]', '[test2]', '[test3] ', ' [test4]', '[test 5]'];

for (let s of a) {
    if (EXP.test(s)) {
        print(s)
    }
}

Gjs ダメだった。

JavaScript エンジンは複数あるので全部かどうかは試していないけど。
エスケープすれば全部イケたのでブラケット内もエスケープしたほうがいいかと。

deprecated: SafeConfigParser

Python 3.11 は SafeConfigParser が非推奨になっていた。

deprecated

3.12 で完全削除のようです、3.11 は一応まだ動く。
今のうちに 2013 年に作った下記ページを書き換えとかないと。

INI ファイルの読み書き – Paepoi

てか GTK3 のコードだし、require_version していないし。
それと文字列をシングルクォートに統一でココは手をつけていないし。
自作クラスのほうも今なら正規表現にするし。

configparser — 設定ファイルのパーサー ? Python 3.11.1 ドキュメント

configparser.ConfigParser にすればいいのかな?
GTK4 で書き換えしてみよう。

#! /usr/bin/env python3

import os, configparser, gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk

INI = 'test.conf'

class Win(Gtk.ApplicationWindow):
    '''
        ini(conf) の読み書き例
        Window の位置と大きさを保存と復元
    '''
    def __init__(self, a):
        '''
            起動時に ini を読み込む
            GTK4 は位置指定できないのかな?
        '''
        Gtk.ApplicationWindow.__init__(self, application=a, title='ini')
        # 設定ファイルを探す
        if os.path.exists(INI):
            # configparser を作成し読み込む
            conf = configparser.ConfigParser()
            conf.read(INI)
            # 後での追記を考えて has_opthon しておこう
            cx = 200
            cy = 200
            if conf.has_option('window', 'width'):
                cx = conf.getint('window', 'width')
            if conf.has_option('window', 'height'):
                cy = conf.getint('window', 'height')
            self.set_default_size(cx, cy)

    def do_close_request(self):
        '''
            GTK3 では do_delete_event
            終了時に ini に書き込み
        '''
        conf = configparser.ConfigParser()
        if os.path.exists(INI):
            conf.read(INI)
        # [window] セクションが存在しなければ追加
        if not 'window' in conf.sections():
            conf.add_section('window')
        # サイズを取得(タプルで戻る)して conf にセット
        cx, cy = self.get_default_size()
        conf.set('window', 'width', str(cx))
        conf.set('window', 'height', str(cy))
        # ファイルに書き込む
        with open(INI, 'w') as f:
            conf.write(f)
        return False

app = Gtk.Application()
app.connect('activate', lambda a: Win(a).present())
app.run()

これでイケるようだ。
そういえば GTK4 でウインドウの位置指定ってまだ調べていなかったな。