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

GtkListBox

GTK+ 3.10 で追加された Widget に GtkListBox がある。

Windows でいうところの LISTBOX とは全然違って GtkBox のサブクラス。
GtkListStore のデータを表示するのではなく GtkWidget を普通に表示する。
その詰め込んだ GtkWidget を選択可能にする、ということみたい。

何に利用すれば便利なのかよく解らない。
自力で色々試しても GtkTreeView で別にイイじゃんと思ってしまう。
GtkTreeView の代わりに使うとデータバインドができなくて不便なだけ。

使い道が解らないのも悔しいのでもっと調べてみる。
GNOME(GTK+) 3.10 で導入されたのだからどこかで使っているはず。
どうやらユニバーサルアクセスや検索の設定ダイアログで使っているっぽい。

univ

久々にこのダイアログを見たけどこんなに変わっていたのか。
そういえば Android スマートフォンの設定画面がこんな感じだ。
ListBox という名前に惑わされて想像力が偏っていたみたい。
よし使い道はなんとなく理解した、次はサンプルコードを探そう。

https://github.com/bratsche/gtk-/blob/master/tests/testlist.c

良さげなのを見つけたけど gtk_main_quit(); 処理が無いのは何故だろう。
海外を探すとこんなのが多いよね、ささやかなイジワルなのでしょうか。
端末からテスト起動して Ctrl+C で終了するだけだから気にしていないだけかも。

とりあえず上記を参考にユニバーサルアクセスダイアログっぽいのを作ってみる。
もちろん Python で、C は面倒臭いもん。

#!/usr/bin/env python3

from gi.repository import Gtk

CSS = '''
GtkListBoxRow {
    border-width: 1px;
    border-style: outset;
    border-color: lightgray;
}'''

DATA = ["YAMAHA", "HONDA", "KAWASAKI", "SUZUKI"]

class Row(Gtk.ListBoxRow):
    def __init__(self, label):
        Gtk.ListBoxRow.__init__(self)
        self.label = Gtk.Label(label)
        self.switch = Gtk.Switch()
        hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        hbox.pack_start(self.label, False, False, 0)
        hbox.pack_end(self.switch, False, False, 0)
        self.add(hbox)
        self.show_all()

class ListWin(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        # GtkCssProvider
        provider = Gtk.CssProvider()
        provider.load_from_data(CSS.encode("utf-8"))
        # GtkStyleContext
        Gtk.StyleContext.add_provider_for_screen(
                self.get_screen(),
                provider,
                Gtk.STYLE_PROVIDER_PRIORITY_USER)
        # ListBox
        listbox = Gtk.ListBox()
        listbox.connect("row-activated", self.on_listbox_row_activated)
        for data in DATA:
            row = Row(data)
            listbox.add(row)
        # Selection
        combobox = Gtk.ComboBoxText.new()
        combobox.append_text("Gtk.SelectionMode.NONE")
        combobox.append_text("Gtk.SelectionMode.SINGLE")
        combobox.set_active(listbox.get_selection_mode())
        combobox.connect("changed", self.on_combobox_changed, listbox)
        # CheckButton
        checkbutton = Gtk.CheckButton.new_with_label("Single Click Mode")
        checkbutton.set_active(listbox.get_activate_on_single_click())
        checkbutton.connect("clicked", self.on_checkbutton_clicked, listbox)
        # Pack
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 20)
        vbox.pack_start(listbox, False, False, 0)
        vbox.pack_start(combobox, False, False, 0)
        vbox.pack_start(checkbutton, False, False, 0)
        self.add(vbox)
        self.set_border_width(10)
        self.show_all()

    def do_delete_event(self, event):
        Gtk.main_quit()

    def on_listbox_row_activated(self, listbox, row, data=None):
        b = row.switch.get_active()
        row.switch.set_active(not b)

    def on_combobox_changed(self, combo, listbox):
        b = combo.get_active()
        listbox.set_selection_mode(b)

    def on_checkbutton_clicked(self, button, listbox):
        b = button.get_active()
        listbox.set_activate_on_single_click(b)

ListWin()
Gtk.main()

listtest

データを生で管理するのではなく構造体やクラスにするといいみたい。
ソート機能はたとえばよくクリックするものをカウントして順に並べるとかに使えそう。
GtkBox の各ペーンに選択機能とクリック感知機能を追加したと考えればよさげ。
名前に惑わされなくなると結構使い道が思い付く、不思議なものです。

Gst step

Y901x 1.1.0 を公開。
多重起動防止は行わないけど GtkApplicationWindow 化。
アプリケーションメニューに About を移動、新しいウインドウ追加。

つまり今の GNOME アプリっぽくしたかっただけ。
次でメニューバーを取っ払う予定、どうにでもなれ。

おまけでポーズ時のみ左右矢印キーでコマ送りできるように。
しかし Gst.Event.new_step() 関数ではゼロ以下の指定ができない。
ようするにコマ送りはできるけどコマ戻しはできない。

self.player.seek() の第一引数をマイナスにすると逆転再生できる。
実はその状態でコマ送りするとコマ戻しになる、なんとも変な感じだけど。

なので一度逆転させて送った後に元に戻せばイケるはず。
なんだけどちっとも上手く動作してくれないんだな、これが。

tinbergen/tinbergen.py at master ? biogeo/tinbergen ? GitHub

PyGtk の頃から同じだったみたい。
やっぱりみんな最初はそうしようとするよね。

しかたがないのでフレームレートから自力計算でシーク処理。
したんだけど上手く動くファイルがほとんどないや。
GstSeekFlags をどう変更しても変わらないってどうよ。

二秒送りとかなら少しはマシだけどソレさえ綺麗に動かない場合がある。
Avidemux くらいの細かいコマ戻しが理想なんだけど、どうすれば…

ちなみに上記をまんま PyGI 化してもフレームレートは取得できない。

framerate = 0

vsink = self.player.props.video_sink
if vsink:
    for pad in vsink.pads:
        caps = pad.get_current_caps()
        success, num, denom = caps.get_structure(0).get_fraction('framerate')
        framerate = num

こんな方法を使ったけどもっとイイ方法があるかも。

GtkStack GtkStackSwitcher GtkHeaderBar

GNOME 3.10 で誰でもすぐ気が付くこと。
nautilus, gnome-system-monitor 等のタイトルバーがコンテナに。

gnome_system_monitor

今までどおりとタッチパネルの両立を考えると上手い手段だよね。
タッチ操作では常に最大化、なのでタイトルバーにボタンを配置。
双方で別々に UI を作る必要がなくなる、違和感は最初だけだし。

メニューバーと右クリックを排除、アクティブウインドウが一画面。
アプリケーションメニューがスマホやタブレットのメニューボタン。
という考え方で UI を構築していけば勝手にタッチ対応、みたいに。

で、このタイトルバーにするにはどうするのかな。

GtkHeaderBar を作って set_titlebar() だけでいいようだ。
set_custom_title() で中心、pack_start() で左寄せにできる。
同じく GTK+ 3.10 で追加された GtkStack で Notebook みたいに使える。
GtkStackTransitionType にてアニメーションの指定まで可能。

#!/usr/bin/env python3

from gi.repository import Gtk

class Win(Gtk.Window):
    """
        Python with GTK+ 3.10 new widget
        GtkStack, GtkStackSwitcher, GtkHeaderBar
    """
    def __init__(self):
        Gtk.Window.__init__(self)
        # GtkStack
        stack = Gtk.Stack()
        stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
        # GtkStackSwitcher
        sw = Gtk.StackSwitcher()
        sw.set_stack(stack)
        # page1
        tv = Gtk.TextView()
        buf = tv.get_buffer()
        buf.set_text("This is a\nGtkTextView")
        stack.add_titled(tv, "view", "view")
        # page2
        label = Gtk.Label("This is a\nGtkLabel")
        stack.add(label)
        stack.child_set_property(label, "title", "label")
        # GtkHeaderBar
        hbar = Gtk.HeaderBar()
        hbar.set_custom_title(sw)
        hbar.set_show_close_button(True)
        self.set_titlebar(hbar)
        self.add(stack)
        self.resize(400, 200)
        self.show_all()

    def do_delete_event(self, event):
        Gtk.main_quit()

Win()
Gtk.main()

gtk_headerbar

gtk_stack_add_titled() で add と title 指定が一発。
調べると単なる gtk_container_add_with_properties() のラッパーみたい。
なので child_set_property() にて title を変更することもできる。

予想より簡単だったし今後はコレでいこうと思う。
問題は GnomeShell 以外の環境ではどういう見た目になるか…

GtkApplication: delete-event and remove_window

destroy and delete-event Signal | PaePoi

の問題がやっと解決した。
正しい手段ではないだろうけどと前置きして。

delete-event を emit してもウインドウは破棄されない。
でも[閉じる]ボタンなら普通に破棄される。
destroy を投げる手段は単独ウインドウならイケるけど複数だと駄目だった。
GtkApplication から remove すれば当然強制破棄される。

だったらこうすりゃいいじゃないか。

#!/usr/bin/env python3

import sys
from gi.repository import Gtk, Gdk

class RemoveWin(Gtk.ApplicationWindow):
    """
        emit delete-event to remove_window
    """
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        button = Gtk.Button("remove_window")
        button.connect("clicked", self.on_button_clicked)
        self.add(button)
        self.resize(320, 240)
        self.show_all()

    def on_button_clicked(self, button, data=None):
        # emit the delete-event
        self.emit("delete-event", Gdk.Event(Gdk.EventType.DELETE))
        # remove
        self.props.application.remove_window(self)

    def do_delete_event(self, event):
        # check the window geometry
        x, y = self.get_size()
        print(x, y) #=> 320 240

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self)
        
    def do_activate(self):
        RemoveWin(self)

    def do_window_removed(self, window):
        # check the window geometry
        x, y = window.get_size()
        print(x, y) #=> 320 240 or 123 32
        # call the default
        Gtk.Application.do_window_removed(self, window)

app = App()
app.run(sys.argv)

delete-event を投げて remove_window で強制破棄。
かなり強引な処理だと思うけどこの手段ならボタンやメニューから確実に終了できる。
かつボタンでも[閉じる]ボタンでも delete-event を通る。

ついでに delete_event と window_removed でジオメトリを確認。
window_removed では[閉じる]ボタンの場合はサイズがおかしい。
やっぱり強引すぎるかな…

つまり conf 等に終了時サイズを記録したい場合は delete_event ハンドラ時に。
いや、自アプリで必要だったので。

ところで do_* はハンドラではなく関数のオーバーライドみたい。
ハンドラからこの関数が呼ばれているという解釈でいいのかな。
do_window_removed は多分こうだろうと適当に書いたら普通に通った。
全部のハンドラがこの仕組みではないようだけど覚えておいたほうがいいね。

Python with GTK3 Auto-connected

今まで PyGI で散々ウソを書いていた。

#!/usr/bin/env python3

from gi.repository import Gtk, Gdk

class Win(Gtk.Window):
    """
        Auto-connected by the prefix of do_
    """
    def __init__(self):
        Gtk.Window.__init__(self)
        self.set_events(Gdk.EventMask.BUTTON_PRESS_MASK)
        #self.connect("button-press-event", self.on_button_press_event)
        #self.connect("delete-event", Gtk.main_quit)
        self.show_all()

    def do_delete_event(self, event):
        Gtk.main_quit()

    def do_button_press_event(self, event):
        self.begin_move_drag( event.button, event.x_root, event.y_root, event.time)

Win()
Gtk.main()

まさかこんな方法があったなんて、、、、、

ハイフンをアンダーバーに置換して do_ のプリフィクスだけで自動コネクト。
パッキングされた Widget は無理なので素直に自前コネクトを…
いや、コレも手段を知らないだけかも。

自前コネクトでも動くのでお好みで、ということで。

#!/usr/bin/env python3

import sys
from gi.repository import Gtk, Gio

"""
    ApplicationMenu does not apply in GtkWindow
"""

#class Win(Gtk.ApplicationWindow):
class Win(Gtk.Window):
    def __init__(self, app):
        #Gtk.ApplicationWindow.__init__(self, application=app)
        Gtk.Window.__init__(self, application=app)
        self.show_all()

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self)

    def do_activate(self):
        self.window = Win(self)
        self.window.present()

    def do_startup(self):
        Gtk.Application.do_startup(self)
        # AppMenu
        menu = Gio.Menu()
        menu.append("New", "app.new")
        menu.append("Quit", "app.quit")
        self.set_app_menu(menu)
        # option "new"
        new_action = Gio.SimpleAction.new("new", None)
        new_action.connect("activate", self.new_cb)
        self.add_action(new_action)
        # option "quit"
        quit_action = Gio.SimpleAction.new("quit", None)
        quit_action.connect("activate", self.quit_cb)
        self.add_action(quit_action)

    def new_cb(self, action, parameter):
        print("New")

    def quit_cb(self, action, parameter):
        self.quit()

if __name__ == "__main__":
    app = App()
    app.run(sys.argv)

更に覚書ページでは GtkWindow を add_window していたけど
GtkApplicationWindow にしないとアプリケーションメニューが出せない。
自アプリに使おうとして適用されなくて初めて気が付いた。
application property は GtkWindow にあるのにさ。

実際に何か作らないと気が付かないことって多いよなぁ。
それにしても。

appmenu

アプリケーションメニューは Lubuntu 等ではタイトルバーの下になる。
LXDE が Qt に移行する最大の原因はコレなのかな。
どうせ Qt もタッチパネル向け UI に変わると思うけど。