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

UTF8_STRING

PyGObject Tips 書き直しもやっと最後の項目になった。
そのドラッグアンドドロップについてチマチマ調べている。

とりあえず文字列のドロップを追加することに、したんだけど。
ようするに Gedit 等で文字列選択してドラッグしたもののことね。
下記コメントアウトが昔書いたやり方です。

#!/usr/bin/env python3

import sys, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib#, Gdk

class Win(Gtk.ApplicationWindow):
    '''
        TreeView
    '''
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app, title='Py')
        # DnD
        '''
        uri = Gtk.TargetEntry.new('text/uri-list', 0, 0)
        plain = Gtk.TargetEntry.new('text/plain', 0, 0)
        self.drag_dest_set(
                Gtk.DestDefaults.MOTION |
                Gtk.DestDefaults.HIGHLIGHT |
                Gtk.DestDefaults.DROP,
                [uri, plain],
                Gdk.DragAction.COPY )
        '''
        self.drag_dest_add_uri_targets()
        self.drag_dest_add_text_targets()
        ###self.drag_dest_add_image_targets()
        #
        self.label = Gtk.Label(label='Please drop your files')
        self.add(self.label)
        self.show_all()

    def do_drag_data_received(self, context, x, y, data, info, time):
        '''
            data: GtkSelectionData
        '''
        #print(data.targets_include_text()) # All False
        name = data.get_data_type().name()
        self.props.title = name
        #if name == 'text/plain':
        if name == 'UTF8_STRING':
            s = data.get_text()
            self.label.set_text(s)
        elif name == 'text/uri-list':
            uris = data.get_uris()
            l = []
            for uri in uris:
                fn = GLib.filename_from_uri(uri)[0]
                l.append(GLib.path_get_basename(fn))
            self.label.set_text('\n'.join(l))
        else:
            self.label.set_text(name)

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

    def do_startup(self):
        Gtk.Application.do_startup(self)
        Win(self)

    def do_activate(self):
        self.props.active_window.present()

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

えぇ。。。
gtk_drag_dest_add_text_targets を指定するだけだった。
てか UTF8_STRING という ContentType があったんだ、知らなかった。
新しいのかと思ったら GTK+2.6 からみたい、何故知らなかったんだ俺!

gtk_selection_data_targets_include_text は何をやっても False だ。
上記手段で判別はできるけど、この関数っていったい何なんだろう?

gtk_drag_dest_add_image_targets は簡単に試す手段が無かった。
だいたい使い方は解るので Tips ページを作る時に。
今週こそ終わらせなきゃ、おかげで mac 関連が完全に止まっているし。
macOS がバージョンアップする前にさわっておきたい。

PyGObject: Set Child Property

GtkPopoverMenu の submenu 続き。
Child Properties にある submenu にコードでアクセスする手段が解った。

gtk_container_child_set_property って関数があるヤン!
devhelp で GtkContainer の所を見るだけで解決してしまった。

それと GtkPopoverMenu の Description を見ると
親メニューに戻るボタンを最初に入れる、inverted, centered を使え。
そしてメインメニュー名は ‘main’ です、とある。

何だ、全部 devhelp に書いてあった。

英語が読めない?それは読もうとしないだけだ。
筆者だって Google 翻訳が無かったらこんなことワカランわい。

おまけで。
GtkPopoverMenu の use-markup は勝手に True にセットされるようだ。
visible も show_all を使うなら特に設定は不要、今まで無駄を書いていた。
メニューボタンも GNOME アプリは GtkMenuButton を使っているみたい。

そんなこんなで。
前回のサンプルコードをコードだけで書き直し。
プラス、メインメニューに戻るメニューの追加。
プラス、GtkMenuButton に変更及び F10 キーへの対応。

#!/usr/bin/env python3

import sys, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio, Gdk

class Win(Gtk.ApplicationWindow):
    '''
        GtkPopoverMenu submenu Sample
        Do not use GtkBuilder
    '''
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app, title='Py')
        # First Contents
        menu_open  = Gtk.ModelButton(action_name='app.new_window_action', text='_New Window')
        menu_new   = Gtk.ModelButton(menu_name = 'tool_page', text='_Tool')
        menu_quit  = Gtk.ModelButton(action_name='app.quit_action', text='_Quit Application')
        #
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, margin=10)
        vbox.pack_start(menu_open, False, False, 0)
        vbox.pack_start(menu_new, False, False, 0)
        vbox.pack_start(Gtk.Separator(), False, False, 0)
        vbox.pack_start(menu_quit, False, False, 0)
        # Second Contents
        menu_back   = Gtk.ModelButton(menu_name = 'main', inverted=True, centered=True, text='_Tool')
        menu_set    = Gtk.ModelButton(action_name='app.set_title_action', text='_Set Titlebar Text')
        menu_append = Gtk.ModelButton(action_name='app.append_title_action', text='_Append Titlebar Text')
        #
        tool_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, margin=10)
        tool_box.pack_start(menu_back, False, False, 0)
        tool_box.pack_start(menu_set, False, False, 0)
        tool_box.pack_start(menu_append, False, False, 0)
        # GtkPopoverMenu
        self.popovermenu = Gtk.PopoverMenu()
        self.popovermenu.add(vbox)
        self.popovermenu.add(tool_box)
        self.popovermenu.child_set_property(tool_box, 'submenu', 'tool_page')
        # MenuButton
        open_image = Gtk.Image(icon_name='open-menu-symbolic', icon_size=Gtk.IconSize.MENU)
        self.menu_button = Gtk.MenuButton(image=open_image, popover=self.popovermenu)
        self.menu_button.connect('toggled', self.on_menu_button_toggled)
        # GtkHeaderBar
        hbar = Gtk.HeaderBar(show_close_button=True)
        hbar.pack_end(self.menu_button)
        self.set_titlebar(hbar)
        # F10
        accelgroup = Gtk.AccelGroup()
        self.add_accel_group(accelgroup)
        accelgroup.connect(Gdk.KEY_F10, 0, Gtk.AccelFlags.VISIBLE, self.on_f10)
        # self
        self.resize(240, 200)
        self.show_all()

    def on_f10(self, group, acceleratable, keyval, modifier):
        self.menu_button.clicked()

    def on_menu_button_toggled(self, button):
        if button.props.active:
            self.popovermenu.show_all()

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

    def do_startup(self):
        Gtk.Application.do_startup(self)
        # GAction
        new_window_action = Gio.SimpleAction(name='new_window_action')
        set_title_action = Gio.SimpleAction(name='set_title_action')
        append_title_action = Gio.SimpleAction(name='append_title_action')
        quit_action = Gio.SimpleAction(name='quit_action')
        # Add
        self.add_action(new_window_action)
        self.add_action(set_title_action)
        self.add_action(append_title_action)
        self.add_action(quit_action)
        # Keyboard Shortecut
        self.set_accels_for_action('app.new_window_action', ['N'])
        self.set_accels_for_action('app.set_title_action', ['S'])
        self.set_accels_for_action('app.append_title_action', ['A'])
        self.set_accels_for_action('app.quit_action', ['Q'])
        # Signal
        new_window_action.connect('activate', self.on_new_window_action)
        set_title_action.connect('activate', self.on_set_title_action)
        append_title_action.connect('activate', self.on_append_title_action)
        quit_action.connect('activate', self.on_quit_action)
        # Window
        Win(self)

    def on_set_title_action(self, action, parameter):
        self.props.active_window.props.title = 'Hello'

    def on_append_title_action(self, action, parameter):
        self.props.active_window.props.title += 'Hello'

    def on_new_window_action(self, action, parameter):
        Win(self)

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

    def do_activate(self):
        self.props.active_window.present()

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

なんとか GNOME 標準アプリに近づいてきた。

PyGObject: GtkPopoverMenu submenu

PyGObject Tips の書き換え作業で次はメニューなんだが。。。。。
GtkPopoverMenu の submenu で想像以上に詰まっている。
GtkPopoverMenu | Child Properties にある submenu にアクセスする手段が全然解らない。
この項目はもっと上部に置いていたけどこの理由で下げたのは多分バレていない。

GI.Gtk.Objects.PopoverMenu

遠回りしまくってやっとこんなのを見つける。
筆者は経験値でなんとなく解るけどさ、こんなサンプルで理解できる人いるのか?
ということで、そのまんま動くサンプルコードを書いてみる。

#!/usr/bin/env python3

import sys, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio

XMLTEXT = '''<interface>
<object class="GtkPopoverMenu" id="MyMenu">
  <child>
    <object class="GtkBox">
      <property name="visible">True</property>
      <property name="margin">10</property>
      <property name="orientation">vertical</property>
      <child>
        <object class="GtkModelButton">
          <property name="visible">True</property>
          <property name="action-name">app.new_window_action</property>
          <property name="text" translatable="yes">_New Window</property>
        </object>
      </child>
      <child>
        <object class="GtkModelButton">
          <property name="visible">True</property>
          <property name="menu-name">more</property>
          <property name="text" translatable="yes">More</property>
        </object>
      </child>
      <child>
        <object class="GtkModelButton">
          <property name="visible">True</property>
          <property name="action-name">app.quit_action</property>
          <property name="text" translatable="yes">_Quit Application</property>
        </object>
      </child>
    </object>
  </child>
  <child>
    <object class="GtkBox">
      <property name="visible">True</property>
      <property name="margin">10</property>
      <property name="orientation">vertical</property>
      <child>
        <object class="GtkModelButton">
          <property name="visible">True</property>
          <property name="action-name">app.set_title_action</property>
          <property name="text" translatable="yes">Set Titlebar Text</property>
        </object>
      </child>
      <child>
        <object class="GtkModelButton">
          <property name="visible">True</property>
          <property name="action-name">app.append_title_action</property>
          <property name="text" translatable="yes">Append Titlebar Text</property>
        </object>
      </child>
    </object>
    <packing>
      <property name="submenu">more</property>
    </packing>
  </child>
</object>
</interface>'''

class Win(Gtk.ApplicationWindow):
    '''
        GtkPopoverMenu submenu Sample
        from GtkBuilder
    '''
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app, title='Py')
        # Menu Button
        button = Gtk.Button.new_from_icon_name('open-menu-symbolic', Gtk.IconSize.MENU)
        button.connect('clicked', self.on_menu_button_clicked)
        # Create Popover Menu
        builder = Gtk.Builder.new_from_string(XMLTEXT, -1)
        self.popovermenu = builder.get_object('MyMenu')
        self.popovermenu.props.relative_to = button
        # Add GtkHeaderBar
        hbar = Gtk.HeaderBar(show_close_button=True)
        hbar.pack_end(button)
        # self
        self.set_titlebar(hbar)
        self.show_all()

    def on_menu_button_clicked(self, widget):
        self.popovermenu.show_all()

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

    def do_startup(self):
        Gtk.Application.do_startup(self)
        # GAction
        new_window_action = Gio.SimpleAction(name='new_window_action')
        set_title_action = Gio.SimpleAction(name='set_title_action')
        append_title_action = Gio.SimpleAction(name='append_title_action')
        quit_action = Gio.SimpleAction(name='quit_action')
        # Add
        self.add_action(new_window_action)
        self.add_action(set_title_action)
        self.add_action(append_title_action)
        self.add_action(quit_action)
        # Keyboard Shortecut
        self.set_accels_for_action('app.new_window_action', ['<Control>N'])
        self.set_accels_for_action('app.set_title_action', ['<Control>S'])
        self.set_accels_for_action('app.append_title_action', ['<Control>A'])
        self.set_accels_for_action('app.quit_action', ['<Control>Q'])
        # Signal
        new_window_action.connect('activate', self.on_new_window_action)
        set_title_action.connect('activate', self.on_set_title_action)
        append_title_action.connect('activate', self.on_append_title_action)
        quit_action.connect('activate', self.on_quit_action)
        # Window
        Win(self)

    def on_set_title_action(self, action, parameter):
        self.props.active_window.props.title = 'Hello'

    def on_append_title_action(self, action, parameter):
        self.props.active_window.props.title += 'Hello'

    def on_new_window_action(self, action, parameter):
        Win(self)

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

    def do_activate(self):
        self.props.active_window.present()

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

なんとかサブメニューを表示することができた。
だけど Totem や Gedit のように上部に「戻るボタン」は出ない。
そもそも submenu Property にどうアクセスできているのかさえ XML ではワカンネ!

筆者は「コードだけでアプリを作りたい」人なんじゃい!
は置いておいて。
何がどうなってこうなったかを理解できていないと絶対に詰まる、もっと勉強だ。

PyGObject CSS

PyGObject 覚書ページの更新状況。
CSS 関連が色々と変更されていてワケワカメになっている。
y901x 1.1 の時に解っていて放置していたけどそろそろキチンと調べることに。

#!/usr/bin/env python3
 
import sys, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
 
OLD_CSS = '''
GtkLabel {
    color: blue;
}'''
NEW_CSS = '''
#MyLabel {
    color: red;
}'''
 
class Win(Gtk.ApplicationWindow):
    '''
        Font Set
    '''
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app, title='Py')
        # set the Widget Name
        label = Gtk.Label(label='ABCDEFG', name='MyLabel')
        self.add(label)
        # GtkCssProvider
        provider = Gtk.CssProvider()
        #provider.load_from_data(OLD_CSS.encode('utf-8'))
        provider.load_from_data(NEW_CSS.encode('utf-8'))
        label.get_style_context().add_provider(provider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
        #
        self.show_all()

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

    def do_startup(self):
        Gtk.Application.do_startup(self)
        Win(self)

    def do_activate(self):
        self.props.active_window.present()

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

と。

GtkWidget に名前を付けて # の接頭子でなんとか適用された。
GtkEntry や .entry という以前の指定は無視される、全部名前を付けてねと。
他にも手段があるのだろうけどコレが一番確実みたい。

GTK+ CSS Properties: GTK+ 3 Reference Manual

が指定できる CSS だけど、解り辛いったらありゃしない。
手探りで色々書きまくって覚えるしかない、Linux の世界はそういうものだ。

ところで GtkTextView のフォントを変更するにも CSS しか手段が無いようだ。
探しても探しても古い手段しか出てこない、適用できない。
こんな時は、Gedit のソースコードを見るのが一番、GPL 万歳。
gedit-view.c と gedit-pango.c を解析、ってほど難しいコードではないけど。

Fonts: Pango Reference Manual

PangoFontDescription を得て個別に CSS 文字列に変換していた。
Gedit のソースをよく見ると Italic かどうかを調べていない、あれ?
試したら Gedit は Italic 表示できないジャン、使わないとはいえ初めて知った!

PangoFontDescription には Italic かを調べる手段があるようだ。
それならということで。

#!/usr/bin/env python3

import sys, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Pango

TPL = '''
#MyTextView {{
    font: {0}pt "{1}";
    font-weight: {2};
    font-style: {3};
}}'''

class Win(Gtk.ApplicationWindow):
    '''
        Font Set
    '''
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app, title='Py')
        # GtkTextView
        self.view = Gtk.TextView(name='MyTextView')
        # deprecated
        #font_desc = Pango.font_description_from_string('Monospace 10')
        #view.override_font(font_desc)
        #
        # read the this script file
        with open(__file__) as f:
            s = f.read()
            self.view.get_buffer().set_text(s)
        # GtkFontButton
        button = Gtk.FontButton()
        button.connect('font-set', self.on_font_set)
        # GtkScrolledWindow
        sw = Gtk.ScrolledWindow(child=self.view)
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        vbox.pack_start(button, False, False, 0)
        vbox.pack_start(sw, True, True, 0)
        self.add(vbox)
        self.resize(300, 300)
        self.show_all()

    def on_font_set(self, button):
        '''
        font = button.get_font()
        n = font.rfind(' ')
        name = font[:n]
        size = font[n+1:]
        css = '#MyTextView {{ font: {0}pt "{1}"; }}'.format(size, name)
        '''
        desc = button.get_font_desc()
        size = desc.get_size() // Pango.SCALE
        name = desc.get_family()
        weight = desc.get_weight() // 1 # enum to int
        if weight == Pango.Weight.NORMAL:
            w = 'normal'
        elif weight == Pango.Weight.BOLD:
            w = 'bold'
        else:
            w = weight
        style = desc.get_style()
        if style == Pango.Style.OBLIQUE:
            s = 'oblique'
        elif style == Pango.Style.ITALIC:
            s = 'italic'
        else:
            s = 'normal'
        self.props.title = f'{size}|{name}|{w}|{s}'
        css = TPL.format(size, name, w, s)
        # GtkCssProvider
        provider = Gtk.CssProvider()
        provider.load_from_data(css.encode('utf-8'))
        self.view.get_style_context().add_provider(provider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
        #'''

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

    def do_startup(self):
        Gtk.Application.do_startup(self)
        Win(self)

    def do_activate(self):
        self.props.active_window.present()

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

よし bold も italic も適用されるようになったぞと。
コメントアウト部分だけでイケるかなと思ったけど駄目だった。
しかし GUI アプリなのに CSS でフォント指定って、時代の流れなんでしょうか。
とにかくコレで覚書ページの更新が進む、はず。

child Property

お盆休みが終わってしまった。
この間に PyGObject Tips ページの全書き換えが、終わらなかった。。。。。

しかし想像以上に内容が古かった、特に

#label = Gtk.Label('text') # Compatibility PyGtk => stderr
label = Gtk.Label(label='text')

みたいな箇所が沢山あって全部 stderr を吐くという。
もう PyGtk は闇に葬らないといけないみたいね。

ところで GtkEntryCompletion の所に書いているんだけーが。
Gtk(PyGObject) Tips | 一行 EDIT – L’Isola di Niente

text_column をプロパティで指定したら何も表示されない。
関数で指定すれば問題なく表示されるんだけーが、見つけるのに超時間喰った。
いくら検索しても出てこないし、C だと関数しか手段が無いし当然かもだが。
今月中には全部終わらせたい、明日から仕事だけど。

そういえば、Stock Items が知らぬ間に Deprecated にと気が付く。
と思ったら 3.10 からかよ、何故今まで気が付かなかったんだよ俺!
ダイアログを作るのページで使ってしまったんだが、いつ書き換えよう?

他、こんなページを作っていてあることに気がつく。
GtkContainer には child というプロパティがある。
add で重ねられる Widget は全部コレの派生だ、つまり。

#scroll = Gtk.ScrolledWindow()
#scroll.add(flowbox)
scroll = Gtk.ScrolledWindow(child=flowbox)

これでいいジャン!
毎回毎回作って add してを書いていたのが一行で終わる、素晴らしい。
唯一の大発見であった。