PyGTKでタブ(ノートブック)に「閉じる」ボタンを付ける(後半)
「PyGTKでタブ(ノートブック)に「閉じる」ボタンを付ける(前半)」の続き。
タブの「閉じる」ボタンのクリックをタブのページ全体のイベントとして処理する
タブの「閉じる」ボタンをクリックしたときにそのページ全体のイベントとして処理できるようにすると便利で
- ページ全体を1つのクラスとする
- 「閉じる」ボタンが押されたときの「ページを閉じる」イベント(GObjectシグナル)を定義
- 「閉じる」ボタンが押されたときのハンドラにおいてその(「ページを閉じる」イベントの)シグナルを起こす
として、かつ、このクラスの利用側で
- 「ページを閉じる」イベントとハンドラを関連付ける
- そのハンドラの中でページ全体のGUI部品の破棄処理(gtk.Widgetクラスのメンバ関数destroy())を記述
とすることで、押された「閉じる」ボタンを含んだタブを閉じる(消す)ようにできる。
ユーザ定義のGObjectシグナルは以前扱った「PyGObjectで gobject.GObjectクラスを継承してGObjectプロパティを用いる(前半)」の参考ページをここでも参考にして
class TextViewTab(gtk.ScrolledWindow): __gsignals__ = \ { 'closed' : (gobject.SIGNAL_RUN_FIRST, # ハンドラが呼ばれるタイミング # シグナル発行ステージの段階を指定 # gobject.SIGNAL_RUN_FIRSTでは1段階目 # gobject.SIGNAL_RUN_LAST では3段階目 gobject.TYPE_NONE, # ハンドラの戻り値 or gobject.TYPE_NONE ()) # ハンドラの追加引数タプル } ...
のようにした。「__gsignals__」という辞書にシグナル名とハンドラの設定を記述する形となる。今回は単純なシグナルなのでこのようになった。
例1
前半と本記事のこれまでの内容を踏まえると下のような例ができる。
#! /usr/bin/python # -*- coding: utf-8 -*- import sys try: import pygtk pygtk.require('2.0') except: pass try: import gobject import gtk except: print >> sys.stderr, 'Error: PyGTK is not installed' sys.exit(1) class TextViewTab(gtk.ScrolledWindow): """ テキストビューを含んだタブ """ # ユーザ定義のプロパティとシグナルについては # http://www.pygtk.org/articles/subclassing-gobject/sub-classing-gobject-in-python.htm # を参照 # ユーザ定義プロパティ __gproperties__ = \ { 'label' : (gobject.TYPE_OBJECT, 'tablabel', 'gtk.Label for this tab', gobject.PARAM_READABLE), } # ユーザ定義シグナル # http://web.archive.org/web/20071118225440/www.gnome.gr.jp/docs/glib-2.4.x-refs/gobject/html/gobject-Signals.html#g-signal-new # http://web.archive.org/web/20071118225440/www.gnome.gr.jp/docs/glib-2.4.x-refs/gobject/html/gobject-Signals.html#GSignalFlags __gsignals__ = \ { 'closed' : (gobject.SIGNAL_RUN_FIRST, # ハンドラが呼ばれるタイミング # シグナル発行ステージの段階を指定 # gobject.SIGNAL_RUN_FIRSTでは1段階目 # gobject.SIGNAL_RUN_LAST では3段階目 gobject.TYPE_NONE, # ハンドラの戻り値 or gobject.TYPE_NONE ()) # ハンドラの追加引数タプル } __number = 0 # 表示に用いるタブ番号 def __init__(self): gtk.ScrolledWindow.__init__(self, hadjustment=None, vadjustment=None) TextViewTab.__number += 1 # テキストビューとバッファ self.__textbuf = gtk.TextBuffer() self.__textview = gtk.TextView(self.__textbuf) self.__textview.props.wrap_mode = gtk.WRAP_CHAR self.add(self.__textview) # スクロール self.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) # ラベルテキストとボタン self.__label = gtk.Label('Tab %d' % TextViewTab.__number) self.__button_close = gtk.Button() self.__button_close.props.image = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU) self.__button_close.props.relief = gtk.RELIEF_NONE # ボタンのスタイル self.__button_close.connect('clicked', self.__on_button_close_clicked) # 横に並べる self.__hbox = gtk.HBox() self.__hbox.pack_start(self.__label) self.__hbox.pack_start(self.__button_close, expand=False, fill=False) self.__hbox.show_all() # ツールチップ self.__label.props.tooltip_text = self.__label.props.label self.__button_close.props.tooltip_text = 'Close this tab' def __on_button_close_clicked(self, widget): """ ボタンがクリックされた """ dlg = gtk.MessageDialog(type=gtk.MESSAGE_QUESTION, message_format='Do you really want to close "%s" ?' % self.__label.props.label) dlg.add_buttons(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE) resid = dlg.run() dlg.destroy() if resid == gtk.RESPONSE_CLOSE: # もし閉じる場合はユーザ定義シグナル「closed」を出すことにする self.emit('closed') def do_get_property(self, property): if property.name == 'label': return self.__hbox class MainWindow(gtk.Window): """ メインウィンドウ """ def __init__(self, *args, **kwargs): gtk.Window.__init__(self, *args, **kwargs) # ショートカットキー(アクセラレータ) self.__accelgroup = gtk.AccelGroup() self.add_accel_group(self.__accelgroup) # メニュー項目 self.__item_quit = gtk.ImageMenuItem(gtk.STOCK_QUIT, self.__accelgroup) self.__item_new = gtk.ImageMenuItem(gtk.STOCK_NEW, self.__accelgroup) self.__menu_file = gtk.Menu() self.__menu_file.prepend(self.__item_quit) self.__menu_file.prepend(self.__item_new) self.__item_file = gtk.MenuItem('_File') self.__item_file.props.submenu = self.__menu_file self.__menubar = gtk.MenuBar() self.__menubar.append(self.__item_file) # ノートブック self.__nb = gtk.Notebook() self.__nb.props.tab_border = 0 self.__nb.props.tab_hborder = 0 self.__nb.props.tab_vborder = 0 self.__nb.props.scrollable = True # レイアウト用コンテナ self.__vbox = gtk.VBox() self.__vbox.pack_start(self.__menubar, expand=False, fill=False) self.__vbox.pack_start(self.__nb) # シグナル self.connect('delete_event', gtk.main_quit) self.__item_quit.connect('activate', gtk.main_quit) self.__item_new.connect('activate', self.__on_item_new_activate) # ウィンドウ self.add(self.__vbox) self.set_size_request(350, 300) def __on_item_new_activate(self, widget): """ タブを追加 """ tab = TextViewTab() tab.show_all() tab.connect('closed', self.__on_tab_closed) self.__nb.append_page(tab, tab_label=tab.props.label) def __on_tab_closed(self, widget): """ タブが閉じられた """ print '__on_tab_closed()' # destroy()はその子ウィジェット全てを含めて消す # ここではTextViewTabオブジェクトが消える widget.destroy() class NotebookCloseButtonTest: """ ノートブックの閉じるボタンのテスト """ def main(self): """ アプリケーションのメイン処理 """ win = MainWindow() win.show_all() gtk.main() if __name__ == '__main__': app = NotebookCloseButtonTest() app.main()
geditなどと比べると、タブの高さやボタンの大きさの違いが目につくが、これは後述の方法により解決する。
タブの表示
タブの「閉じる」ボタンを押したときの確認ダイアログ
できるだけ小さなタブとボタンを作る
geditなどを参考にしたところ、下のような方法によってタブとボタンを小さくできることが分かった。一部の要領は先に「PyGTKでツリービューの各テキストセルに対してスタイルを適用する(後半)」の中で扱っている。
- gtk.Widgetクラスのスタイルプロパティfocus-padding,focus-line-widthをともに0にし、同様にxthickness,ythicknessの値も0にした「閉じる」ボタン用スタイルを定義
- 「閉じる」ボタンのオブジェクトのGObjectプロパティnameをそのスタイルのものに変更・もしくは(gtk.Widgetクラスの)set_name()を呼ぶ
- 「閉じる」ボタンのオブジェクトの「style-set」シグナルのハンドラでgtk.icon_size_lookup_for_settings()の戻り値タプルの幅と高さをそれぞれ2足したものを引数にしてメンバ関数set_size_request()を呼ぶ
スタイルプロパティについては「PyGTKでツリービューの各テキストセルに対してスタイルを適用する(後半)」を参照。
例2
例1をもとに、タブ/ボタンをできるだけ小さくしたものとなる。
#! /usr/bin/python # -*- coding: utf-8 -*- import sys try: import pygtk pygtk.require('2.0') except: pass try: import gobject import gtk except: print >> sys.stderr, 'Error: PyGTK is not installed' sys.exit(1) class TextViewTab(gtk.ScrolledWindow): """ テキストビューを含んだタブ """ # ユーザ定義のプロパティとシグナルについては # http://www.pygtk.org/articles/subclassing-gobject/sub-classing-gobject-in-python.htm # を参照 # ユーザ定義プロパティ __gproperties__ = \ { 'label' : (gobject.TYPE_OBJECT, 'tablabel', 'gtk.Label for this tab', gobject.PARAM_READABLE), } # ユーザ定義シグナル # http://web.archive.org/web/20071118225440/www.gnome.gr.jp/docs/glib-2.4.x-refs/gobject/html/gobject-Signals.html#g-signal-new # http://web.archive.org/web/20071118225440/www.gnome.gr.jp/docs/glib-2.4.x-refs/gobject/html/gobject-Signals.html#GSignalFlags __gsignals__ = \ { 'closed' : (gobject.SIGNAL_RUN_FIRST, # ハンドラが呼ばれるタイミング # シグナル発行ステージの段階を指定 # gobject.SIGNAL_RUN_FIRSTでは1段階目 # gobject.SIGNAL_RUN_LAST では3段階目 gobject.TYPE_NONE, # ハンドラの戻り値 or gobject.TYPE_NONE ()) # ハンドラの追加引数タプル } __number = 0 # 表示に用いるタブ番号 def __init__(self): gtk.ScrolledWindow.__init__(self, hadjustment=None, vadjustment=None) TextViewTab.__number += 1 # テキストビューとバッファ self.__textbuf = gtk.TextBuffer() self.__textview = gtk.TextView(self.__textbuf) self.__textview.props.wrap_mode = gtk.WRAP_CHAR self.add(self.__textview) # スクロール self.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) # ラベルテキストとボタン self.__label = gtk.Label('Tab %d' % TextViewTab.__number) self.__button_close = gtk.Button() self.__button_close.props.image = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU) self.__button_close.props.relief = gtk.RELIEF_NONE # ボタンのスタイル self.__button_close.props.name = 'small-close-button' self.__button_close.connect('clicked', self.__on_button_close_clicked) self.__button_close.connect('style-set', self.__on_button_close_style_set) # 横に並べる self.__hbox = gtk.HBox() self.__hbox.pack_start(self.__label) self.__hbox.pack_start(self.__button_close, expand=False, fill=False) self.__hbox.show_all() # ツールチップ self.__label.props.tooltip_text = self.__label.props.label self.__button_close.props.tooltip_text = 'Close this tab' def __on_button_close_style_set(self, widget, prev_style): (width, height) = gtk.icon_size_lookup_for_settings(self.__button_close.get_settings(), gtk.ICON_SIZE_MENU) self.__button_close.set_size_request(width + 2, height + 2) def __on_button_close_clicked(self, widget): """ ボタンがクリックされた """ dlg = gtk.MessageDialog(type=gtk.MESSAGE_QUESTION, message_format='Do you really want to close "%s" ?' % self.__label.props.label) dlg.add_buttons(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE) resid = dlg.run() dlg.destroy() if resid == gtk.RESPONSE_CLOSE: # もし閉じる場合はユーザ定義シグナル「closed」を出すことにする self.emit('closed') def do_get_property(self, property): if property.name == 'label': return self.__hbox class MainWindow(gtk.Window): """ メインウィンドウ """ def __init__(self, *args, **kwargs): gtk.Window.__init__(self, *args, **kwargs) # ショートカットキー(アクセラレータ) self.__accelgroup = gtk.AccelGroup() self.add_accel_group(self.__accelgroup) # メニュー項目 self.__item_quit = gtk.ImageMenuItem(gtk.STOCK_QUIT, self.__accelgroup) self.__item_new = gtk.ImageMenuItem(gtk.STOCK_NEW, self.__accelgroup) self.__menu_file = gtk.Menu() self.__menu_file.prepend(self.__item_quit) self.__menu_file.prepend(self.__item_new) self.__item_file = gtk.MenuItem('_File') self.__item_file.props.submenu = self.__menu_file self.__menubar = gtk.MenuBar() self.__menubar.append(self.__item_file) # ノートブック self.__nb = gtk.Notebook() self.__nb.props.tab_border = 0 self.__nb.props.tab_hborder = 0 self.__nb.props.tab_vborder = 0 self.__nb.props.scrollable = True # レイアウト用コンテナ self.__vbox = gtk.VBox() self.__vbox.pack_start(self.__menubar, expand=False, fill=False) self.__vbox.pack_start(self.__nb) # シグナル self.connect('delete_event', gtk.main_quit) self.__item_quit.connect('activate', gtk.main_quit) self.__item_new.connect('activate', self.__on_item_new_activate) # ウィンドウ self.add(self.__vbox) self.set_size_request(350, 300) def __on_item_new_activate(self, widget): """ タブを追加 """ tab = TextViewTab() tab.show_all() tab.connect('closed', self.__on_tab_closed) self.__nb.append_page(tab, tab_label=tab.props.label) def __on_tab_closed(self, widget): """ タブが閉じられた """ print '__on_tab_closed()' # destroy()はその子ウィジェット全てを含めて消す # ここではTextViewTabオブジェクトが消える widget.destroy() class NotebookCloseButtonTest2: """ ノートブックの閉じるボタンのテスト2 """ def main(self): """ アプリケーションのメイン処理 """ # タブの閉じるボタンのスタイル定義・geditと同じ要領 gtk.rc_parse_string(''' style "small-close-button-style" { GtkWidget::focus-padding = 0 GtkWidget::focus-line-width = 0 xthickness = 0 ythickness = 0 } widget "*.small-close-button" style "small-close-button-style" ''') win = MainWindow() win.show_all() gtk.main() if __name__ == '__main__': app = NotebookCloseButtonTest2() app.main()
関連記事:
- PyGObject/PyGTKにおけるGObjectプロパティの操作について
- PyGObjectで gobject.GObjectクラスを継承してGObjectプロパティを用いる(前半)
- PyGObjectで gobject.GObjectクラスを継承してGObjectプロパティを用いる(後半)
- 端末の256色パレットをGUI上で確認できるツールを更新(ボタンを改善・クリップボードに色の情報を記憶)
- PyGTKでツリービューの各テキストセルに対してスタイルを適用する(前半)
- PyGTKでツリービューの各テキストセルに対してスタイルを適用する(後半)
- PyGTKでタブ(ノートブック)に「閉じる」ボタンを付ける(前半)
関連URL:
使用したバージョン:
- Python 2.6.4
- PyGTK 2.16.0