Skip to content
PyZyBopomofoContext.cc 12 KiB
Newer Older
/* vim:set et ts=4 sts=4:
 *
 * libpyzy - The Chinese PinYin and Bopomofo conversion library.
 *
 * Copyright (c) 2008-2010 Peng Huang <shawn.p.huang@gmail.com>
 * Copyright (c) 2010 BYVoid <byvoid1@gmail.com>
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 * This library 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
 * Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 */
#include "PyZyBopomofoContext.h"
#include "PyZyConfig.h"
#include "PyZySimpTradConverter.h"
#include "PyZyPinyinParser.h"

namespace PyZy {
#include "PyZyBopomofoKeyboard.h"

const static char * bopomofo_select_keys[] = {
    "1234567890",
    "asdfghjkl;",
    "1qaz2wsxed",
    "asdfzxcvgb",
    "1234qweras",
    "aoeu;qjkix",
    "aoeuhtnsid",
    "aoeuidhtns",
    "qweasdzxcr"
};

BopomofoContext::BopomofoContext (Config & config, PhoneticContext::Observer *observer)
Hiroshi Sumita's avatar
Hiroshi Sumita committed
    : PhoneticContext (config, observer)
{
}

BopomofoContext::~BopomofoContext (void)
{
}

Hiroshi Sumita's avatar
Hiroshi Sumita committed
bool
BopomofoContext::insert (char ch)
Hiroshi Sumita's avatar
Hiroshi Sumita committed
    if (keyvalToBopomofo (ch) == BOPOMOFO_ZERO) {
        return false;
    }

    /* is full */
    if (G_UNLIKELY (m_text.length () >= MAX_PINYIN_LEN))
Hiroshi Sumita's avatar
Hiroshi Sumita committed
        return true;

    m_text.insert (m_cursor++, ch);

    if (G_UNLIKELY (!(m_config.option () & PINYIN_INCOMPLETE_PINYIN))) {
        updateSpecialPhrases ();
        updatePinyin ();
    }
    else if (G_LIKELY (m_cursor <= m_pinyin_len + 2)) {
        updateSpecialPhrases ();
        updatePinyin ();
    }
    else {
        if (updateSpecialPhrases ()) {
            update ();
        }
        else {
            updatePreeditText ();
            updateAuxiliaryText ();
        }
    }
Hiroshi Sumita's avatar
Hiroshi Sumita committed
    return true;
Hiroshi Sumita's avatar
Hiroshi Sumita committed
bool
BopomofoContext::removeCharBefore (void)
{
    if (G_UNLIKELY (m_cursor == 0))
Hiroshi Sumita's avatar
Hiroshi Sumita committed
        return false;

    m_cursor --;
    m_text.erase (m_cursor, 1);

    updateSpecialPhrases ();
    updatePinyin ();

Hiroshi Sumita's avatar
Hiroshi Sumita committed
    return true;
Hiroshi Sumita's avatar
Hiroshi Sumita committed
bool
BopomofoContext::removeCharAfter (void)
{
    if (G_UNLIKELY (m_cursor == m_text.length ()))
Hiroshi Sumita's avatar
Hiroshi Sumita committed
        return false;

    m_text.erase (m_cursor, 1);
    updatePreeditText ();
    updateAuxiliaryText ();

Hiroshi Sumita's avatar
Hiroshi Sumita committed
    return true;
Hiroshi Sumita's avatar
Hiroshi Sumita committed
bool
BopomofoContext::removeWordBefore (void)
{
    if (G_UNLIKELY (m_cursor == 0))
Hiroshi Sumita's avatar
Hiroshi Sumita committed
        return false;

    if (G_UNLIKELY (m_cursor > m_pinyin_len)) {
        cursor = m_pinyin_len;
    }
    else {
        const Pinyin & p = *m_pinyin.back ();
        cursor = m_cursor - p.len;
        m_pinyin_len -= p.len;
        m_pinyin.pop_back ();
    }

    m_text.erase (cursor, m_cursor - cursor);
    m_cursor = cursor;
    updateSpecialPhrases ();
    updatePhraseEditor ();
    update ();
Hiroshi Sumita's avatar
Hiroshi Sumita committed
    return true;
Hiroshi Sumita's avatar
Hiroshi Sumita committed
bool
BopomofoContext::removeWordAfter (void)
{
    if (G_UNLIKELY (m_cursor == m_text.length ()))
Hiroshi Sumita's avatar
Hiroshi Sumita committed
        return false;

    m_text.erase (m_cursor, -1);
    updatePreeditText ();
    updateAuxiliaryText ();
Hiroshi Sumita's avatar
Hiroshi Sumita committed
    return true;
Hiroshi Sumita's avatar
Hiroshi Sumita committed
bool
BopomofoContext::moveCursorLeft (void)
{
    if (G_UNLIKELY (m_cursor == 0))
Hiroshi Sumita's avatar
Hiroshi Sumita committed
        return false;

    m_cursor --;
    updateSpecialPhrases ();
    updatePinyin ();

Hiroshi Sumita's avatar
Hiroshi Sumita committed
    return true;
Hiroshi Sumita's avatar
Hiroshi Sumita committed
bool
BopomofoContext::moveCursorRight (void)
{
    if (G_UNLIKELY (m_cursor == m_text.length ()))
Hiroshi Sumita's avatar
Hiroshi Sumita committed
        return false;

    m_cursor ++;

    updateSpecialPhrases ();
    updatePinyin ();

Hiroshi Sumita's avatar
Hiroshi Sumita committed
    return true;
Hiroshi Sumita's avatar
Hiroshi Sumita committed
bool
BopomofoContext::moveCursorLeftByWord (void)
{
    if (G_UNLIKELY (m_cursor == 0))
Hiroshi Sumita's avatar
Hiroshi Sumita committed
        return false;

    if (G_UNLIKELY (m_cursor > m_pinyin_len)) {
        m_cursor = m_pinyin_len;
Hiroshi Sumita's avatar
Hiroshi Sumita committed
        return true;
    }

    const Pinyin & p = *m_pinyin.back ();
    m_cursor -= p.len;
    m_pinyin_len -= p.len;
    m_pinyin.pop_back ();

    updateSpecialPhrases ();
    updatePhraseEditor ();
    update ();

Hiroshi Sumita's avatar
Hiroshi Sumita committed
    return true;
Hiroshi Sumita's avatar
Hiroshi Sumita committed
bool
BopomofoContext::moveCursorRightByWord (void)
{
    return moveCursorToEnd ();
}

Hiroshi Sumita's avatar
Hiroshi Sumita committed
bool
BopomofoContext::moveCursorToBegin (void)
{
    if (G_UNLIKELY (m_cursor == 0))
Hiroshi Sumita's avatar
Hiroshi Sumita committed
        return false;

    m_cursor = 0;
    m_pinyin.clear ();
    m_pinyin_len = 0;

    updateSpecialPhrases ();
    updatePhraseEditor ();
    update ();

Hiroshi Sumita's avatar
Hiroshi Sumita committed
    return true;
Hiroshi Sumita's avatar
Hiroshi Sumita committed
bool
BopomofoContext::moveCursorToEnd (void)
{
    if (G_UNLIKELY (m_cursor == m_text.length ()))
Hiroshi Sumita's avatar
Hiroshi Sumita committed
        return false;

    m_cursor = m_text.length ();
    updateSpecialPhrases ();
    updatePinyin ();

Hiroshi Sumita's avatar
Hiroshi Sumita committed
    return true;
}

void
BopomofoContext::updatePinyin (void)
{
    if (G_UNLIKELY (m_text.empty ())) {
        m_pinyin.clear ();
        m_pinyin_len = 0;
    }
    else {
Hiroshi Sumita's avatar
Hiroshi Sumita committed
        std::wstring bopomofo;
        for(String::iterator i = m_text.begin (); i != m_text.end (); ++i) {
            bopomofo += bopomofo_char[keyvalToBopomofo (*i)];
        }

        m_pinyin_len = PinyinParser::parseBopomofo (bopomofo,            // bopomofo
                                                    m_cursor,            // text length
                                                    m_config.option (),   // option
                                                    m_pinyin,            // result
                                                    MAX_PHRASE_LEN);     // max result length
    }

    updatePhraseEditor ();
    update ();
}

void
BopomofoContext::updateAuxiliaryText (void)
{
    if (G_UNLIKELY (m_text.empty () || !hasCandidate (0))) {
        m_auxiliary_text = "";
Hiroshi Sumita's avatar
Hiroshi Sumita committed
        PhoneticContext::updateAuxiliaryText ();
        return;
    }

    m_buffer.clear ();

    if (m_selected_special_phrase.empty ()) {
        size_t si = 0;
        size_t m_text_len = m_text.length();
        for (size_t i = m_phrase_editor.cursor (); i < m_pinyin.size (); ++i) {
            if (G_LIKELY (i != m_phrase_editor.cursor ()))
                m_buffer << ',';
            m_buffer << (unichar *)m_pinyin[i]->bopomofo;
            for (size_t sj = 0; m_pinyin[i]->bopomofo[sj] == bopomofo_char[keyvalToBopomofo(m_text.c_str()[si])] ; si++,sj++);
            if (si < m_text_len) {
                int ch = keyvalToBopomofo(m_text.c_str()[si]);
                if (ch >= BOPOMOFO_TONE_2 && ch <= BOPOMOFO_TONE_5) {
                    m_buffer.appendUnichar(bopomofo_char[ch]);
                    ++si;
                }
            }
        }

        for (String::iterator i = m_text.begin () + m_pinyin_len; i != m_text.end (); i++) {
            if (m_cursor == (size_t)(i - m_text.begin ()))
                m_buffer << '|';
            m_buffer.appendUnichar (bopomofo_char[keyvalToBopomofo (*i)]);
        }
        if (m_cursor == m_text.length ())
            m_buffer << '|';
    }
    else {
        if (m_cursor < m_text.size ()) {
            m_buffer << '|' << textAfterCursor ();
        }
    }

    m_auxiliary_text = m_buffer;
Hiroshi Sumita's avatar
Hiroshi Sumita committed
    PhoneticContext::updateAuxiliaryText ();
Hiroshi Sumita's avatar
Hiroshi Sumita committed
BopomofoContext::commit (CommitType type)
{
    if (G_UNLIKELY (m_buffer.empty ()))
        return;

    m_buffer.clear ();

Hiroshi Sumita's avatar
Hiroshi Sumita committed
    if (G_LIKELY (type == TYPE_CONVERTED)) {
        m_buffer << m_phrase_editor.selectedString ();


        if (m_selected_special_phrase.empty ()) {
            p = textAfterPinyin (m_buffer.utf8Length ());
        }
        else {
            m_buffer << m_selected_special_phrase;
            p = textAfterCursor ();
        }

        while (*p != '\0') {
            m_buffer.appendUnichar ((unichar)bopomofo_char[keyvalToBopomofo (*p++)]);
Hiroshi Sumita's avatar
Hiroshi Sumita committed

        m_phrase_editor.commit ();
Hiroshi Sumita's avatar
Hiroshi Sumita committed
    else if (type == TYPE_PHONETIC) {
        const char *p = m_text;
Hiroshi Sumita's avatar
Hiroshi Sumita committed
        while (*p != '\0') {
            m_buffer.appendUnichar ((unichar)bopomofo_char[keyvalToBopomofo (*p++)]);
Hiroshi Sumita's avatar
Hiroshi Sumita committed
        }
    } else {
        m_buffer = m_text;
        m_phrase_editor.reset ();
Hiroshi Sumita's avatar
Hiroshi Sumita committed

    resetContext ();
    update ();
Hiroshi Sumita's avatar
Hiroshi Sumita committed
    PhoneticContext::commitText (m_buffer);
}

void
BopomofoContext::updatePreeditText (void)
{
    /* preedit text = selected phrases + highlight candidate + rest text */
    if (G_UNLIKELY (m_phrase_editor.empty () && m_text.empty ())) {
        m_preedit_text.clear ();
Hiroshi Sumita's avatar
Hiroshi Sumita committed
        PhoneticContext::updatePreeditText ();
    size_t edit_begin_word = 0;
    size_t edit_end_word = 0;
    size_t edit_begin_byte = 0;
    size_t edit_end_byte = 0;

    m_buffer.clear ();
    m_preedit_text.clear ();

    /* add selected phrases */
    m_buffer << m_phrase_editor.selectedString ();

    if (G_UNLIKELY (! m_selected_special_phrase.empty ())) {
        /* add selected special phrase */
        m_buffer << m_selected_special_phrase;
        edit_begin_word = edit_end_word = m_buffer.utf8Length ();
        edit_begin_byte = edit_end_byte = m_buffer.size ();
        /* append text after cursor */
        m_buffer << textAfterCursor ();
    }
    else {
        edit_begin_word = m_buffer.utf8Length ();
        edit_begin_byte = m_buffer.size ();

        if (hasCandidate (0)) {
            size_t index = m_focused_candidate;

            if (index < m_special_phrases.size ()) {
                m_buffer << m_special_phrases[index].c_str ();
                edit_end_word = m_buffer.utf8Length ();
                edit_end_byte = m_buffer.size ();

                /* append text after cursor */
                m_buffer << textAfterCursor ();
            }
            else {
                const Phrase & candidate = m_phrase_editor.candidate (index - m_special_phrases.size ());
                if (m_text.size () == m_cursor) {
                    /* cursor at end */
                    if (m_config.modeSimp ())
                        m_buffer << candidate;
                    else
                        SimpTradConverter::simpToTrad (candidate, m_buffer);
                    edit_end_word = m_buffer.utf8Length ();
                    edit_end_byte = m_buffer.size ();
                    /* append rest text */
                    for (const char *p=m_text.c_str() + m_pinyin_len; *p ;++p) {
                        m_buffer.appendUnichar(bopomofo_char[keyvalToBopomofo(*p)]);
                    }
                }
                else {
                    for (const char *p = m_text.c_str (); *p; ++p) {
                        if ((size_t) (p - m_text.c_str ()) == m_cursor)
                            m_buffer << ' ';
                        m_buffer.appendUnichar (bopomofo_char[keyvalToBopomofo (*p)]);
                    }
                    edit_end_word = m_buffer.utf8Length ();
                    edit_end_byte = m_buffer.size ();
                }
            }
        }
        else {
            edit_end_word = m_buffer.utf8Length ();
            edit_end_byte = m_buffer.size ();
            for (const char *p=m_text.c_str () + m_pinyin_len; *p ; ++p) {
                m_buffer.appendUnichar (bopomofo_char[keyvalToBopomofo (*p)]);
            }
        }
    }

    m_preedit_text.selected_text = m_buffer.substr (0, edit_begin_byte);
    m_preedit_text.candidate_text = m_buffer.substr (edit_begin_byte, edit_end_byte - edit_begin_byte);
    m_preedit_text.rest_text = m_buffer.substr (edit_end_byte);
Hiroshi Sumita's avatar
Hiroshi Sumita committed
    PhoneticContext::updatePreeditText ();
static int
keyboard_cmp (const void * p1, const void * p2)
    const int s1 = GPOINTER_TO_INT (p1);
    const unsigned char *s2 = (const unsigned char *) p2;
    return s1 - s2[0];
}

int
BopomofoContext::keyvalToBopomofo(int ch)
    const unsigned int keyboard = m_config.bopomofoKeyboardMapping ();
    const unsigned char *brs;
    brs = (const unsigned char *) std::bsearch (GINT_TO_POINTER (ch),
                                       bopomofo_keyboard[keyboard],
                                       G_N_ELEMENTS (bopomofo_keyboard[keyboard]),
                                       sizeof(bopomofo_keyboard[keyboard][0]),
                                       keyboard_cmp);
    if (G_UNLIKELY (brs == NULL))
        return BOPOMOFO_ZERO;
    return brs[1];
}

};  // namespace PyZy