Python で Curses プログラミング
*******************************

著者:
   A.M. Kuchling, Eric S. Raymond

リリース:
   2.03


概要
^^^^

このドキュメントでは Python 2.x でテキストモードプログラムを表示をコン
トロールする "curses" 拡張モジュールを利用して書く方法について記述しま
す。


curses ってなに?
================

curses ライブラリは、テキストベースの端末、VT100s や Linux コンソール
や xterm や rxvt などの X11 プログラムから提供される端末エミュレータな
どの端末に依存せずに、スクリーンに描画やキーボード処理する機能を供給し
ます。表示端末は様々な制御コードをサポートしていて、カーソル移動、画面
のスクロール、領域の消去などの一般的な操作を実行できます。端末によって
大きく異なるコードを使うことがあるので、しばしば独自の癖があります。

X ディスプレイが当たり前の今となっては、「なんでわざわざ？」と疑問に思
うかもしれません。確かに文字表示端末は時代遅れな技術ではありますが、ニ
ッチな領域が存在していて、意匠を凝らすことができるため、未だ価値あるも
のとなっています。その領域は例えば X サーバを持たない、小さな机上用コ
ンピュータや組み込みの Unix です。他のものとしては OS インストーラーや
カーネル設定ツールなどで、これらは X が利用できるようになる前に動作す
る必要があります。

curses ライブラリは異なる端末の詳細を全て隠蔽し、プログラマに対して、
重なり合わない複数のウィンドウを持つ抽象化されたディスプレイを提供しま
す。ウィンドウの内容は様々な方法で変更されます --- テキストの追加、消
去、外観の変更 --- そして curses ライブラリは送るべき制御コードを自動
的に理解し、正しい出力を生成してくれます。

curses ライブラリは元々 BSD UNIX 向けに書かれました; 後の AT&T から出
た Unix System V バージョンで多くの機能と新機能が追加されました。BSD
curses はいまやメンテナンスされておらず、これは AT&T インターフェース
のオープンソース実装である ncurses にとって置き換えられました。Linux
や FreeBSD のようなオープンソース Unix を利用している場合は、おそらく
システムは ncurses を利用しています。現在のほとんどの商用 Unix は
System V のコードを基にしているため、ここで述べる全ての関数が利用でき
るはずです。しかし、古いバージョンの curses を持ついくつかのプロプライ
エタリ Unix は全てには対応していないでしょう。

curses モジュールの Windows 移植は作られていません。 Windows プラット
フォーム上では、Fredrik Lundh が書いた Console モジュールを試してみて
ください。 Console モジュールはカーソル位置を指定してテキスト出力する
ことができ、マウス、キーボード入力の両方を完全にサポートしていて、
http://effbot.org/zone/console-index.htm から入手できます。


Python の curses module
-----------------------

Python モジュールは curses が提供する C 関数に対するまったく単純なラッ
パーです; 既に C での curses プログラミングに慣れ親しんでいるなら、そ
の知識を Python に持ち込むのは実に簡単です。最大の違いは Python インタ
ーフェースが異なる関数 "addstr()", "mvaddstr()", "mvwaddstr()" を一つ
のメソッド "addstr()" メソッドに統合して単純化していることです。このこ
とは後でより詳しく扱います。

この HOWTO は curses と Python を使ってテキストプログラムを書くための
簡単な入門記事です。 curses API に対する完全な解説をすることは意図して
いません; その目的のためには Python ライブラリガイドの ncurses 節と
ncurses の C 言語マニュアルページを参照してください。しかし、この文章
が基本的な考えを提供してくれるでしょう。


curses アプリケーションの起動と終了
===================================

何をするにもまず、curses を初期化する必要があります。初期化は
"initscr()" 関数を呼びだすことでできます、この関数は端末のタイプを決定
し、必要とされるセットアップコードを端末に送り、様々な内部データ構造を
作成します。成功すると、 "initscr()" は画面全体を表わすウィンドウオブ
ジェクトを返します; これは、 "stdscr" と呼ばれます、この名前は C での
対応する変数にちなんでいます。:

   import curses
   stdscr = curses.initscr()

通常 curses アプリケーションは画面へのキーエコーを自動的に止めます、こ
れはキーを読みとり特定状況下でのみで表示するためです。これには
"noecho()" 関数を呼び出す必要があります。:

   curses.noecho()

通常アプリケーションはまた、Enter キーを押すことなく、キーに対してすぐ
に反応する必要があります; これは cbreak モードと呼ばれ、通常の入力がバ
ッファされるモードと逆に動作します。

   curses.cbreak()

端末は通常、カーソルキーや Page Up や Home といった操作キーなどの特別
なキーをマルチバイトエスケープシーケンスとして返します。それらのシーケ
ンスを想定して対応する処理を行うアプリケーションを書けるように、
curses はそれを "curses.KEY_LEFT" のような特別な値を返して行ってくれま
す。 curses にその仕事をさせるには、キーパッドモードを有効にする必要が
あります。

   stdscr.keypad(1)

curses アプリケーションを終了させるのは起動よりも簡単です。 curses に
親和的な端末設定を元に戻すために以下を呼び出す必要があります:

   curses.nocbreak(); stdscr.keypad(0); curses.echo()

そして、 "endwin()" 関数を呼び出し、端末設定を通常の操作モードに復旧し
ます。:

   curses.endwin()

curses アプリケーションをデバッグするときの一般的な問題は、アプリケー
ションが端末を以前の状態に復旧することなく異常終了したときに端末がめち
ゃめちゃになることです。Python ではこの問題はコードにバグがあって、捕
捉できない例外が発生したときによく起きます。タイプしたキーはもはやエコ
ーされません、例えば、シェルを使うのが難しくなります。

Python では、 "curses.wrapper()" 関数をインポートするとこの複雑な問題
が避けられ、デバッグが簡単になります。 この関数は呼び出し可能オブジェ
クトを引数に取り、上で述べた初期化をし、カラーサポートがあればカラーの
初期化をします。 そして、与えられた呼び出し可能オブジェクトを実行し、
最終的に適切に初期化の逆操作 (deinitialization) を行います。 呼び出し
可能オブジェクトは例外を捕捉する try-catch 節内部で呼び出され、 curses
の初期化操作の逆操作を実行し、そして例外が上に渡されます。 このおかげ
で例外が発生しても端末はおかしな状態にならずにすみます。


ウィンドウとパッド
==================

ウィンドウは curses での基本的な抽象概念です。ウィンドウオブジェクトは
画面の長方形の領域を表わし、テキストの表示、消去、ユーザに文字列入力を
許可などの様々なメソッドをサポートしています。

"initscr()" 関数によって返される "stdscr" オブジェクトはウィンドウオブ
ジェクトで画面全体を扱います。多くのプログラムはこのウィンドウだけを必
要としますが、画面を小さなウィンドウに分けてそれらを別々に再描画したり
消去したいと思うかもしれません。 "newwin()" 関数は新しいウィンドウを与
えられたサイズで作成し、新しいウィンドウオブジェクトを返します。:

   begin_x = 20; begin_y = 7
   height = 5; width = 40
   win = curses.newwin(height, width, begin_y, begin_x)

curses で利用されている座標系について、言っておくことがあります: 座標
は常に *y,x* の順で渡し、ウィンドウの左上の座標を (0,0) とします。これ
は *x* 座標が最初にくる一般的な慣習には反しています。これは多くの計算
機のアプリケーションにとって不幸な違いですが、 curses が最初に書かれて
以来そうだったので、今から変えるには遅すぎます。

メソッドを呼びだして、テキストを表示、消去するとき、その効果はディスプ
レイにすぐには現われません。それは curses が元々 300 ボーの端末接続を
念頭にいれていたためですl それらの端末では画面の再描画時間を減らすこと
がとても重要です。このため curses は画面への変更を積み上げ、最も効率良
い方法でそれらを表示します。例えば、プログラムがウィンドウにいくつかの
文字を表示し、それからウィンドウを消去する場合、元々の文字を送信する必
要はありません、それらはもう見られることはないのです。

従って curses に明示的にウィンドウの再描画を明示的に伝えてやる必要があ
ります、それはウィンドウオブジェクトのメソッド "refresh()" を使うこと
でできます。実際には、これは curses を使ったプログラムをそれほど複雑に
するものではありません。ほとんどのプログラムはせわしなく動いた後キーを
押すなどのユーザからの動作を待ちます。あなたがするべきことはユーザから
の入力を待つ前に単に "stdscr.refresh()" や関連するウィンドウの
"refresh()" メソッドを呼び出して画面を再描画するだけです。

パッドはウィンドウの特別な場合を指します; それは実際に画面に表示される
ものより大きくなることがあり、画面にはその一部だけが表示されます。パッ
ドを作るのに必要なものはパッドの高さと幅だけです、一方でパッドの再描画
には、表示される部分を指す画面上の領域の座標を指定する必要があります。
:

   pad = curses.newpad(100, 100)
   #  These loops fill the pad with letters; this is
   # explained in the next section
   for y in range(0, 100):
       for x in range(0, 100):
           try:
               pad.addch(y,x, ord('a') + (x*x+y*y) % 26)
           except curses.error:
               pass

   #  Displays a section of the pad in the middle of the screen
   pad.refresh(0,0, 5,5, 20,75)

"refresh()" はパッドの一部、画面上の座標 (5,5) から (20,75) に及ぶ長方
形領域を表示します; 表示される一部の左上の角はパッドの座標 (0,0) です
。この違いを除けば、パッドは通常のウィンドウと全く同じで、同じメソッド
を持ちます。

複数のウィンドウとパッドが画面上にある場合、より効率的な方法があり、再
描画時に画面をちらつかせるのを防いでくれます。各ウィンドウの
"noutrefresh()" メソッドを利用して、画面の望ましい状態を表わすデータ構
造をアップデートし、 "doupdate()" 関数で望む状態に合った物理的な画面に
変更します。通常の "refresh()" メソッドは自身の動作時の最後に
"doupdate()" を呼び出します。


テキストの表示
==============

C 言語のプログラマの視点からすると、curses はしばしば、混乱を招きやす
い、微妙に似た関数を持っています。例えば "addstr()" は "stdscr" ウィン
ドウの現在のカーソル位置の文字列を表示し、一方で "mvaddstr()" は与えら
れた y,x 座標にまず移動して、文字列を表示します。 "waddstr()" はほとん
ど "addstr()" に似ていますが、デフォルトの "stdscr" を使う代わりに使う
ウィンドウを指定できます。 "mvwaddstr()" も同様です。

幸運にも、Python インターフェースはこれらの詳細を全て隠蔽してくれます;
"stdscr" は他のものと同様のウィンドウオブジェクトであり、 "addstr()"
のようなメソッドは複数の引数形式を許容してくれます。通常それらは4つの
形式です。

+-----------------------------------+-------------------------------------------------+
| 形式                              | 説明                                            |
+===================================+=================================================+
| *str* または *ch*                 | 文字列 *str* または文字 *ch* を現在位置に表示し |
|                                   | ます                                            |
+-----------------------------------+-------------------------------------------------+
| *str* または *ch*, *attr*         | 文字列 *str* または文字 *ch* を属性 *attr* を利 |
|                                   | 用して現在位置に表示し ます                     |
+-----------------------------------+-------------------------------------------------+
| *y*, *x*, *str* または *ch*       | ウィンドウ内の位置 *y,x* に移動し *str* または  |
|                                   | *ch* を表示します                               |
+-----------------------------------+-------------------------------------------------+
| *y*, *x*, *str* または *ch*,      | ウィンドウ内の位置 *y,x* に移動し属性 *attr* を |
| *attr*                            | 利用して *str* または *ch* を表示します         |
+-----------------------------------+-------------------------------------------------+

属性によって表示するテキストをハイライトすることができます、ボールド体
、アンダーライン、反転、カラーなど。より詳しくは次の小節で説明します。

"addstr()" 関数は Python 文字列を引数にとり、表示します。一方
"addch()" 関数は文字を引数にとります、引数は長さ1の文字列か整数のどち
らでもかまいません。文字列の場合には、表示する文字は 0 から 255 の間に
制限されます。 SVr4 curses は文字拡張のための定数を提供しています; そ
れらの定数は 255 より大きい整数です。例えば "ACS_PLMINUS" は +/- 記号
で "ACS_ULCORNER" はボックスの左上角です (境界を描くのに便利です)。

ウィンドウは最後の操作の後のカーソル位置を覚えているため、 *y,x* 座標
をうっかり忘れてしまっても、文字列や文字は最後の操作位置に表示されます
。 "move(y,x)" メソッドでカーソルを移動させることもできます。常に点滅
するカーソルを表示する端末もあるため、カーソルが特定の位置にいることを
保証して注意が反れないようにしたいと思うかもしれません; ランダムに見え
る位置でカーソルが点滅すると面を食らってしまいます。

アプリケーションが点滅するカーソルを全く必要としない場合、
"curs_set(0)" を呼び出してカーソル見えなくすることができます。同じこと
を、古い curses バージョンに対する互換性を保ちつつ行うために
"leaveok(bool)" 関数があります。 *bool* が true の場合、curses ライブ
ラリは点滅するカーソルを外に出さなくするので、変な場所に現われるのを心
配する必要は無くなります。


属性とカラー
------------

文字は様々な方法で表示することができます。テキストベースアプリケーショ
ンでのステータスラインは通常反転して表示されます; テキストビュアーは特
定の単語をハイライトする必要があるかもしれせん。 curses は属性を画面上
の各セルに対して指定することで、それをサポートします。

属性は整数値で、それぞれのビットが異なる属性を表わします。複数の属性ビ
ットをセットしてテキストの表示を試みることができますが、curses は全て
の組み合わせが利用可能であるかや視覚的に区別できるかどうかは保証してく
れません、それらは利用している端末の能力に依存しているため、最も安全な
のは、最も一般的に利用可能な属性を設定する方法です、ここに列挙します。

+------------------------+----------------------------------------+
| 属性                   | 説明                                   |
+========================+========================================+
| "A_BLINK"              | テキストを点滅                         |
+------------------------+----------------------------------------+
| "A_BOLD"               | 高輝度またはボールドテキスト           |
+------------------------+----------------------------------------+
| "A_DIM"                | 低輝度テキスト                         |
+------------------------+----------------------------------------+
| "A_REVERSE"            | 反転テキスト                           |
+------------------------+----------------------------------------+
| "A_STANDOUT"           | 利用できる最良のハイライトモード       |
+------------------------+----------------------------------------+
| "A_UNDERLINE"          | 下線付きテキスト                       |
+------------------------+----------------------------------------+

つまり、反転するステータスラインを画面の最上部に表示するには、コードを
こうします:

   stdscr.addstr(0, 0, "Current mode: Typing mode",
                 curses.A_REVERSE)
   stdscr.refresh()

curses ライブラリはカラー機能を提供している端末でのカラーもサポートし
ています。そんな端末の中で最も一般的なものは Linux コンソールで、color
xterm もそれに続きます。

カラーを利用するには、 "initscr()" を呼び出したすぐ後に
"start_color()" 関数を呼びし、デフォルトカラーセットを初期化しなければ
いけません ("curses.wrapper.wrapper()" 関数はこれを自動的に行ないます)
。一旦それを行えば、 "has_colors()" 関数は、端末が実際にカラーを表示で
きる場合に TRUE を返します。 (ノート: curses はカナダ/イギリスつづりに
'colour' ではなくアメリカつづりの 'color' を使います。イギリスつづりを
使っている場合には、これらの関数のミススペルを修正する必要があります。
)

curses ライブラリはたかだか有限個のフォラグラウンド(またはテキスト)カ
ラーとバックグラウンドカラーペアを保持します。カラーペアに対応する属性
値は "color_pair()" 関数で取得できます; これは "A_REVERSE" のような他
の属性と OR 論理演算組み合わせることができます、ただし、繰り返しになり
ますが、組み合わせは全ての端末で保証されていません。

例として、テキスト行をカラーペア 1 を使って表示します:

   stdscr.addstr("Pretty text", curses.color_pair(1))
   stdscr.refresh()

前に述べたように、カラーペアはフォアグラウンドカラーとバックグラウンド
カラーから構成されています。 "start_color()" はカラーモードを有効にし
た場合 8 の基本カラーを初期化します。基本カラーは: 0:black, 1:red,
2:green, 3:yellow, 4:blue, 5:magenta, 6:cyan, 7:white です。 curses モ
ジュールは各名前に対する名前付き定数を定義しています:
"curses.COLOR_BLACK", "curses.COLOR_RED", など。

"init_pair(n, f, b)" 関数はカラーペア *n* の定義をフォアグラウンド f
バックグラウンド b に変更します。カラーペア 0 は黒背景に白で組み込まれ
ていて変更できません。

やってみましょう。カラー 1 を白背景に赤に変更してみましょう、こうして
呼び出します:

   curses.init_pair(1, curses.COLOR_RED, curses.COLOR_WHITE)

カラーペアを変更するときには、既に表示された任意のテキストが利用するカ
ラーペアを新しい色に変更します。新しいテキストをこの色で使うこともでき
ます:

   stdscr.addstr(0,0, "RED ALERT!", curses.color_pair(1))

凝ったターミナルは実際の色の定義を与えられた RGB 値に変更することがで
きます。これによってふつうは赤であるカラー1を紫や青など好きな色に変更
されます。不幸にも Linux コンソールはこれをサポートしていません、その
ためこの機能は試すことも例を出すこともできません。
"can_change_color()" を呼び出すことで端末が使えるのか調べることができ
、対応していれば、TRUE を返します。もしそのような telnet 端末を持つ幸
運に巡りあえたなら、より多くの情報の情報を得るためにシステムの man ペ
ージを参照することを勧めます。


ユーザ入力
==========

curses ライブラリは自身で単純なインプット機構を備えています。 Python
はテキスト入力ウィジェットを追加して、いくつかの足りない部分を補ってい
ます。

ウィンドウに対する入力を得るための最も一般的な方法はウィンドウの
"getch()" メソッドを利用することです。 "getch()" はユーザが待機してキ
ーを打つのを待ち、 "echo()" が先に呼ばれていれば、それを表示します。オ
プションとして待機する前にカーソルが移動するべき座標を指定することがで
きます。

この挙動は "nodelay()" メソッドで変更することができます。 "nodelay(1)"
の後、ウィンドウに対する "getch()" はノンブロッキングになり、入力が準
備されていないときには "curses.ERR" (-1 の値) を返します。
"halfdelay()" 関数もあり、(事実上) 各 "getch()" に対してタイマーを設定
するのに使うことができます; 指定したディレイの間に(10分の1秒単位で測ら
れます)入力が得られなかった場合 curses は例外を送出します。

"getch()" メソッドは整数を返します; もしそれが 0 から 255 までなら、そ
れは押されたキーの ASCII コードを表わします。 255 より大きな値は Page
Up, Home またはカーソルキーのような特別なキーです。返された値を
"curses.KEY_PPAGE", "curses.KEY_HOME" または "curses.KEY_LEFT" のよう
な定数と比較することが可能です。通常プログラムのメインループはこのよう
になります:

   while 1:
       c = stdscr.getch()
       if c == ord('p'):
           PrintDocument()
       elif c == ord('q'):
           break  # Exit the while()
       elif c == curses.KEY_HOME:
           x = y = 0

"curses.ascii" モジュールは ASCII クラスのメンバーシップ関数を提供し、
整数と1文字引数のどちらもとることができます; これらは独自のコマンドイ
ンタプリタに対するより読み易いテストを書くのに便利でしょう。整数と1文
字引数のどちらもとることができ、引数と同じ型を返す変換関数も提供します
。例えば "curses.ascii.ctrl()" は引数に応じた型で制御文字を返します。

文字列全体を取得するメソッド "getstr()" もあります。これは頻繁に使われ
るものではありません、なぜならこの機能はとても制限的なものだからです;
利用可能な編集キーはバックスペースと Enter キーの文字列を終了させるも
のだけです。オプションとして文字列の長さを固定長に限定することもできま
す。:

   curses.echo()            # Enable echoing of characters

   # Get a 15-character string, with the cursor on the top line
   s = stdscr.getstr(0,0, 15)

Python の "curses.textpad" モジュールはよりよいものを提供します。これ
を使うことで、ウィンドウをEmacs のようなキーバインドをサポートするテキ
ストボックスにすることができます。 "Textbox" クラスの様々なメソッドが
入力の検証つきの編集をサポートシ前後のスペースつき、または無しで編集結
果を収集します。詳しくは "curses.textpad" のライブラリドキュメントを参
照して下さい。


より多くの情報
==============

この HOWTO ではいくつかの進んだ話題、スクリーンスクレイピングや xterm
インスタンスからマウスイベントを捉えるなど、については扱っていません。
しかし、Python の curses モジュールのライブラリページはいまやかなり充
実しています。次はこれを見るべきです。

ncurses のあらゆるエントリポイントの詳細な挙動について疑問があれば、
curses 実装が ncurses か、プロプライエタリな Unix ベンダーのものかによ
らず curses 実装のマニュアルページをみることを参照して下さい。マニュア
ルページにはあらゆる癖がドキュメントにされていて、全ての関数、属性、
"ACS_*" 文字の完全なリストが提供されています。

curses API は巨大なので、Python インターフェースではいくつかの関数はサ
ポートされていません、ですがそれは必要としている人がいまのところいない
ためです。気兼ねなく、足りないものを追加してパッチを提出して下さい。ま
た、ncurese に関連したメニューやパネルライブラリのサポートも行なわれて
いません; 気兼ねなく追加して下さい。

面白い小さなプログラムを書いたら、新たなデモとして気兼ねなく提出して下
さい。私達は常により多くのデモ使うことができます!

The ncurses FAQ: http://invisible-island.net/ncurses/ncurses.faq.html
