2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4 * Copyright (c) 2007 Sun Microsystems, Inc. All Rights Reserved.
6 * The contents of this file are subject to the terms of either the GNU Lesser
7 * General Public License Version 2.1 only ("LGPL") or the Common Development and
8 * Distribution License ("CDDL")(collectively, the "License"). You may not use this
9 * file except in compliance with the License. You can obtain a copy of the CDDL at
10 * http://www.opensource.org/licenses/cddl1.php and a copy of the LGPLv2.1 at
11 * http://www.opensource.org/licenses/lgpl-license.php. See the License for the
12 * specific language governing permissions and limitations under the License. When
13 * distributing the software, include this License Header Notice in each file and
14 * include the full text of the License in the License file as well as the
17 * NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND DISTRIBUTION LICENSE
19 * For Covered Software in this distribution, this License shall be governed by the
20 * laws of the State of California (excluding conflict-of-law provisions).
21 * Any litigation relating to this License shall be subject to the jurisdiction of
22 * the Federal Courts of the Northern District of California and the state courts
23 * of the State of California, with venue lying in Santa Clara County, California.
27 * If you wish your version of this file to be governed by only the CDDL or only
28 * the LGPL Version 2.1, indicate your decision by adding "[Contributor]" elects to
29 * include this software in this distribution under the [CDDL or LGPL Version 2.1]
30 * license." If you don't indicate a single choice of license, a recipient has the
31 * option to distribute your version of this file under either the CDDL or the LGPL
32 * Version 2.1, or to extend the choice of license to its licensees as provided
33 * above. However, if you add LGPL Version 2.1 code and therefore, elected the LGPL
34 * Version 2 license, then the option applies only if the new code is made subject
35 * to such option by the copyright holder.
42 #include "imi_view_classic.h"
43 #include "imi_uiobjects.h"
47 CIMIClassicView::CIMIClassicView()
48 : CIMIView(), m_cursorFrIdx(0), m_candiFrIdx(0),
53 CIMIClassicView::~CIMIClassicView()
57 size_t CIMIClassicView::top_candidate_threshold = 5;
60 CIMIClassicView::attachIC(CIMIContext* pIC)
62 CIMIView::attachIC(pIC);
67 CIMIClassicView::clearIC(void)
69 if (!m_pIC->isEmpty()) {
70 m_cursorFrIdx = m_candiFrIdx = m_candiPageFirst = 0;
73 m_pPySegmentor->clear();
75 //m_tailSentence.clear ();
78 return PREEDIT_MASK | CANDIDATE_MASK;
84 CIMIClassicView::updateWindows(unsigned mask)
89 if (mask & PREEDIT_MASK) {
90 m_uiPreeditString.clear();
91 getPreeditString(m_uiPreeditString);
92 // m_pWinHandler->updatePreedit(&ps);
95 if ((mask & PREEDIT_MASK) || (mask & CANDIDATE_MASK)) {
96 // calculate all possible best sentences
99 for (size_t i = 0; i < m_pIC->getNBest(); i++) {
101 unsigned word_num = m_pIC->getBestSentence(sentence, i,
103 if (word_num == 0) goto pass; // when sentence is not worthy of
107 print_wide(sentence.c_str());
111 for (size_t j = 0; j < m_sentences.size(); j++) {
112 if (sentence == m_sentences[j].second) goto pass;
115 if (best_rank < 0 && word_num > 1) {
118 m_sentences.push_back(std::make_pair(i, sentence));
122 // build all possible tails with best_rank
123 std::vector<CCandidates> tails =
124 m_pIC->getBestSentenceTails(best_rank, m_candiFrIdx);
126 for (size_t i = 0; i < tails.size(); i++) {
127 CCandidates& tail = tails[i];
129 // translate this tail into text
130 for (size_t j = 0; j < tail.size(); j++) {
131 tail_text += tail[j].m_cwstr;
133 m_tails.push_back(std::make_pair(tail_text, tail));
137 if (mask & CANDIDATE_MASK) {
138 m_uiCandidateList.clear();
139 getCandidateList(m_uiCandidateList, m_candiPageFirst,
141 // m_pWinHandler->updateCandidates(&cl);
142 handlerUpdateCandidates(&m_uiPreeditString, &m_uiCandidateList);
145 if (mask & PREEDIT_MASK) {
146 handlerUpdatePreedit(&m_uiPreeditString);
151 CIMIClassicView::onKeyEvent(const CKeyEvent& key)
153 unsigned changeMasks = 0;
155 unsigned keycode = key.code;
156 unsigned keyvalue = key.value;
157 unsigned modifiers = key.modifiers;
160 printf("Classic View got a key (0x%x-0x%x-0x%x)...",
161 keycode, keyvalue, modifiers);
162 if (((modifiers & IM_CTRL_MASK) != 0) &&
163 (keyvalue == 'P' || keyvalue == 'p'))
164 m_pIC->printLattice();
167 if (m_pHotkeyProfile && m_pHotkeyProfile->isModeSwitchKey(key)) {
168 setStatusAttrValue(CIMIWinHandler::STATUS_ID_CN, (!m_bCN) ? 1 : 0);
169 if (!m_pIC->isEmpty()) {
170 changeMasks |= CANDIDATE_MASK | PREEDIT_MASK;
173 } else if (m_pHotkeyProfile && m_pHotkeyProfile->isPunctSwitchKey(key)) {
174 // On CTRL+. switch Full/Half punc
175 changeMasks |= KEYEVENT_USED;
176 setStatusAttrValue(CIMIWinHandler::STATUS_ID_FULLPUNC,
177 (!m_bFullPunct) ? 1 : 0);
178 } else if (m_pHotkeyProfile && m_pHotkeyProfile->isSymbolSwitchKey(key)) {
179 // On SHIFT+SPACE switch Full/Half symbol
180 changeMasks |= KEYEVENT_USED;
181 setStatusAttrValue(CIMIWinHandler::STATUS_ID_FULLSYMBOL,
182 (!m_bFullSymbol) ? 1 : 0);
183 } else if (modifiers == IM_CTRL_MASK && keycode == IM_VK_LEFT) {
185 if (!m_pIC->isEmpty()) {
186 changeMasks |= KEYEVENT_USED;
187 _moveLeft(changeMasks);
189 } else if (modifiers == IM_CTRL_MASK && keycode == IM_VK_RIGHT) {
191 if (!m_pIC->isEmpty()) {
192 changeMasks |= KEYEVENT_USED;
193 _moveRight(changeMasks);
195 } else if (((modifiers == 0 && keycode == IM_VK_PAGE_UP)
196 || (m_pHotkeyProfile && m_pHotkeyProfile->isPageUpKey(key)))
197 && !m_pIC->isEmpty()) {
198 changeMasks |= KEYEVENT_USED;
199 if (m_candiPageFirst > 0) {
200 if (m_candiPageFirst > m_candiWindowSize)
201 m_candiPageFirst -= m_candiWindowSize;
203 m_candiPageFirst = 0;
205 changeMasks |= CANDIDATE_MASK;
207 } else if (((modifiers == 0 && keycode == IM_VK_PAGE_DOWN)
208 || (m_pHotkeyProfile && m_pHotkeyProfile->isPageDownKey(key)))
209 && !m_pIC->isEmpty()) {
210 changeMasks |= KEYEVENT_USED;
211 if (m_candiPageFirst + m_candiWindowSize < candidateListSize()) {
212 m_candiPageFirst += m_candiWindowSize;
213 changeMasks |= CANDIDATE_MASK;
215 } else if (m_pHotkeyProfile
216 && m_pHotkeyProfile->isCandiDeleteKey(key, m_candiWindowSize)
217 && !m_pIC->isEmpty()) {
218 changeMasks |= KEYEVENT_USED;
219 unsigned sel = (keyvalue == '0' ? 9 : keyvalue - '1');
220 deleteCandidate(sel, changeMasks);
222 } else if ((modifiers &
223 (IM_CTRL_MASK | IM_ALT_MASK | IM_SUPER_MASK |
224 IM_RELEASE_MASK)) == 0) {
225 if ((keyvalue >= '0' && keyvalue <= '9')
226 && (m_candiWindowSize >= 10
227 || keyvalue < ('1' + m_candiWindowSize))) {
228 // try to make selection
229 if (!m_pIC->isEmpty()) {
230 changeMasks |= KEYEVENT_USED;
231 unsigned sel = (keyvalue == '0' ? 9 : keyvalue - '1');
232 makeSelection(sel, changeMasks);
233 } else if (m_smartPunct) {
234 m_pIC->omitNextPunct();
239 if (keyvalue > 0x60 && keyvalue < 0x7b) {
240 /* islower(keyvalue) */
241 changeMasks |= KEYEVENT_USED;
242 _insert(keyvalue, changeMasks);
243 } else if (keyvalue > 0x20 && keyvalue < 0x7f) {
244 /* isprint(keyvalue) && !isspace(keyvalue) */
245 changeMasks |= KEYEVENT_USED;
246 if (m_pIC->isEmpty()) {
247 _insert(keyvalue, changeMasks);
251 _insert(keyvalue, changeMasks);
253 } else if (keycode == IM_VK_BACK_SPACE || keycode == IM_VK_DELETE) {
254 if (!m_pIC->isEmpty()) {
255 changeMasks |= KEYEVENT_USED;
256 _erase(keycode == IM_VK_BACK_SPACE, changeMasks);
258 } else if (keycode == IM_VK_SPACE) {
259 if (!m_pIC->isEmpty()) {
260 changeMasks |= KEYEVENT_USED;
261 makeSelection(0, changeMasks);
263 wstring wstr = (m_pIC->fullPuncOp())(keyvalue);
266 changeMasks |= KEYEVENT_USED;
269 } else if (keycode == IM_VK_ENTER) {
270 if (!m_pIC->isEmpty()) {
271 changeMasks |= KEYEVENT_USED | CANDIDATE_MASK | PREEDIT_MASK;
275 } else if (keycode == IM_VK_ESCAPE) {
276 if (!m_pIC->isEmpty()) {
277 changeMasks |= KEYEVENT_USED | CANDIDATE_MASK | PREEDIT_MASK;
280 } else if (keycode == IM_VK_LEFT) { // move left syllable
281 if (!m_pIC->isEmpty()) {
282 changeMasks |= KEYEVENT_USED;
283 _moveLeftSyllable(changeMasks);
285 } else if (keycode == IM_VK_RIGHT) { // move right syllable
286 if (!m_pIC->isEmpty()) {
287 changeMasks |= KEYEVENT_USED;
288 _moveRightSyllable(changeMasks);
290 } else if (keycode == IM_VK_HOME) { // move home
291 if (!m_pIC->isEmpty()) {
292 changeMasks |= KEYEVENT_USED;
293 _moveHome(changeMasks);
295 } else if (keycode == IM_VK_END) { // move end
296 if (!m_pIC->isEmpty()) {
297 changeMasks |= KEYEVENT_USED;
298 _moveEnd(changeMasks);
306 m_pHotkeyProfile->rememberLastKey(key);
311 printf(" |-->(Mask=0x%x)\n", changeMasks);
314 updateWindows(changeMasks);
315 return changeMasks & KEYEVENT_USED;
319 CIMIClassicView::onCandidatePageRequest(int pgno, bool relative)
321 unsigned changeMasks = 0;
322 int ncandi, lastpgidx;
324 if (!m_pIC->isEmpty()) {
325 changeMasks |= KEYEVENT_USED;
326 size_t sz = candidateListSize();
327 lastpgidx = (sz - 1) / m_candiWindowSize * m_candiWindowSize;
328 if (relative == true) {
329 ncandi = m_candiPageFirst + pgno * m_candiWindowSize;
330 if (ncandi >= (int) sz)
334 if (ncandi != (int) m_candiPageFirst) {
335 m_candiPageFirst = ncandi;
336 changeMasks |= CANDIDATE_MASK;
339 if (pgno == -1) { //last page
342 ncandi = pgno * m_candiWindowSize;
343 if (ncandi > lastpgidx)
346 if (ncandi != (int) m_candiPageFirst) {
347 m_candiPageFirst = ncandi;
348 changeMasks |= CANDIDATE_MASK;
353 updateWindows(changeMasks);
358 CIMIClassicView::onCandidateSelectRequest(int index)
360 unsigned changeMasks = 0;
362 if (!m_pIC->isEmpty())
363 makeSelection(index, changeMasks);
365 updateWindows(changeMasks);
370 CIMIClassicView::getPreeditString(IPreeditString& ps)
374 wstring &wstr = ps.getString();
375 IPreeditString::CCharTypeVec& charTypes = ps.getCharTypeVec();
377 m_pIC->getSelectedSentence(wstr, 0, m_candiFrIdx);
379 int caret = wstr.size();
380 charTypes.reserve(caret);
381 for (int i = 0; i < caret; ++i)
383 IPreeditString::HANZI_CHAR | IPreeditString::USER_CHOICE);
385 const wstring& pystr = m_pPySegmentor->getInputBuffer();
386 std::vector<unsigned>& seg_path = m_pIC->getBestSegPath();
391 CLattice& lattice = m_pIC->getLattice();
392 unsigned last = 0, len = 0;
393 for (std::vector<unsigned>::iterator it = seg_path.begin();
394 it != seg_path.end(); ++it) {
396 if (*it <= m_candiFrIdx) {
400 if (last < m_cursorFrIdx && m_cursorFrIdx <= last + len)
401 caret = wstr.size() + (m_cursorFrIdx - last);
403 CLatticeFrame &fr = lattice[last + len];
404 int ct = IPreeditString::PINYIN_CHAR;
405 if (fr.isSyllableSepFrame()) {
406 ct = IPreeditString::BOUNDARY | IPreeditString::USER_CHOICE;
407 } else if (fr.m_type == CLatticeFrame::ASCII) {
408 ct = IPreeditString::ASCII_CHAR;
409 } else if (fr.m_type == CLatticeFrame::SYMBOL) {
410 ct = IPreeditString::SYMBOL_CHAR;
413 wstr.insert(wstr.end(), pystr.begin() + last,
414 pystr.begin() + last + len);
415 for (size_t c = 0; c < len; ++c)
416 charTypes.push_back(ct);
418 if (fr.isSyllableFrame() && !fr.isSyllableSepFrame()) {
419 if (it != seg_path.end() - 1
420 && !lattice[last + len + 1].isSyllableSepFrame()) {
422 charTypes.push_back(IPreeditString::BOUNDARY);
427 // segment path might be missing or incomplete, therefore append the input
428 // buffer left to wstr
429 wstr.insert(wstr.end(), pystr.begin() + last, pystr.end());
435 CIMIClassicView::getCandidateList(ICandidateList& cl, int start, int size)
441 cl.setTotal(candidateListSize());
444 for (size_t i = 0; i < m_sentences.size(); i++) {
445 cl.pushBackCandidate(m_sentences[i].second,
446 ICandidateList::BEST_TAIL, i);
449 for (size_t i = 0; i < m_tails.size(); i++) {
450 cl.pushBackCandidate(m_tails[i].first,
451 ICandidateList::OTHER_BEST_TAIL, i);
454 for (size_t i = 0; i < m_candiList.size(); i++) {
456 cl.pushBackCandidate(m_candiList[i].m_cwstr,
457 ICandidateList::BEST_WORD, i);
459 cl.pushBackCandidate(m_candiList[i].m_cwstr,
460 ICandidateList::NORMAL_WORD, i);
466 CIMIClassicView::_insert(unsigned keyvalue, unsigned &changeMasks)
468 changeMasks |= KEYEVENT_USED;
470 if (m_pPySegmentor->getInputBuffer().size() >= MAX_LATTICE_LENGTH - 2)
473 if (m_cursorFrIdx == m_pIC->getLastFrIdx())
474 m_pPySegmentor->push(keyvalue);
476 m_pPySegmentor->insertAt(m_cursorFrIdx, keyvalue);
480 if (m_pIC->buildLattice(m_pPySegmentor))
483 changeMasks |= PREEDIT_MASK | CANDIDATE_MASK;
487 CIMIClassicView::_erase(bool backward, unsigned &changeMasks)
490 // if possible to cancel the last selection
491 if (m_backspaceCancel) {
492 if (m_candiFrIdx > 0) {
493 m_candiFrIdx = m_pIC->cancelSelection(m_candiFrIdx, true);
497 if (m_cursorFrIdx == m_pIC->getLastFrIdx()) {
498 m_pPySegmentor->pop();
499 } else if (m_cursorFrIdx > 0) {
500 m_pPySegmentor->deleteAt(m_cursorFrIdx - 1, backward);
504 _moveLeft(changeMasks, true);
506 if (m_cursorFrIdx == m_pIC->getLastFrIdx() - 1) {
507 m_pPySegmentor->pop();
508 } else if (m_cursorFrIdx < m_pIC->getLastFrIdx() - 1) {
509 m_pPySegmentor->deleteAt(m_cursorFrIdx - 1, backward);
515 if (m_pIC->buildLattice(m_pPySegmentor))
518 changeMasks |= PREEDIT_MASK | CANDIDATE_MASK | KEYEVENT_USED;
522 CIMIClassicView::_getCandidates()
524 m_candiPageFirst = 0;
525 m_pIC->getCandidates(m_candiFrIdx, m_candiList);
529 CIMIClassicView::_commitChar(TWCHAR ch)
531 TWCHAR wa[2] = { ch, 0 };
532 m_pWinHandler->commit(wa);
536 CIMIClassicView::_commitString(const wstring& wstr)
538 m_pWinHandler->commit(wstr.c_str());
542 CIMIClassicView::_doCommit(bool bConvert)
548 m_pIC->getSelectedSentence(bs);
549 handlerCommit(bs.c_str());
551 bs += m_pPySegmentor->getInputBuffer();
552 handlerCommit(bs.c_str());
557 CIMIClassicView::_moveLeft(unsigned& mask, bool searchAgain)
559 if (m_cursorFrIdx == 0)
560 return _moveEnd(mask);
562 mask |= PREEDIT_MASK;
563 if (m_cursorFrIdx == m_candiFrIdx) {
564 mask |= CANDIDATE_MASK;
565 m_candiFrIdx = m_pIC->cancelSelection(m_candiFrIdx, searchAgain);
569 return --m_cursorFrIdx;
573 CIMIClassicView::_moveLeftSyllable(unsigned& mask, bool searchAgain)
575 if (m_cursorFrIdx == 0)
576 return _moveEnd(mask);
578 mask |= PREEDIT_MASK;
580 if (m_cursorFrIdx == m_candiFrIdx) {
581 mask |= CANDIDATE_MASK;
582 m_candiFrIdx = m_pIC->cancelSelection(m_candiFrIdx, searchAgain);
586 std::vector<unsigned>& seg_path = m_pIC->getBestSegPath();
587 std::vector<unsigned>::iterator it =
588 std::upper_bound(seg_path.begin(), seg_path.end(), m_cursorFrIdx - 1);
589 return m_cursorFrIdx = *(--it);
593 CIMIClassicView::_moveHome(unsigned& mask, bool searchAgain)
595 if (m_cursorFrIdx == 0)
598 mask |= PREEDIT_MASK;
600 if (m_candiFrIdx != 0) {
601 std::vector<unsigned>& best_path = m_pIC->getBestPath();
602 std::vector<unsigned>::iterator it = best_path.begin();
603 std::vector<unsigned>::iterator ite = best_path.end();
604 CLattice& lattice = m_pIC->getLattice();
606 for (; it != ite; ++it) {
607 if (lattice[*it].m_bwType & CLatticeFrame::USER_SELECTED)
608 m_pIC->cancelSelection(*it, false);
611 mask |= CANDIDATE_MASK;
615 if (searchAgain) m_pIC->searchFrom();
623 CIMIClassicView::_moveRight(unsigned& mask)
625 if (m_cursorFrIdx < m_pIC->getLastFrIdx()) {
626 mask |= PREEDIT_MASK;
628 return m_cursorFrIdx;
631 return _moveHome(mask);
635 CIMIClassicView::_moveRightSyllable(unsigned& mask)
637 if (m_cursorFrIdx < m_pIC->getLastFrIdx()) {
638 mask |= PREEDIT_MASK;
640 std::vector<unsigned>& seg_path = m_pIC->getBestSegPath();
641 std::vector<unsigned>::iterator it = std::upper_bound(
642 seg_path.begin(), seg_path.end(), m_cursorFrIdx);
644 return m_cursorFrIdx;
647 return _moveHome(mask);
651 CIMIClassicView::_moveEnd(unsigned& mask)
653 if (m_cursorFrIdx < m_pIC->getLastFrIdx()) {
654 mask |= PREEDIT_MASK;
655 m_cursorFrIdx = m_pIC->getLastFrIdx();
658 return m_cursorFrIdx;
662 CIMIClassicView::makeSelection(int candiIdx, unsigned& mask)
664 if (m_candiList.size() == 0 || m_sentences.size() == 0) {
665 // user might delete all the left over pinyin characters, this will
666 // make m_candiList empty
667 // 0 or space choices should commit previous selected candidates
668 mask |= PREEDIT_MASK | CANDIDATE_MASK;
674 // candiIdx += m_candiPageFirst;
675 if (candiIdx >= m_uiCandidateList.size()) {
678 int idx = m_uiCandidateList.getUserIndex(candiIdx);
679 int type = m_uiCandidateList.getCandiTypeVec()[candiIdx];
680 bool selected = false;
682 mask |= PREEDIT_MASK | CANDIDATE_MASK;
683 if (type == ICandidateList::BEST_TAIL) {
684 // get the rank of that sentence and select it
685 m_pIC->selectSentence(m_sentences[idx].first);
688 } else if (type == ICandidateList::OTHER_BEST_TAIL) {
689 CCandidates& tail = m_tails[idx].second;
690 for (size_t i = 0; i < tail.size(); i++) {
691 m_pIC->makeSelection(tail[i]);
693 m_candiFrIdx = tail.back().m_end;
695 } else if (type == ICandidateList::BEST_WORD
696 || type == ICandidateList::NORMAL_WORD) {
697 CCandidate& candi = m_candiList[idx];
698 m_pIC->makeSelection(candi);
699 m_candiFrIdx = candi.m_end;
701 } else if (type == ICandidateList::PLUGIN_TAIL) {
702 handlerCommit(m_uiCandidateList.getCandiStrings()[candiIdx]);
707 if (m_cursorFrIdx < m_candiFrIdx)
708 m_cursorFrIdx = m_candiFrIdx;
710 CLattice& lattice = m_pIC->getLattice();
711 while (m_candiFrIdx < m_pIC->getLastFrIdx() &&
712 !lattice[m_candiFrIdx + 1].isUnusedFrame() &&
713 !lattice[m_candiFrIdx + 1].isSyllableFrame()) {
715 lattice[m_candiFrIdx].m_bwType |= CLatticeFrame::IGNORED;
718 if (m_candiFrIdx == m_pIC->getLastFrIdx()) {
722 m_candiPageFirst = 0;
729 CIMIClassicView::deleteCandidate(int candiIdx, unsigned& mask)
731 // candiIdx += m_candiPageFirst;
732 int idx = m_uiCandidateList.getUserIndex(candiIdx);
733 int type = m_uiCandidateList.getCandiTypeVec()[candiIdx];
735 if (type == ICandidateList::BEST_TAIL) {
736 // try to remove candidate 0 which is a calculated sentence
737 std::vector<unsigned> wids;
738 m_pIC->getSelectedSentence(wids, m_candiFrIdx);
739 m_pIC->removeFromHistoryCache(wids);
741 /* if the sentence wid length is 1, also delete this word */
742 if (wids.size() == 1) {
743 unsigned wid = wids[0];
744 m_pIC->deleteCandidateByWID(wid);
746 } else if (type == ICandidateList::BEST_WORD
747 || type == ICandidateList::NORMAL_WORD) {
748 // remove an ordinary candidate
749 CCandidate& candi = m_candiList[idx];
750 m_pIC->deleteCandidate(candi);
754 mask |= PREEDIT_MASK | CANDIDATE_MASK;