平成 20 年度計算機演習 マルチクライアント音声チャットサーバ (4) GUI

1
平成 20 年度 計算機演習
マルチクライアント音声チャットサーバ (4)
GUI プログラミング
担当:谷川智洋 講師,西村邦裕 助教
TA:仲野潤一,伊東 里香,土山 裕介,南部 愛子
2008 年 6 月 9 日・7 月 8 日
1 演習の目的
前回までの演習で,音声デバイスのコントロール,ネットワーク越しで 1 対 1 でチャットを行うプログラ
ム,複数人の人が同時にチャットを行うプログラムの作成を行ってきた.
• デバイスのコントロール
• ソケット通信
• マルチスレッド
• マルチクライアントへの拡張
第 4 回となる今回は,本演習では,C 言語と GUI(Graphical User Interface) ツールキット・ライブラリを
用いた,X Window System 上で動作する音声ファイルの取り扱いやネットワークプログラムの作成を通し
て,GUI プログラミングの基礎を学ぶ.第 5 回で,これまで学習したプログラムに GUI を付け加えることで,
最終的に普段我々が使っているアプリケーションに近いマルチクライアント音声チャットの作成を目指す.
2 Gtk+ による GUI プログラミングの基礎
2.1 GUI プログラミング
GUI プログラミングは,マウスなどのポインティングデバイスを介した入力と,グラフィカルな出力を求
められるので,これまで学んできた文字ベースのプログラミングに比べると,本質的な機能の部分よりもユー
ザーインターフェースの実装により多くの労力をとられてしまう.また,これまで学んできた,文字ベースの
逐次処理型のプログラムとは異なり,GUI 環境では,プログラムは常にユーザーの入力を待機し,ユーザーか
らの操作があって初めて機能が呼び出される「イベント駆動型 (event driven) 型」のプログラミングとなり,
プログラミングの手法も大きく異なってくる.
GUI ツールキットは,GUI プログラミングに伴う煩わしい作業を簡素化するために用いられるライブラ
リである.多くの開発者は GUI ツールキットを用いて GUI アプリケーションの開発を行なっている.GUI
2
2 Gtk+ による GUI プログラミングの基礎
ツールキットには,ボタン,ダイアログボックス,スクロールバー,メニューなど GUI を構成する数多くの”
ウィジェット (widget =部品)”があらかじめ用意されていて,プログラムのソースコードからはほんの数行
の呼び出しの手続きを書くだけで,美しくデザインされたこれらのウィジェットを自分のプログラムで利用す
ることができる.
近年,数多くの優れた GUI ツールキットが公開されているが,今回はその中でも急速に普及しつつある
Gtk+ を取り上げて,GUI プログラミングを実践する.本演習で取り上げるのは Gtk+ のみであるが,他の
GUI ツールキットを用いても基本的なプログラミングスタイルは似ており,演習で学んだことは,今後,あら
ゆる場面で役立てることができる.
2.2 Gtk アプリケーションの処理の流れ
まずは,簡単な例題プログラムを使って,GUI プログラミングにおけるウィジェットの作成,イベントの
処理について見てみよう.最初にサンプルの sample1.c のコンパイルを行って,実行ファイルを作成する.
以下のの Web ページ (http://www.cyber.t.u-tokyo.ac.jp/t̃ani/class/mech enshu/) からリンクをたどり,
enshu2008gui1.tar.gz をダウンロードし,以下のコマンドを実行する.
¶
³
% tar xvzf enshu2008gui1.tar.gz
% cd 2008gui1
% make
µ
´
でき上がったファイルを実行し,図 1 のようなウィンドウが現れれば,コンパイルは成功である.
図 1 sample1.c 実行時の画面
Gtk+ を利用するには,Gtk+ ライブラリのヘッダファイル gtk.h をソースコードの冒頭でインクルードす
る必要がある.sample1.c 12 行目の#include<gtk/gtk.h>がそれにあたる.gtk.h には,Gtk+ を扱うのに
必要な関数,構造体,マクロ等が定義されている.*1
次に 54 行目以降のメイン関数を見てみよう.Gtk アプリケーションにおける処理の流れは,基本的には
初期化 → ウィジェットの生成 → 入力待機状態の開始
のようになっている.以下でそれぞれの処理について説明する.
*1
実際は gtk.h 自体は,さらに細かい機能ごとに定義されたヘッダファイルをインクルードしているだけである.興味のある人は一
度 gtk.h を覗いてみるとよい.
2.3 ウィジェット
3
初期化
初期化の処理では主に,プログラム呼び出し時の引数の処理,ロケールの設定 (文字コードなどの言語に関
する設定),設定ファイルの読み込み等を行なう.最も単純な Gtk プログラムのメイン関数は大体以下のよう
になるはずである.
int main (int argc, char* argv[]) {
{
gtk_set_local();
/* ロケールの設定
gtk_init(&argc, &argv);
/* ライブラリ初期化と引数処理 */
gtk_get_rc_parse("設定ファイル"); /* Gtk 設定ファイルの読みこみ
*/
*/
...
}
sample1.c ではロケールの設定と設定ファイルの読みこみは省略してある.gtk_init() へは,アプリケー
ション自体への引数のリスト argv がそのまま渡され,アプリケーション呼び出しの際に指定できる Gtk オプ
ションが処理される.処理された引数は消去され,それ以外の引数は引数リストに残るので,gtk_init() の
あとで,アプリケーション固有の引数を処理すればよい.
ウィジェットの生成
Gtk+ をはじめとする GUI ツールキットではウィンドウやボタンなどの要素を「ウィジェット (部品)」と
呼ぶ.ウィジェットには,ボタンやテキストボックス,スライダーなど目に見える GUI 部品の他に,パッキ
ングボックスと呼ばれるウィジェットを配置を管理するための枠も含まれ (後述),作成したウィジェットを
パッキングボックスへ格納 (=パック) することで GUI のレイアウトを行なう.
基本的に Gtk+ での GUI の構築は、ウィジェットを格納する GtkWidget 型の構造体を確保し,パッキン
グボックスに格納したあと gtk_widget_show() 画面に表示する,という手順を踏む.gtk_widget_show()
を呼び出す順番はとくに決りはないが,gtk_widget_show() が呼ばれても,その下にあるウィジェットが表
示されないかぎり表示できないので,一番最後にメインウィンドウを表示するように記述すると,全てのウィ
ジェットが一度に表示されるようになる.
入力待機状態の開始
全てのウィジェットを作成したあとは,GUI プログラムはユーザーからの入力などのイベントを待機し,イ
ベントが生じた場合に必要な処理を行なう関数を呼び出すという状態に入る.Gtk プログラミングにおいては
開発者が待機状態のコードを自前で用意する必要はなく,125 行目にあるように gtk_main() を呼び出すだけ
でよい.プログラムを終了するためのイベントが生じると gtk_main() を抜けだし,プログラムは main() 関
数の最後まで到達して終了する.
2.3 ウィジェット
Gtk+ で最も単純な 3 つのウィジェットである,ウィンドウ,ラベル,ボタンの使い方を述べる.
4
2 Gtk+ による GUI プログラミングの基礎
window ウィジェット
Gtk+ のアプリケーションで最初に作成すべきウィジェットは,メインウィンドウそのもので,sample1.c
では 67∼69 行目にあたる.メインウィンドウの作成は以下のように行なう.
/* ウィジェットの宣言 */
GtkWidget *window;
/* 新しい window ウィジェットの確保 */
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
/* ウィンドウタイトルの設定 */
gtk_window_set_title(GTK_WINDOW(window), "ウィンドウタイトル");
/* ウィンドウの枠から中味までの間隔の設定 */
gtk_container_border_width(GTK_CONTAINER(window), 10);
label ウィジェット
ウィンドウ上に文字列を表示するためには label ウィジェットを用いる.label ウィジェットの作成は以下
のようなコードを書けば良い.
/* ウィジェットの宣言 */
GtkWidget *label;
/* 新しい label ウィジェットの確保,文字列の設定 */
label = gtk_label_new("ラベル文字列");
sample3.c 78∼80 行目ではウィンドウにあらわれるボタンを押した回数の表示のための label ウィジェット
を定義している.label の文字列は,アプリケーションの動作中に,動的に変更することもできる.この場合,
gtk_label_set(GTK_LABEL(label), "新しいラベル");
とすればよい.
button ウィジェット
ボタンの表示も,これまで見て来たウィジェットと同様に
2.4 コールバック関数の設定
5
/* ウィジェットの宣言 */
GtkWidget *button;
/* 新しい button ウィジェットの確保,文字列の設定 */
button = gtk_button_new_with_label("ボタンに表示される文字列");
で作成できる.ただし,ボタンの場合ユーザーの操作という “イベント” 発生に伴う処理を開発者が自ら定義
してやらなければならない(次節参照).
2.4 コールバック関数の設定
イベント処理の細かい概念や実装は,GUI ツールキットや OS によって多少異なる.Gtk+ では基本的に
イベント発生時に “シグナル” が発行されるようになっており,開発者はシグナル発生時に行なうべき手続を
記述した関数 (シグナルハンドラ) を登録することによってウィジェット操作時の動作を定義する.このイベ
ントに対応づけられたシグナルハンドラのように,プログラムに対する入力等の通知を処理する手続をコール
バックと呼ぶ.
コールバックの登録は主に 2 種類ある.これはコールバック関数への引数の数によって使いわける.より汎
用性が高いのは
gint gtk_signal_connect( GtkObject
gchar
*object,
*name,
GtkSignalFunc
func,
gpointer
func_data );
object
: シグナルを発行する GTK オブジェクト (ウィジェット)
name
: シグナル名
func
: コールバック関数
func_data : コールバック関数へ渡すデータへのポインタ
で,この場合コールバック関数の型式は,
void callback_func( GtkWidget *widget,
gpointer
data );
widget
: シグナルを発行したウィジェット
data
: コールバックへ渡されるデータへのポインタ
となる.一方,より簡便なのは,
6
2 Gtk+ による GUI プログラミングの基礎
gint gtk_signal_connect_object( GtkObject
gchar
GtkSignalFunc
GtkObject
*object,
*name,
func,
*slot_object );
object
: シグナルを発行する GTK オブジェクト (ウィジェット)
name
: シグナル名
func
: コールバック関数
slot_object : func への最初の引数として渡す GTK オブジェクト
で,この場合コールバック関数は,
void callback_func( GtkWidget *widget );
widget
: シグナルを発行したウィジェット
である.つまり,コールバック関数に何かしらのデータを引数として渡したい場合は,gtk_signal_connect(),
それ以外の場合は,gtk_signal_connect_object() を使えばよい.
シグナル名はあらかじめ定義されており,表 1 のようなものがある.
表 1 Gtk+ のイベントの例
イベント名
説明
pressed
マウスボタンが押された
released
マウスボタンが離された
clicked
マウスボタンがクリックされた
enter
マウスポインタが重なった
leave
マウスポインタが外れた
sample1.c では,ボタンが押されるとカウンター変数 counter をインクリメントし,さらにラベルの表示
を変更して,ボタンが押された回数をカウントするようにしている.
¶
◇ 課題 1 ◇
³
1. sample1.c をコンパイルし,実行せよ.
2. sample1.c に button2 を追加し,カウンターを減らす機能を実装せよ(図 2).
µ
´
2.5 ウィジェットの配置
より多機能な GUI プログラムを作るためには、複数のウィジェットを思い通りの位置に配置する必要があ
る.しかし、各ウィジェットの位置を座標の絶対値で指定するだけでは,ユーザーの操作によってウィンド
2.5 ウィジェットの配置
7
図 2 課題 1.2 実行時の画面
ウのサイズや GUI の概観,フォントの種類が変更されると,開発者の意図どおりの配置を保つことが難しく
なる.また,ウィジェットの配置の調整に多くの労力を要してしまう.そこで,Gtk+ をはじめとして多くの
GUI ツールキットでは “パッキング” という操作によってウィジェットの配置を行なう.Gtk+ での基本的な
ウィジェットの配置を理解するためには,パッキングボックスの概念を知る必要がある.
パッキングの操作では,“見えない箱” の中にウィジェットを詰め (=パック),さらにその箱をより大きな
箱に詰めていって,多数のウィジェットを高率良くウィンドウの中に整理して並べるのが基本である.この “
見えない箱” を “パッキングボックス” と呼ぶ.パッキングボックスには以下の2種類がある.
図4
図3
水平ボックスと垂直ボックス
パッキングボックスを更にパックするこ
とで,自由にウィジェットをレイアウトするこ
とができる.
水平ボックス 水 平 ボ ッ ク ス は ウ ィ ジ ェ ッ ト を ,横 に 並 べ て 配 置 す る .水 平 ボ ッ ク ス の 作 成 は
gtk_hbox_new() で 行 な う .水 平 ボ ッ ク ス の 場 合 ,ウ ィ ジ ェ ッ ト は gtk_box_pack_start() で
パックした順に左から右へ,あるいは gtk_box_pack_end() でパックした順に右から左へ詰めこま
れる.
垂直ボックス 垂 直 ボ ッ ク ス は ウ ィ ジ ェ ッ ト を ,縦 に 並 べ て 配 置 す る .垂 直 ボ ッ ク ス の 作 成 は
8
2 Gtk+ による GUI プログラミングの基礎
gtk_vbox_new() で 行 な う .垂 直 ボ ッ ク ス の 場 合 ,ウ ィ ジ ェ ッ ト は gtk_box_pack_start() で
パックした順に上から下へ,あるいは gtk_box_pack_end() でパックした順に下から上へ詰めこま
れる.
¶
◇ 課題 2 ◇
³
1. sample2.c のプログラムを元に電卓の数字ボタン及び C(クリア) ボタンの挙動を実現せよ.
(図 5)
2. 演算 (+,-,*,/) ボタンのコールバック関数を実装せよ.(オプション)
3. 追加した各ボタンにコールバック関数を自由に割り当てよ.(オプション)
µ
´
図 5 課題 2.1 実行時の画面
9
3 音声デバイスプログラミング
一般に,グラフィカルユーザインタフェースを持つシングルスレッドのアプリケーションでは,時間が掛
かる処理を行うためのボタンが押されたら “待ちカーソル”(砂時計など) を表示して,動かなくなる.アプリ
ケーションをマルチスレッド化することで,アプリケーションとユーザインタフェースを多くのリクエストに
応答することができる.例えば,長時間かかる処理のボタンが押されたら,独立したスレッドにその処理を行
わせ,その間もユーザーインタフェースは別スレッドで実行を続けることができる.
3.1 マルチスレッド補足:スレッドへの引数の受け渡し
新しいスレッドのスタートルーチンに渡す引数は 1 個のポインタである.もっと多くの引数が必要なら,構
造体を準備し引数がそれを指すようにすればよい.
struct two_args {
int arg1;
int arg2;
};
void *needs_2_args(void *);
void a()
{
pthread_t t;
struct two_args *ap;
ap = (struct two_args *)malloc(sizeof (struct two_args));
ap->arg1 = 1;
ap->arg2 = 2;
error = pthread_create(&t, NULL, needs_2_args, (void *) ap);
....
}
void *needs_2_args(void *ap)
{
struct two_args *argp = (struct two_args *)ap;
int a1, a2;
a1 = argp->arg1;
a2 = argp->arg2;
free(argp);
....
}
10
3 音声デバイスプログラミング
3.2 ウィジェットからの情報の取得
また,GUI によるインタラクティブなプログラムを作るためには,ボタンのような押す/離すの単純な入力
だけでなく,文字列や数値など,より複雑な情報を入力できるようにする必要がある.ここでは,文字列の入
力方法として entry ウィジェット,数値の入力方法として scale ウィジェットの使い方を学ぶ.
entry ウィジェット
文字列を入力するためには entry ウィジェットを用いる.entry ウィジェットの作成は以下のようなコード
を書けば良い.
/* ウィジェットの宣言 */
GtkWidget *entry;
/* 新しい entry ウィジェットの確保,文字数の設定 */
entry = gtk_entry_new_with_max_length (50);
また,entry ウィジェットの文字列を取り出すには以下のようにすればよい.
gchar *entry_text;
entry_text = gtk_entry_get_text (GTK_ENTRY(entry));
scale ウィジェット
数値を入力するためには,entry ウィジェットに直接数値を打ち込んでも良いが,より GUI らしい入力方法
として,scale ウィジェットが用意されている.scale ウィジェットの使用方法はこれまでのウィジェットより
若干複雑になっている.まず,scale ウィジェットの範囲や初期値,増減値などを設定するための,adjustment
オブジェクトを作成する.adjustment オブジェクトを作成するための関数 gtk_adjustment_new() の使い
方は以下の通りである.
3.2 ウィジェットからの情報の取得
11
GtkObject* gtk_adjustment_new ( gfloat value,
gfloat lower,
gfloat upper,
gfloat step_increment,
gfloat page_increment,
gfloat page_size );
value
: 初期値
lower
: 最小値
upper
: 最大値
step_increment : 増減値(小)
page_increment : 増減値(大)
page_size
: スケールを移動するウィジェットの大きさ
upper は表示領域の最大値であり,値の最大値ではないことに注意せよ.値の最大値は upper - page_size
になる.
その後,これまでのウィジェットと同様に scale ウィジェットを宣言して確保し,先に作成した adjustment
オブジェクトを指定してやればよい.まとめると,以下のようなコードを書けばよい.
/* オブジェクト,ウィジェットの宣言 */
GtkObject *adjustment;
GtkWidget *scale;
/* 新しい adjustment オブジェクトの確保 */
adjustment = gtk_adjustment_new(0, 0, 255, 1, 1, 0);
/* コールバック関数の設定(ここでは on_slider_moved() という関数をよそで定
義している) */
gtk_signal_connect(GTK_OBJECT(adjustment), "value_changed",
GTK_SIGNAL_FUNC(on_slider_moved), adjustment);
/* 新しい水平 scale ウィジェットの確保,adjustment オブジェクトの指定 */
scale = gtk_vscale_new(GTK_ADJUSTMENT(adjustment));
/* 小数点以下の桁数の設定 */
gtk_scale_set_digits(GTK_SCALE(scale), 0);
また,scale ウィジェットの値をコールバック関数から取り出すには以下のようにすればよい.
12
3 音声デバイスプログラミング
void on_slider_moved(GtkWidget * widget, GtkAdjustment *adj)
{
int scale_value;
scale_value = adj->value;
}
¶
◇ 課題 3 ◇
³
1. recorder.c をコンパイルし,実行せよ.(図 6).
2. ファイルの保存・読み込みを entry ウィジットを使って実装せよ.
3. 録音ボタン,再生ボタンを実装する.再生中は GUI が反応しないことを確認せよ.
4. 録音スレッド,再生スレッドに分け,録音中・再生中でも停止可能にせよ.(オプション)
5. 取り込んだ波形を表示する.(図 7).(オプション)
6. 早送り・巻き戻しを実装.ただし,再生中の早送り・巻き戻しに対応する必要はない.(オプ
ション)
µ
´
図 6 課題 3.1 実行時の画面
図7
課題 3.4 実行時の画面