L'Isola di Niente
L'Isola di Niente » PyGObject Tips » コンテナ(3.10 以降)

コンテナ(3.10 以降)

パーソナルコンピューターの GUI は十年以上変わらなかった。
それが iPhone, iPad 登場によって覆された、タッチ操作はこうすれば良かったと。
そのおかげで iOS の操作性はスタンダードになりタッチ操作の基盤ができた。

のかどうか本当にところは知りませんが。
GTK+ 3.10 からは結構大胆にタッチパネル向けな変更が行われています。

GtkHeaderBar

タイトルバーをコンテナ化して Widget を置くことができるようにしたもの
小さな画面でもクライアントエリアのサイズを最大限に大きくできるメリットがある
#!/usr/bin/env python3

from gi.repository import Gtk

class Win(Gtk.Window):
    """
        GtkHeaderBar へのパッキングは
        set_custom_title(widget) で中心
        pack_start|end(widget)   で左右に
    """
    def __init__(self):
        Gtk.Window.__init__(self)
        # Back-Forward Button
        back = Gtk.Button.new_from_icon_name("go-previous-symbolic", Gtk.IconSize.MENU)
        forw = Gtk.Button.new_from_icon_name("go-next-symbolic", Gtk.IconSize.MENU)
        back.set_valign(Gtk.Align.CENTER)
        forw.set_valign(Gtk.Align.CENTER)
        # Back-Forward Containers
        npbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        Gtk.StyleContext.add_class (npbox.get_style_context(), "linked")
        npbox.pack_start(back, False, False, 0)
        npbox.pack_start(forw, False, False, 0)
        # Close Button
        close = Gtk.Button.new_from_icon_name("window-close-symbolic", Gtk.IconSize.MENU)
        close.set_relief(Gtk.ReliefStyle.NONE)
        close.set_valign(Gtk.Align.CENTER)
        close.connect("clicked", Gtk.main_quit)
        # GtkHeaderBar
        hbar = Gtk.HeaderBar()
        hbar.pack_start(npbox)
        hbar.pack_end(close) # hbar.set_show_close_button(True)
        # self
        self.set_titlebar(hbar)
        self.resize(400, 200)
        self.show_all()

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

Win()
Gtk.main()

img/gtk_headerbar.png

GtkStack

GtkStack はコンテナです、 GtkNotebook のように単独ページを切り替えできます。
add したコンテナやウイジェットを切り替えして一画面として利用します。
切り替えのエフェクトを GtkStackTransitionType で指定することも可能。
GtkStackSwitcher と合わせて使うのでサンプルコードは以下にて。

GtkStackSwitcher

GtkStackSwitcher は GtkStack の各ページを切り替えするものです。
set された GtkStack の child_property を読み込んで必要なボタンを表示します。
#!/usr/bin/env python3

from gi.repository import Gtk

class Win(Gtk.Window):
    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()

img/gtk_stack.png

GtkRevealer

GtkRevealer は Widget の表示非表示をアニメーションするコンテナです。
親ウインドウの show_all() に左右されず視覚効果も指定できて便利。
#!/usr/bin/env python3

from gi.repository import Gtk

class RevearerWin(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.connect("delete-event", Gtk.main_quit)
        # button
        button = Gtk.Button.new_with_label("Click")
        button.connect("clicked", self.on_button_clicked)
        # reveal
        self.reveal = Gtk.Revealer.new()
        self.reveal.set_transition_type(Gtk.RevealerTransitionType.CROSSFADE)
        self.reveal.set_transition_duration(3000)
        # reveal child
        label = Gtk.Label("Hello\nWorld")
        self.reveal.add(label)
        hbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 10)
        hbox.pack_start(button, False, False, 0)
        hbox.pack_end(self.reveal, False, False, 0)
        self.add(hbox)
        self.show_all()

    def on_button_clicked(self, widget, data=None):
        b = self.reveal.get_reveal_child()
        self.reveal.set_reveal_child(not b)

RevearerWin()
Gtk.main()

img/gtk_revealer.png

GtkListBox

GtkListBox は GtkBox をリストビューのように選択可能にするコンテナです。
また選択状態になった、クリックされた等のシグナルも受け取れます。

Windows でいうところの LISTBOX とは全然違い上記が可能なコンテナです。
Android, iOS のスマートフォン設定画面を思い浮かべると理解しやすい。
#!/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()

img/gtk_listbox.png

GtkActionBar

GtkActionBar は Widget 配置を左右及び中心に綺麗に振り分けるコンテナ。
GtkBox にダミーを入れて振り分ける必要もう無い(実際に筆者はやっていた)
最上部は GtkHeaderBar があるので最下部で振り分けたい時に便利。
#!/usr/bin/env python3

from gi.repository import Gtk, Gio

class UnderBar(Gtk.Window):
    """
        pack_start 等は GtkHeaderBar と同じ
    """
    def __init__(self):
        Gtk.Window.__init__(self)
        # DrawingAre
        area = Gtk.DrawingArea.new()
        # Center Widget
        c1 = self.get_image_button("go-previous-symbolic")
        c2 = self.get_image_button("camera-photo-symbolic")
        c3 = self.get_image_button("go-next-symbolic")
        hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        hbox.pack_start(c1, False, False, 0)
        hbox.pack_start(c2, False, False, 0)
        hbox.pack_start(c3, False, False, 0)
        Gtk.StyleContext.add_class (hbox.get_style_context(), "linked")
        # Left, Right
        left = self.get_image_button("user-trash-symbolic")
        right = self.get_image_button("open-menu-symbolic")
        # ActionBar
        bar = Gtk.ActionBar.new()
        bar.pack_start(left)
        bar.set_center_widget(hbox)
        bar.pack_end(right)
        # pack
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        vbox.pack_start(area, True, True, 0)
        vbox.pack_start(bar, False, False, 0)
        # self
        self.add(vbox)
        self.connect("delete-event", Gtk.main_quit)
        self.set_title("UnderBar")
        self.resize(400, 100)
        self.show_all()

    def get_image_button(self, svg_name):
        button = Gtk.Button.new()
        image = Gtk.Image()
        image.set_from_icon_name(svg_name, Gtk.IconSize.MENU)
        button.set_image(image)
        return button

UnderBar()
Gtk.main()

img2/actionbar.png

GtkPopover

GtkPopover はコンテンツをポップアップする Widget です。
メニューのような単一用途ではなくどんな Widget でも入れることができる。
常に表示は不要だけど即呼び出したい補助機能に利用すると便利。
#!/usr/bin/env python3

from gi.repository import Gtk, Gio

class PopUp(Gtk.Window):
    """
        new の引数はポップアップの基準位置
        add で表示したいコンテンツを挿入
    """
    def __init__(self):
        Gtk.Window.__init__(self)
        # DrawingAre
        area = Gtk.DrawingArea.new()
        # Popup Button
        button = Gtk.Button.new_with_label("Option")
        button.connect("clicked", self.on_option_button_clicked)
        # ActionBar
        bar = Gtk.ActionBar.new()
        bar.pack_end(button)
        #
        # Popup Contents
        c1 = Gtk.CheckButton.new_with_label("ムチを使う")
        c2 = Gtk.CheckButton.new_with_label("ロウソクを使う")
        c3 = Gtk.CheckButton.new_with_label("三角木馬を使う")
        popbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        popbox.pack_start(c1, False, False, 0)
        popbox.pack_start(c2, False, False, 0)
        popbox.pack_start(c3, False, False, 0)
        # Popover
        self.pop = Gtk.Popover.new(button)
        self.pop.add(popbox)
        #
        # pack
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        vbox.pack_start(area, True, True, 0)
        vbox.pack_start(bar, False, False, 0)
        # self
        self.add(vbox)
        self.connect("delete-event", Gtk.main_quit)
        self.set_title("PopUp")
        self.resize(300, 200)
        self.show_all()

    def on_option_button_clicked(self, widget):
        """
            コンテンツ Widget は非表示になっているので注意
            全部表示でいいなら show_all でいい
        """
        self.pop.show_all()

PopUp()
Gtk.main()

img2/popover.png

GtkFlowBox

GtkFlowBox は Widget を幅に合わせて自動的に改行するコンテナ。
CSS3 の FlexBox みたいなのを期待したけど少し違います。
どうやら最小横幅を基準にコンテンツの高さを決定するようです。

GtkIconView のセルと違いコチラはどんな Widget でも入れられる。
それと GtkListBox 同様にコンテンツを選択することもできる。
#!/usr/bin/env python3

import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib, Gio, GdkPixbuf

NAE_PATH = "/home/sasakima-nao/pic/game/gf/nae_yuki_ssr"

class FlowBoxTest(Gtk.Window):
    """
        Easy GtkIconView
    """
    def __init__(self):
        Gtk.Window.__init__(self)
        # GtkFlowBox
        flowbox = Gtk.FlowBox.new()
        flowbox.set_valign(Gtk.Align.START)
        self.get_all_picture(flowbox)
        #flowbox.set_selection_mode(Gtk.SelectionMode.MULTIPLE)
        scroll = Gtk.ScrolledWindow.new()
        scroll.add(flowbox)
        # self
        self.index = 0
        self.add(scroll)
        self.resize(400, 300)
        self.show_all()

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

    def get_all_picture(self, flowbox):
        """
            Thumbnail JPEG File
        """
        d = Gio.file_new_for_path(NAE_PATH);
        # Get GFileEnumerator
        enum = d.enumerate_children(
                Gio.FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
                Gio.FileQueryInfoFlags.NONE)
        for info in enum:
            # info is GFileInfo
            content_type = info.get_content_type()
            if content_type == "image/jpeg":
                name = info.get_name()
                fullpath = "{0}/{1}".format(NAE_PATH, name)
                thumbnail = GdkPixbuf.Pixbuf.new_from_file_at_scale(fullpath, 100, 100, True)
                image = Gtk.Image.new_from_pixbuf(thumbnail)
                flowbox.add(image)

FlowBoxTest()
Gtk.main()

img2/flowbox.png

GtkPopoverMenu

GtkPopoverMenu は GtkPopover のサブクラス。
ポップアップコンテンツをメニューに特化したものです。

メニューアイテムは GtkMenuItem ではなく GtkModelButton を使います。
この GtkModelButton の action-name プロパティに GAction を指定しハンドラにします。
ウインドウで利用の場合 [win] のプリフィクスが必要。
#!/usr/bin/env python3
 
from gi.repository import Gtk, Gio

POPOVER = '''<interface>
<object class="GtkPopoverMenu" id="menu">
  <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="active">True</property>
          <property name="action-name">win.hello</property>
          <property name="text" translatable="yes">Hello</property>
        </object>
      </child>
      <child>
        <object class="GtkModelButton">
          <property name="visible">True</property>
          <property name="action-name">win.world</property>
          <property name="text" translatable="yes">World</property>
        </object>
      </child>
    </object>
  </child>
</object>
</interface>'''
 
class PopoverMenu(Gtk.ApplicationWindow):
    """
        GtkPopoverMenu Test
    """
    def __init__(self):
        Gtk.ApplicationWindow.__init__(self)
        # DrawingAre
        area = Gtk.DrawingArea.new()
        # Popup Button
        button = Gtk.Button.new_with_label("Option")
        button.connect("clicked", self.on_option_button_clicked)
        # ActionBar
        bar = Gtk.ActionBar.new()
        bar.pack_end(button)
        #
        # Popup Contents
        builder = Gtk.Builder.new_from_string(POPOVER, -1)
        self.menu = builder.get_object("menu")
        self.menu.set_relative_to(button)
        action = Gio.SimpleAction.new("hello", None)
        action.connect("activate", self.hello_cb)
        self.add_action(action)
        action2 = Gio.SimpleAction.new("world", None)
        action2.connect("activate", self.world_cb)
        self.add_action(action2)
        #
        # pack
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        vbox.pack_start(area, True, True, 0)
        vbox.pack_start(bar, False, False, 0)
        # self
        self.add(vbox)
        self.connect("delete-event", Gtk.main_quit)
        self.set_title("PopUp")
        self.resize(300, 200)
        self.show_all()

    def hello_cb(self, action, parameter):
        self.set_title("Hello")

    def world_cb(self, action, parameter):
        self.set_title("World")

    def on_option_button_clicked(self, widget):
        """
            Show Menu
        """
        self.menu.show_all()
 
PopoverMenu()
Gtk.main()


GtkPlaceSidebar

Nautilus の左ペインと同様に使えるもの。
GVfs でアクセスするので afp:// や ftp:// にも対応。
外部機器の接続も自動認識してくれます。
#!/usr/bin/env python3

import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio, GLib

class SidebarTest(Gtk.Window):
    """
        Nautilus Sidebar
    """
    def __init__(self):
        Gtk.Window.__init__(self)
        # GtkPlacesSidebar
        sidebar = Gtk.PlacesSidebar.new()
        # Select Documents Directory
        doc = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DOCUMENTS)
        f = Gio.File.new_for_path(doc)
        sidebar.set_location(f)
        # Add Shortcut
        f = Gio.File.new_for_path("/home/sasakima-nao/doc/html")
        if f.query_exists():
            sidebar.add_shortcut(f)
        # Signal
        sidebar.connect("open-location", self.on_sidebar_open_location)
        # etc...
        #sidebar.set_show_recent(False)
        #sidebar.set_show_other_locations(True)
        #sidebar.set_show_enter_location(True)
        self.add(sidebar)
        self.show_all()

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

    def on_sidebar_open_location(self, sidebar, location, open_flags):
        """
            location @ GFile
        """
        uri = location.get_uri()
        self.set_title(uri)

SidebarTest()
Gtk.main()

Copyright(C) sasakima-nao All rights reserved 2002 --- 2017.