diff --git a/CMakeLists.txt b/CMakeLists.txt index f1b3deb..5da90d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,11 @@ cmake_minimum_required(VERSION 3.21) project(fcitx5-quwei) +find_package(Gettext REQUIRED) find_package(Fcitx5Core REQUIRED) # Setup some compiler option that is generally useful and compatible with Fcitx 5 (C++17) include("${FCITX_INSTALL_CMAKECONFIG_DIR}/Fcitx5Utils/Fcitx5CompilerSettings.cmake") add_subdirectory(src) +add_subdirectory(po) diff --git a/po/CMakeLists.txt b/po/CMakeLists.txt new file mode 100644 index 0000000..461c734 --- /dev/null +++ b/po/CMakeLists.txt @@ -0,0 +1 @@ +fcitx5_install_translation(fcitx5-quwei) diff --git a/po/LINGUAS b/po/LINGUAS new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/po/LINGUAS @@ -0,0 +1 @@ + diff --git a/po/fcitx5-quwei.pot b/po/fcitx5-quwei.pot new file mode 100644 index 0000000..15d4bfe --- /dev/null +++ b/po/fcitx5-quwei.pot @@ -0,0 +1,22 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the fcitx5-quwei package. +# +msgid "" +msgstr "" +"Project-Id-Version: fcitx5-quwei\n" +"Report-Msgid-Bugs-To: fcitx-dev@googlegroups.com\n" +"POT-Creation-Date: 2021-11-16 10:07-0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: LANG\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Translatable name of the input method +#: build/src/quwei-addon.conf.in:3 src/quwei-addon.conf.in.in:3 +#: src/quwei.conf.in:4 +msgid "Quwei" +msgstr "" diff --git a/po/zh_CN.po b/po/zh_CN.po new file mode 100644 index 0000000..15d4bfe --- /dev/null +++ b/po/zh_CN.po @@ -0,0 +1,22 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the fcitx5-quwei package. +# +msgid "" +msgstr "" +"Project-Id-Version: fcitx5-quwei\n" +"Report-Msgid-Bugs-To: fcitx-dev@googlegroups.com\n" +"POT-Creation-Date: 2021-11-16 10:07-0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: LANG\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Translatable name of the input method +#: build/src/quwei-addon.conf.in:3 src/quwei-addon.conf.in.in:3 +#: src/quwei.conf.in:4 +msgid "Quwei" +msgstr "" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 37188b5..44a2155 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,13 +1,14 @@ # Make sure it produce quwei.so instead of libquwei.so add_library(quwei SHARED quwei.cpp) target_link_libraries(quwei PRIVATE Fcitx5::Core) -set_target_properties(quwei PROPERTIES PREFIX "") install(TARGETS quwei DESTINATION "${FCITX_INSTALL_LIBDIR}/fcitx5") # Addon config file # We need additional layer of conversion because we want PROJECT_VERSION in it. -configure_file(quwei-addon.conf.in quwei-addon.conf) +configure_file(quwei-addon.conf.in.in quwei-addon.conf.in) +fcitx5_translate_desktop_file("${CMAKE_CURRENT_BINARY_DIR}/quwei-addon.conf.in" quwei-addon.conf) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/quwei-addon.conf" RENAME quwei.conf DESTINATION "${FCITX_INSTALL_PKGDATADIR}/addon") # Input Method registration file -install(FILES "quwei.conf" DESTINATION "${FCITX_INSTALL_PKGDATADIR}/inputmethod") +fcitx5_translate_desktop_file(quwei.conf.in quwei.conf) +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/quwei.conf" DESTINATION "${FCITX_INSTALL_PKGDATADIR}/inputmethod") diff --git a/src/quwei-addon.conf.in b/src/quwei-addon.conf.in.in similarity index 92% rename from src/quwei-addon.conf.in rename to src/quwei-addon.conf.in.in index e605629..66bdaac 100644 --- a/src/quwei-addon.conf.in +++ b/src/quwei-addon.conf.in.in @@ -2,7 +2,7 @@ Name=Quwei Category=InputMethod Version=@PROJECT_VERSION@ -Library=quwei +Library=libquwei Type=SharedLibrary OnDemand=True Configurable=True diff --git a/src/quwei.conf b/src/quwei.conf.in similarity index 100% rename from src/quwei.conf rename to src/quwei.conf.in diff --git a/src/quwei.cpp b/src/quwei.cpp index 29505a8..4ee6741 100644 --- a/src/quwei.cpp +++ b/src/quwei.cpp @@ -5,11 +5,272 @@ * */ #include "quwei.h" +#include +#include +#include +#include +#include +#include + +namespace { + +// Template to help resolve iconv parameter issue on BSD. +template +struct function_traits; + +// partial specialization for function pointer +template +struct function_traits { + using result_type = R; + using argument_types = std::tuple; +}; + +template +using second_argument_type = typename std::tuple_element< + 1, typename function_traits::argument_types>::type; + +static const std::array selectionKeys = { + fcitx::Key{FcitxKey_1}, fcitx::Key{FcitxKey_2}, fcitx::Key{FcitxKey_3}, + fcitx::Key{FcitxKey_4}, fcitx::Key{FcitxKey_5}, fcitx::Key{FcitxKey_6}, + fcitx::Key{FcitxKey_7}, fcitx::Key{FcitxKey_8}, fcitx::Key{FcitxKey_9}, + fcitx::Key{FcitxKey_0}, +}; + +class QuweiCandidateWord : public fcitx::CandidateWord { +public: + QuweiCandidateWord(QuweiEngine *engine, std::string text) + : engine_(engine) { + setText(fcitx::Text(std::move(text))); + } + + void select(fcitx::InputContext *inputContext) const override { + inputContext->commitString(text().toString()); + auto state = inputContext->propertyFor(engine_->factory()); + state->reset(); + } + +private: + QuweiEngine *engine_; +}; + +class QuweiCandidateList : public fcitx::CandidateList, + public fcitx::PageableCandidateList, + public fcitx::CursorMovableCandidateList { +public: + QuweiCandidateList(QuweiEngine *engine, fcitx::InputContext *ic, + const std::string &code) + : engine_(engine), ic_(ic), code_(std::stoi(code)) { + setPageable(this); + setCursorMovable(this); + for (int i = 0; i < 10; i++) { + const char label[2] = {static_cast('0' + (i + 1) % 10), '\0'}; + labels_[i].append(label); + labels_[i].append(". "); + } + generate(); + } + + const fcitx::Text &label(int idx) const override { return labels_[idx]; } + + const fcitx::CandidateWord &candidate(int idx) const override { + return *candidates_[idx]; + } + int size() const override { return 10; } + fcitx::CandidateLayoutHint layoutHint() const override { + return fcitx::CandidateLayoutHint::NotSet; + } + bool usedNextBefore() const override { return false; } + void prev() override { + if (!hasPrev()) { + return; + } + --code_; + auto state = ic_->propertyFor(engine_->factory()); + state->setCode(code_); + } + void next() override { + if (!hasNext()) { + return; + } + code_++; + auto state = ic_->propertyFor(engine_->factory()); + state->setCode(code_); + } + + bool hasPrev() const override { return code_ > 0; } + + bool hasNext() const override { return code_ < 999; } + + void prevCandidate() override { cursor_ = (cursor_ + 9) % 10; } + + void nextCandidate() override { cursor_ = (cursor_ + 1) % 10; } + + int cursorIndex() const override { return cursor_; } + +private: + void generate() { + for (int i = 0; i < 10; i++) { + auto code = code_ * 10 + (i + 1); + auto qu = code / 100; + auto wei = code % 100; + + // Quwei to GB2312 (0xA0 + qu, 0xA0 + wei) + char in[3]; + if (qu >= 95) { /* Process extend Qu 95 and 96 */ + in[0] = qu - 95 + 0xA8; + in[1] = wei + 0x40; + + /* skip 0xa87f and 0xa97f */ + if (in[1] >= 0x7f) { + in[1]++; + } + } else { + in[0] = qu + 0xa0; + in[1] = wei + 0xa0; + } + + size_t insize = 2, avail = FCITX_UTF8_MAX_LENGTH + 1; + std::remove_pointer_t> + inbuf = in; + + char out[FCITX_UTF8_MAX_LENGTH + 1]; + char *outbuf = out; + iconv(engine_->conv(), &inbuf, &insize, &outbuf, &avail); + *outbuf = '\0'; + candidates_[i] = std::make_unique(engine_, out); + } + } + + QuweiEngine *engine_; + fcitx::InputContext *ic_; + fcitx::Text labels_[10]; + std::unique_ptr candidates_[10]; + int code_; + int cursor_ = 0; +}; + +} // namespace + +void QuweiState::keyEvent(fcitx::KeyEvent &event) { + if (auto candidateList = ic_->inputPanel().candidateList()) { + int idx = event.key().keyListIndex(selectionKeys); + if (idx >= 0 && idx < candidateList->size()) { + event.accept(); + candidateList->candidate(idx).select(ic_); + return; + } + if (event.key().checkKeyList( + engine_->instance()->globalConfig().defaultPrevPage())) { + if (auto *pageable = candidateList->toPageable(); + pageable && pageable->hasPrev()) { + event.accept(); + pageable->prev(); + ic_->updateUserInterface( + fcitx::UserInterfaceComponent::InputPanel); + } + return event.filterAndAccept(); + } + + if (event.key().checkKeyList( + engine_->instance()->globalConfig().defaultNextPage())) { + if (auto *pageable = candidateList->toPageable(); + pageable && pageable->hasNext()) { + pageable->next(); + ic_->updateUserInterface( + fcitx::UserInterfaceComponent::InputPanel); + } + return event.filterAndAccept(); + } + } + + if (buffer_.empty()) { + if (!event.key().isDigit()) { + return; + } + } else { + if (event.key().check(FcitxKey_BackSpace)) { + buffer_.backspace(); + updateUI(); + return event.filterAndAccept(); + } + if (event.key().check(FcitxKey_Return)) { + ic_->commitString(buffer_.userInput()); + reset(); + return event.filterAndAccept(); + } + if (event.key().check(FcitxKey_Escape)) { + reset(); + return event.filterAndAccept(); + } + if (!event.key().isDigit()) { + return event.filterAndAccept(); + } + } + + buffer_.type(event.key().sym()); + updateUI(); + return event.filterAndAccept(); +} + +void QuweiState::setCode(int code) { + if (code < 0 || code > 999) { + return; + } + buffer_.clear(); + auto codeStr = std::to_string(code); + while (codeStr.size() < 3) { + codeStr = "0" + codeStr; + } + buffer_.type(std::to_string(code)); + updateUI(); +} + +void QuweiState::updateUI() { + auto &inputPanel = ic_->inputPanel(); + inputPanel.reset(); + if (buffer_.size() == 3) { + inputPanel.setCandidateList(std::make_unique( + engine_, ic_, buffer_.userInput())); + } + if (ic_->capabilityFlags().test(fcitx::CapabilityFlag::Preedit)) { + fcitx::Text preedit(buffer_.userInput(), + fcitx::TextFormatFlag::HighLight); + inputPanel.setClientPreedit(preedit); + } else { + fcitx::Text preedit(buffer_.userInput()); + inputPanel.setPreedit(preedit); + } + ic_->updateUserInterface(fcitx::UserInterfaceComponent::InputPanel); + ic_->updatePreedit(); +} + +QuweiEngine::QuweiEngine(fcitx::Instance *instance) + : instance_(instance), factory_([this](fcitx::InputContext &ic) { + return new QuweiState(this, &ic); + }) { + conv_ = iconv_open("UTF-8", "GB18030"); + if (conv_ == reinterpret_cast(-1)) { + throw std::runtime_error("Failed to create converter"); + } + instance->inputContextManager().registerProperty("quweiState", &factory_); +} void QuweiEngine::keyEvent(const fcitx::InputMethodEntry &entry, fcitx::KeyEvent &keyEvent) { FCITX_UNUSED(entry); - FCITX_INFO() << keyEvent.key() << " isRelease=" << keyEvent.isRelease(); + if (keyEvent.isRelease() || keyEvent.key().states()) { + return; + } + // FCITX_INFO() << keyEvent.key() << " isRelease=" << keyEvent.isRelease(); + auto ic = keyEvent.inputContext(); + auto *state = ic->propertyFor(&factory_); + state->keyEvent(keyEvent); +} + +void QuweiEngine::reset(const fcitx::InputMethodEntry &, + fcitx::InputContextEvent &event) { + auto *state = event.inputContext()->propertyFor(&factory_); + state->reset(); } FCITX_ADDON_FACTORY(QuweiEngineFactory); diff --git a/src/quwei.h b/src/quwei.h index 39bc2ea..b2dbcf8 100644 --- a/src/quwei.h +++ b/src/quwei.h @@ -7,18 +7,62 @@ #ifndef _FCITX5_QUWEI_QUWEI_H_ #define _FCITX5_QUWEI_QUWEI_H_ +#include #include +#include +#include +#include #include +#include +#include +#include + +class QuweiEngine; + +class QuweiState : public fcitx::InputContextProperty { +public: + QuweiState(QuweiEngine *engine, fcitx::InputContext *ic) + : engine_(engine), ic_(ic) {} + + void keyEvent(fcitx::KeyEvent &keyEvent); + void setCode(int code); + void updateUI(); + void reset() { + buffer_.clear(); + updateUI(); + } + +private: + QuweiEngine *engine_; + fcitx::InputContext *ic_; + fcitx::InputBuffer buffer_{{fcitx::InputBufferOption::AsciiOnly, + fcitx::InputBufferOption::FixedCursor}}; +}; class QuweiEngine : public fcitx::InputMethodEngineV2 { +public: + QuweiEngine(fcitx::Instance *instance); + void keyEvent(const fcitx::InputMethodEntry &entry, fcitx::KeyEvent &keyEvent) override; + + void reset(const fcitx::InputMethodEntry &, + fcitx::InputContextEvent &event) override; + + auto factory() const { return &factory_; } + auto conv() const { return conv_; } + auto instance() const { return instance_; } + +private: + fcitx::Instance *instance_; + fcitx::FactoryFor factory_; + iconv_t conv_; }; class QuweiEngineFactory : public fcitx::AddonFactory { fcitx::AddonInstance *create(fcitx::AddonManager *manager) override { FCITX_UNUSED(manager); - return new QuweiEngine; + return new QuweiEngine(manager->instance()); } };