PythonによるGtkのチュートリアル的な? あるいは, Hellow world.

What?

これは, Gtk 3を使うにあたって, よいチュートリアル的なものが見つからなかったので, 自分の試行錯誤を人柱として残しておくものです.

Gtk 3の入門的なwebページなどはたくさんありますし, 検索をすればすぐ見つかりますし, 初心者でもわかりやすく丁寧にまとめられているものもいっぱいあります. それにも関わらず, よいチュートリアル的なものが見つからなかったといったのは, 以下の意味です: 実はGtk 3はその初版が世に出てから10年くらいがたっています. 初期のapiの中には今は非推奨となっているものも少なくありません. 例えば, gtk_main() などは, 使わないのがモダンな書き方だと思われます. Gtk 3のチュートリアル的な文書の中には(もしくは, 多くは), その初期のapiの使い方を引きずっているものも多くあります. 最近書かれた文書であっても, 過去の文書をベースに書かれていたりするため, 古い書き方なのかどうかは書かれた時期を見ても判断できません. また, 非推奨なものでも, apiとしては残っているので, エラーが出るわけでもなく, 判断できません. Gtk 4という新しいバージョンのapiがでましたが, このバージョンアップで古いapiは捨てられたようです. 新しいバージョンがでたものの, この新しいバージョンが標準となるのは少し先になりそうなので, まだGtk 3を使う機会はあるかと思いますが, いずれGtk 4に移るときに備え, 移行が容易な書き癖を身につけておくのが得策かと思います.

基本的には私の試行錯誤を だらだらと載せていきます. 実行ファイルを徐々に書き足していく形をとります. 冗長かもしれません. 不定期に内容が増えていく予定です.

本文

まずはGUI以前の準備から.

お決まりの.

とりあえず, pythonで書くので, お決まりの文をかいて, test.pyという名前で保存.


#!/usr/bin/env python3

def main():
    pass

if __name__ == "__main__":
    main()

最初のは書かなくてもいいけど, 一応おきまりなので書いておく. ./test.py とやるだけで, python3 test.py を実行してもらうためのおまじない(SHEBAN).

main() という関数を定義しているが, その中身は pass のみ. pass はなにもしないコマンド. なにか行がないと文法エラーになるようなときに, とりあえずいれるもの.

(moduleとしてimportされるという形で呼び出されたのではなく) このファイルが直接実行されるたときには, __name__ という変数に, "__main__" という文字列が代入されている. それを見て, moduleとして呼び出されたのか, 直接実行されたのかを判断して, 直接実行されたときには, main() という関数を呼んでいる.

Moduleのインポート.

とりあえず, 実行時のオプションなどを解析する必要があるだろうから, SHEBANの直後に次を追加し sysを読み込む.


import sys

次に Gtkをimportする. バージョンは '3.0'を指定する. 具体的には次を書く.


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

結果以下のようなものができる. 何も起こらないが, エラーも出ないはず. ここでエラーが出るようなら, ライブラリがちゃんと入っていないということなので, 適宜インストールする.


#!/usr/bin/env python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

def main():
    pass

if __name__ == "__main__":
    main()

まずは何もしないアプリを作る.

Gtk.Application.

Gtk でアプリを書くときは, Gtk.Application というクラスを利用する. Gtk.Application の派生クラスを定義しておき, main()ではそのインスタンスを作り, 実行するというのが基本. main()では, 基本的にそれしかやらない. コマンドラインのオプションを解析するとかいうのが必要だったりしたときも, mainではなく, Gtk.Applicationの派生クラス に担当させる.

Gtk.Applicationの派生クラス を定義し, 起動時に実際にやること(や終了時にやること)は, do_なんとか というMethodを実際に書いていく. 色々と複雑なこともできるようにいろいろなものが用意されているけども, すごく単純なアプリ (コマンドラインオプションを解析しなくて良い, 実行のたびに新しいwindowが開く, etc.) なら, とりあえず, 毎回の起動時に実行される do_activate だけを実装しておけば良い.

do_なんとかは, 状況に合わせて色々あり, 必要なのものを実装していけば, それなりになる. 今は, とりあえず動かすことが重要なので, もう少し詳しいことは, 後日に回す.

とりあえず, TestApplication というクラスを Gtk.Application の派生クラスとして定義していく. 今回は, 起動時にdone do_actvateとだけ標準出力に出力するアプリにすることにして, 以下を追加する.


class TestApplication(Gtk.Application):
    def do_activate(self):
        print("done do_activate")

main()では, 今書いたTestApplicationのインスタンスを作り, runというメソッドを実行する. 具体的には, Mainの定義を以下のように書き換える.


def main():
    app = TestApplication()
    app.run(sys.argv)

ここまでの変更で, 以下のようになっているはずで, python3 test.py とやると done do_activate と標準出力に出力されるはずである. 当然ながらまだwindowなどが出るわけではない.


#!/usr/bin/env python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

class TestApplication(Gtk.Application):
    def do_activate(self):
        print("done do_activate")

def main():
    app = TestApplication()
    app.run(sys.argv)

if __name__ == "__main__":
    main()

Gtk.ApplicationWindow.

GUIのプログラムを組むならウインドウを開く必要があるが, Gtk.Applicationから呼び出すのは, Gtk.Windowでも悪くないけど, Gtk.ApplicationWindowのほうが良いと マニュアルに書いてある.

Gtk.ApplicationWindowの派生クラスを用意し, メインのウインドウとして使う. とりあえず今回は, TestAppWindowというクラスを用意する. 何もしないので, とりあえずコンストラクタだけ書くが, 親クラスのやつをただ呼び出すだけということにして, 以下を追加.


class TestAppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

TestApplicationdo_activateでは, TestAppWindowのインスタンスをつくり, そのpresentというメソッドを実行し, windowを表示する. TestAppWindowのインスタンスを作る際には, application=self を与える. 具体的には TestApplicationdo_activateの定義を以下のように変更.


    def do_activate(self):
        window=TestAppWindow(application=self)
        window.present()

ここまでの変更で以下のようになっているはずであるが, 実行すると, ウインドウが表示されると思う. 特に何もしていないが, ウィンドウの閉じるボタンを押すと終了するなどの 基本的な動作はするようになっている.


#!/usr/bin/env python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

class TestAppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

class TestApplication(Gtk.Application):
    def do_activate(self):
        window=TestAppWindow(application=self)
        window.present()

def main():
    app = TestApplication()
    app.run(sys.argv)

if __name__ == "__main__":
    main()

gtk_main()を呼び出すのは古いやり方. window.connect("destroy", Gtk.main_quit) とかもやらなくていい.

何もしないけど見た目はそれっぽいアプリを作る.

Menu

まずはUI

ただウインドウを開くだけで何もするわけではないけども, とりあえず, メニューバーなんかがあると, それっぽい雰囲気にはなるので, メニューバーあたりを実装する.

LinuxやWindowsならウインドウの一番上, Macなら画面の一番上にあるような, ファイル, 編集, 表示, ヘルプなどの文字列が並んでいて基本的な操作ができるるやつ(メニュー)をとりあえず作る. メニューは, (ウィンドウにではなく) Applicationに対して, 設定をする. (LinuxやWindowsの実装を見ていたらウィンドウに指定したくなるけども, Macの実装をみると, ウィンドウではなくアプリがメニューを知っていると考えるのが, 自然なのかもしれない) Gtk.Applicationの持っているset_menubarというメソッドを使えばよい. このメソッドには, Gio.MenuModel (GMenuModel) を渡してメニューの構造を指定する. (渡すのはメニューの構造であって, メニューのwidgetではない. WidgetではないのでGtkではなくGioのクラスであるので, マニュアルなどを見るときは注意が必要. ただ, 実際にGioのクラス を直接どうこうすることは殆どないので気にしなくて良い.)

Gio.MenuModelは, Bulidableというものなので, XMLで書いたデータを元にインスタンスを作成する方法が用意されている. Menuの構造は単純な入れ子構造になっている(はずな)ので, XMLと相性がよいから, XMLで書くほうが楽だと思う. XMLをGtk.Builder.new_from_string に放り込み, そこから実際のインスタンスを手に入れるということをする.

まずは, XMLを記述してMenuModelの構造を記述していく. 何か別ファイルを用意してそこにXMLを記述していくのが筋が良いとは思うんだけど, とりあえず今回は, ソースのグローバル変数にベタ書きすることにする. import などが終わった直後に以下を追加する.


MENUBAR_XML = """



"""

最初の行は単にxmlのお決まりの文句を書いているだけ. 多分, なくても動作するとは思うんだけど, 念の為書いておく. interfaceタグの中に, メニューの構造を記述していく.

まず Menuタグを追加. get_obejectでインスタンスを作る際に 要素を指定する必要があるので, idを指定しておく. ここでは, menubarとしておく.


MENUBAR_XML = """


 
 

"""

この中にメニューの構造を記述していくわけだが, いまは, メニューバーには, FileとHelpの2種類のサブメニューがあるので, それぞれ記述していく. サブメニューを作るにはsubmenuタグを使う. FileとHelpのために2つを用意する.


MENUBAR_XML = """


 
  
  
  
  
 

"""

このままでは, submenuのタイトル(ラベル)がないので, それを attibuteタグをつかって指定する. 例えば, Fileの方なら _File を入れる. neme="label"はこのタグがラベルを指定していることを表している. translatable="yes" はいずれ多国語対応するなら必要 (gettextがらみの指定. 自動で翻訳されるとか言うのではない.). そんな本格的なものを作るわけじゃないなら必要ないけど, まぁ書いておいても損はないので書いておいた. _File は ラベルがFileであるという意味. File でもラベルはFileになる. _Fileと書いているのは, こう書いておけばAlt+Fというショートカットが作られるから. _の次の文字がショートカットになる. ラベルの途中でもよく, 大文字と小文字は区別しない. ラベルに_自体を含めたい場合は, __のように二重に書くことでエスケープできる.


MENUBAR_XML = """


 
  
   _File 
  
  
   _Help 
  
 

"""

次にFileの中身を書いていく. Fileの中には, Quitという項目をおく予定だった. このために, itemタグを使う. 項目のラベルは, attibuteタグをつかって指定する. 今の場合だと, _Quit をいれることになる. これをsubmenuに追記する.


MENUBAR_XML = """


 
  
   _File
   
    _Quit
    
  
  
   _Help 
  
 

"""

次にHelpの中身を書いていく. 同様の作業をして以下のようになる.


MENUBAR_XML = """


 
  
   _File
   
    _Quit
    
  
  
   _Help
   
    _Content
    
   
    _About
    
  
 

"""

Helpの中身のContent(ヘルプの目次)と, About(バージョン情報等) では, 仕切り線を入れて区切りたいかもしれない. このような場合は, sectionタグを使ってグルーピングをする.


MENUBAR_XML = """


 
  
   _File
   
    _Quit
    
  
  
   _Help
    
_Content
_About
"""

これで, menubarの構造は記述できたので, これを読み込みAppliactionについかするということをする必要がある. 直接どこかに書いてもいいが, 一旦 build_menu というメソッドを作り, それを然るべきところから呼び出す形にする.

やることは割と単純. まず, Gtk.Builder.new_from_stringに引数として XMLが入った文字列を与えGtk.Builderのインスタンスを作る. そのインスタンスのget_obectに引数としてidであるところのmenubarを与えメニューバーの構造を得る. それを, Gtk.Applicationset_menubarに渡せば, メニューができる. 具体的には次をTestApplication(Gtk.Application)のところに追記すればよい.


    def build_menubar(self):
        builder = Gtk.Builder.new_from_string(MENUBAR_XML, -1)
        menubar = builder.get_object("menubar")
        self.set_menubar(menubar)

これを実行してもエラーは出ないが, メニューも追加されていない. それは今定義したbuild_menubarは定義されたが, どこからも実行されていないから. これを実行するものを, do_なんとか のどこかに書く必要があるが, どこに書くべきかについて少し考える.

コマンドラインオプションを与えたときに挙動変えたいということはよくありますが, そういったことをやりやすいように, Applicationdo_なんとかはなっています. コマンドラインオプションが何もないときには, do_activate が実行されますが, ファイル名を指定したときなどは, 別のdo_なんとかが実行されます. いずれの場合も do_startupが実行されてから, do_activateなどが実行されます. メニューの設定のようなアプリケーションに共通な 設定は, do_startupで行うべきでしょう. メニューの設定などは, do_startupに書くことにします. 以下のように書きます. 親クラスのdo_startupを最初に呼び出していることに注意してください.


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

ここまでの変更でソースは以下のようになっています. ファイルとヘルプというメニューが追加されました. ただし, これらをクリックしても今は何も起こりません.


#!/usr/bin/env python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk,Gio

MENUBAR_XML = """


 
  
   _File
   
    _Quit
    
  
  
   _Help
    
_Content
_About
""" class TestAppWindow(Gtk.ApplicationWindow): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) class TestApplication(Gtk.Application): def do_startup(self): Gtk.Application.do_startup(self) self.build_menubar() def do_activate(self): window=TestAppWindow(application=self) window.present() def build_menubar(self): builder = Gtk.Builder.new_from_string(MENUBAR_XML, -1) menubar = builder.get_object("menubar") self.set_menubar(menubar) def main(): app = TestApplication() app.run(sys.argv) if __name__ == "__main__": main()

昔は Gtk.menubarというwidgetを作って, 直接windowに追加していたが, これは古いやり方. メニューは各windowではなくapplicationがもつもの.

とりあえず, 文字列としてXMLを記述して Gtk.Builderを作った. これは, ソースファイルが一つにまとまるという点でありがたいけど, このままここに載せると, ハイライトがおかしくなるので, xmlは別ファイルにして, そのファイルからGtk.Builderを作る ように変更する. このようにしておいたほうがメンテナンスの観点からも良いかもしれない. まずtest.pyと同じフォルダに test.xmlという名前で以下の内容のファイルを保存する. (MENUBAR_XMLの内容そのもの)




 
  
   _File
   
    _Quit
    
  
  
   _Help
    
_Content
_About

test.pyの方は, まず, MENUBAR_XMLを削除し, 変わりに, MENUBAR_XML_FILENAME = "./test.xml"を追加. Gtk.Builder.new_from_stringになっていたのを, Gtk.Builder.new_from_file に変更し, 引数は MENUBAR_XML_FILENAMEのみを与える.


#!/usr/bin/env python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk,Gio

MENUBAR_XML_FILENAME = "./test.xml"

class TestAppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

class TestApplication(Gtk.Application):
    def do_startup(self):
        Gtk.Application.do_startup(self)
        self.build_menubar()

    def do_activate(self):
        window=TestAppWindow(application=self)
        window.present()

    def build_menubar(self):
        builder = Gtk.Builder.new_from_file(MENUBAR_XML_FILENAME)
        menubar = builder.get_object("menubar")
        self.set_menubar(menubar)

def main():
    app = TestApplication()
    app.run(sys.argv)

if __name__ == "__main__":
    main()
Quitを選んだときの挙動の実装

メニューがあっても何もしないのであれば意味がないので, とりあえず, Quitを選んだときの挙動を実装していきます. Quitを選んだときの処理を行うメソッドを自分で作ってからそれを登録します. 名前は何でも良いですが, わかりやすいように on_quit としてやることにします. アプリを終了するには, Gtk.Applicationquitというメソッドを使います. 具体的にはTestApplicationに以下を追記します.


    def on_quit(self,action,param):
        self.quit()

このメソッドをQuitを選んだときの挙動として登録するには, 以下の手順を踏みます. まずtest.xmlを編集します. Quitに関するitemの中に app.quit というタグを追加します. quitは別の名前でもよいですが, 何をするのかがわかりやすいほうが良いと思います. app.なんとかは Applicationに, win.なんとかは ApplicationWindowに送られます.




 
  
   _File
   
    _Quit
    app.quit
    
  
  
   _Help
    
_Content
_About

次に, test.pyを修正し, メニューからGtk.Applicationに送られるイベントに対する処理として, on_quitを登録するということをします. そのために, Gio.SimpleActionというものを使う必要がありますので, Gioもimportするように変更します.


from gi.repository import Gtk,Gio

Gio.SimpleAction.new("quit", None)quitに対応するものを作り, そのactivateアクションの際に実行するメソッドとしてon_quitメソッドを指定し, それをTestApplicationに登録するということをします. 具体的には build_uiのなかに, 以下を追加します.


        action = Gio.SimpleAction.new("quit", None)
        action.connect("activate", self.on_quit)
        self.add_action(action)

結局以下のような状況になっています. これでメニューのQuitを選ぶとウィンドウが閉じるようになりました.


#!/usr/bin/env python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk,Gio

MENUBAR_XML_FILENAME = "./test.xml"

class TestAppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

class TestApplication(Gtk.Application):
    def do_startup(self):
        Gtk.Application.do_startup(self)
        self.build_menubar()

    def do_activate(self):
        window=TestAppWindow(application=self)
        window.present()

    def build_menubar(self):
        builder = Gtk.Builder.new_from_file(MENUBAR_XML_FILENAME)
        menubar = builder.get_object("menubar")
        self.set_menubar(menubar)

        action = Gio.SimpleAction.new("quit", None)
        action.connect("activate", self.on_quit)
        self.add_action(action)

    def on_quit(self,action,param):
        self.quit()

def main():
    app = TestApplication()
    app.run(sys.argv)

if __name__ == "__main__":
    main()



 
  
   _File
   
    _Quit
    app.quit
    
  
  
   _Help
    
_Content
_About

gtk.main_quit()を使ってプログラムを止めるのは, 古いやり方.

About

次は, Aboutを選んだときの挙動を記述します. TestApplicationon_about というメソッドを作成し, このアプリについての情報を載せたダイアログを出すようにします. ダイアログは一般には Gtk.DialogGtk.MessageDialogを使って書きますが, Aboutようのダイアログは特別なものが用意されています. それを使って以下のように書きます. (とりあえずmodalにしたりとかはしていません. 最低限だけです)


    def on_about(self, action, param):
        about_dialog = Gtk.AboutDialog()
        about_dialog.set_program_name("TestApp") 
        about_dialog.present()

あとは, Quitのときと同じように, アクションと結びつければ終わりです.


#!/usr/bin/env python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk,Gio

MENUBAR_XML_FILENAME = "./test.xml"

class TestAppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

class TestApplication(Gtk.Application):
    def do_startup(self):
        Gtk.Application.do_startup(self)
        self.build_menubar()

    def do_activate(self):
        window=TestAppWindow(application=self)
        window.present()

    def build_menubar(self):
        builder = Gtk.Builder.new_from_file(MENUBAR_XML_FILENAME)
        menubar = builder.get_object("menubar")
        self.set_menubar(menubar)

        action = Gio.SimpleAction.new("quit", None)
        action.connect("activate", self.on_quit)
        self.add_action(action)

        action = Gio.SimpleAction.new("about", None)
        action.connect("activate", self.on_about)
        self.add_action(action)

    def on_quit(self,action,param):
        self.quit()

    def on_about(self, action, param):
        about_dialog = Gtk.AboutDialog()
        about_dialog.set_program_name("TestApp") 
        about_dialog.present()

def main():
    app = TestApplication()
    app.run(sys.argv)

if __name__ == "__main__":
    main()



 
  
   _File
   
    _Quit
    app.quit
    
  
  
   _Help
    
_Content
_About app.about
Content

最後に, Contentを選んだときの挙動を記述します. マニュアルを表示するのが適切ですが, マニュアルはwebにおくことにして, そこへのリンクがあるダイアログを表示させることにします.

外部へのリンクはGtk.LinkButtonを使います. またメッセージを表示させるためのダイアログは, Gtk.MessageDialogをつかいます. 多分間違った使い方ですが, メインのエリアに強引に外部リンクを追加する方法で実装します. TestApplicationon_helpという メソッドを以下の内容で追記します.


    def on_help(self, action, param):
        help_dialog = Gtk.MessageDialog(flags=0,buttons=Gtk.ButtonsType.OK,text="Help")
        help_dialog.format_secondary_text("Help for TestApp")
        link=Gtk.LinkButton.new_with_label("https://google.co.jp/","link to help")
        link.show()
        contentarea=help_dialog.get_content_area()
        contentarea.add(link)
        help_dialog.run()
        help_dialog.destroy()

それをイベントに接続したりするのは同様にやればよいので, 最終的には以下のようになります


#!/usr/bin/env python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk,Gio

MENUBAR_XML_FILENAME = "./test.xml"

class TestAppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

class TestApplication(Gtk.Application):
    def do_startup(self):
        Gtk.Application.do_startup(self)
        self.build_menubar()

    def do_activate(self):
        window=TestAppWindow(application=self)
        window.present()

    def build_menubar(self):
        builder = Gtk.Builder.new_from_file(MENUBAR_XML_FILENAME)
        menubar = builder.get_object("menubar")
        self.set_menubar(menubar)

        action = Gio.SimpleAction.new("quit", None)
        action.connect("activate", self.on_quit)
        self.add_action(action)

        action = Gio.SimpleAction.new("about", None)
        action.connect("activate", self.on_about)
        self.add_action(action)

        action = Gio.SimpleAction.new("help", None)
        action.connect("activate", self.on_help)
        self.add_action(action)

    def on_quit(self,action,param):
        self.quit()

    def on_about(self, action, param):
        about_dialog = Gtk.AboutDialog()
        about_dialog.set_program_name("TestApp") 
        about_dialog.present()

    def on_help(self, action, param):
        help_dialog = Gtk.MessageDialog(flags=0,buttons=Gtk.ButtonsType.OK,text="Help")
        help_dialog.format_secondary_text("Help for TestApp")
        link=Gtk.LinkButton.new_with_label("https://google.co.jp/","link to help")
        link.show()
        contentarea=help_dialog.get_content_area()
        contentarea.add(link)
        help_dialog.run()
        help_dialog.destroy()

def main():
    app = TestApplication()
    app.run(sys.argv)

if __name__ == "__main__":
    main()



 
  
   _File
   
    _Quit
    app.quit
    
  
  
   _Help
    
_Content app.help
_About app.about

アプリケーションメニュー

set_menubar でメニューを追加すると, Fileなどの文字列が並んだ(昔ながらの)メニューが作成されます. 一方最近では, メニューをまとめて, ハンバーガアイコン(三本線のメニューアイコン) で表示するアプリもよくあります. このようなアプリケーションメニューを作成するには, set_app_menu を使います. 環境によっては, set_app_menu で作られるものは, ハンバーガアイコンかもしれませんし, もしかしたら, 単にアプリケーションというのがメニューバーに追加されるだけかもしれません. やることは, set_menubarと同じなので, 割愛します.

コマンドラインオプションについて

デフォルト

python3 test.py filenameなどとやって, 起動時にオプションを渡すことを考えます. オプションの解析や仕様に合わない入力があったときのエラー処理などは 基本的にやってくれるので, 自分で書く必要はありません.

デフォルトでは何もオプションを受け付けません. ですので, もし何か引数をつけて実行すると, -f は不明なオプションです とか This application can not open files. などといって終了します.

ファイルを指定して開く.

引数としてファイル名を渡し実行するという形式はよくあります. このようなものは簡単にかけるようになっています. まず, Applicationに, Gio.ApplicationFlags.HANDLES_OPEN を指定します. これは, インスタンスを作成するときに設定されるべきなのでコンストラクタのなかで 行います. 基底クラスのコンストラクタを呼び出す際にオプション引数で渡しても良いですが, ここでは, set_flagsというメソッドを使います. TestApplication(Gtk.Application) に以下を追記します:


    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_flags(Gio.ApplicationFlags.HANDLES_OPEN)

実行時に引数が渡されなかった場合は, 今までどおり, do_startupのあとにdo_activateが実行されます. 実行時に引数が渡された場合は, do_startupのあとにdo_openが実行されます. この場合do_activateは実行されません. 引数として複数のファイル名を与えることもできます.

do_openを定義していきます. do_open(self,files,n_files,hint)で, n_filesは指定されたファイルの総数, filesGio.Fileのリストです. hintは文字列です. (通常は""のようですが, なんのために開かれたのかという情報を表す文字列(view, edit)を渡して挙動を変えることができるようです.) 今は何もしません. とりあえず, 与えられたファイル名を表示するように, 以下をTestApplication(Gtk.Application)に追記します.


    def do_open(self,files,n_files,hint):
        for gfile in files:
            print(gfile.get_path())
        print(hint)

ここまでの変更で以下のようになっています.


#!/usr/bin/env python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk,Gio

MENUBAR_XML_FILENAME = "./test.xml"

class TestAppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

class TestApplication(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_flags(Gio.ApplicationFlags.HANDLES_OPEN)

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

    def do_activate(self):
        window=TestAppWindow(application=self)
        window.present()

    def do_open(self,files,n_files,hint):
        for gfile in files:
            print(gfile.get_path())
        print(hint)

    def build_menubar(self):
        builder = Gtk.Builder.new_from_file(MENUBAR_XML_FILENAME)
        menubar = builder.get_object("menubar")
        self.set_menubar(menubar)

        action = Gio.SimpleAction.new("quit", None)
        action.connect("activate", self.on_quit)
        self.add_action(action)

        action = Gio.SimpleAction.new("about", None)
        action.connect("activate", self.on_about)
        self.add_action(action)

        action = Gio.SimpleAction.new("help", None)
        action.connect("activate", self.on_help)
        self.add_action(action)

    def on_quit(self,action,param):
        self.quit()

    def on_about(self, action, param):
        about_dialog = Gtk.AboutDialog()
        about_dialog.set_program_name("TestApp") 
        about_dialog.present()

    def on_help(self, action, param):
        help_dialog = Gtk.MessageDialog(flags=0,buttons=Gtk.ButtonsType.OK,text="Help")
        help_dialog.format_secondary_text("Help for TestApp")
        link=Gtk.LinkButton.new_with_label("https://google.co.jp/","link to help")
        link.show()
        contentarea=help_dialog.get_content_area()
        contentarea.add(link)
        help_dialog.run()
        help_dialog.destroy()

def main():
    app = TestApplication()
    app.run(sys.argv)

if __name__ == "__main__":
    main()

複雑なオプション.

コマンドラインで起動する際にファイル名だけではなく, 複雑な設定をすることをも行いたいときがあります. 例えば, --versionとか-vなどとやってバージョン情報を表示させるような挙動を実装していこうと思います.

コマンドラインオプションを細かく設定するには, まず, Gio.ApplicationFlags.HANDLES_COMMAND_LINE をセットします. そのために, 以下のように変更をします:


    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_flags(Gio.ApplicationFlags.HANDLES_COMMAND_LINE)

このフラグをセットすると, do_setupのあとにdo_command_line が実行されるようになります. オプションを指定していなくても do_command_lineが実行され, do_activateは実行されなくなります. また, HANDLES_OPENを追加で指定していても, do_openは実行されません.

do_command_line(self,command_line) を定義します. command_line には, コマンドラインオプションを解析したものが入っています. とりあえず, 渡されたコマンドラインオプションを表示するように, 実装してみます. return 0のように整数を返す必要があります.


    def do_command_line(self,command_line):
        options_dict = command_line.get_options_dict()
        options = options_dict.end().unpack()
        print(options)
        return 0

このままでは, 何も表示されません. 理由は, 有効なオプションを登録していないため, どんな引数も無向なオプションとして捨てられているからです. コンストラクタで, add_main_optionというメソッドを使い, 解釈するオプションを追加します. オプションの種類などを指定するフラグが, GLibで定義されているので, importします.


from gi.repository import Gtk,Gio,GLib

--version または -vで指定し, 追加の文字列を伴わないものであれば, 以下のように書きます. --helpをつけて実行すると, --versionの説明としてversion infoとでてきます.


    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_flags(Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
        self.add_main_option("version",ord("v"),GLib.OptionFlags.IN_MAIN,GLib.OptionArg.NONE,"version info",None)

do_command_lineを, versionオプションなら バージョン情報を表示しそこで終了, そうでなければactivateするように変更します.


    def do_command_line(self,command_line):
        options_dict = command_line.get_options_dict()
        options = options_dict.end().unpack()
        if "version" in options:
            if options["version"]:
                print("version:",0)
                return 0
        self.activate()
        return 0

なお, オプションとして処理されなかった引数たちは, command_line.get_arguments()で文字列の配列として得ることができます.

ここまでの変更で, 以下のようになっています:


#!/usr/bin/env python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk,Gio,GLib

MENUBAR_XML_FILENAME = "./test.xml"

class TestAppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

class TestApplication(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_flags(Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
        self.add_main_option("version",ord("v"),GLib.OptionFlags.IN_MAIN,GLib.OptionArg.NONE,"version info",None)

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

    def do_activate(self):
        window=TestAppWindow(application=self)
        window.present()

    def do_open(self,files,n_files,hint):
        for gfile in files:
            print(gfile.get_path())
        print(hint)

    def do_command_line(self,command_line):
        options_dict = command_line.get_options_dict()
        options = options_dict.end().unpack()
        if "version" in options:
            if options["version"]:
                print("version:",0)
                return 0
        self.activate()
        return 0

    def build_menubar(self):
        builder = Gtk.Builder.new_from_file(MENUBAR_XML_FILENAME)
        menubar = builder.get_object("menubar")
        self.set_menubar(menubar)

        action = Gio.SimpleAction.new("quit", None)
        action.connect("activate", self.on_quit)
        self.add_action(action)

        action = Gio.SimpleAction.new("about", None)
        action.connect("activate", self.on_about)
        self.add_action(action)

        action = Gio.SimpleAction.new("help", None)
        action.connect("activate", self.on_help)
        self.add_action(action)

    def on_quit(self,action,param):
        self.quit()

    def on_about(self, action, param):
        about_dialog = Gtk.AboutDialog()
        about_dialog.set_program_name("TestApp") 
        about_dialog.present()

    def on_help(self, action, param):
        help_dialog = Gtk.MessageDialog(flags=0,buttons=Gtk.ButtonsType.OK,text="Help")
        help_dialog.format_secondary_text("Help for TestApp")
        link=Gtk.LinkButton.new_with_label("https://google.co.jp/","link to help")
        link.show()
        contentarea=help_dialog.get_content_area()
        contentarea.add(link)
        help_dialog.run()
        help_dialog.destroy()

def main():
    app = TestApplication()
    app.run(sys.argv)

if __name__ == "__main__":
    main()

ApplicationWindowで処理するメニュー

最大化のメニュー

メニューバーにViewという項目を用意し, その中にMaximizeというウィンドウを最大化するための項目を用意します. Maximizeは, Applicationではなく, ウィンドウに行われるべきものなので, actionはapp.ではなくwin.miximizeとします. test.xmlのFileとHelpのsubmenuの間に以下を追加します.


  
   _View
   
       _Maximize
       win.miximize
   
  

次にMiximizeが選ばれたときに実行するメソッドを, TestAppWindow(Gtk.ApplicationWindow)に実装していきます. 実行した際に, 最大化されていなかったら最大化し, 最大化されていたら最大化を解除する, というように, 状況によって切り替えるようにします. そのために, 対応するactionをstatefulにし, windowに加えます. 具体的にはまず以下を, TestAppWindow__init__の末尾に追加します.


        maximize_action = Gio.SimpleAction.new_stateful("maximize", None, GLib.Variant.new_boolean(False))
        self.add_action(maximize_action)

ここで実行をすると, メニューバーにはViewという項目があり, さらにそこにはMaximizeという項目があります. Maximizeにはチェックボックスがあり, 選択するたびに切り替わります. 起動時の状態は, Falseを指定しているので, 選択されていません.

この項目を選択するとchange-stateというイベントが発生するので, そのときに実行されるメソッドを定義します. on_maximize_toggle(self, action, value)を定義しますが, 変更前の値がvalueに代入されて実行されるので, その値に応じて処理を変えます.


    def on_maximize_toggle(self, action, value):
        if value.get_boolean():
            self.maximize()
        else:
            self.unmaximize()

次にこのメソッドをアクションと接続します. __init__に以下を追加します.


        maximize_action.connect("change-state", self.on_maximize_toggle)

これでほぼ終わりですが, メニューバーを使わずウィンドウの最大化をした場合に, メニューバーの状態が正しくなくなってしまうので, このままではよくありません. そこで, 最大化されたときに, actionの状態を追従させるよう __init__に以下を追加します.


        self.connect("notify::is-maximized",lambda obj, pspec: maximize_action.set_state(GLib.Variant.new_boolean(obj.props.is_maximized)),)

ここまでの修正で以下のようになっていると思います.


#!/usr/bin/env python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk,Gio,GLib

MENUBAR_XML_FILENAME = "./test.xml"

class TestAppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        maximize_action = Gio.SimpleAction.new_stateful("maximize", None, GLib.Variant.new_boolean(False))
        maximize_action.connect("change-state", self.on_maximize_toggle)
        self.add_action(maximize_action)
        self.connect("notify::is-maximized",lambda obj, pspec: maximize_action.set_state(GLib.Variant.new_boolean(obj.props.is_maximized)),)

    def on_maximize_toggle(self, action, value):
        if value.get_boolean():
            self.maximize()
        else:
            self.unmaximize()
class TestApplication(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_flags(Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
        self.add_main_option("version",ord("v"),GLib.OptionFlags.IN_MAIN,GLib.OptionArg.NONE,"version info",None)

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

    def do_activate(self):
        window=TestAppWindow(application=self)
        window.present()

    def do_open(self,files,n_files,hint):
        for gfile in files:
            print(gfile.get_path())
        print(hint)

    def do_command_line(self,command_line):
        options_dict = command_line.get_options_dict()
        options = options_dict.end().unpack()
        if "version" in options:
            if options["version"]:
                print("version:",0)
                return 0
        self.activate()
        return 0

    def build_menubar(self):
        builder = Gtk.Builder.new_from_file(MENUBAR_XML_FILENAME)
        menubar = builder.get_object("menubar")
        self.set_menubar(menubar)

        action = Gio.SimpleAction.new("quit", None)
        action.connect("activate", self.on_quit)
        self.add_action(action)

        action = Gio.SimpleAction.new("about", None)
        action.connect("activate", self.on_about)
        self.add_action(action)

        action = Gio.SimpleAction.new("help", None)
        action.connect("activate", self.on_help)
        self.add_action(action)

    def on_quit(self,action,param):
        self.quit()

    def on_about(self, action, param):
        about_dialog = Gtk.AboutDialog()
        about_dialog.set_program_name("TestApp") 
        about_dialog.present()

    def on_help(self, action, param):
        help_dialog = Gtk.MessageDialog(flags=0,buttons=Gtk.ButtonsType.OK,text="Help")
        help_dialog.format_secondary_text("Help for TestApp")
        link=Gtk.LinkButton.new_with_label("https://google.co.jp/","link to help")
        link.show()
        contentarea=help_dialog.get_content_area()
        contentarea.add(link)
        help_dialog.run()
        help_dialog.destroy()

def main():
    app = TestApplication()
    app.run(sys.argv)

if __name__ == "__main__":
    main()



 
  
   _File
   
    _Quit
    app.quit
    
  
  
   _View
   
       _Maximize
       win.miximize
   
  
  
   _Help
    
_Content app.help
_About app.about

NewとOpen

FileにNewとOpenを追加します. test.xmlの_Fileを表すsubmenu の中を以下のように変更します. sectionでグループ化しますが, グループ化しているものとされているものが混ざっていると, 区切られないので, _Closeの含まれるグループはそれのみですがsection でくくられていることに中が必要です.


    _File
    
_New app.new _Open app.open
_Close app.quit

Newの方は, 対応するon_newを作成しますが, そのなかでやることはself.activateを実行するだけです. 具体的には, 以下をTestApplicationに追加するだけです.


    def on_new(self,action,param):
        self.activate()

これをbuild_menubarで アクションと結びつけます. 具体的には以下を追加します>


        action = Gio.SimpleAction.new("new", None)
        action.connect("activate", self.on_new)
        self.add_action(action)

次にOpenの方を実装しますが, 対応するon_openを作成します. 一旦なにもしないメソッドとしてon_openを以下のように作成します.


    def on_open(self,action,param):
        pass

そしてbuild_menubarに以下を追加します.


        action = Gio.SimpleAction.new("open", None)
        action.connect("activate", self.on_open)
        self.add_action(action)

これでエラーが出ないことを確認したら, do_openを実装していきます. ファイルを選ぶには, Gtk.FileChooserDialog を使います. add_buttonをつかって, ダイアログに用意するボタンと, ボタンが押されたときに帰ってくる値を指定します. そのあとrun() を呼び出すとダイアログが開かれ, ボタンが押されると値が返ってきます. 返ってきた値に応じた処理をしたあと, destroy()を実行すると, ダイアログが消えます. これが基本的な流れです. とりあえず値に応じた処理は後で書くことにして雛形を作ってしまいます.


    def on_open(self,action,param):
        dialog = Gtk.FileChooserDialog(action=Gtk.FileChooserAction.OPEN)
        dialog.add_buttons(Gtk.STOCK_CANCEL,Gtk.ResponseType.CANCEL,Gtk.STOCK_OPEN,Gtk.ResponseType.OK)
        response = dialog.run()
        dialog.destroy()

とりあえず, ダイアログが開かれることと, ボタンを押すとダイアログが消えることを確認したら, 値に応じた処理を実装していきます. なお, Openに関するものだったので, action=Gtk.FileChooserAction.OPEN としましたが, SaveAs(名前を付けて保存)の場合には, action=Gtk.FileChooserAction.SAVE とします. まだタイトルがついていないようなものをSave(上書き保存)を選んだときには, action=Gtk.FileChooserAction.SAVE をつかい, set_current_nameでデフォルトの名前を"Untitled" にでも設定しておくと良いでしょう. また, ファイルではなくフォルダを選択させる場合は, Gtk.FileChooserAction.SELECT_FOLDER にします.

run()をした結果に応じての挙動を実装します. 開くを選んだ場合は, ファイルの情報をダイアログから取り出し, そのファイルを開き, そのあとダイアログをdestroyします. ファイルの情報は, Gtk.FileChooserDialogget_file というメソッドを使います. Gio.Fileが返ってきます. これを引数に Applicationopenというメソッドを実行します. openにはFileのリスト他, "view""edit" といった挙動の付加的な情報を表す文字列を与えます. 付加情報は必要なければ""を与えます(Noneではない). ファイルを開く際に行う動作は実際には, do_open に実装しました/すが, openを実行するとそれが実行されます. response = dialog.run() のあと (dialog.destroy()の前) に 以下を追加します.


        if response == Gtk.ResponseType.OK:
	    file=dialog.get_file()
            self.open([file],"")
        elif response == Gtk.ResponseType.CANCEL:
            print("Cancel clicked")

openを呼ぶためには, flagにGio.ApplicationFlags.HANDLES_OPEN が指定されていないといけません. そのため TestApplication__init__set_flags をするのを以下のように変更します.


        self.set_flags(Gio.ApplicationFlags.HANDLES_OPEN|Gio.ApplicationFlags.HANDLES_COMMAND_LINE)

ここまでの変更で, 以下のようになっていると思います.


#!/usr/bin/env python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk,Gio,GLib

MENUBAR_XML_FILENAME = "./test.xml"

class TestAppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        maximize_action = Gio.SimpleAction.new_stateful("maximize", None, GLib.Variant.new_boolean(False))
        maximize_action.connect("change-state", self.on_maximize_toggle)
        self.add_action(maximize_action)
        self.connect("notify::is-maximized",lambda obj, pspec: maximize_action.set_state(GLib.Variant.new_boolean(obj.props.is_maximized)),)

    def on_maximize_toggle(self, action, value):
        if value.get_boolean():
            self.maximize()
        else:
            self.unmaximize()
class TestApplication(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_flags(Gio.ApplicationFlags.HANDLES_OPEN|Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
        self.add_main_option("version",ord("v"),GLib.OptionFlags.IN_MAIN,GLib.OptionArg.NONE,"version info",None)

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

    def do_activate(self):
        window=TestAppWindow(application=self)
        window.present()

    def do_open(self,files,n_files,hint):
        for gfile in files:
            print(gfile.get_path())
        print(hint)

    def do_command_line(self,command_line):
        options_dict = command_line.get_options_dict()
        options = options_dict.end().unpack()
        if "version" in options:
            if options["version"]:
                print("version:",0)
                return 0
        self.activate()
        return 0

    def build_menubar(self):
        builder = Gtk.Builder.new_from_file(MENUBAR_XML_FILENAME)
        menubar = builder.get_object("menubar")
        self.set_menubar(menubar)

        action = Gio.SimpleAction.new("new", None)
        action.connect("activate", self.on_new)
        self.add_action(action)

        action = Gio.SimpleAction.new("open", None)
        action.connect("activate", self.on_open)
        self.add_action(action)

        action = Gio.SimpleAction.new("quit", None)
        action.connect("activate", self.on_quit)
        self.add_action(action)

        action = Gio.SimpleAction.new("about", None)
        action.connect("activate", self.on_about)
        self.add_action(action)

        action = Gio.SimpleAction.new("help", None)
        action.connect("activate", self.on_help)
        self.add_action(action)

    def on_quit(self,action,param):
        self.quit()

    def on_about(self, action, param):
        about_dialog = Gtk.AboutDialog()
        about_dialog.set_program_name("TestApp") 
        about_dialog.present()

    def on_help(self, action, param):
        help_dialog = Gtk.MessageDialog(flags=0,buttons=Gtk.ButtonsType.OK,text="Help")
        help_dialog.format_secondary_text("Help for TestApp")
        link=Gtk.LinkButton.new_with_label("https://google.co.jp/","link to help")
        link.show()
        contentarea=help_dialog.get_content_area()
        contentarea.add(link)
        help_dialog.run()
        help_dialog.destroy()

    def on_new(self,action,param):
        self.activate()

    def on_open(self,action,param):
        dialog = Gtk.FileChooserDialog(action=Gtk.FileChooserAction.OPEN)
        dialog.add_buttons(Gtk.STOCK_CANCEL,Gtk.ResponseType.CANCEL,Gtk.STOCK_OPEN,Gtk.ResponseType.OK)
        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            file=dialog.get_file()
            self.open([file],"")
        elif response == Gtk.ResponseType.CANCEL:
            print("Cancel clicked")
        dialog.destroy()

def main():
    app = TestApplication()
    app.run(sys.argv)

if __name__ == "__main__":
    main()



 
  
    _File
    
_New app.new _Open app.open
_Close app.quit
_View _Maximize win.miximize _Help
_Content app.help
_About app.about

Filter

ファイルの一覧に関連のないファイルまで表示されると選択が大変なので, 拡張子で絞る機能を追加します.

Gtk.FileFilter を, ダイアログにadd_filterで追加することでやりたいことができます. Gtk.FileFilter には, 関連するmime typeもしくはワイルドカードを使ったファイル名のパターンを指定します. add_mime_typeadd_patternを使って指定します. 複数の条件(unionになる)を指定することができます. また, ダイアログに表示するときの名前をset_nameで指定します. Zipファイルに制限する(本質的にzipファイルであるdocxなどもふくまれるかもしれません)フィルターと, すべてのファイルを表示するフィルターを用意するためには, 以下を, dialog.run()をする前に追加します.


        filter = Gtk.FileFilter()
        filter.set_name("Zip files")
        filter.add_pattern("*.zip")
        filter.add_mime_type("application/zip")
        dialog.add_filter(filter)

        filter = Gtk.FileFilter()
        filter.set_name("All files")
        filter.add_pattern("*")
        dialog.add_filter(filter)

ここまでの変更で, 以下のようになっていると思います.


#!/usr/bin/env python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk,Gio,GLib

MENUBAR_XML_FILENAME = "./test.xml"

class TestAppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        maximize_action = Gio.SimpleAction.new_stateful("maximize", None, GLib.Variant.new_boolean(False))
        maximize_action.connect("change-state", self.on_maximize_toggle)
        self.add_action(maximize_action)
        self.connect("notify::is-maximized",lambda obj, pspec: maximize_action.set_state(GLib.Variant.new_boolean(obj.props.is_maximized)),)

    def on_maximize_toggle(self, action, value):
        if value.get_boolean():
            self.maximize()
        else:
            self.unmaximize()
class TestApplication(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_flags(Gio.ApplicationFlags.HANDLES_OPEN|Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
        self.add_main_option("version",ord("v"),GLib.OptionFlags.IN_MAIN,GLib.OptionArg.NONE,"version info",None)

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

    def do_activate(self):
        window=TestAppWindow(application=self)
        window.present()

    def do_open(self,files,n_files,hint):
        for gfile in files:
            print(gfile.get_path())
        print(hint)

    def do_command_line(self,command_line):
        options_dict = command_line.get_options_dict()
        options = options_dict.end().unpack()
        if "version" in options:
            if options["version"]:
                print("version:",0)
                return 0
        self.activate()
        return 0

    def build_menubar(self):
        builder = Gtk.Builder.new_from_file(MENUBAR_XML_FILENAME)
        menubar = builder.get_object("menubar")
        self.set_menubar(menubar)

        action = Gio.SimpleAction.new("new", None)
        action.connect("activate", self.on_new)
        self.add_action(action)

        action = Gio.SimpleAction.new("open", None)
        action.connect("activate", self.on_open)
        self.add_action(action)

        action = Gio.SimpleAction.new("quit", None)
        action.connect("activate", self.on_quit)
        self.add_action(action)

        action = Gio.SimpleAction.new("about", None)
        action.connect("activate", self.on_about)
        self.add_action(action)

        action = Gio.SimpleAction.new("help", None)
        action.connect("activate", self.on_help)
        self.add_action(action)

    def on_quit(self,action,param):
        self.quit()

    def on_about(self, action, param):
        about_dialog = Gtk.AboutDialog()
        about_dialog.set_program_name("TestApp") 
        about_dialog.present()

    def on_help(self, action, param):
        help_dialog = Gtk.MessageDialog(flags=0,buttons=Gtk.ButtonsType.OK,text="Help")
        help_dialog.format_secondary_text("Help for TestApp")
        link=Gtk.LinkButton.new_with_label("https://google.co.jp/","link to help")
        link.show()
        contentarea=help_dialog.get_content_area()
        contentarea.add(link)
        help_dialog.run()
        help_dialog.destroy()

    def on_new(self,action,param):
        self.activate()

    def on_open(self,action,param):
        dialog = Gtk.FileChooserDialog(action=Gtk.FileChooserAction.OPEN)
        dialog.add_buttons(Gtk.STOCK_CANCEL,Gtk.ResponseType.CANCEL,Gtk.STOCK_OPEN,Gtk.ResponseType.OK)

        filter = Gtk.FileFilter()
        filter.set_name("Zip files")
        filter.add_pattern("*.zip")
        filter.add_mime_type("application/zip")
        dialog.add_filter(filter)

        filter = Gtk.FileFilter()
        filter.set_name("All files")
        filter.add_pattern("*")
        dialog.add_filter(filter)

        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            file=dialog.get_file()
            self.open([file],"")
        elif response == Gtk.ResponseType.CANCEL:
            print("Cancel clicked")
        dialog.destroy()

def main():
    app = TestApplication()
    app.run(sys.argv)

if __name__ == "__main__":
    main()

Satatusbar



../