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


概要
^^^^

このドキュメントでは "curses" 拡張モジュールでテキストモードディスプレ
イを制御する方法について記述します。


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

curses ライブラリは、VT100s や Linux コンソール、さまざまなプログラム
が提供するエミュレーション端末といったテキストベースの端末（ターミナル
）のために、端末に依存しないスクリーン描画や、キーボードの処理を提供し
ます。端末はカーソルの移動や、画面のスクロール、領域の消去といった共通
の操作を行うための様々な制御コードをサポートします。端末の種類によって
大きく異なる制御コードを使うことがあり、しばしば独特の癖があります。

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

curses ライブラリが提供するのは比較的基本的な機能です。 画面を、互いに
重ならない複数のテキストウィンドウとして扱う仕組みを提供します。 ウィ
ンドウの内容は、テキストの追加や削除、見た目の変更など、さまざまな方法
で変更できます。 curses ライブラリは、意図した表示にするために端末へ送
る制御コードを判断します。 ボタン、チェックボックス、ダイアログなどの
ユーザーインターフェース部品の多くは提供しません。 そのような機能が必
要なら、Urwid などのユーザーインターフェースライブラリを検討してくださ
い。

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

Windows 版の Python には "curses" モジュールが含まれていません。 サー
ドパーティーの windows-curses パッケージが、Windows で同じインターフェ
ースを提供します。


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

Python のモジュールは、curses が提供する C 関数の比較的単純なラッパで
す。 C での curses プログラミングに慣れていれば、その知識を Python で
も簡単に生かせます。 最大の違いは、Python のインターフェースでは
"addstr()"、"mvaddstr()"、"mvwaddstr()" のような別々の C 関数を 1 つの
"addstr()" メソッドにまとめ、扱いやすくしていることです。 詳しくは後で
説明します。

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


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

何をするにしても、まず curses を初期化する必要があります。 "initscr()"
関数を呼び出すと、端末の種類を判定し、必要な設定コードを端末へ送り、内
部の各種データ構造を作成します。 成功すると、"initscr()" は画面全体を
表すウィンドウオブジェクトを返します。 これは対応する C の変数名にちな
んで、通常 "stdscr" と呼ばれます。

   import curses
   stdscr = curses.initscr()

通常、curses アプリケーションではキー入力を自動的に画面へ表示する機能
(エコー) を無効にし、キーを読み取った後、必要な場合にだけ表示します。
そのためには "noecho()" 関数を呼び出します。

   curses.noecho()

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

   curses.cbreak()

端末は通常、カーソルキーや Page Up、Home などの特殊キーを、複数バイト
のエスケープシーケンスとして返します。 これらのシーケンスを自分で処理
することもできますが、curses に任せると "curses.KEY_LEFT" のような特別
な値として受け取れます。 そのためには、キーパッドモードを有効にします
。

   stdscr.keypad(True)

curses アプリケーションを終了させるのは起動よりも簡単です。 以下を呼び
出す必要があります:

   curses.nocbreak()
   stdscr.keypad(False)
   curses.echo()

cursesに親和性の高い設定をもとに戻します。そして、"endwin()" 関数を呼
び出し、端末を通常の操作モードに復旧します。

   curses.endwin()

curses アプリケーションのデバッグでよくある問題は、端末の状態を復元す
る前にアプリケーションが終了し、端末がおかしな状態のままになることです
。 Python では、コードのバグで例外が発生し、それが捕捉されなかった場合
によく起こります。 例えば、入力したキーが画面に表示されなくなり、シェ
ルを使いにくくなります。

Pythonでは、これらの複雑さを避けデバッグをより容易にするために、
"curses.wrapper()" 関数をインポートして、このように使います:

   from curses import wrapper

   def main(stdscr):
       # 画面を消去する
       stdscr.clear()

       # i == 10 のとき ZeroDivisionError が発生する。
       for i in range(0, 11):
           v = i-10
           stdscr.addstr(i, 0, '10 divided by {} is {}'.format(v, 10/v))

           stdscr.refresh()
           stdscr.getkey()

   wrapper(main)

"wrapper()" 関数は呼び出し可能オブジェクトを受け取り、上記の初期化を行
います。 色がサポートされていれば、色の初期化も行います。 その後、
"wrapper()" は渡された呼び出し可能オブジェクトを実行します。 その呼び
出しから戻ると、"wrapper()" は端末をもとの状態に戻します。 呼び出しは
"try"..."except" の中で行われ、例外が発生すると、それを捕捉して端末の
状態を復元してから再送出します。 そのため、例外が発生しても端末が異常
な状態のままにならず、例外のメッセージやトレースバックを読めます。


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

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.LINES" と "curses.COLS" 変数からそれぞれ *y*
方向と *x* 方向の大きさを取得して調べられます。 有効な座標の範囲は
"(0,0)" から "(curses.LINES - 1, curses.COLS - 1)" までです。

テキストを表示または消去するメソッドを呼び出しても、その結果はすぐには
画面に現れません。 画面を更新するには、ウィンドウオブジェクトの
"refresh()" メソッドを呼び出す必要があります。

これは、curses が当初、300 ボーという低速な端末接続を想定して書かれた
ためです。 そのような端末では、画面の再描画にかかる時間を最小限にする
ことが非常に重要でした。 そこで curses は画面への変更を蓄積し、
"refresh()" が呼ばれたときに最も効率的な方法で表示します。 例えば、プ
ログラムがウィンドウにテキストを表示してからそのウィンドウを消去した場
合、テキストは実際には表示されないので、端末へ送る必要がありません。

実際には、ウィンドウの再描画を明示的に指示しても、curses のプログラミ
ングはそれほど複雑になりません。 多くのプログラムは一連の処理を行った
後、キー入力などの利用者の操作を待って休止します。 入力待ちに入る前に
"stdscr.refresh()" または必要なほかのウィンドウの "refresh()" を呼び出
し、画面を再描画しておけばよいのです。

パッドは特殊なウィンドウです。 実際の画面より大きくでき、一度に表示す
るのはその一部だけです。 作成時にはパッドの高さと幅を指定し、更新時に
はパッドの一部を表示する画面上の領域の座標を指定します。

   pad = curses.newpad(100, 100)
   # このループでパッドを文字で埋める。addch() は
   # 次の節で説明する。
   for y in range(0, 99):
       for x in range(0, 99):
           pad.addch(y,x, ord('a') + (x*x+y*y) % 26)

   # 画面中央にパッドの一部を表示する。
   # (0,0) : 表示するパッド領域の左上隅の座標。
   # (5,5) : パッドの内容を表示するウィンドウ領域の
   #         左上隅の座標。
   # (20, 75) : パッドの内容を表示するウィンドウ領域の
   #          : 右下隅の座標。
   pad.refresh( 0,0, 5,5, 20,75)

"refresh()" の呼び出しは、画面上の座標 (5,5) から (20,75) までの長方形
に、パッドの一部を表示します。 表示する部分の左上隅は、パッド上の座標
(0,0) です。 この点以外は、パッドは通常のウィンドウとまったく同じで、
同じメソッドを使えます。

画面上に複数のウィンドウやパッドがある場合は、各部分の更新に伴うちらつ
きを防ぎ、画面をより効率的に更新する方法があります。 "refresh()" は実
際には次の 2 つの処理を行っています。

1. それぞれのウィンドウの "noutrefresh()" メソッドを呼び出して、配下に
   ある、スクリーンの望ましい状態を表すデータ構造を更新します。

2. "doupdate()" 関数を呼び出して、データ構造に記録された望ましい状態に
   合致するように、物理スクリーンを更新します。

代わりに、複数のウィンドウで "noutrefresh()" を呼んでデータ構造を更新
し、その後 "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 の文字列またはバイト列
を受け取ります。 バイト列の内容はそのまま端末へ送られます。 ワイド文字
に対応していないビルドでは、文字列はウィンドウの "encoding" 属性の値で
エンコードされます。 この属性のデフォルト値は、"locale.getencoding()"
が返すシステムのデフォルトエンコーディングです。

"addch()" メソッドは文字を受け取ります。 文字は、長さ 1 の文字列、長さ
1 のバイト列、または整数で指定できます。

端末の代替文字集合の文字に対応する定数が用意されています。 例えば、
"ACS_PLMINUS" は +/- 記号、"ACS_ULCORNER" は枠の左上隅で、罫線を描くの
に便利です。 対応する Unicode 文字を使うこともできます。

ウィンドウは最後の操作後のカーソル位置を覚えているので、*y,x* 座標を省
略すると、文字列や文字は直前の操作でカーソルが残った位置に表示されます
。 "move(y,x)" メソッドでカーソルを移動することもできます。 常に点滅す
るカーソルを表示する端末もあるので、邪魔にならない位置にカーソルを置く
とよいでしょう。 無関係に見える場所でカーソルが点滅すると、混乱を招く
ことがあります。

アプリケーションで点滅するカーソルがまったく必要なければ、
"curs_set(False)" を呼んで非表示にできます。 ウィンドウの "leaveok()"
メソッドは、それとは異なる働きをします。 引数が真の場合、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()" はこれを自動的
に行います)。 初期化後、使用中の端末が実際にカラー表示に対応していれば
、"has_colors()" は真を返します。 なお、curses はカナダやイギリスの綴
り colour ではなく、アメリカの綴り color を使います。 イギリス式の綴り
に慣れている方も、これらの関数では「綴り間違い」を我慢してください。

curses ライブラリは、前景色 (文字の色) と背景色からなる有限個のカラー
ペアを管理します。 "color_pair()" 関数で、カラーペアに対応する属性値を
取得できます。 この値は "A_REVERSE" などのほかの属性とビット単位の OR
で組み合わせられますが、その組み合わせがすべての端末で動くとは限りませ
ん。

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

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

As I said before, a color pair consists of a foreground and background
color. The "init_pair(n, f, b)" function changes the definition of
color pair *n*, to foreground color f and background color b.  Color
pair 0 is hard-wired to white on black, and cannot be changed.

色には番号が付いており、"start_color()" はカラーモードを有効にするとき
に 8 つの基本色を初期化します。 0 は黒、1 は赤、2 は緑、3 は黄、4 は青
、5 はマゼンタ、6 はシアン、7 は白です。 "curses" モジュールは各色に
"curses.COLOR_BLACK"、"curses.COLOR_RED" などの定数を定義しています。

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

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

カラーペアを変更すると、そのペアを使って既に表示されているテキストも新
しい色になります。 次のように、その色で新たなテキストを表示することも
できます。

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

Very fancy terminals can change the definitions of the actual colors
to a given RGB value.  This lets you change color 1, which is usually
red, to purple or blue or any other color you like.  Unfortunately,
the Linux console doesn't support this, so I'm unable to try it out,
and can't provide any examples.  You can check if your terminal can do
this by calling "can_change_color()", which returns "True" if the
capability is there.  If you're lucky enough to have such a talented
terminal, consult your system's man pages for more information.


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

C の curses ライブラリが提供する入力機能は、ごく単純なものだけです。
Python の "curses" モジュールは、基本的なテキスト入力ウィジェットを追
加しています。 (Urwid などのほかのライブラリには、より豊富なウィジェッ
トがあります。)

ウィンドウから入力を取得するメソッドは 3 つあります。

* "get_wch()" は画面を更新してから、利用者のキー入力を待ちます。 事前
  に "echo()" が呼ばれていれば、入力されたキーを表示します。 待機する
  前にカーソルを移動する座標を指定することもできます。

* "getch()" は同じ処理をしますが、文字の代わりにキーのコードを返します
  。 ncurses では、これは現在のロケールでエンコードされたキーの 1 バイ
  トなので、複数バイトの文字を読むには、1 回に 1 バイトずつ複数回呼び
  出す必要があります。

* "getkey()" は "getch()" と同じ処理をしますが、文字列を返します。 通
  常のキーは 1 文字の文字列、特殊キーは "KEY_UP" のような名前になりま
  す。

ウィンドウの "nodelay()" メソッドを使うと、利用者の入力を待たずに済む
ようにできます。 "nodelay(True)" の後は、そのウィンドウの読み取りはノ
ンブロッキングになります。 入力がない場合、"get_wch()" と "getkey()"
は例外を送出し、"getch()" は "-1" を返します。 また、"halfdelay()" を
使うと、実質的に各読み取りにタイマーを設定できます。 指定した待ち時間
(0.1 秒単位) 内に入力がなければ、読み取りは同じように失敗します。

Special keys such as Page Up, Home, or the cursor keys are returned by
all three as one of the KEY_* constants, all larger than 255. You can
compare the value returned to constants such as "curses.KEY_PPAGE",
"curses.KEY_HOME", or "curses.KEY_LEFT".  The main loop of your
program may look something like this:

   while True:
       c = stdscr.get_wch()
       if c == 'p':
           PrintDocument()
       elif c == 'q':
           break  # while ループを抜ける
       elif c == curses.KEY_HOME:
           x = y = 0

"curses.ascii" モジュールには、整数または 1 文字の文字列を受け取り、
ASCII の文字分類を判定する関数があります。 こうしたループの条件判定を
読みやすく書くのに役立つでしょう。 また、整数または 1 文字の文字列を受
け取り、同じ型を返す変換関数もあります。 例えば、"curses.ascii.ctrl()"
は引数に対応する制御文字を返します。

1 行全体を取得する "getstr()" メソッドもあります。 機能がかなり限られ
るため、あまり使われません。 編集に使えるのは文字消去文字、行消去文字
と、行を確定する Enter キーだけです。 バイト列オブジェクトを返し、取得
するバイト数に上限を設けることもできます。

   curses.echo()            # 入力文字の表示を有効にする

   # カーソルを最上行に置き、最大 15 バイトの行を取得する
   s = stdscr.getstr(0,0, 15)

"curses.textpad" モジュールは、Emacs に似たキーバインドを使えるテキス
トボックスを提供します。 "Textbox" の各種メソッドでは、入力を検証しな
がら編集したり、末尾の空白を残すか取り除くかを選んで編集結果を取得した
りできます。 次に例を示します。

   import curses
   from curses.textpad import Textbox, rectangle

   def main(stdscr):
       stdscr.addstr(0, 0, "Enter IM message: (hit Ctrl-G to send)")

       editwin = curses.newwin(5,30, 2,1)
       rectangle(stdscr, 1,0, 1+5+1, 1+30+1)
       stdscr.refresh()

       box = Textbox(editwin)

       # Ctrl-G が押されるまで、利用者に編集させる。
       box.edit()

       # 編集結果を取得する
       message = box.gather()

さらなる詳細についてはライブラリのドキュメント "curses.textpad"  を参
照してください。


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

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

curses 関数の詳しい動作に疑問があれば、使用している実装が ncurses であ
っても、プロプライエタリな Unix ベンダーのものであっても、その実装のマ
ニュアルページを参照してください。 特有の動作に加え、利用可能なすべて
の関数、属性、ACS_* 文字の一覧が記載されています。

curses の API は非常に大きいため、Python のインターフェースではサポー
トしていない関数もあります。 多くの場合、実装が難しいからではなく、ま
だ誰も必要としていないためです。 また、Python はまだ ncurses に付属す
るメニューライブラリにも対応していません。 これらへの対応を追加するパ
ッチを歓迎します。 Python へのパッチの提出方法については、Python
Developer's Guide を参照してください。

* Writing Programs with NCURSES: C プログラマ向けの詳しいチュートリア
  ル。

* The ncurses man page

* ncurses の FAQ

* "Use curses... don't swear": curses または Urwid を使って端末を制御
  する PyCon 2013 講演ビデオです。

* "Console Applications with Urwid": Urwid で書かれたいくつかのアプリ
  ケーションを紹介する PyCon CA 2012 の講演動画。
