KDEによるGUI作成
Richard J. Moore
日本語訳: 菰田泰生 19990618導入
KDEでGUIを作ることはたくさんの既存の利用可能なGUIコンポーネントや、開発者のためのQtが提供するフレームワークのおかげでとても簡単にできます。不幸にも大量のパブリックドメインのソースコード(例えばKDE全体)や詳細なクラスドキュメントのせいで、GUIを作るためにどうやって物を組み合わせていくかを説明するチュートリアルがありませんでした。この文書の目的はそのような文書を提供することです。具体的に議論するために`ドラッグアンドドロップ'、URLサポート、オンラインヘルプのような機能を含むとても簡単なKDEアプリケーションを作っていきたいと思います。この文書はC++や基本的なコンパイルの問題については説明しません。どうやってGUIを組み合わせるかだけを説明します。始めるにあたって1つのメニューを含むメニューバーだけから構成されるとても簡単なGUIを作ってみましょう。`Exit'コマンドだけをメニューに含みます。このむしろ謙虚なものから、私たちはアプリケーションをシンプルながら機能的なエディタに拡張していきたいと思います。これはチュートリアルにしては手ごわいものかも知れませんが、QtやKDEによって提供されているコンポーネントを再利用することを見ることで簡単に練習できます。私は最初にコードを見せてあとで詳しく追っていきます。全てのサンプルコードはKDEの開発者用ウェブサイトのhttp://developer.kde.org/examples.tar.gzから自由にダウンロードできます。
最小のアプリケーション
私たちはこれから最小のKDEアプリケーションについて詳細に見ていきたいと思います。それは`Exit'コマンドだけを提供しますが、すでにフローティングメニューバー、セッションマネージメント、国際化もサポートしています。
#ifndef EDIT_H #define EDIT_H #include <ktopwidget.h> class Edit : public KTopLevelWidget { Q_OBJECT public: Edit(); ~Edit(); public slots: void commandCallback(int id_); private: // 子ウィジェット KMenuBar *menu; }; #endif // EDIT_H |
Editは全てのKDEアプリケーションの基本クラスであるKTopLevelWidget (KTLW)の派生クラスです。KTLWはアプリケーションのメニューバー、ステータスバー、ツールバー、セッションマネージメントを管理します。次にクラス宣言にあるのは`Q_OBJECT'マクロです。これは重要なQtの特徴である"シグナル"と"スロット"のサポートのために必要です。Editクラスのただ一つのスロットがcommandCallback()メソッドで、`public slots:'のようにパブリックアクセスのスロットとして宣言されています。スロットは本質的にタイプセーフなコールバックメソッドです。オブジェクトによって、「Editクラスは connect()メソッドで使われると表現されているもの」として呼ばれます。残りのクラス宣言は他のC++クラスと同様です。
#include <kapp.h> #include <kmenubar.h> #include "edit.h" const int ID_EXIT= 111; Edit::Edit() { QPopupMenu *file = new QPopupMenu; file->insertItem(klocale->translate("Exit"), ID_EXIT); menu = new KMenuBar( this ); CHECK_PTR( menu ); menu->insertItem( klocale->translate("File"), file ); menu->show(); setMenu(menu); // 互いを接続します connect (file, SIGNAL (activated (int)), SLOT (commandCallback (int))); } Edit::~Edit() { } void Edit::commandCallback(int id_) { switch(id_) { case ID_EXIT: exit(0); break; } } #include "edit.moc" |
私たちは今、コンストラクタを最初に見ることによりEditクラスのインプリメントを学びます。コンストラクタはQPopupMenuオブジェクトを生成します、これはFileメニューです。私たちはinsertItem() メソッドを使って`Exit'アイテムを追加します。最初のパラメターはメニューで表示される文字列で、2番目は整数のidです(あなたはクラスドキュメンテーションを見ることで、クラスの全てのメソッドを見つけることができます)。翻訳のためのアプレットの準備としてただの'Exit'文字列のラベルをセットする代わりにklocale->translate() を呼びます。ユーザーが見るであろうどんな文字列もこうされるべきです。私たちは私たち独自のKLocaleオブジェクトを作る必要はありません、というのはKApplication(全てのKDEアプリケーションオブジェクトの基本クラス)が作ってくれるからです。私たちはkapp.hによって提供されるklocaleマクロを使ってKLocateオブジェクトにアクセスします。
次に私たちがやることは私たちのメニューを含むメニューバーを作ることです、私たちはKMenuBarウィジェットを使ってこれをやります。CHECK_PTRマクロ(ウィジェットがうまく作られたかを確かめる)はQtが供給するパラメターが0かどうかをチェックするマクロです(もしパラメターが0ならアプリケーションをエラーともに終了させます)。私たちは私たちがメニューエントリを作ったのとほぼ同じ方法で'File'アイテムを作ります(この一致は驚くに足りません、なぜならQPopupMenuとKMenuBarは同じ基本クラスQMenuDataを継承しているからです)。再び私たちはKLocaleを呼ぶ必要があります、というのは'File'という文字列はユーザーに見えるからです。私たちはshow() メソッドを使ってメニューを表示し、KTLWにsetMenu()でメニューを管理することを知らせます。
私たちのアプレットが動くようにするための最後のステージは、メニューバーエントリをアプリケーションが終了するためのコードと接続することです、これはconnect()
呼び出しによって行なわれます。Qtアプリケーション(つまりKDEアプリケーション)はウィジェットを繋げるためにシグナルとスロットを使います。ウィジェット(もしくは他のあらゆるQObject)は状態が変わった時はいつでもシグナルを発行します。私たちのアプレットではQPopupMenuがユーザーがメニューアイテムを選択した時に発行する
activated()
シグナルを使用します。このシグナルはEdit クラスで宣言された
commandCallback()
スロットに接続しなければなりません。 commandCallback() を見て分かるように、スロットは通常のメソッドと違いはそうありません。唯一の違いは宣言で、このメソッドはヘッダーファイルにあるスロットです。commandCallback()
は渡されたパラメターが定数 ID_EXIT--私たちが'Exit'メニューアイテムを識別するのと同じ定数--だったらプログラムを終了します。シグナルとスロットはパラメターを持て、それらは他のメソッドのパラメターと同様に扱えます。Qtはシグナルのtype
signatureがスロットのそれとマッチするか(そしてマッチしなければエラーを返します)をチェックします。さて、Editコンストラクタのconnect()
メソッド呼び出しのところを見てみましょう。
connect (file, SIGNAL (activated (int)), SLOT (commandCallback (int))); |
最初のパラメターはシグナルを排除するオブジェクトで、私たちのケースでは QPopupMenu file です。次のパラメターは接続されるべきシグナルです。あなたはシグナルの名前とそのパラメターの型をSIGNAL()マクロを使って特定しなくてはなりません(というのはパラメターはクラスヘッダーファイルかクラスドキュメントに現れるからです)。connect() が取る最後のパラメターはSLOT() マクロを使って特定されるスロットで、前と同様そのパラメターも特定します。Qtは実行時に、もし接続が無効だと知らせます。QObjectはたくさんのオーバーロードバージョンのconnect() メソッドを提供します、詳しくはQt クラスドキュメントを御覧下さい。ひとつのシグナルをひとつ以上のスロットに接続することや、ひとつ以上のシグナルをひとつのスロットに接続することは全く問題はありません。
この簡単なアプレットで注意しておかなければならない最後の特徴はクラスインプリメントの最後の方でインクルードしたedit.mocファイルです。`moc'ファイルはmocプリプロセッサーの出力で、QObjectを継承するどんなクラスにも適用されなければいけません。あなたは通常直接mocを呼び出すよりmakefile経由で呼び出すでしょう。そしてその出力はあなたのアプリケーションにコンパイル、リンクされることを確かめておいて下さい。これを確かめるもっとも簡単な方法は単純にmoc出力をここで使ったクラスインプリメントのアプローチのようにインクルードすることです。あなたがクラス宣言の部分を変更した時にmocが再実行されることを確かめておくことは重要です、というのはそうでないなら変なエラーが出てしまうからです。
あなたはこんな全く使えないアプレットを作るには多過ぎる作業だと考えたでしょう、しかし私たちは長い道のりを来ました。私たちはセッションマネージメント、メニュー作成、国際化、Qtの基礎シグナルとスロットを学びました。次のステージでは私たちのアプレットを実際に何かをさせるようにします!
機能を追加する
今私たちは基礎を知っているのでもうちょっと速く進めるでしょう。私はさっきのセクションでやったように詳しくコードを追うようなことはしません。私たちの目的はアプレットに何かをさせるようにすることで、ここでは文章を編集したいと思います。あなたはこのチュートリアルのソースファイルのexample2でこのバージョンの Edit の完全なコードが見れます。
Qtはテキストを編集するウィジェット、QMultiLineEditを提供します。これは私たちのエディタアプレットの核をなします。私たちはEdit
クラス宣言の中にあるウィジェットのメンバ変数をこのように宣言します:
QMultiLineEdit *view; |
view= new QMultiLineEdit(this, "Main View"); setView(view); |
私たちは既にどうやってメニューアイテムを追加するかを知っています、よってloadとsaveのために新しいものを追加するのは簡単です。そのコードは'Exit'
アイテムとほぼ同じです(今私たちには ID_SAVEのような定数が新しいコマンドのために必要なことに注意して下さい)。
file->insertItem(klocale->translate("Open..."), ID_OPEN ); file->insertItem(klocale->translate("Save"), ID_SAVE); file->insertItem(klocale->translate("Save As..."), ID_SAVEAS); |
今私たちはメニューアイテムを作成しました。残りの仕事はそれらが何かをするようにすることです。必要なコードは非常に直接的です。最初に`Open'
コマンドを見てみましょう。私たちがしなければならないことはommandCallback()
スロットのswitchのところに新しいcaseを作ることです。
case ID_OPEN: name= QFileDialog::getOpenFileName(); if (!name.isEmpty()) { load(name); } break; |
loadメソッドは選択したファイルの中身を読み込むためにQFileクラスを使います。QFileクラスはQtによって提供され、プラットフォーム非依存のファイルアクセスの方法を提供します。ここでは一回にファイルの中身を1行読むことのできるQTestStreamと組み合わせて使われています。それぞれの行は読まれるたびにテキストウィジェットに付加します。現在のファイル名のトラック名を残しておくことは重要なので(それによって'Save'がインプリメントできる)、即席の
filename_
変数をEditクラスに加えます。この値はloadメソッドによってセットされます。
void Edit::load(const char *filename) { QFile f( filename ); if ( !f.open( IO_ReadOnly ) ) return; view->setAutoUpdate( FALSE ); view->clear(); QTextStream t(&f); while ( !t.eof() ) { QString s = t.readLine(); view->append( s ); } f.close(); view->setAutoUpdate( TRUE ); view->repaint(); filename_= filename; } |
'Save As'コマンドはほとんど'Open'コマンドと同じです。私たちは getSaveFileName()を使います、これはgetOpenFileName()と違って選んだファイル名が存在するファイル名であることを要求しません。saveコマンドは簡単です。もし私たちがファイル名を知っているのなら文章を保存し、そうでないならsaveAs()
コマンドが呼び出します。
case ID_SAVE: if (!filename_.isEmpty()) saveAs(filename_); else { name= QFileDialog::getSaveFileName(); if (!name.isEmpty()) saveAs(name); } break; case ID_SAVEAS: name= QFileDialog::getSaveFileName(); if (!name.isEmpty()) saveAs(name); break; |
void Edit::saveAs(const char *filename) { QFile f( filename ); if ( !f.open( IO_WriteOnly ) ) return; QTextStream t(&f); t << view->text(); f.close(); setHint(filename); filename_= filename; } |
case ID_ABOUT: QMessageBox::about(this, "About Edit", "This is a simple text editor example program."); break; |
ユーザーインターフェースの改良
わたしたちのアプレットは今では有用な仕事をこなせますが、ユーザーインターフェースが貧弱です。たくさんの落度があります、例えば変更を保存しないで終了しようとした時の警告、現在開いているファイルの名前の表示、キーボードショートカット、などです。このような欠点を改良しようと思います。最初に取り組むことはアプレットにステータスバーを追加することです。わたしたちはそれを現在開いているファイルの名前を表示させるのに使います。KDEはステータスバーウィジェットKStatusBarを提供し、KTLWはその管理ができるのでわたしたちがしなければならないことはそれほど残されていません。KStatusBarはメニューウィジェットととても似たAPIを持っているので何が行なわれているかあなたは分かるでしょう。ステータスバーを表示したあとKTLWにそれを管理させるために報告して下さい。私たちはID_HINTTEXT定数を使ってステータスバーに表示させる文字列を変えることができます。これは
setHint()スロットにインプリメントします。
void Edit::initStatusBar() { statusbar= new KStatusBar(this); statusbar->insertItem("Welcome to Edit", ID_HINTTEXT); statusbar->show(); setStatusBar(statusbar); } void Edit::setHint(const char *text) { statusbar->changeItem(text, ID_HINTTEXT); } |
私たちは例えば読み込みや保存のようにファイル名を変えるようなメソッドをユーザーが呼び出した時、ステータスバーに文字列をセットします。
今度はツールバーを追加します。これはちょっと複雑です、というのは私たちがボタンアイコンの割り当てやツールチップのセットをする必要があるからです。私たちはツールバーアイコンの読み込み(とキャッシング)を扱うためにKIconLoaderクラスを利用します。
このクラスはKDE File System Standardによって指定されるディレクトリでアイコン(私たちはただ普通の、用途に合うアイコンを使います)を検索します。
私たちはKApplicationによって作られるアイコンローダーにアクセスするためkapp.h
によって提供されるマクロを使います。
void Edit::initToolBar() { KIconLoader *loader = kapp->getIconLoader(); toolbar = new KToolBar(this); toolbar->insertButton(loader->loadIcon("filenew.xpm"), ID_NEW, TRUE, klocale->translate("Create a new file")); toolbar->insertButton(loader->loadIcon("fileopen.xpm"), ID_OPEN, FALSE, klocale->translate("Open a file")); toolbar->insertButton(loader->loadIcon("filefloppy.xpm"), ID_SAVE, FALSE, klocale->translate("Save the current file")); addToolBar(toolbar); toolbar->setBarPos(KToolBar::Top); toolbar->show(); connect(toolbar, SIGNAL(clicked(int)), SLOT(commandCallback(int))); } |
私たちはツールバー"clicked"シグナルをメニューで使ったのと同じスロットに接続します。これは全く安全です。というのはコマンドそれぞれに同じidナンバーを使っているので、わたしたちは多くのシグナルを欲しいだけスロットに自由に接続することができます。
ユーザーインターフェースのデザイン時にユーザーが働きようがない操作を呼び出すことを防止するのは一般的に良い練習です。私たちはこれをQtでアプリケーションの状態に依存して、ウィジェットを有効・無効にすることで行ないます。例えばもしあなたが文章を変更していないのに、元のファイルに保存することはナンセンスです(ただし違うファイルに保存することは有用です)。全てのQt,KDEのウィジェットはウィジェットを`grey out'し無効にする setEnabled() メソッドを持っています。
KMenuBarとKToolbarは両方ともコマンドidによって特定されるエントリーの状態をセットするメソッドを提供することによってそれらのエントリーを有効・無効にするプロセスを簡単に作れます。わたしたちはこれら両方enableCommand()のインプリメントの中で呼び出します。
void Edit::enableCommand(int id) //スロット { toolbar->setItemEnabled(id, true); menu->setItemEnabled(id, true); } void Edit::disableCommand(int id) // スロット { toolbar->setItemEnabled(id, false); menu->setItemEnabled(id, false); } |
void Edit::textChanged() { modified= true; enableCommand(ID_SAVE); enableCommand(ID_SAVEAS); } |
あなたがこのセクションのエディタに最後に加えることは、エディタに保存されていない変更がある時にユーザーが終了しようとした時に表示される確認ダイアログです。
ダイアログはexit()メソッドを使ってインプリメントされます。このメソッドは単に変更を指すためにわたしたちが作ったフラグをテストし、警告ダイアログを表示するためにもうひとつのQMessageBoxスタティックメソッドを使います。
int Edit::exit() { int die= 0; if (!modified) die= 1; else if (QMessageBox::warning(this, klocale->translate("Unsaved Changes"), "You have unsaved changes, you will loose " "them if you exit now.", "Exit", "Cancel", 0, 1, 1)) die= 0; else die= 1; return die; } |
間もなくできます...
- kdehelpサポートの追加
- QToolTipGroup
- KConfigを使う
- ドラッグ&ドロップ
- ネットワークファイルアクセスの追加
This document is Copyright Richard J. Moore 1997-1998 (rich@kde.org)
You are free to distribute the examples given here in any form what so ever, and under any license terms. You are however NOT free to do the same with the text of the tutorial itself. You are permitted to distribute the text of the tutorial in electronic formats as long as it is distributed in its entirity. You may not charge for this document, though you may recoup packaging costs. You may make paper copies for personal use only. If you want to distribute this under other terms then drop me a mail.
翻訳者追記
オリジナルと比べて誤訳、おかしい記述があったら菰田泰生: tklab@ma6.seikyou.ne.jpまで連絡して下さい。なおライセンスについては元の文書に従います。