対象読者
- Rubyの入門書を一読した方
- 練習用にRubyのプログラムを作りたいが良い課題が見つからない方
必要なもの
- Ruby本体とお好きなエディタを用意してください。
- 筆者は以下の環境で執筆しています。
- ruby 1.8.6-p111 / SAKURAエディタ / WindowsXP SP2
今回作る物
例題として作っていきたいのはテキストエディタです。
テキストエディタといってもいろいろありますが、次のような感じのものを作りたいと思います。
- コンソールアプリケーション(もちろんですね)
- ごくごく基本的な機能のみを実装
- Viのようなモードを持つ
モードというのはコマンドモードや追加モード、挿入モードなどの事で、それらを切り替えながらテキストファイルを編集していく事になります。昨今のUIデザインではこのようなモードを持つのはよくないデザインと考えられています。しかしモードを持つソフトウェアの代表ともいえるViは根強い人気を持ちますので、(慣れてしまえば)それほど悪くはないのかなと思います。
完成時の画面イメージは次のようになります。
- 編集エリアは、開いたテキストファイルを表示し編集するエリアです。
- 情報表示エリアは、現在のモードなどを表示する(予定の)エリアです。
- そのほかに現在開いているファイル名を画面中央に表示します。
このテキストエディタの名前ですが、FileEditを略してFeとしましょう。名前を決めておくのは(主にモチベーションの面で)けっこう重要です。:)
クラス構成
クラスの構成を下図のようにしようと思います。矢印は呼び出し関係を表します。
メインクラスの名前は、そのままアプリケーションの名前でFeとします。それ以外のクラスは順次、この記事の中で説明していきます。
まずは見えるところから作る
プログラムを作るときは、動いていることを視覚的に確認しながら進めるとモチベーションも上がりますし、進めている方向が正しい事が分かって安心もできます。まずは見えるところから作っていく事にしましょう。
最初はメインクラスから作ります。
Feは起動時に編集するファイル名を受け取ることにしたいので、コマンドラインから引数を一つ受け取ります。受け取れなかった場合は使用方法を表示してプログラムを終了します。
お決まりのinit_screen
を呼び出してコンソール画面を初期化しCurses
を使えるようにします。続く2行は設定です。cbreak
はキーボード入力のバッファリングを停止し、noecho
で入力に対するエコー表示を停止します。コンソールアプリケーションを作成する場合は大抵はこの設定になると思います。
あとは前述の画面構成となるようにサブウィンドウを二つとファイル名の表示を行い、テキストファイルを開いて最初の数十行(サブウィンドウの行数分)を表示します。
後半はほとんど別クラスに処理を任せていますが流れだけ押さえておいてください。
require "curses" require "editwind" require "commandwind" #ファイル名をコマンドライン引数として受け取る if ARGV.size != 1 #コマンドライン引数がない場合 printf("usage: fe file_name\n"); exit else file_name = ARGV[0] end #コンソール画面を初期化し設定を行う Curses.init_screen Curses.cbreak Curses.noecho # デフォルトウィンドウを取得 defo_wind = Curses.stdscr # 編集エリアウィンドウを作成 edit_wind = EditWind.new(defo_wind) # 情報表示エリアウィンドウを作成 cmmd_wind = CommandWind.new(defo_wind,file_name) # ファイルをオープンし内容を編集エリアに表示する edit_wind.display(file_name) # すぐ消えるのを防ぐために入力待ちとする edit_wind.getch #コンソール画面を終了 Curses.close_screen
さて次は編集エリアウィンドウのクラスです。
これはテキストファイルの内容を表示し編集するクラスですが、まだ表示(それも初期表示)しかしていません。一行ずつ読み込み、それを順次配列に追加しています。追加する際にはあらかじめ改行コードを取り除いてあります。これをしないと画面上の表示がくずれてしまいます。
require "curses" class EditWind def initialize(wind) # デフォルトウィンドウの高さを少し小さくしたサブウィンドウを作成 @window = wind.subwin(wind.maxy-3,wind.maxx,0,0) # スクロール機能をONにする(後述します) @window.scrollok(true) end def display(file_name) @file_name = file_name begin # ファイルをオープンし、全内容を配列に読み込んでおく @file = open(@file_name, "a+") @data = [] @file.each_line do |line| # 行末にある改行を取り除いた上で配列に入れる @data.push(line.chop) end # 初期表示として0行目からウィンドウの最大行数まで一行ずつ表示する @data[0..(@window.maxy-1)].each_with_index do |line, idx| @window.setpos(idx, 0) @window.addstr(line) end rescue # ファイルをオープンできない等、なにか例外が起きた場合 raise IOError,"FILE OPEN ERROR: #{file_name}" end @window.setpos(0,0) @window.refresh end def getch return @window.getch end end
最後は情報表示エリアウィンドウのクラスです。
サブウィンドウを作る事と、ファイル名を受け取りそれを反転表示の帯と共に表示することしかしていません。
require "curses" class CommandWind def initialize(wind,file_name="") max_y = wind.maxy max_x = wind.maxx begin_y = wind.maxy - 3 wind.setpos(begin_y,0) wind.standout # 以後の文字表示を反転色にする wind.addstr(" " * max_x) # 画面横サイズ分の空白で帯を表現 wind.standend # 反転色解除 # 帯の真ん中にfile名を表示 wind.setpos(begin_y,(max_x/2)-(file_name.length/2)) wind.addstr(file_name) # 情報表示用のサブウィンドウを作成 @window = wind.subwin((max_y-begin_y),max_x,begin_y,0) @window.refresh end end
一つだけパッと見、分かりにくい所を解説します。
(max_x/2)-(file_name.length/2)
という計算は、テキストを画面の真ん中に表示するための描画開始位置を決めています。こういったプログラムを作る際には良く出てきますので定石として覚えておくといいかもしれません。説明のため図を書いてみました。