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

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

PyGTKでクリップボード上のテキストの内容を扱う

ここではPyGTKを用いたクリップボード上のテキストの処理についてを扱う。また、テキストビューでクリップボードのテキストを処理するための便利な方法についても扱う。

クリップボードを管理するオブジェクト

gtk.Clipboardオブジェクトを用いることにより、クリップボードのデータを簡単にやりとりできるようになっている。文字列以外にも画像とリッチテキストといったものも扱えるが、それぞれの種類ごとに処理ができる。
テキストを扱う場合には

の2つを主に使用する。request_text()wait_for_text()に近いが値をクリップボードに要求した後、値が得られると別のハンドラを呼ぶ形となる。

記憶の種類について

mlterm,Emacsと外部アプリケーション間のコピー/貼り付けについて」やまとめドキュメントなどでも書いているが、X Window System上のクリップボード的な「記憶」には幾つかの種類があり、その中でも

  • PRIMARY selection(選択/ドラッグ範囲・マウス中クリックで貼り付け)
  • CLIPBOARD(多くのGUIアプリケーションがコピペ作業を行うのに使用される)

がよく使用される。gtk.Clipboardオブジェクトの既定の指定ではCLIPBOARDが使用されるが

clipboard = gtk.Clipboard(selection='PRIMARY')

あるいは

clipboard = gtk.clipboard_get(selection='PRIMARY')

のように引数selectionを「PRIMARY」に指定することにより、PRIMARY用のクリップボードオブジェクトを生成することもできる。

クリップボードの内容に関する変更の監視について

クリップボードの内容が変更されるとシグナル「owner_change」を受け取るため、このシグナルとハンドラを関連付けることにより、クリップボードの監視が簡単に行える。

テキストビューのバッファへの操作

テキストビューに表示されるバッファであるgtk.TextBufferオブジェクトには

  • cut_clipboard()
  • copy_clipboard()
  • paste_clipboard()

といったメンバ関数があり、クリップボードオブジェクトを渡してこれらの操作を行うことが簡単に行える。これらはテキストビュー上の選択範囲に対して操作を行う。
貼り付け(paste_clipboard())は、選択範囲がなくてもテキストビュー上のカーソル位置に対して行われる。

テキストビューの横に切り取り/コピー/貼り付け/クリアのボタンが並び、テキストビュー上の選択範囲に対してそれぞれの操作が行える。
クリップボードの内容が更新されると端末に「owner_changed」と表示される。

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

import sys
try:
  import pygtk
  pygtk.require('2.0')
except:
  pass
try:
  import gtk
except:
  print >> sys.stderr, 'Error: PyGTK is not installed'
  sys.exit(1)
if gtk.pygtk_version < (2,2,0):
  print >> sys.stderr, 'PyGTK >= 2.2.0 required'
  sys.exit(1)


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.__menu_file = gtk.Menu()
    self.__menu_file.add(self.__item_quit)
    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.__textbuf = gtk.TextBuffer()
    self.__textview = gtk.TextView(self.__textbuf)
    self.__textview.props.wrap_mode = gtk.WRAP_CHAR
    # テキストビュー向けスクロールウィンドウ
    self.__sw = gtk.ScrolledWindow()
    self.__sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
    self.__sw.add(self.__textview)
    # ボタン
    self.__button_cut = gtk.Button(stock=gtk.STOCK_CUT)
    self.__button_copy = gtk.Button(stock=gtk.STOCK_COPY)
    self.__button_paste = gtk.Button(stock=gtk.STOCK_PASTE)
    self.__button_clear = gtk.Button(stock=gtk.STOCK_CLEAR)
    # レイアウト用コンテナ
    self.__vbox_btn = gtk.VBox()  # ボタンを縦に並べる
    self.__vbox_btn.pack_start(self.__button_cut, expand=False, fill=False)
    self.__vbox_btn.pack_start(self.__button_copy, expand=False, fill=False)
    self.__vbox_btn.pack_start(self.__button_paste, expand=False, fill=False)
    self.__vbox_btn.pack_start(self.__button_clear, expand=False, fill=False)
    self.__hbox = gtk.HBox()  # テキストビューとボタン群
    self.__hbox.pack_start(self.__sw)
    self.__hbox.pack_start(self.__vbox_btn, expand=False, fill=False)
    self.__vbox = gtk.VBox()  # 全体
    self.__vbox.pack_start(self.__menubar, expand=False, fill=False)
    self.__vbox.pack_start(self.__hbox)
    # クリップボード
    # 既定では「CLIPBOARD」が使用されるが
    # 「selection='PRIMARY'」で選択範囲を扱うこともできる
    # 一部の端末エミュレータやエディタなどはPRIMARYのみを使用
    # X11上のEmacsは両対応
    #  * clipboard-kill-regionでCLIPBOARD切り取り
    #  * clipboard-kill-ring-saveでCLIPBOARDコピー
    #  * clipboard-yankでCLIPBOARD貼り付け
    #  これらの「clipboard-」を除いた名前のバージョンではPRIMARYを使用
    self.__clipboard = gtk.Clipboard()
#    self.__clipboard = gtk.Clipboard(selection='PRIMARY')
    # シグナル
    self.connect('delete_event', gtk.main_quit)
    self.__item_quit.connect('activate', gtk.main_quit)
    self.__button_cut.connect('clicked', self.__on_button_cut_clicked)
    self.__button_copy.connect('clicked', self.__on_button_copy_clicked)
    self.__button_paste.connect('clicked', self.__on_button_paste_clicked)
    self.__button_clear.connect('clicked', self.__on_button_clear_clicked)
    self.__clipboard.connect('owner-change', self.__on_clipboard_owner_change)
    # ウィンドウ
    self.add(self.__vbox)
    self.set_size_request(350, 300)
  def __on_button_cut_clicked(self, widget):
    """
    切り取りボタンが押されたら
    テキストビューの内容をクリップボードにコピーしてからクリア
    """
    # 方法1
    # テキストバッファではcut_clipboard()が便利
    self.__textbuf.cut_clipboard(self.__clipboard, True)
    # 方法2
    # 手動で切り取る場合
#    selection = self.__textbuf.get_selection_bounds()
#    if selection:
#      self.__clipboard.set_text(self.__textbuf.get_text(selection[0], selection[1]))
#      self.__textbuf.delete_selection(False, True)
  def __on_button_copy_clicked(self, widget):
    """
    コピーボタンが押されたら
    テキストビューの内容をクリップボードにコピー
    """
    # 方法1
    # テキストバッファではcopy_clipboard()が便利
    self.__textbuf.copy_clipboard(self.__clipboard)
    # 方法2
    # 手動でコピーする場合
#    selection = self.__textbuf.get_selection_bounds()
#    if selection:
#      self.__clipboard.set_text(self.__textbuf.get_text(selection[0], selection[1]))
  def __on_button_paste_clicked(self, widget):
    """
    貼り付けボタンが押されたら
    クリップボードにテキストデータを要求
    """
    # 方法1
    # テキストバッファではpaste_clipboard()が便利
    self.__textbuf.paste_clipboard(self.__clipboard, None, True);
    # 方法2
    # request_text()はデータを受け取ると別の関数を呼ぶが
    # wait_for_text()は直接値を取れる(受け取るまでの間はメインループ内で待つ)
#    self.__clipboard.request_text(self.__on_clipboard_text_received, None)
    # 方法3
    # wait_for_text()は戻り値として内容が得られる
#    text = self.__clipboard.wait_for_text()
#    if text:  # Noneの場合がある
#      self.__textbuf.insert_at_cursor(text)
  def __on_button_clear_clicked(self, widget):
    """
    クリアボタンが押されたら
    テキストビューの内容をクリア
    """
    self.__textbuf.props.text = ''
  def __on_clipboard_text_received(self, clipboard, text, data):
    """
    クリップボードからテキストを取得したらテキストビューに表示する
    """
    if text:
      self.__textbuf.insert_at_cursor(text)
  def __on_clipboard_owner_change(self, clipboard, event):
    """
    クリップボードの内容が変わったときの処理
    """
    # クリップボードに何かをコピーすると下のメッセージが端末に表示される
    print 'owner_changed'
    # ここでrequest_text()やwait_for_text()を呼ぶと
    # クリップボードマネージャのように履歴を管理することもできる

class PyGTKClipboardTest:
  """
  クリップボードのテスト
  """
  def main(self):
    """
    アプリケーションのメイン処理
    """
    win = MainWindow()
    win.show_all()
    gtk.main()


if __name__ == '__main__':
  app = PyGTKClipboardTest()
  app.main()

関連記事:

参考URL:

使用したバージョン: