BREW プログラミング環境
BREW は、コオペラティブなシングルスレッド、かつイベントドリブンなプログラミング環境です。
BREW FAQ : コオペラティブとは? / BREW FAQ : プリエンプティブとは?
携帯 Java から BREW へ移植するときの考慮点
- BREW にはガーベッジコレクションがありません。
- BREW はシングルスレッドです。
- BREW には文字列クラスがありません。
- BREW では、配列のインデックス範囲外にも書き込めます。
- BREW には、GUI がありません。ソフトキーはアプリで管理します。
携帯 Java 版 LifeGame の BREW への移植
携帯 Java 版 LifeGame を、Java と C++ のクラスと一対一対応させて C++ 版 BREW アプリにします。
BREW では、どこからでもアクセス可能な変数はアプレット構造体に定義します。( リスト 1 )
▼リスト 1 アプレット構造体の中身 typedef struct _LifeGameApp { AEEApplet a ; //変更してはいけない AEEDeviceInfo DeviceInfo; //変更・追加してよい IDisplay *pIDisplay; IShell *pIShell; WindowManager *WindowManager; } LifeGameApp;
アプレット構造体は携帯 Java 版の LifeGameApplication クラスに対応します。アプレット構造体にはメンバ関数を持たせず、WindowManager がアプリの全体調整や Window を管理するようにします。そのため、WindowManager へアクセスポインタをアプレット構造体に置きます。
BREW には Java の Panel や Canvas などの UI コンポーネントがありません。Window クラスを定義し、携帯 Java 版 LifeGame にあった XXXPanel や XXXCanvas は Window から継承させます。( リスト 2 )
XXXPanel や XXXCanvas では、paint() と keyPress ( uint16 key ) を実装します。paint() はウィンドウの描画処理、keyPress ( uint16 key ) はキーが押された時の処理です。
▼リスト2 Panel やCanvas の代わりとなる Window クラス class Window { protected: WindowManager* wm; public: Window(WindowManager* wm_) { wm = wm_; } virtual ~Window() {} virtual void paint() = 0; virtual void keyPress(uint16 key) = 0; };
キーの押下などのイベントが発生すると、イベントハンドラ LifeGameApp_HandleEvent が呼び出されます。( リスト 3 )
▼リスト 3 イベントハンドラ static boolean LifeGameApp_HandleEvent(LifeGameApp* app, AEEEvent eCode, uint16 wParam, uint32 dwParam) { if (app->WindowManager != NULL) { if (app->WindowManager->textCtl1 != NULL) { if (ITEXTCTL_HandleEvent(app->WindowManager->textCtl1, eCode, wParam, dwParam)) { return(TRUE); } } if (app->WindowManager->textCtl2 != NULL) { if (ITEXTCTL_HandleEvent(app->WindowManager->textCtl2, eCode, wParam, dwParam)) { return(TRUE); } } } switch (eCode) { case EVT_APP_START: //アプリ開始時に実行 app->WindowManager = new WindowManager(app); app->WindowManager->setCurrent(new LifeGameMainPanel(app->WindowManager)); return(TRUE); case EVT_APP_STOP: delete app->WindowManager; return(TRUE); case EVT_APP_SUSPEND: // サスペンド処理は省略 return(TRUE); case EVT_APP_RESUME: // レジューム処理は省略 return(TRUE); case EVT_APP_MESSAGE: return(TRUE); case EVT_KEY: app->WindowManager->keyPress(wParam); return(TRUE); default: break; } return FALSE; }
携帯 Java 版の LifeGameApplication クラスの機能は WindowManager クラスが担当します。( リスト 4 )
setCurrent(new XXXWindow(this)); とすることで、ウィンドウ(Window クラスのこと ) を前面に配置し、以前のウィンドウを削除します。キーが押されたら最前面にあるウィンドウの keyPress(key) を呼び出します。 changeState(state) は携帯 Java 版の LifeGameApplication クラスのものとほぼ同じです。
▼リスト 4 Window を管理するクラス class WindowManager { public: IDisplay* display; IShell* shell; LifeGame* lifeGame; Window* frontWindow; ITextCtl* textCtl1; ITextCtl* textCtl2; public: SoftKeyWindow* softkeyWindow; public: WindowManager (LifeGameApp* app); ~WindowManager(); void changeState(STATE_ENUM state); void setCurrent(Window* window); void keyPress(uint16 key) { frontWindow->keyPress(key); } void update(IDisplay* disp); };
次に XXXPanelやXXXCanvas を移植します。Window を継承する、LifeGameMainCanvas に対応するクラスを作ります。( リスト 5 )
▼リスト 5 携帯 Java 版 LifeGameMainCanvas に対応するクラス class LifeGameMainCanvas : public Window { public: LifeGameMainCanvas(WindowManager* wm_); virtual ~LifeGameMainCanvas(); virtual void paint(); virtual void keyPress(uint16 key); static void OnTimerEntry(void* data); void next(); }; #define TIMER_INTERVAL 300 LifeGameMainCanvas::LifeGameMainCanvas(WindowManager* wm_) : Window(wm_) { //タイマーのセット ISHELL_SetTimer(wm->shell, TIMER_INTERVAL, LifeGameMainCanvas::OnTimerEntry, this); wm->softkeyWindow->setText("戻る", "", "終了"); paint(); } LifeGameMainCanvas::~LifeGameMainCanvas() { //タイマーのキャンセル ISHELL_CancelTimer(wm->shell, LifeGameMainCanvas::OnTimerEntry, this); } void LifeGameMainCanvas::paint() { IGraphics* graphic; // IGraphics オブジェクトの取得 ISHELL_CreateInstance(wm->shell, AEECLSID_GRAPHICS, (void**)&graphic); IGRAPHICS_SetBackground(graphic, 255, 255, 255); IGRAPHICS_ClearViewport(graphic); wm->lifeGame->paint(wm->display); wm->update(wm->display); } void LifeGameMainCanvas::keyPress(uint16 key) { switch(key) { case AVK_SOFT1: wm->changeState(STATE_TITLE); break; case AVK_SOFT2: ISHELL_CloseApplet(wm->shell, FALSE); break; } return; } void LifeGameMainCanvas::OnTimerEntry(void* data) { ((LifeGameMainCanvas*)data)->next(); } void LifeGameMainCanvas::next(){ wm->lifeGame->next(); //ライフゲームを1世代進める paint(); //タイマーの再セット ISHELL_SetTimer(wm->shell, TIMER_INTERVAL, LifeGameMainCanvas::OnTimerEntry, this); }
LifeGameMainCanvas が前面に出ている時にキーを押すと、LifeGameMainCanvas の keyPress が呼び出されます。ソフトキーは keyPress で処理されます。
ライフゲームでは一定時間ごとに世代を進める必要があります。Java ではこの処理を別のスレッドで管理します。BREW はシングルスレッドなので、タイマー API ( ISHELL_SetTimer ) を使います。※
※ BREW では 1 つのイベントハンドラで 1 秒以上の処理を継続できないので、 1 秒以上の長時間処理は ISHELL_SetTimer を使って複数回に分けて処理します。
リスト 6 は 携帯 Java 版の LifeGameMainPanel に対応するクラスのコードです。
▼リスト 6 携帯 Java 版 LifeGameMainPanel への対応クラス class LifeGameMainPanel : public Window { private: int cursor; public: LifeGameMainPanel(WindowManager* wm_); virtual void paint(); virtual void keyPress(uint16 key); }; LifeGameMainPanel::LifeGameMainPanel(WindowManager* appli_) : Window(appli_) { cursor = 0; wm->softkeyWindow->setText("", "", "終了"); paint(); } void LifeGameMainPanel::paint() { IGraphics* graphic; // IGraphics オブジェクトの取得 ISHELL_CreateInstance(wm->shell, AEECLSID_GRAPHICS, (void**)&graphic); IGRAPHICS_SetBackground(graphic, 255, 255, 255); IGRAPHICS_ClearViewport(graphic); //画面を消去する //フォントの高さを取得 const int height = IDISPLAY_GetFontMetrics(wm->display, AEE_FONT_NORMAL, NULL, NULL); const int space = 6; DrawText(wm->display, "Life Game", 15, space); DrawText(wm->display, "Start", 15, height + space * 2); DrawText(wm->display, "Edit", 15, height * 2 + space * 3); DrawText(wm->display, "Save", 15, height * 3 + space * 4); DrawText(wm->display, "Load", 15, height * 4 + space * 5); DrawText(wm->display, "Change size", 15, height * 5 + space * 6); DrawRect(wm->display, 13, height + space * 2, 80, height, MAKE_RGB(0, 0, 0)); DrawRect(wm->display, 13, height * 2 + space * 3, 80, height, MAKE_RGB(0, 0, 0)); DrawRect(wm->display, 13, height * 3 + space * 4, 80, height, MAKE_RGB(0, 0, 0)); DrawRect(wm->display, 13, height * 4 + space * 5, 80, height, MAKE_RGB(0, 0, 0)); DrawRect(wm->display, 13, height * 5 + space * 6, 140, height, MAKE_RGB(0, 0, 0)); int dx = 80 + 4; if (cursor == 4) dx = 140+4; DrawRect(wm->display, 13-2, (height + space) * (cursor + 1) + space -2, dx, height+4, MAKE_RGB(255, 0, 0)); wm->update(wm->display); } void LifeGameMainPanel::keyPress(uint16 key) { switch(key) { case AVK_DOWN: if (cursor < 4) { cursor++; paint(); } break; case AVK_UP: if (cursor > 0) { cursor--; paint(); } break; case AVK_SELECT: switch (cursor) { case 0: wm->changeState(STATE_GAMESTART); break; case 1: wm->changeState(STATE_EDIT); break; case 2: wm->changeState(STATE_SAVE); break; case 3: wm->changeState(STATE_LOAD); break; case 4: wm->changeState(STATE_CHANGESIZE); break; } break; case AVK_SOFT2: ISHELL_CloseApplet(wm->shell, FALSE); break; } }
「ボタン」は四角形とテキストで描画しています。上下キーを押すと赤いカーソルが動きます。右下のテキスト(「終了」)はソフトキーです。BREW ではボタンなどのコントロールを描画しなければいけません。上下キーを押したときのカーソルの管理は cursor 変数を使います。
SoftKeyWindow は、ソフトキーの文字を表示するウィンドウです。( リスト 7 )
▼リスト7 ソフトキーの扱い class SoftKeyWindow : public Window { private: // 左下,中央下,右下に表示させる文字列 // BPPString は 「ヒープと文字列クラスの実装」で作成したクラス BPPString textLeft, textCenter, textRight; int width; //画面幅 public: SoftKeyWindow(WindowManager* wm_, int width_); //ソフトキー領域(画面下)を計算し,白で塗りつぶし,文字列を描く.ここでは省略 void paint(); //テキストをセット.ここでは省略 void setText(const BPPString& textLeft_, const BPPString& textCenter_, const BPPString& textRight_); void keyPress(uint16 key) {}; };
リスト 8 は SoftKeyWindow を管理する WindowManager の update 関数の実装です。 他のウィンドウの paint 関数の中でこの関数を呼び出せば、SoftKeyWindow も描画されます。
▼リスト8 WindowManager にあるupdate の実装 void WindowManager::update(IDisplay* disp) { //ソフトキーウィンドウの描画 softkeyWindow->paint(); //画面の描画を更新するBREW API IDISPLAY_Update(disp); }