From 2ca1d29417d670898874e0e0f67ce9336468f029 Mon Sep 17 00:00:00 2001 From: AIdrifter Date: Sat, 7 Jun 2014 20:19:07 +0800 Subject: [PATCH 1/4] Update ChewingTextService.cpp for output define string(ctrl1~ctrl9) Now we can save our string to dictionary.txt(C:/Users/usernmae/dictionary.txt) line 1~line 9 (line 0 can't be uesd)and output our string on uncomposition mode. We will read string from dictoinary.txt when pressing first button. Add changed buffer to dictoinary.txt on composition-mode and reload ctrl buffer after pressing button next time. However, we find another problem that '`' will be triggered when press ctrl+1 or ctrl+0 on composition mode. I don't know how to fix because this behavior has existed before previous version. --- ChewingTextService/ChewingTextService.cpp | 122 ++++++++++++++++++++-- 1 file changed, 115 insertions(+), 7 deletions(-) diff --git a/ChewingTextService/ChewingTextService.cpp b/ChewingTextService/ChewingTextService.cpp index 5f98486..2c5bd9a 100644 --- a/ChewingTextService/ChewingTextService.cpp +++ b/ChewingTextService/ChewingTextService.cpp @@ -26,6 +26,10 @@ #include "resource.h" #include #include +#include +#include +#include + using namespace std; @@ -181,7 +185,7 @@ bool TextService::filterKeyDown(Ime::KeyEvent& keyEvent) { // FIXME: we only need Ctrl in composition mode for adding user phrases. // However, if we turn on easy symbol input with Ctrl support later, // we'll need th Ctrl key then. - return false; + return true; } // we always need further processing in full shape mode since all English chars, @@ -220,6 +224,23 @@ bool TextService::filterKeyDown(Ime::KeyEvent& keyEvent) { return true; } +//Detect for nummber +//ctrl[0] buffer can't be uesd so we put ctrl+1 string to ctrl[1] +bool ctrlSwitch(UINT charCode,bool *ctrlFlag){ + if(charCode >= '1' && charCode <= '9'){ + ctrlFlag[charCode - '1'] = 1; + return true; + } + return false; +} + +// convert wstring to UTF-8 string +std::string wstring_to_utf8 (const std::wstring& str) +{ + std::wstring_convert> myconv; + return myconv.to_bytes(str); +} + // virtual bool TextService::onKeyDown(Ime::KeyEvent& keyEvent, Ime::EditSession* session) { assert(chewingContext_); @@ -236,6 +257,32 @@ bool TextService::onKeyDown(Ime::KeyEvent& keyEvent, Ime::EditSession* session) #endif UINT charCode = keyEvent.charCode(); + + //dirty add varaible + enum Mode {composition_mode, noncomposition_mode}; + static Mode mode = noncomposition_mode; + static bool ctrlFlag[11]={0}; + static wstring ctrl[11]; + static char line[30]; + static bool readFlag=1; + static int lineNum; + + //read form ctrl+num string from dictionary.txt + std::fstream fs; + if(readFlag){ + fs.open("dictionary.txt",ios::in | ios::app); + if(fs){ + for(int i=0; fs.getline(line, sizeof(line)); i++){ + ctrl[i] = utf8ToUtf16(line); + } + } + fs.flush(); + fs.clear(); + fs.close(); + readFlag=0; + } + + if(charCode && isprint(charCode)) { // printable characters (exclude extended keys?) int oldLangMode = ::chewing_get_ChiEngMode(chewingContext_); bool temporaryEnglishMode = false; @@ -252,8 +299,17 @@ bool TextService::onKeyDown(Ime::KeyEvent& keyEvent, Ime::EditSession* session) invertCase = true; // need to convert upper case to lower, and vice versa. } - if(langMode_ == SYMBOL_MODE) { // English mode - ::chewing_handle_Default(chewingContext_, charCode); + // 1.Detect Ctrl + number (0~9) + if(mode == noncomposition_mode&&keyEvent.isKeyDown(VK_CONTROL) && ctrlSwitch(charCode,ctrlFlag)){ + + } + else if(langMode_ == SYMBOL_MODE) { // English mode + // 2.Detect Ctrl + number (0~9) for English mode + if(keyEvent.isKeyDown(VK_CONTROL) && isdigit(charCode) && ctrlSwitch(charCode,ctrlFlag)){ + + } + else + ::chewing_handle_Default(chewingContext_, charCode); } else if(temporaryEnglishMode) { // temporary English mode ::chewing_set_ChiEngMode(chewingContext_, SYMBOL_MODE); // change to English mode temporarily @@ -266,13 +322,15 @@ bool TextService::onKeyDown(Ime::KeyEvent& keyEvent, Ime::EditSession* session) ::chewing_set_ChiEngMode(chewingContext_, oldLangMode); // restore previous mode } else { // Chinese mode + mode = composition_mode; if(isalpha(charCode)) // alphabets: A-Z ::chewing_handle_Default(chewingContext_, tolower(charCode)); else if(keyEvent.keyCode() == VK_SPACE) // space key ::chewing_handle_Space(chewingContext_); - else if(keyEvent.isKeyDown(VK_CONTROL) && isdigit(charCode)) // Ctrl + number (0~9) + else if(keyEvent.isKeyDown(VK_CONTROL) && isdigit(charCode)){ // Ctrl + number (0~9) + ctrlSwitch(charCode,ctrlFlag);//3.Add string to Ctrl+num of position ::chewing_handle_CtrlNum(chewingContext_, charCode); - else if(keyEvent.isKeyToggled(VK_NUMLOCK) && keyEvent.keyCode() >= VK_NUMPAD0 && keyEvent.keyCode() <= VK_DIVIDE) + }else if(keyEvent.isKeyToggled(VK_NUMLOCK) && keyEvent.keyCode() >= VK_NUMPAD0 && keyEvent.keyCode() <= VK_DIVIDE) // numlock is on, handle numpad keys ::chewing_handle_Numlock(chewingContext_, charCode); else { // other keys, no special handling is needed @@ -366,7 +424,7 @@ bool TextService::onKeyDown(Ime::KeyEvent& keyEvent, Ime::EditSession* session) } // has something to commit - if(::chewing_commit_Check(chewingContext_)) { + if(mode == composition_mode && ::chewing_commit_Check(chewingContext_)) { char* buf = ::chewing_commit_String(chewingContext_); std::wstring wbuf = utf8ToUtf16(buf); ::chewing_free(buf); @@ -400,24 +458,74 @@ bool TextService::onKeyDown(Ime::KeyEvent& keyEvent, Ime::EditSession* session) compositionBuf.insert(pos, wbuf); } + + //examine ctrl flag to printf + if(mode == noncomposition_mode){ + for(int i=0;i<9;i++){ + if( ctrlFlag[i]){ + //output string ctrl+num when non-composition mode + ctrlFlag[i]=0; + setCompositionString(session, ctrl[i+1].c_str(), ctrl[i+1].length()); + endComposition(session->context()); + } + } + } + // has something in composition buffer if(!compositionBuf.empty()) { if(!isComposing()) { // start the composition startComposition(session->context()); + } setCompositionString(session, compositionBuf.c_str(), compositionBuf.length()); + + + // add string to txt + if(mode == composition_mode){ + for(int i=0;i<9;i++){ + if(ctrlFlag[i]){ + + ctrl[i+1].clear(); + ctrl[i+1] = compositionBuf; + std::locale::global(std::locale("")); + std::ofstream wf; + //ctrl[0] can't be uesd so we write line 1 + char *ctrl1="define your string"; + wf.open("dictionary.txt",std::ios_base::binary ); + if(wf){ + std::wstring dd=L"\r\n"; + wf.write(ctrl1,sizeof(ctrl1)); + wf.write("\r\n",2); + for(int i=1; i<10 ;i++){ + ctrl[i]+=dd; + string ctrlTmp=wstring_to_utf8(ctrl[i]); + wf.write(ctrlTmp.c_str() , ctrlTmp.size() ); + } + } + wf.flush(); + wf.clear(); + wf.close(); + readFlag=1; + ctrlFlag[i]=0; + } + } + } + + + } else { // nothing left in composition buffer, terminate composition status if(isComposing()) { // clean composition before end it setCompositionString(session, compositionBuf.c_str(), compositionBuf.length()); - + // We also need to make sure that the candidate window is not currently shown. // When typing symbols with ` key, it's possible that the composition string empty, // while the candidate window is shown. We should not terminate the composition in this case. if(!showingCandidates()) endComposition(session->context()); } + mode = noncomposition_mode; } // update cursor pos From 5a4052147e4ba3c103b2c6aca20b5063ff9d128b Mon Sep 17 00:00:00 2001 From: AIdrifter Date: Fri, 18 Jul 2014 14:49:46 +0800 Subject: [PATCH 2/4] class defstring to handle buffer and file operation(ChewingTextService.cpp) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 這邊我想想說切乾淨一點 把需要自訂字串的部分獨立成一個class出來 主要功能解說如下 平常利用 examineCtrlFalg判斷ctrl+num到底有沒有被觸發 if 被觸發了 if 在非組字模式 就把txt內預存的字串丟出 (當然一開始就先把所有txt檔都讀到buf內了 else 非組字模式 就把buf換成我們新加入的字串 else 不做任何事 目前修正版本 1.已經不會有之前觸發到ctrl+` 的問題(why? 包成class 會有差? 2.有照RAII的模式 在function內把檔案的讀寫完成 並用mutex做控制 不過我現在的版本是在 object defString死掉以後 才把ctrl buf一次寫到txt內 感覺這樣會發生一個問題 因為其實不同process load的dll是不一樣的 1.開啟IE 在IE中加入字定字串ctrl+2 切大西瓜 2.開啟notepad notepad的ctrlbuf 是從txt檔內在讀一次 所以沒有被還是舊的 感覺這樣好像不太好 用以前更換buf 就寫進去txt內的方法好像比較不用擔心這個問題 是不是要再改回去? 3.用register可以解決這問題? 不知道會不會3~4個dll共用同一組register 但是calss的寫法 好像沒辦法在member中宣告register 4.現在 是一行一行讀 一行一行寫入 但是使用者在讀檔的時候 如果檔案內沒有10行字串 對應的位置是會錯誤的 這邊我暫時想不到解決方法 還有哪些方法讓使用者可以在txt檔為空的時候 只加入ctrl+ 5 "切大西瓜" 就印出 "切大西瓜" 而其他ctrl卻都指印岀空字串? 感覺不在一開始的txt 就先寫好10組字串 這些事都沒有辦法完成 (印像中PCMan 說這邊需要用nsi 包安裝檔 不知道我有沒有記錯 目前這版本就是這些問題 會朝class 的memory laout 與life cycle 這方面的地方找資料 因為這關聯到兩邊印出字串有沒有同步的問題 --- ChewingTextService/ChewingTextService.cpp | 226 +++++++++++++++------- 1 file changed, 154 insertions(+), 72 deletions(-) diff --git a/ChewingTextService/ChewingTextService.cpp b/ChewingTextService/ChewingTextService.cpp index 2c5bd9a..98f5ee8 100644 --- a/ChewingTextService/ChewingTextService.cpp +++ b/ChewingTextService/ChewingTextService.cpp @@ -29,7 +29,8 @@ #include #include #include - +#include +#include using namespace std; @@ -224,9 +225,138 @@ bool TextService::filterKeyDown(Ime::KeyEvent& keyEvent) { return true; } + + + +class defString{ +public: + defString(); + ~defString(); + //bool writeToFile(int,const std::wstring); + bool writeToFile(); + bool readFromFile(); + bool ctrlSwitch(UINT); + bool writeToBuf(int,const std::wstring ); + bool examineCtrlFlag(std::wstring &); + int examineCtrlFlag(); + string wstring_to_utf8(const std::wstring &); + +private: + bool ctrlFlag[10]; //limit 10 + std::wstring ctrlStr[10]; //limit 10 + bool read; +}; + +defString::defString(){ + for(int i=0;i<9;i++){ + ctrlFlag[i]=0; + } + read=1; +} + +defString::~defString(){ + writeToFile(); +} + +bool defString::writeToBuf(int num,const std::wstring wBuf){ + if(wBuf.empty()) + return false; + ctrlStr[num]=wBuf; + return true; +} + + +//call by reference +bool defString::examineCtrlFlag(std::wstring& ctrlBuf){ + for(int i=0;i<9;i++){ + if(ctrlFlag[i]==1){ + ctrlFlag[i]=0; + ctrlBuf=ctrlStr[i]; + } + } + return -1; +} + +//call by reference +int defString::examineCtrlFlag(){ + for(int i=0;i<9;i++){ + if(ctrlFlag[i]==1){ + ctrlFlag[i]=0; + return i; + } + } + return -1; +} + +// convert wstring to UTF-8 string +string defString::wstring_to_utf8 (const std::wstring& str) +{ + std::wstring_convert> myconv; + return myconv.to_bytes(str); +} + + +// write to file using RALL(Resource acquisition is initialization) +//bool defString::writeToFile(int num,const std::wstring buf){ +bool defString::writeToFile(){ + //mutex to protect file access + static std::mutex mutex; + //lock mutex before accessing file + std::lock_guard lock(mutex); + + //reflush ctrlStr + //ctrlStr[num].clear(); + //ctrlStr[num]=buf; + + //try to open File + std::locale::global(std::locale("")); + std::ofstream wf("userString.txt"); + if (!wf.is_open()){ + throw std::runtime_error("unable to open file"); + return false; + } + + // write message to file + std::wstring eof=L"\r\n"; + + for(int i=1; i<10 ;i++){ + ctrlStr[i]+=eof; + string ctrlTmp=wstring_to_utf8(ctrlStr[i]); + wf.write(ctrlTmp.c_str() , ctrlTmp.size()); + } + read=1; + return true; +} + + +// write to file using RALL(Resource acquisition is initialization) +bool defString::readFromFile(){ + //mutex to protect file access + if(read==0) + return false; + + static std::mutex mutex; + //lock mutex before accessing file + std::lock_guard lock(mutex); + + //try to open File + //std::ofstream file("example.txt"); + std::fstream fs("userString.txt"); + if (!fs.is_open()) + throw std::runtime_error("unable to open file"); + + // read message from file + char buf[30]; + for(int i=0; fs.getline(buf, sizeof(buf))&&i<10; i++){ + ctrlStr[i]=utf8ToUtf16(buf); + } + read=0; + return true; +} + //Detect for nummber //ctrl[0] buffer can't be uesd so we put ctrl+1 string to ctrl[1] -bool ctrlSwitch(UINT charCode,bool *ctrlFlag){ +bool defString::ctrlSwitch(UINT charCode){ if(charCode >= '1' && charCode <= '9'){ ctrlFlag[charCode - '1'] = 1; return true; @@ -234,12 +364,7 @@ bool ctrlSwitch(UINT charCode,bool *ctrlFlag){ return false; } -// convert wstring to UTF-8 string -std::string wstring_to_utf8 (const std::wstring& str) -{ - std::wstring_convert> myconv; - return myconv.to_bytes(str); -} + // virtual bool TextService::onKeyDown(Ime::KeyEvent& keyEvent, Ime::EditSession* session) { @@ -258,31 +383,13 @@ bool TextService::onKeyDown(Ime::KeyEvent& keyEvent, Ime::EditSession* session) UINT charCode = keyEvent.charCode(); - //dirty add varaible - enum Mode {composition_mode, noncomposition_mode}; - static Mode mode = noncomposition_mode; - static bool ctrlFlag[11]={0}; - static wstring ctrl[11]; - static char line[30]; - static bool readFlag=1; - static int lineNum; //read form ctrl+num string from dictionary.txt - std::fstream fs; - if(readFlag){ - fs.open("dictionary.txt",ios::in | ios::app); - if(fs){ - for(int i=0; fs.getline(line, sizeof(line)); i++){ - ctrl[i] = utf8ToUtf16(line); - } - } - fs.flush(); - fs.clear(); - fs.close(); - readFlag=0; - } + static defString defObj; + defObj.readFromFile(); + enum Mode {composition_mode, noncomposition_mode}; + static Mode mode=noncomposition_mode; - if(charCode && isprint(charCode)) { // printable characters (exclude extended keys?) int oldLangMode = ::chewing_get_ChiEngMode(chewingContext_); bool temporaryEnglishMode = false; @@ -300,13 +407,13 @@ bool TextService::onKeyDown(Ime::KeyEvent& keyEvent, Ime::EditSession* session) } // 1.Detect Ctrl + number (0~9) - if(mode == noncomposition_mode&&keyEvent.isKeyDown(VK_CONTROL) && ctrlSwitch(charCode,ctrlFlag)){ - + if(mode == noncomposition_mode&&keyEvent.isKeyDown(VK_CONTROL)){ + defObj.ctrlSwitch(charCode); } else if(langMode_ == SYMBOL_MODE) { // English mode // 2.Detect Ctrl + number (0~9) for English mode - if(keyEvent.isKeyDown(VK_CONTROL) && isdigit(charCode) && ctrlSwitch(charCode,ctrlFlag)){ - + if(keyEvent.isKeyDown(VK_CONTROL) && isdigit(charCode)){ + defObj.ctrlSwitch(charCode); } else ::chewing_handle_Default(chewingContext_, charCode); @@ -328,7 +435,7 @@ bool TextService::onKeyDown(Ime::KeyEvent& keyEvent, Ime::EditSession* session) else if(keyEvent.keyCode() == VK_SPACE) // space key ::chewing_handle_Space(chewingContext_); else if(keyEvent.isKeyDown(VK_CONTROL) && isdigit(charCode)){ // Ctrl + number (0~9) - ctrlSwitch(charCode,ctrlFlag);//3.Add string to Ctrl+num of position + defObj.ctrlSwitch(charCode);//3.Add string to Ctrl+num of position ::chewing_handle_CtrlNum(chewingContext_, charCode); }else if(keyEvent.isKeyToggled(VK_NUMLOCK) && keyEvent.keyCode() >= VK_NUMPAD0 && keyEvent.keyCode() <= VK_DIVIDE) // numlock is on, handle numpad keys @@ -459,15 +566,14 @@ bool TextService::onKeyDown(Ime::KeyEvent& keyEvent, Ime::EditSession* session) } - //examine ctrl flag to printf + //examine ctrl flag to printf if(mode == noncomposition_mode){ - for(int i=0;i<9;i++){ - if( ctrlFlag[i]){ - //output string ctrl+num when non-composition mode - ctrlFlag[i]=0; - setCompositionString(session, ctrl[i+1].c_str(), ctrl[i+1].length()); - endComposition(session->context()); - } + bool OK; + wstring ctrlBuf; + OK=defObj.examineCtrlFlag(ctrlBuf); + if(OK==true){ + setCompositionString(session, ctrlBuf.c_str(), ctrlBuf.length()); + endComposition(session->context()); } } @@ -479,40 +585,16 @@ bool TextService::onKeyDown(Ime::KeyEvent& keyEvent, Ime::EditSession* session) } setCompositionString(session, compositionBuf.c_str(), compositionBuf.length()); - // add string to txt + // if defStrinf be destoryed write data to userString.txt if(mode == composition_mode){ - for(int i=0;i<9;i++){ - if(ctrlFlag[i]){ - - ctrl[i+1].clear(); - ctrl[i+1] = compositionBuf; - std::locale::global(std::locale("")); - std::ofstream wf; - //ctrl[0] can't be uesd so we write line 1 - char *ctrl1="define your string"; - wf.open("dictionary.txt",std::ios_base::binary ); - if(wf){ - std::wstring dd=L"\r\n"; - wf.write(ctrl1,sizeof(ctrl1)); - wf.write("\r\n",2); - for(int i=1; i<10 ;i++){ - ctrl[i]+=dd; - string ctrlTmp=wstring_to_utf8(ctrl[i]); - wf.write(ctrlTmp.c_str() , ctrlTmp.size() ); - } - } - wf.flush(); - wf.clear(); - wf.close(); - readFlag=1; - ctrlFlag[i]=0; - } + int num=defObj.examineCtrlFlag(); + if(num!=-1){ + //defObj.writeToFile(num,compositionBuf); + defObj.writeToBuf(num,compositionBuf); } } - - } else { // nothing left in composition buffer, terminate composition status if(isComposing()) { From c97414a9e36a0155177db132fa0f0321feb51be4 Mon Sep 17 00:00:00 2001 From: AIdrifter Date: Sun, 27 Jul 2014 21:39:01 +0800 Subject: [PATCH 3/4] Update ChewingTextService in order to write string to file immediately There are some problem before this version. 1.Function getline(buf) via using buf has been not cleared. Buf will get gibberish while translating to UTF16. This problem is modified now. 2.Write string to defUser.txt immediately instead of writting string after closed Chewing.dll, which mean openning both A.notepad and B.notepad and then A.notepad add new string to defuser.txt. B.notepad can update buffer immediately --- ChewingTextService/ChewingTextService.cpp | 33 ++++++++++++----------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/ChewingTextService/ChewingTextService.cpp b/ChewingTextService/ChewingTextService.cpp index 98f5ee8..b6fb290 100644 --- a/ChewingTextService/ChewingTextService.cpp +++ b/ChewingTextService/ChewingTextService.cpp @@ -243,26 +243,27 @@ class defString{ private: bool ctrlFlag[10]; //limit 10 - std::wstring ctrlStr[10]; //limit 10 + std::wstring ctrlStr[10]; //limit 10 bool read; }; defString::defString(){ - for(int i=0;i<9;i++){ + for(int i=0;i<9;i++){ ctrlFlag[i]=0; - } - read=1; + } + read=1; } defString::~defString(){ - writeToFile(); + writeToFile(); } bool defString::writeToBuf(int num,const std::wstring wBuf){ if(wBuf.empty()) - return false; - ctrlStr[num]=wBuf; - return true; + return false; + ctrlStr[num]=wBuf; + writeToFile(); + return true; } @@ -274,7 +275,7 @@ bool defString::examineCtrlFlag(std::wstring& ctrlBuf){ ctrlBuf=ctrlStr[i]; } } - return -1; + return -1; } //call by reference @@ -317,9 +318,9 @@ bool defString::writeToFile(){ } // write message to file - std::wstring eof=L"\r\n"; + std::wstring eof=L"\n"; - for(int i=1; i<10 ;i++){ + for(int i=0; i<10 ;i++){ ctrlStr[i]+=eof; string ctrlTmp=wstring_to_utf8(ctrlStr[i]); wf.write(ctrlTmp.c_str() , ctrlTmp.size()); @@ -341,16 +342,18 @@ bool defString::readFromFile(){ //try to open File //std::ofstream file("example.txt"); - std::fstream fs("userString.txt"); + std::fstream fs("userString.txt"); if (!fs.is_open()) throw std::runtime_error("unable to open file"); // read message from file - char buf[30]; + char buf[30]={'\0'}; for(int i=0; fs.getline(buf, sizeof(buf))&&i<10; i++){ - ctrlStr[i]=utf8ToUtf16(buf); + ctrlStr[i]=utf8ToUtf16(buf); + for(int j=0;j<29;j++) + buf[j]='\0'; } - read=0; + read=0; return true; } From 00fd0b5d7a96fc27ac2981dbd48df1160c3eb040 Mon Sep 17 00:00:00 2001 From: AIdrifter Date: Sun, 27 Jul 2014 23:00:17 +0800 Subject: [PATCH 4/4] modify Indent for ctr1~ctr9 output define string This final version. --- ChewingTextService/ChewingTextService.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/ChewingTextService/ChewingTextService.cpp b/ChewingTextService/ChewingTextService.cpp index b6fb290..e57ff7a 100644 --- a/ChewingTextService/ChewingTextService.cpp +++ b/ChewingTextService/ChewingTextService.cpp @@ -286,14 +286,14 @@ int defString::examineCtrlFlag(){ return i; } } - return -1; + return -1; } // convert wstring to UTF-8 string string defString::wstring_to_utf8 (const std::wstring& str) { - std::wstring_convert> myconv; - return myconv.to_bytes(str); + std::wstring_convert> myconv; + return myconv.to_bytes(str); } @@ -305,20 +305,16 @@ bool defString::writeToFile(){ //lock mutex before accessing file std::lock_guard lock(mutex); - //reflush ctrlStr - //ctrlStr[num].clear(); - //ctrlStr[num]=buf; - //try to open File std::locale::global(std::locale("")); std::ofstream wf("userString.txt"); - if (!wf.is_open()){ - throw std::runtime_error("unable to open file"); + if (!wf.is_open()){ + throw std::runtime_error("unable to open file"); return false; } // write message to file - std::wstring eof=L"\n"; + std::wstring eof=L"\n"; for(int i=0; i<10 ;i++){ ctrlStr[i]+=eof;