2008年10月13日 星期一

假日的小探險~高美濕地~

週日下午,該辦的事都差不多了
對於自己又沒有早起去騎個山路感到後悔
決定延續上週騎到火力發電廠的平地探險之旅
但火力發電廠邊實在太無趣啦
隨手翻開地圖再瞄了一下
瞬間就想起同在台中港區附近的高美濕地

拚到高美濕地再回來
跟想像中的差不多,除了東北季風的強勁讓我覺得有些吃力外
也只比火力發電廠稍稍遠一點,來回大約1個小時半左右
人很多,尤其是情侶,所以風景很不錯(什麼嘛.....)
還有看到有人在拍婚紗照,那個長長的禮服就這樣整個垂到地上
沾到泥土,碰到海水,我想洗的人會很嘔吧(真沒浪漫情懷....)

提到高美濕地,那個巨大的風力發電機是著名的景色
那對新人在拍婚紗照的同時,也是以此為背景主題
可是無可救藥的我,怎樣都揮之不去的印象
是村雨良與城茂的戰鬥
假面騎士Spirits第二部最後一話,ZX vs 強人
果然無可救藥啦

2008年10月8日 星期三

Ogre中文輸入 Final

最新的精簡修改版本→Ogre中文輸入Final修改
想看長篇心得的可以繼續看

嗯…還是從頭講起好了
Ogre是個免費而又強大的OpenSource 3次元圖像引擎
但對於想開發中文(或所有其它多字元)的程式來說
輸入法的無法使用是個大問題(現在顯示已經沒什麼大問題)

原先,所有Ogre的Windows訊息全部被封裝在OgreWindowEventUtilities裡(1.4.X版)
所以實行中文輸入所必須取得的IME訊息均無法正常取得
目前由免費打工仔在簡體網站所製作修改支援中文輸入的部分主要是從這裡切入
不過必須修改原始碼而動到LGPL授權是一個問題
這邊作者也透露了另一個不修改原始碼的可能,直接自行建立自己的視窗,然後手動建立Ogre的Render視窗為子視窗,就這樣取得母視窗的主控制權,進而建立自己想要的訊息處理。

OK,講起來很容易,問題在於要怎麼寫?
Ogre的中國網站只先簡單地點出這段程式。
//假设之前已经执行完创建窗口以及Ogre::Root对象的过程
//hWnd为窗口句柄,root为Ogre::Root类型实例
Ogre::NameValuePairList params;//构造参数
std::stringstream ss;
ss<<hwnd; //窗口句柄
params["externalWindowHandle"] = ss.str();//把窗口句柄做为字符串形式设置到参数中
root->initialise(false);//Ogre::Root对象初始化参数为false,表示手动创建渲染窗口
//下面创建渲染窗口
Ogre::RenderWindow * window = _root->createRenderWindow("name", //名称
        width,//宽度
        height, //高度
        false, //是否全屏显示
        &params);
後續交給讀者...

喔,這可是個說簡單不簡單,說難也不會很難的問題。
如果已經直接讀完ExampleApplication與ExampleFrameListener範例程式碼的人。
手動建立一個Ogre視窗不會很難,你只要照著那個步驟,一個一個建立就行。
可是完全沒用到Windows的基本型式,這也無法先手動產生一個母視窗。
更何況訊息處理咧?還有IME的處理啊!
對於初學者來說,全部都很陌生領域。

所以這邊就大概講一下怎麼繼承ExampleApplication與ExampleFrameListener來做中文輸入。
以改造核心處理的ExampleFrameListener開始。首先建立一個檔案ExampleCFrameListener.h,
並用檔名做繼承的類別名稱。然後除了繼承的,我們需要再加入Singleton與IME用的表頭檔。
#include "ExampleFrameListener.h"
//為了使用Singleton
#include "OgreSingleton.h"
//使用IME函式庫
#include "imm.h"  

建構式建立的地方請直接參考後面的程式碼。
接著就是我們必須設定兩個空的函數去取得轉換完成與還在轉換中的字串
///這邊是取得轉換完成的字串,直接傳給GUI System的injectChar
virtual void GetIMECompResultString(const Ogre::UTFString& tempString) { } ///這邊是取得轉換中的字串,主要是給像新注音需要另外產生視窗來顯示暫時性文字的 virtual void GetIMECompString(const Ogre::UTFString& tempString) { }

然後在這邊加入視窗訊息處理函式。
static LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

這邊有點麻煩的是此函式必須為static設定,所以在裡面的處理需要取得指標來運算。
我們為了把GetIMECompResultString與GetIMECompString往這裡丟,所以才設定此物件有Singleton功能以方便馬上利用(註1)。如果還有其它的方式,麻煩請告知。

既然都到這裡了,那當然,就是把上面那兩個函數放在WM_IME_COMPOSITON的訊息裡囉。
不過當然還是要加一些IME本身的字串取得與處理。
//自己處理的IME轉換
case WM_IME_COMPOSITION:

HIMC hImc = ImmGetContext(hwnd);
//正式取得轉換的字串
if (lParam & GCS_RESULTSTR)
{
//ImmGetCompositionStringA( hImc, GCS_RESULTSTR, tCharPtr, dwBufLen );
DWORD dwBufLen;
wchar_t* tCharPtr;
dwBufLen = ImmGetCompositionStringW( hImc, GCS_RESULTSTR, 0, 0 );
if(dwBufLen > 0)
{
tCharPtr = new wchar_t[dwBufLen];
memset( tCharPtr, 0, sizeof(wchar_t) * dwBufLen );
ImmGetCompositionStringW( hImc, GCS_RESULTSTR, tCharPtr, dwBufLen );
tempString = tCharPtr;
ExampleCFrameListener::getSingletonPtr()->GetIMECompResultString(tempString);
}
}// if(lParam...
//取得轉換中的字串
else if (lParam & GCS_COMPSTR)
{
//ImmGetCompositionStringA( hImc, GCS_RESULTSTR, tCharPtr, dwBufLen );
DWORD dwBufLen;
wchar_t* tCharPtr;
dwBufLen = ImmGetCompositionStringW( hImc, GCS_COMPSTR, 0, 0 );
if(dwBufLen > 0)
{
tCharPtr = new wchar_t[dwBufLen];
memset( tCharPtr, 0, sizeof(wchar_t) * dwBufLen );
ImmGetCompositionStringW( hImc, GCS_COMPSTR, tCharPtr, dwBufLen);
tempString = tCharPtr;
ExampleCFrameListener::getSingletonPtr()->GetIMECompString(tempString);
}
else
ExampleCFrameListener::getSingletonPtr()->GetIMECompString("");

}// if(lParam...
ImmReleaseContext(hwnd, hImc);
break;

之所以要取得轉換中的字串,其實是為了新注音。
因為那個還沒轉好的字(正式輸出)是不可能就主動顯示在要輸入的地方,而舊注音有子視窗飄著沒問題。當然按上下鍵選字時,新注音的視窗也會跳出,不過還是看不到一整串的字。
所以才加了GetIMECompString來加以處理這部分。
先不管Singleton的額外設定,輸入中文最主要的部分在此。

再來是ExampleApplication的改造。一樣建個ExampleCApplication.h的檔案,然後引入舊的ExampleApplication.h與剛剛的ExampleCFrameListener.h。
單純繼承的部分就不說了,然後重點首先在於變數。
請加入
protected:
HINSTANCE mHInstance; // HInstance of application, for ime
HWND hwnd;//HWND of appliction for the new windows


然後再建立一個configure的函式修改如下:
if(mRoot->showConfigDialog())
{
// If returned true, user clicked OK so initialise
// Here we choose to let the system create a default rendering window by passing 'true'
/// But the chinese need a manual window. Set it false for manual.
mWindow = mRoot->initialise(false);

/// then create a new window for IME
setupWindow();
return true;
}


接著就是手動建立視窗的重頭戲了。
理所當然我們建立一個setupWindow()的新函式在後面。
接著取得目前的 hinstance
mHInstance = GetModuleHandle( NULL );

註冊一個WNDCLASS如下:
WNDCLASS wincl = { 0, ExampleCFrameListener::WindowProcedure, 0, 0, mHInstance,
LoadIcon(0, IDI_APPLICATION), LoadCursor(NULL, IDC_ARROW),
(HBRUSH)GetStockObject(BLACK_BRUSH), 0, "OgreChineseWnd" };

RegisterClass (&wincl);

請注意那個ExampleCFrameListener::WindowProcedure就是加在這裡。

接著我們建立並指定視窗的大小位置與視窗類別等等
取得其HWND
hwnd = CreateWindow("OgreChineseWnd", "", WS_OVERLAPPEDWINDOW,
left, top, width, height, HWND_DESKTOP, 0, mHInstance, 0);

這時我希望有個不同過去的中文化標題
SetWindowTextW( hwnd, L"Ogre中文輸入測試視窗");

再來就是照著最先的部分,讓Ogre設定一個同樣大小的視窗,為子視窗
Ogre::NameValuePairList params;
params["externalWindowHandle"] = StringConverter::toString((int)hwnd);
mWindow = mRoot->createRenderWindow("Ogre Render Window", width, height, false, &params);


最後不要忘了,讓母視窗顯示,才不會什麼都看不到
ShowWindow (hwnd, SW_SHOWNORMAL);


這樣大致上的設定就差不多了。
接下來的部分,首先要繼承上面的新類別(廢話 XD)
然後編譯的參數要加入imm32,這樣IME才有作用。
當然也不能忘了,要讓GUI System或Ogre能顯示中文字才有辦法看得到輸入
這部分就不加贅述了
要補充的是輸入的部分。
以CEGUI為例:
在CEGUI::System::getSingleton().injectChar( arg.text );前請加入這一行
if ( !ImmIsIME(GetKeyboardLayout(0)) )
讓IME啟動時,無法輸入英數,才不會在轉換前多了一堆自己Keyin的亂碼。
加入GetIMECompResultString的函式如下:
void GetIMECompResultString(const Ogre::UTFString& tempString)

Ogre::UTFString temp = tempString;
for (int i = 0; i < temp.size();i++)
{
CEGUI::System::getSingleton().injectChar( temp[i] ); }

injectChar只能處理單一字元,所以才會用迴圈一個一個輸入整個字串。
另一個GUI System "QuickGUI"的部分大同小異,要多記得把Ogre的中文顯示設定好(它沒辦法像CEGUI本身自己支援Unicode轉碼,要靠Ogre自已顯示),並使用setSupportedCodePoints
把要中文的字碼先往裡面塞,不然就算有輸入,它也會因為找不到對應的,而自動取消,會白忙一場。

最後,修改的完整範例在這裡:
ExampleCFrameListener.h
ExampleCApplication.h

一些地方略有差異,最主要就是第二個檔案的setupWindow的函式有參考RenderSystem的原始碼
可以自動生成置中且與一開始設定相同大小的視窗。
我想在Win32的環境下,使用中文輸入應該就這樣子吧。
當然還有一些IME的功能沒有很完整地利用,不過我想這個任務就交給其它有更深需求的人來做吧。我想寫的東西只要能輸入名字就很足夠了。

感謝在我研究IME過程給我幫助的網友,不然現在可能還是尾巴會帶出一堆亂碼來的版本。
另外測試的電腦有灌過Unicode補完計畫,我不清楚是否對IME有所影響。因為我所寫的是專門給寬字元(Wide Char)用的GetIme的函式,有可能會在不支援unicode的輸入法出差錯。
還有我的編譯器是mingw的gcc,VC還沒試著跑過,不過我想問題應該不大。

這東西其實搞起來難度不高,而且看來在1.4.X版出來後,就有辦法實做出來。
到底是搞中文的人少呢?還是即然已經有修改程式碼的辦法,就無所謂呢?
總之,斷斷續續,忙裡偷閒地搞了至少3年Ogre。
能實現一種中文輸入成功真的很高興。在此分享給大家。

註1:其實我還是個半生不熟的學習者,只知道可以拿來用,還不知道為什麼可以用,以及是否有其它延伸的問題。還忘多多海涵。

2008年10月7日 星期二

Ogre的中文輸入 Part2

這次花了點時間修改了ExampleApplication與ExampleFrameLisntener的範例用檔案,讓可以使用IME的視窗自動產生。然後把WindowProcedure直接放入ExampleFrameLisntener裡,本來是想讓這個訊息處理直接使用ExampleFrameLisnetner裡面的函式,可是失敗了,因為它必須為Static宣告。
所以只好再用Singleton的方式解決,然後把函式宣告為virtual,內容為空,讓範例可以直接修改使用。
另外新注音無法顯示尚未結束轉換的字,目前還是只能用另一個StaticText(CEGUI)或是TextBox(QuickGUI)來幫助顯示,比較起來日文的IME還是比較好啊,可以浮空顯示。
最後的最後,參考了MSDN上的IME範例,還是沒辦法解決那個IME轉換後,會有隨機不確定的亂碼產生,反正產生出來殺了就OK,就先放著不管好了。

2008年10月5日 星期日

九月的噴射氣流攻擊(ジェットストリームアタック)

九月份真不是個騎自行車的好日子啊!
一連來了三個颱風不說,還大部分都往假日撞啊。
週六都已經因工,因為工作而無法出門
還來這套噴射氣流攻擊實在讓我整個人活力降到了幾近谷底。

這時候要是有鋼彈就好
「是呀,爸爸做的鋼彈是無敵的(湯尼調)」
只要我的自行車像鋼彈一樣
連颱風天都能出門啊,哈哈哈哈哈
....妄想結束
不過現實終究是現實,颱風當天先不管
颱風走後,山區均是一片殘破
大雪山那邊據說也是處處坍崩
13KM就斷了的樣子(一週前左右的消息)
今年初剛開放的雪見游憩區(司馬限林道)也是暫時封閉
看來要深秋或是入冬才有辦法再去朝聖

最近就先在自家附近的小丘崚地好好地練習吧