機能「追加」は決して機能「改良」ではない。
新しい機能が使えるようになるという意味で「改良」だが、システムの複雑度が増すという意味で「改悪」。開発運用のコストも上がるし、ユーザからしても、機能全体を理解するのが難しくなる。
逆に、初期に導入したけど実際に運用しはじめたら実は要らなかったって機能があるなら、どんどん言ってほしい、って個人的には思う。要らない機能を削除することで、開発・運用がやりやすくなり、それは機能「改良」になる。
機能「追加」は決して機能「改良」ではない。
新しい機能が使えるようになるという意味で「改良」だが、システムの複雑度が増すという意味で「改悪」。開発運用のコストも上がるし、ユーザからしても、機能全体を理解するのが難しくなる。
逆に、初期に導入したけど実際に運用しはじめたら実は要らなかったって機能があるなら、どんどん言ってほしい、って個人的には思う。要らない機能を削除することで、開発・運用がやりやすくなり、それは機能「改良」になる。
Fedora17上で gnome-terminal を使っていたら、F10キーで右クリックメニューが表示されてしまう。
vim のキーバインドで F10 を使っているのでこれは困った。
一応、メニューの「編集」→「キーボードショートカット...」で F10 を無効にする設定は存在する。これを「有効」に設定していると F10 キーを押した時点で上部のメニューが開く。で、無効にするとその挙動はなくなるのかなぁと思ったのだが、上部のメニューが開かない代わりに、マウスカーソルの位置に右クリックメニューが表示されてしまう。一応、vim のキーバインドも機能はしているようだが、いちいちメニューが表示されるのはうっとうしい。
いろいろ調べてみたところ、Gnome3の設定ファイルを追加すればいいようだ。
~/.config/gtk-3.0/gtk.css というファイルを新規に作成し、以下の内容を記述。
@binding-set NoKeyboardNavigation { unbind "<shift>F10" } * { gtk-key-bindings: NoKeyboardNavigation }
これでメニューは表示されなくなった。めでたしめでたし。
XML文書をパースする機能が必要になったので、libxml2 を使ってみた。
今までオイラは XML のパースは軽量言語(Perl、PHPなど)でしかやったことがなかった。libxml2をそのまんま使うとものすごく面倒なんだろうなぁと若干恐れおののいていたのだが、いざ使ってみると案外そうでもない。(少なくともフォーマットが分かっているXMLから必要な項目を取り出すだけなら)
ということで、XML文書から商品名と価格を取り出すという簡単なサンプルを作ってみた。
(例によってサンプルコード全体は github にあげてあります)
サンプルデータ(sample.xml)はこんな感じ。
<?xml version="1.0" encoding="UTF-8"?> <fruits> <fruit> <item>りんご</item> <price>150</price> </fruit> <fruit> <item>みかん</item> <price>250</price> </fruit> <fruit> <item>メロン</item> <price>1000</price> </fruit> </fruits>
で、これを処理してみる。プログラム側のざっくりとした流れは以下のようになる。
xmlTextReaderPtr reader; int ret; // Readerの作成 reader = xmlNewTextReaderFilename("./sample.xml"); // 次のノードに移動 ret = xmlTextReaderRead(reader); while (ret == 1) { // 現在のノードを処理(processNodeは別途定義) processNode(reader); // 次のノードに移動 ret = xmlTextReaderRead(reader); } // Reader のすべてのリソースを開放 xmlFreeTextReader(reader);
ここで言う「ノード」とは、開始タグ・終了タグやテキストなどのこと。例えば
<item>りんご</item>
このようなXMLは、
の3つのノードとして認識される。
// Readerの作成 reader = xmlNewTextReaderFilename("./sample.xml");
xmlNewTextReaderFilename() は、ある XML リソースを読み込むためのReaderを作成する関数。引数に指定するのは、処理対象のリソースURI。今回はファイル名を指定したが、http://〜 で始まる文字列を指定すればネット上からリソースを取得することも可能。
// 次のノードに移動 ret = xmlTextReaderRead(reader); while (ret == 1) { // 現在のノードを処理(processNodeは別途定義) processNode(reader); // 次のノードに移動 ret = xmlTextReaderRead(reader); }
xmlTextReaderRead() は、XMLストリーム上の次のノードへ読み進める関数(初回は最初のノード)。戻り値は
よって、1 を返している間は読み進めたノードの処理を繰り返す。
処理本体は processNode() という別関数で定義した。
// 1つのノードを処理する void processNode(xmlTextReaderPtr reader) { …
現在 reader がポイントしているノードのさまざまな情報を取得する関数が用意されている。例えば以下のようなものがある。
項目名 | 取得用関数 | 概要 |
---|---|---|
NodeType | xmlTextReaderNodeType() | ノードの種類 |
Name | xmlTextReaderName() | ノード名 |
Value | xmlTextReaderValue() | Textノードの場合、そのText |
Depth | xmlTextReaderDepth() | XMLツリー内の階層の深さ(root=0) |
NodeType は整数の値で表される。libxml/xmlreader.h では以下の18種類の値が定義されている。
typedef enum { XML_READER_TYPE_NONE = 0, XML_READER_TYPE_ELEMENT = 1, XML_READER_TYPE_ATTRIBUTE = 2, XML_READER_TYPE_TEXT = 3, XML_READER_TYPE_CDATA = 4, XML_READER_TYPE_ENTITY_REFERENCE = 5, XML_READER_TYPE_ENTITY = 6, XML_READER_TYPE_PROCESSING_INSTRUCTION = 7, XML_READER_TYPE_COMMENT = 8, XML_READER_TYPE_DOCUMENT = 9, XML_READER_TYPE_DOCUMENT_TYPE = 10, XML_READER_TYPE_DOCUMENT_FRAGMENT = 11, XML_READER_TYPE_NOTATION = 12, XML_READER_TYPE_WHITESPACE = 13, XML_READER_TYPE_SIGNIFICANT_WHITESPACE = 14, XML_READER_TYPE_END_ELEMENT = 15, XML_READER_TYPE_END_ENTITY = 16, XML_READER_TYPE_XML_DECLARATION = 17 } xmlReaderTypes;
今回のサンプルでは、1 = 開始タグ、3 = テキスト、15 = 終了タグ の3つのみ使用する。
さて、さっそく現在処理中のノードタイプとノード名を取得してみる。
xmlElementType nodeType; xmlChar *name, *value; // ノード情報の取得 nodeType = xmlTextReaderNodeType(reader); // ノードタイプ name = xmlTextReaderName(reader); // ノード名 if (!name) name = xmlStrdup(BAD_CAST "---");
name は常に取得可能とは限らず、NULL が返る場合もある。今回は取得出来なかった場合 "---" という文字列を代わりに代入することにした。BAD_CAST というのは、libxml2 で定義されているマクロで、通常の文字列を xmlChar* 型にキャストしても安全と分かっている場合のキャスト用に使用する。
if (nodeType == XML_READER_TYPE_ELEMENT) { // 開始 if ( xmlStrcmp(name, BAD_CAST "item") == 0 ) { state = STATE_ITEM; } else if ( xmlStrcmp(name, BAD_CAST "price") == 0 ) { state = STATE_PRICE; }
ノードが開始タグである場合は、ノード名を調べる。"item" または "price" というノードなら変数 state を変更して、現在それらのノードの中にいることが分かるようにする。
} else if (nodeType == XML_READER_TYPE_END_ELEMENT) { // 終了 state = STATE_NONE;
ノードが終了タグである場合は、"item" または "price" タグから抜けたということを表すため state を変更する。"item" "price" 以外のタグから抜けた場合でも無駄に処理されるが気にしない。w
} else if (nodeType == XML_READER_TYPE_TEXT) { // テキスト // テキストを取得する value = xmlTextReaderValue(reader); if (!value) value = xmlStrdup(BAD_CAST "---"); if ( state == STATE_ITEM ) { printf("品名: %s\n", value); } else if ( state == STATE_PRICE ) { printf("価格: %s円\n", value); }
ノードがテキストノードだった場合は、現在どのノード内にいるかを調べ、適切な文字列を表示する。テキストノードの値を取得するには xmlTextReaderValue() を使用する。
xmlFree(value); } xmlFree(name); }
xmlChar* 型の変数を宣言し、かつ xmlTextReader〜() 関数を使って文字列を取得できた場合は、使用後に xmlFree() で適切に開放しなければならない。
// Reader のすべてのリソースを開放
xmlFreeTextReader(reader);
すべての XML パース処理が終わったら、Reader のリソースをすべて開放する。
コンパイルは以下のとおり行う。ライブラリやインクルードファイルのパス指定は、xml2-config を使うと楽。
gcc -Wall -o xmlread xmlread.c `xml2-config --cflags --libs`
実行結果
----------------------- 品名: りんご 価格: 150円 ----------------------- 品名: みかん 価格: 250円 ----------------------- 品名: メロン 価格: 1000円 -----------------------
更新が1ヶ月に1回とか悲惨な事になってますが、今年はもうちょっと頑張ります。(希望)
http://library.gnome.org/devel/gtk/stable/GtkTreeView.html
ツリー構造やリストを表示するためのウィジェット。
GtkTreeView というからにはツリー構造、つまり階層を持つデータ構造を表示するのが唯一の用途のようにも見える。しかし階層構造の有無を問わず、行と列を使って2次元的に表示したいのであれば、GtkTreeView での表示に適している。
GtkTreeView はいわゆる MVC(Model-View-Controller)構造をとっていて、データを保持する Model、表示方法を決める View、そしてそれらを利用しながら全体をコントロールする Controller が分離されている。
Model と View については、専用のクラスが用意されている (Modelであれば、GtkListStore, GtkTreeStore など、View であれば GtkTreeViewColumn など)。これらのクラスを用途に応じて選び、大本の GtkTreeView に関連付けて使うことになる。
生成自体は gtk_tree_view_new() でできる。
#include <gtk/gtk.h> int main(int argc, char **argv) { GtkWidget *window, *treeview; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(window), "明日の天気"); g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL); /* GtkTreeView の生成 */ treeview = gtk_tree_view_new(); gtk_container_add(GTK_CONTAINER(window), treeview); gtk_widget_show_all(window); gtk_main(); return 0; }
もちろん中のデータもその表示方法も定義していない状態では、真っ白のウィンドウが表示されるだけで、面白いことは何も起きない。それでも、一応ウィンドウ上に GtkTreeView ウィジェットは存在しているはず(多分)。
というわけで、この上に何か意味のあるものを表示させていこうと思う。まずはデータを格納するための Model = GtkTreeModel の準備から。
GtkTreeModel は、データを保持して GtkTreeView にデータを提供するための仕組みを備えている。といっても GtkTreeModel 自身はインタフェースにすぎず、実際には GtkTreeModel を実装したクラスを使用する。ビルトインで用意されているものとして
があるので、このどちらかを使っていればよい。
GtkListStore と GtkTreeStore の異なる点は、扱うことのできるデータ構造。GtkListStore は、階層構造を持たない、線形リストのみ扱うことができる。対して GtkTreeStore は、階層構造を含んだデータを扱うことができる。一見 GtkTreeStore のほうが優秀で、こちらだけあれば良いようにも見えるが、GtkListStore は構造が単純な分処理が高速なため、階層構造が特に必要なければ GtkListStore を使ったほうがよいだろう。(使い方もやや単純)
というわけで、まずは GtkListStore を使ってみる。
まずは今回作るものを決めよう。今回は、各地の天気予報データを表示するサンプルアプリを作ることにした。都市名と、天気、最高・最低気温を表示していくことにする。データ自体は Livedoor の Weather Hacks から、2010年12月4日時点での明日の天気を取得させていただいた。
場所 | 天気 | 最高気温 | 最低気温 |
東京 | 晴のち曇 | 17 | 9 |
群馬 | 晴時々曇 | 18 | 5 |
大阪 | 晴れ | 16 | 7 |
最初にデータの格納先について定義する。
/* * Model の定義 */ void create_model(GtkWidget *treeview) { GtkListStore *store; /* GtkListStore の生成、および列定義 */ store = gtk_list_store_new( 4, // 要素の個数 G_TYPE_STRING, // 場所 G_TYPE_STRING, // 天気 G_TYPE_INT, // 最高気温 G_TYPE_INT // 最低気温 ); /* treeview に結びつける */ gtk_tree_view_set_model(GTK_TREE_VIEW(treeview), GTK_TREE_MODEL(store)); /* 一旦結びつけたら store 自体は開放してよい */ g_object_unref(store); }
gtk_list_store_new() の引数を使って、格納データの形式を定義する。RDBでいうDDLのようなものだと思うとイメージつかみやすいかも。まず列の数を指定し、その後にそれぞれの型を順番に指定していく。型の指定はここの冒頭にある #define 文あたりを参考にすれば大抵の用途には充分かと。
指定が終わったら、大元の GtkTreeView に関連付ける。これでこの GtkListStore が treeview 上で使用可能になる。
/* * Model に実データ追加 */ void set_data(GtkWidget *treeview) { GtkListStore *store; GtkTreeIter iter; // treeview に結びつけられている Model を取得する。 // 一旦、中のデータをすべて消去する。 store = GTK_LIST_STORE( gtk_tree_view_get_model( GTK_TREE_VIEW(treeview) ) ); gtk_list_store_clear(store); /* 新しいレコードを生成する(3レコード) */ gtk_list_store_append(store, &iter); gtk_list_store_set(store, &iter, // 格納先レコード 0, "東京", // 0番めの値 1, "晴のち曇", // 1番めの値 2, 17, // 2番めの値 3, 9, // 3番めの値 -1 // 終端 ); gtk_list_store_append(store, &iter); gtk_list_store_set(store, &iter, 0, "群馬", 1, "晴時々曇", 2, 18, 3, 5, -1); gtk_list_store_append(store, &iter); gtk_list_store_set(store, &iter, 0, "大阪", 1, "晴れ", 2, 16, 3, 7, -1); }
次に、この GtkListStore に対して、実際のデータをセットする。データをセットするには、まずレコード格納用の領域を生成する必要がある。それを行っているのが gtk_list_store_append()。レコードが生成されると、変数 iter に新規レコードへの参照が格納される。
そして新規レコードに対して値をセットする。1番目のレコードのみ見やすいようコメントを入れたが、格納先レコードを指定したあと、第3引数以降で、データの位置-値のセットを指定していく。
そしてこれらを main()関数から呼び出す。
/* Model の定義 */ create_model(treeview); /* Model に実データ追加 */ set_data(treeview);
もちろんこの時点で実行しても何も表示されない。最後に、データの表示を行うための view を追加しよう。
View の定義は「列」単位で行われる。列を定義するために使用されるクラスは GtkTreeViewColumn。このインスタンスを、必要な列の数だけ TreeView に追加していく。
GtkTreeViewColumn 自体は、列の「枠」を用意するにすぎない。実際、GtkTreeViewColumn の properties を見てみると、列幅やヘッダ行に表示されるタイトルなどの外形的なものを定義できるだけで、その中に表示される具体的なデータには何ら関心を持っていないことが分かる。
データを表示するためには、その「枠」の中に、表示機能を司る「何か」を入れてやらなければならない。その「何か」というのがGtkCellRenderer。GtkTreeViewColumn に含めることのできる、具体的なデータを扱うためのオブジェクトだ。GtkCellRendererのサブクラスとして、以下の4つがビルトインで用意されている。
例えば GtkCellRendererText を GtkTreeViewColumn に追加すれば、その列にテキストを表示することが可能になる。その他のクラスを選べば、画像やプログレスバー、トグルスイッチを表示させることもできる。追加可能な GtkCellRenderer は1つとは限らないので、例えば1つの列にアイコン (GtkCellRendererPixbuf) とテキスト(GtkCellRendererText) を並べて表示させるということも可能だ。
また、GtkCellRenderer はデータの表示に関する一切のことを請け負っているため、データ表示そのものだけでなく、その表示のされ方についても設定することができる。例えばテキストであれば、表示色やフォントについても GtkCellRenderer(Text) に対して指定することになる。
まぁ今回はシンプルに、テキストのみを扱うことにする。
/* * treeview に列を1つ追加 */ void append_column_to_treeview(GtkWidget *treeview, const char *title, const int order) { GtkTreeViewColumn *column; GtkCellRenderer *renderer; /* CellRenderer の作成 */ renderer = gtk_cell_renderer_text_new(); /* * 列を作成し、CellRenderer を追加する。 * さらに Model との関連を定義する */ column = gtk_tree_view_column_new_with_attributes( title, // ヘッダ行に表示するタイトル renderer, // CellRenderer "text", order, // Model の order 番めのデータを、renderer の属性 "text" に結びつける NULL // 終端 ); /* 今生成した列を、大元の treeview に追加する */ gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column); } /* * treeview に View を定義する */ void create_view(GtkWidget *treeview) { append_column_to_treeview(treeview, "場所", 0); append_column_to_treeview(treeview, "天気", 1); append_column_to_treeview(treeview, "最高気温", 2); append_column_to_treeview(treeview, "最低気温", 3); }
gtk_tree_view_column_new_with_attributes()について補足しておく。renderer の "text" 属性に、Model の order 番めのデータを結びつけているが、ここでは Model と View のマッピングを行っている。例えば一番最初に作成した列であれば order = 0 なので、Model に設定した 0 番目の情報、つまり場所情報(データでいうと「東京」「群馬」「大阪」)が、この列の GtkCellRenderer の属性 "text"、つまり表示される文字列そのものとしてセットされる。
ちなみに設定可能な GtkCellRenderer の属性は、text だけに限らない。例えば foreground に Model の値をセットするようにすれば、色の指定も可能となる。
Python 上で動く Web フレームワーク、Django の超クイックスタート。簡素化しまくってるのでご容赦を。
またプログラム中、コメント行はすべて省略。
環境は以下のとおり。Python と Django はすでにインストールされているものとする。
またネーミングは以下のとおり。
django-admin startproject myproject
cd myproject python manage.py startapp myapp
settings.py を編集。INSTALLED_APPS に今回作成するアプリケーション(myproject.myapp)を追加。
INSTALLED_APPS = (
...(中略)...
'myproject.myapp',
)
urls.py を編集。'myhello/' という URL がリクエストされたら、ビュー myhello が適用されるようにマッピングを行う。ビュー myhello は、このあと myproject/myapp/views.py で定義するつもりなので、myproject.myapp.views.myhello と指定する。
from django.conf.urls.defaults import * urlpatterns = patterns('', (r'^myhello/$', 'myproject.myapp.views.myhello'), )
myapp/views.py を編集し、ビュー myhello を定義する。
from django.http import HttpResponse def myhello(request): html = '<html><body>Hello, world.</body></html>' return HttpResponse(html)
python manage.py runserver
Social Games that Sway Behavior - Technology Review
最近ソーシャルゲームがあちこちで話題になっているが、そのソーシャルゲームを現実世界と結びつけようという試みが始まっているらしい。
この記事で最初に紹介されている HealthSeeker は、Facebook上でプレイすることができるソーシャルゲームなのだが、プレイヤーとして想定しているのは「現実世界で」糖尿病を患っている人たち。
ここではRPGのようにいくつかのミッションが用意されているのだが、それは例えば「コーヒーを飲むときに砂糖を入れない」など、糖尿病の人たちの生活を改善するためのものばかりだ。プレイヤーはその中からチャレンジしたいミッションを選ぶ。そしてそれを現実の世界で実践することができたら、自己申告によってミッションのレベルに応じた経験値がもらえる。またミッションに成功するとFacebook上の知り合いに知らされ、お祝いのプレゼント(仮想世界の)がもらえたりするそうだ。
#オイラはFacebookやってなくて実際にプレイしてないので、細部の紹介が間違ってるかもしれないけど(汗
悪い生活習慣って、改善しようと思っても意志が強くないとなかなかできない。でっかい紙に「○○をやめる!」と書いて壁にはっておいたところで、せいぜい1〜2日守れるかどうかってとこ。(←体験者。w)
しかしそれは現実世界のみで解決しようとした場合の話。最近流行りのソーシャルゲームを現実世界の問題を解決するために応用するって考え方は、まさに今の時代ならでは。特に、目標を立ててそれを成し遂げようとするときに、人に見守られている、ちょっとでも進歩があればみんなからの賞賛を受けられる、というのは何歳になっても嬉しいものだし、ソーシャルゲームはそれを、現実世界よりかなり上手く実現することができるはずだ。
禁煙とか、もっといろんなところに応用の余地がありそうだ。