Prompterプログラムの解説(3/3)
イベントの処理
既に述べたように、GTK+プログラムはイベントドリブンで動作します。キーボード入力を処理する関数が設定されているので、ユーザーがキーボード操作を行うと次の関数の実行が行われます。
static gint pmpt_gui_key_press_handler (GtkWidget* widget, GdkEventKey *event, gpointer data) { PmptGUI* gui = (PmptGUI*) data; if (event->type != GDK_KEY_PRESS) return false; switch (event->keyval) { case GDK_Q: case GDK_q: pmpt_gui_quit (); break; case GDK_space: case GDK_Return: case GDK_Right: case GDK_Down: case GDK_Page_Down: case GDK_N: case GDK_n: pmpt_ctrl_next (gui->ctrl); break; case GDK_BackSpace: case GDK_Left: case GDK_Up: case GDK_Page_Up: case GDK_P: case GDK_p: pmpt_ctrl_prev (gui->ctrl); break; default: break; } return TRUE; }
この関数は、ずらずらと条件が並べられているわりにはいたってシンプルで、以下の3つの場合だけに対応しています。
- Qやqが押されたとき
- スペースバー、リターン、矢印キーの右や下、ページダウンキー、あるいはNやnが押されたとき
- バックスペース、矢印キーの左や上、ページアップキー、あるいはPやpが押されたとき
それぞれ、1.ではプログラムを終了させるためにイベントループから抜け出す関数を呼び、それ以外ではスライドを前後させるための処理に移ります。
スライドの遷移
次のスライドへ移る処理は、「ctrl.c」で定義されるpmpt_ctrl_next()で行います。なおひとつ前のスライドに戻る処理pmpt_ctrl_prev()も、移動の向きが反対になる以外はほぼ同様の処理が行われます。
bool
pmpt_ctrl_next (PmptCtrl* ctrl)
{
unsigned char* str;
bool status = pmpt_data_increment (ctrl->data);
/* reached at the end of the presentation */
if (status == false) return false;
pmpt_gui_update (ctrl->gui, ctrl->data);
return true;
}
この更新処理も非常にシンプルです。まずデータをひとつ進めて(pmpt_data_increment())、そのあとでスライドやノートの表示などGUIをアップデート(pmpt_gui_update())します。
GUIの更新手順は次のようになっています。この関数の構造は、既に見た関数に似てますね。pmpt_gui_config()と同様、ノート表示、外部ディスプレイに出力するスライド表示、発表者画面に出す現在のスライド、次のスライドの順番で各々の表示を更新する関数を呼んでいます。ノート表示以外がif文で選択的に呼び出すようになっている点も同じです。
void pmpt_gui_update (PmptGUI* gui, PmptData* data) { PmptPage* page = pmpt_data_get_current_page (data); PmptPage* nextpage = pmpt_data_get_next_page (data); /* 1. update note text */ pmpt_gui_update_note (gui, page->note); /* 2. update slide */ if (pmpt_opt_is_fullscreen (gui->ctrl->opt)) { pmpt_gui_update_slide (gui, page->image_filename); } /* 3. update current slide */ if (pmpt_opt_is_currentslide (gui->ctrl->opt)) { pmpt_gui_update_current_slide (gui, page->image_filename); } /* 4. update next slide */ if (pmpt_opt_is_nextslide (gui->ctrl->opt)) { pmpt_gui_update_next_slide (gui, nextpage->image_filename); } }
ノート表示の更新
ノート表示の更新は簡単です。現在表示しているノート文書を破棄して(文字列の範囲を先頭と末尾に設定して、すべての範囲を消去しています)、引数で与える新たな文字列をセットし直すだけです。なお文字列をセットする関数の最後の引数で文字列の長さを指定できますが、-1の指定は、その文字列の長さに合わせてセットすることを意味しています。ただしこの場合は必ず、終端にヌル文字がセットされた文字列でなければなりません。
static void pmpt_gui_update_note (PmptGUI* gui, unsigned char* str) { GtkTextIter start; GtkTextIter end; GtkTextBuffer* text_buffer = gui->text_buffer; /* 1. delete data */ gtk_text_buffer_get_start_iter (text_buffer, &start); gtk_text_buffer_get_end_iter (text_buffer, &end); gtk_text_buffer_delete (text_buffer, &start, &end); /* 2. show next page */ gtk_text_buffer_set_text (text_buffer, str, -1); }
スライド表示の更新
外部ディスプレイに出力するスライドの更新と、発表者画面に出す現在のスライド、同じく発表者画面に表示される次のスライドの更新には、各々のための更新用関数が対応しています。しかし実は引数で設定を変えているだけで、いずれの場合も実際には次のスライド表示を更新するための関数pmpt_gui_update_window()が実行されることになります。
static void pmpt_gui_update_window (int width, int height, GtkImage* image_window, unsigned char* image_filename) { GdkPixbuf* pixbuf = NULL; GdkPixbuf* scaled = NULL; if (image_filename != NULL) { g_clear_error (gdk_pixbuf_error); pixbuf = gdk_pixbuf_new_from_file (image_filename, gdk_pixbuf_error); if (pixbuf == NULL) { g_warning ("cannot load the image file: \"%s\"", image_filename); } else { scaled = gdk_pixbuf_scale_simple (pixbuf, width, height, GDK_INTERP_HYPER); gdk_pixbuf_unref (pixbuf); } } if (scaled == NULL) { scaled = gdk_pixbuf_new (GDK_COLORSPACE_RGB, false, 8, width, height); } gtk_image_set_from_pixbuf (image_window, scaled); gdk_pixbuf_unref (scaled); }
この関数では、ふたつのGdkPixbuf型データを使います。まずはじめに、各ページに対応するスライドの画像ファイルから、GdkPixbufオブジェクトをgdk_pixbuf_new_from_file()を利用して直接、作成します。続いて、ウィンドウの大きさに合わせて画像サイズを変換しています。そして最後に、変換したGdkPixbufオブジェクトを、GtkImageウィジットにセットしなおします(gtk_image_set_from_pixbuf())。
GdkPixbufを使うときには、ひとつ注意しておくべきことがあります。ここでのコードの例のように、使い終わったらgdk_pixbuf_unref()で参照を解消しておきましょう。これをしておかないとどこからも参照されなくなったデータがいつまでたっても開放されず、メモリリークとなってしまいます。
まとめ
以上、駆け足でアンチョコ付きプレゼンテーションツール「Prompter」の実装を紹介しました。GTK+アプリケーション実装の雰囲気だけでも掴んでいただければ幸いです。
Prompterは私自身が必要に迫られて作成したアプリケーションですが、時間もあまりなかったため、クイック・ハックで仕上げたソフトウェアです。したがってその実装にはまだ多くの問題が残されています。現在の時点でどうにかしたいと考えている改善点としては、次のような項目があります。
- 毎回イメージデータを読み込んでいるため、次のスライドへ移るときの動作に一瞬の間がある。バックグラウンドで先読みしておくとか、データをキャッシュするなどのコードを入れて、ストレスなく操作できるようにしたい
- 発表者用の画面には、タイマーや時計も表示できるといい。タイマーを実装するならば、さらに発表練習のためにスライドを切り替えたタイミングをログとして残せるとなおよい
- スライド表示用の画像ファイルの指定方法が不十分。現バージョンではカレントディレクトリにあるファイルしか対象になっていないので使いにくい。もう少し柔軟に指定できるように修正したい
- 発表者画面に表示するスライドの大きさが、現バージョンではプログラム中にハードコードされている。それだと不便なこともあるので、設定ファイルやコマンドラインオプションなど何らかの方法で変更できるようにしたい
- ノート文書に使えるテキストデータは、現在のバージョンではUTF-8で書かれたものに限定されている。文字コード判定の処理を入れて、その他の文字コードで書かれたデータにも対応できるようにするか、そのあたりの処理を含むフロントエンドのスクリプトを用意したい
Prompterの開発はSourceForge.netでホストされています。Prompterに興味を持たれた皆さんで我こそはと思しき方、ぜひとも開発に参加して上記の課題をクリアすべく、チャレンジしてみませんか?
