試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

PyGTKでウィンドウの位置とサイズを記憶し、壊さずに隠した後で再表示を行う

QjackCtlというツールがある。これはJACKの制御を行うGUIでツールキットも別(Qt4)なのだが、メインウィンドウと一部の情報表示ウィンドウに関して、その位置とサイズを保持しつつ、表示したり隠したりすることができる。
ここでは、それにならって、PyGTKの「PyGTKでシステムトレイを使用してメニューを表示する」で使用したコードをもとに、メニューから開くウィンドウに対して、位置とサイズを記憶しつつ、隠した後で同じ位置とサイズ、更にそれ以外のGUI部品の状態も保持した状態で再表示を行うことを試してみる。

位置とサイズの保存と復元について

  • ウィンドウオブジェクト(gtk.Window)のメンバ関数get_position()を使用すると、ウィンドウ位置情報が取得できる(ダイアログの場合もgtk.Windowの子クラスなので取得可能)
  • 同様にget_size()でウィンドウサイズを取得できる
  • ウィンドウの位置を変更するにはmove()を使用し、先ほど取得した位置情報を引数に指定する
  • 同様に、サイズの変更はresize()で行い、取得済みのサイズ情報を渡す

隠す操作と再表示について

  • あるウィンドウを隠すには、基本的にはそのウィンドウのオブジェクトに対して、メンバ関数hide()を呼べばよい
  • ウィンドウマネージャの「閉じる」ボタンなどで閉じる操作を行うと「delete_event」というシグナルが発生し、ウィンドウは壊されてしまうが、(connect()で関連付けた)ハンドラ関数の戻り値がTrueのときに限り壊されない(閉じることもない)
  • 「delete_event」のハンドラの戻り値のところに使用するのに便利なのがgtk.Widgetクラスのメンバ関数hide_on_delete()で、ウィジェット自身を隠しつつ、戻り値として必ずTrueを返し、壊されることを防いでもくれる(文字通り、delete時に隠すためのメンバ関数となっている)

コード例

[任意]ファイル名: savegeometrytest.py

#! /usr/bin/python
# -*- encoding: utf-8 -*-

import sys
try:
  import pygtk
  pygtk.require("2.0")
except:
  pass
try:
  import gtk
except:
  print >> sys.stderr, "Error: PyGTK not installed"
  sys.exit(1)
if gtk.pygtk_version < (2,10,0):
  errtitle = "Error"
  errmsg = "PyGTK 2.10.0 or later required"
  if gtk.pygtk_version < (2,4,0):
    print >> sys.stderr, errtitle + ": " + errmsg
  else:
    errdlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR, buttons = gtk.BUTTONS_OK)
    errdlg.set_title(errtitle)
    errdlg.set_markup(errmsg)
    errdlg.run()
  sys.exit(1)

class TestWindow(gtk.Window):
  """
  テスト用ウィンドウ
  """
  pos  = None  # 復元処理が最初だけ行われないように
  size = None  # 分岐する関係で、Noneにしている
  def __init__(self, item, **args):
    """
    ウィンドウの初期化
    """
    gtk.Window.__init__(self, **args)  # 親クラスのコンストラクタ

    self.item = item
    self.set_title("Test window")
    self.set_size_request(300, 180)  # 最小サイズ

    # スクロールバーの付いたテキストビューを1つ配置
    self.textview = gtk.TextView()
    self.sw = gtk.ScrolledWindow()
    self.sw.add(self.textview)
    self.sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
    self.add(self.sw)

    # シグナル
    self.connect("delete_event", self.delete)
  def delete(self, widget, event):
    """
    閉じるボタンなどにより閉じられたときの処理
    """
    # 座標とサイズの記憶(2要素のタプル型)
    self.pos = self.get_position()  # X座標とY座標
    self.size = self.get_size()     # 幅と高さ
    # メニュー項目のチェックを外す
    self.item.set_active(False)
    # 隠しつつ、Trueを返して破壊を防ぐ
    return self.hide_on_delete()  # gtk.Widgetクラスのメンバ関数
  def set_visible_restored(self, visible):
    """
    記憶された位置とサイズを復元して表示する・表示中であれば隠す
    """
    if visible == True:  # gtk.CheckMenuItemのチェック状態を引数として渡している
      # 位置とサイズを復元してから再表示・最初は復元しない
      if self.pos:      # 初期値はNoneにしているため、最初は復元されない
        (x, y) = self.pos           # タプル要素の分解
        self.move(x, y)             # 移動
      if self.size:
        (width, height) = self.size
        self.resize(width, height)  # サイズ変更
      self.show_all()               # 再表示
    else:
      # 既に表示されているときには隠すようにする
      self.emit("delete_event", None)  # delete_eventを起こす

class TrayMenu(gtk.Menu):
  """
  システムトレイのメニュー
  """
  def __init__(self):
    gtk.Menu.__init__(self)

    # メニュー項目(チェック形式)
    self.item_win = gtk.CheckMenuItem("Window")
    self.item_win.connect("activate", self.item_win_activate)
    self.append(self.item_win)

    # 区切り線
    self.append(gtk.SeparatorMenuItem())

    # 終了
    self.item_quit = gtk.ImageMenuItem(stock_id=gtk.STOCK_QUIT)
    self.item_quit.connect('activate', self.quit_activate)
    self.append(self.item_quit)

    # このメニューを表示可能にする
    self.show_all()

    # ウィンドウを準備しておく
    self.win = TestWindow(item=self.item_win, type=gtk.WINDOW_TOPLEVEL)
  def activate_item_win(self, widget):
    # item_winを選択したことにする(チェックの状態変化 + ウィンドウ表示状態変化)
    self.item_win.emit("activate")
  def item_win_activate(self, widget):
    """
    項目item_winが選択されたときの処理
    """
    self.win.set_visible_restored(self.item_win.get_active())
  def quit_activate(self, widget):
    """
    終了の項目が選択されたときの処理
    """
    gtk.main_quit()

class PyGTKSaveGeometryTest:
  """
  ウィンドウの位置とサイズを記憶し、隠した後に再表示を行うテスト
  """
  def main(self):
    tray = gtk.StatusIcon()   # PyGTK 2.10以上
    self.menu = TrayMenu()    # gtk.Menuの子クラス
    # 今回はgtk.STOCK_DIALOG_INFOのストックアイコンを使用
    # set_from_file()やset_from_icon_name()、set_from_pixbuf()も使える
    tray.set_from_stock(gtk.STOCK_DIALOG_INFO)
    tray.set_tooltip("test")  # マウスポインタを少し乗せたときに文字列
    tray.connect("activate", self.menu.activate_item_win)  # 通常クリック時の処理
    tray.connect("popup-menu", self.show_menu)  # メニューを出す
    gtk.main()
  def show_menu(self, widget, button, time):
    """
    メニューのポップアップを行う
    """
    # gtk.status_icon_position_menu()でメニューをアイコンの位置に合わせる
    self.menu.popup(None, None, gtk.status_icon_position_menu, button, time, widget)

if __name__ == "__main__":
  app = PyGTKSaveGeometryTest()
  app.main()

(2009/7/22)メイン処理の一部の流れと一部関数を微調整

実行と挙動の確認

上のコードを実行し、システムトレイのメニューからウィンドウの項目を選択するかアイコンをクリックすると、スクロール可能な複数行テキスト入力欄(テキストビュー)だけを含んだウィンドウが表示される。
この中に適当な文字列を入力し、ウィンドウのサイズを変更したり場所を移動したりしてから、ウィンドウマネージャの「閉じる」ボタンを押すか、システムトレイのメニューからウィンドウの項目を選択もしくはアイコンのクリックを行うと、ウィンドウは画面上から消える。
その後、最初と同じようにして表示させると、先ほどと同じ位置に同じサイズでウィンドウが現れ、先ほど入力した文字列もそのままになっていることが確認できる。

関連記事:

参考URL: