Skip to content
PyZyPhoneticContext.cc 8.27 KiB
Newer Older
/* vim:set et ts=4 sts=4:
 *
 * ibus-pinyin - The Chinese PinYin engine for IBus
 *
 * Copyright (c) 2008-2010 Peng Huang <shawn.p.huang@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include "PyZyPhoneticContext.h"
#include "PyZySimpTradConverter.h"
#include "PyZyDatabase.h"
#include "PyZyPhraseEditor.h"

namespace PyZy {

PhoneticContext::PhoneticContext (Config & config, PhoneticContext::Observer *observer)
    : m_config (config),
      m_phrase_editor (config),
      m_observer (observer)
{
    reset ();
}

PhoneticContext::~PhoneticContext ()
{
}

bool
PhoneticContext::processKeyEvent (unsigned short key)
{
    const guint key_code = key & 0x00ff;
    const guint vkey_code = key & 0xff00;

    if (vkey_code == 0) {
        if ('a' <= key_code && key_code <= 'z') {
            return insert (key_code);
        }
    } else {
        const guint key_num = key_code - '1';

        switch (vkey_code) {
        case VKEY_COMMIT:
            commit ();
            return TRUE;
        case VKEY_RESET:
            reset ();
            update ();
            return TRUE;

        case VKEY_CURSOR_RIGHT:
            if (m_phrase_editor.unselectCandidates ()) {
                update ();
                return TRUE;
            } else {
                return moveCursorRight();
            }
        case VKEY_CURSOR_LEFT:
            if (m_phrase_editor.unselectCandidates ()) {
                update ();
                return TRUE;
            } else {
                return moveCursorLeft();
            }
        case VKEY_CURSOR_RIGHT_BY_WORD:
            if (m_phrase_editor.unselectCandidates ()) {
                update ();
                return TRUE;
            } else {
                return moveCursorRightByWord ();
            }
        case VKEY_CURSOR_LEFT_BY_WORD:
            if (m_phrase_editor.unselectCandidates ()) {
                update ();
                return TRUE;
            } else {
                return moveCursorLeftByWord ();
            }
        case VKEY_CURSOR_TO_BEGIN:
            if (m_phrase_editor.unselectCandidates ()) {
                update ();
                return TRUE;
            } else {
                return moveCursorToBegin();
            }
        case VKEY_CURSOR_TO_END:
            if (m_phrase_editor.unselectCandidates ()) {
                update ();
                return TRUE;
            } else {
                return moveCursorToEnd();
            }

        case VKEY_CANDIDATE_SELECT:
            return selectCandidateInPage (key_num);
        case VKEY_CANDIDATE_FOCUS:
            return focusCandidateInPage (key_num);
        case VKEY_CANDIDATE_RESET:
            return resetCandidateInPage (key_num);

        case VKEY_PAGE_PREVIOUS:
            selectPage (MAX(1, page ()) - 1);
            return TRUE;
        case VKEY_PAGE_NEXT:
            selectPage (page () + 1);
            return TRUE;
        case VKEY_PAGE_BEGIN:
            selectPage (0);
            return TRUE;
        case VKEY_PAGE_END:
            {
                const guint guint_max = (guint)-1;
                selectPage (guint_max);
            }
            return TRUE;

        case VKEY_DELETE_CHARACTER_BEFORE:
            return removeCharBefore();
        case VKEY_DELETE_CHARACTER_AFTER:
            return removeCharAfter();
        case VKEY_DELETE_WORD_BEFORE:
            return removeWordBefore();
        case VKEY_DELETE_WORD_AFTER:
            return removeWordAfter();
        }
    }

    g_warning ("Can't handle KeyEvent (keycode=%d, vkeycode=%d)\n",
               key_code, vkey_code);
    return FALSE;
}

gboolean
PhoneticContext::updateSpecialPhrases (void)
{
    guint size = m_special_phrases.size ();
    m_special_phrases.clear ();

    if (!m_config.specialPhrases ())
        return FALSE;

    if (!m_selected_special_phrase.empty ())
        return FALSE;

    guint begin = m_phrase_editor.cursorInChar ();
    guint end = m_cursor;

    if (begin < end) {
        SpecialPhraseTable::instance ().lookup (
            m_text.substr (begin, m_cursor - begin),
            m_special_phrases);
    }

    return size != m_special_phrases.size () || size != 0;
}

void
PhoneticContext::updateLookupTable (void)
{
    m_candidates.clear ();

    for (gint i = 0; i < m_special_phrases.size (); ++i) {
        Candidate candidate;
        candidate.text = m_special_phrases[i];
        candidate.type = SPECIAL_PHRASE;
        m_candidates.push_back (candidate);
    }

    const PhraseArray & phrase_array = m_phrase_editor.candidates ();
    for (gint i = 0; i < phrase_array.size (); ++i) {
        CandidateType candidate_type;

        if (i < m_special_phrases.size ()) {
            candidate_type = SPECIAL_PHRASE;
        } else if (m_phrase_editor.candidateIsUserPhrase (i - m_special_phrases.size ())) {
            candidate_type = USER_PHRASE;
        } else {
            candidate_type = NORMAL_PHRASE;
        }

        Candidate candidate;
        candidate.text = phrase_array[i].phrase;
        candidate.type = candidate_type;
        m_candidates.push_back (candidate);
    }

    m_observer->lookupTableChanged ();
}

void
PhoneticContext::reset (void)
{
    m_pinyin.clear ();
    m_pinyin_len = 0;
    m_phrase_editor.reset ();
    m_special_phrases.clear ();
    m_selected_special_phrase.clear ();

    m_cursor = 0;
    m_focused_candidate = 0;
    m_text.clear ();
    m_preedit_text.clear ();
}

gboolean
PhoneticContext::focusCandidateInPage (guint i)
{
    return focusCandidate (page () * m_config.pageSize () + i);
}

gboolean
PhoneticContext::focusCandidate (guint i)
{
    if (G_UNLIKELY (i >= m_candidates.size ())) {
        g_warning ("Too big index. Can't focus to selected candidate.");
        return FALSE;
    }
    m_focused_candidate = i;

    update ();

    return TRUE;
}

void
PhoneticContext::selectPage (guint i)
{
    if (G_UNLIKELY (m_candidates.size () == 0)) {
        m_focused_candidate = 0;
        return;
    }

    const guint size = m_config.pageSize ();
    const guint max_page = (m_candidates.size () - 1) / size;
    if (i > max_page) {
        i = max_page;
    }

    m_focused_candidate = MIN (m_candidates.size (), i * size + m_focused_candidate % size);

    update ();
}

void
PhoneticContext::update ()
{
    updateLookupTable ();
    updatePreeditText ();
    updateAuxiliaryText ();
}

gboolean
PhoneticContext::selectCandidate (guint i)
{
    if (i >= m_config.pageSize ()) {
        g_warning ("selectCandidate(%ud): Too big index!\n", i);
    }

    if (i < m_special_phrases.size ()) {
        // select a special phrase
        m_selected_special_phrase = m_special_phrases[i];
        m_focused_candidate = 0;
        if (m_cursor == m_text.size ()) {
            commit ();
        }
        else {
            updateSpecialPhrases ();
            update ();
        }

        return TRUE;
    }

    i -= m_special_phrases.size ();
    if (m_phrase_editor.selectCandidate (i)) {
        m_focused_candidate = 0;
        if (m_phrase_editor.pinyinExistsAfterCursor () ||
            *textAfterPinyin () != '\0') {
            updateSpecialPhrases ();
            update ();
        }
        else {
            commit ();
        }
        return TRUE;
    }

    return FALSE;
}

gboolean
PhoneticContext::selectCandidateInPage (guint i)
{
    return selectCandidate (page () * m_config.pageSize () + i);
}

gboolean
PhoneticContext::resetCandidate (guint i)
{
    if (i < m_special_phrases.size ()) {
        return FALSE;
    }
    i -= m_special_phrases.size ();

    if (m_phrase_editor.resetCandidate (i)) {
        update ();
    }

    return TRUE;
}

gboolean
PhoneticContext::resetCandidateInPage (guint i)
{
    return resetCandidate (page () * m_config.pageSize () + i);
}

};  // namespace PyZy