diff --git a/CMakeLists.txt b/CMakeLists.txt index bd839bc..05047e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,14 +25,19 @@ add_definitions(-DFCITX_RIME_VERSION=\"${PROJECT_VERSION}\") include("${FCITX_INSTALL_CMAKECONFIG_DIR}/Fcitx5Utils/Fcitx5CompilerSettings.cmake") +option(ENABLE_GUI "Build Fcitx5 Rime Config GUI Tool" ON) + add_subdirectory(po) add_subdirectory(src) add_subdirectory(data) +if(ENABLE_GUI) + add_subdirectory(gui) +endif() + fcitx5_translate_desktop_file(org.fcitx.fcitx5-rime.metainfo.xml.in org.fcitx.fcitx5-rime.metainfo.xml XML) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/org.fcitx.fcitx5-rime.metainfo.xml" DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) - diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt new file mode 100644 index 0000000..3e7d5d0 --- /dev/null +++ b/gui/CMakeLists.txt @@ -0,0 +1,38 @@ +set(REQUIRED_QT5_VERSION 5.7) + +find_package(Qt5 ${REQUIRED_QT5_VERSION} CONFIG REQUIRED COMPONENTS Core Widgets) +find_package(Fcitx5Qt5WidgetsAddons REQUIRED) + +set(RIME_CONFIG_SRCS + Main.cpp + ConfigMain.cpp + Model.cpp + RimeConfigParser.cpp + keylistwidget.cpp) + +set(RIME_CONFIG_HDRS + Main.h + ConfigMain.h + Model.h + Common.h + RimeConfigParser.h + keylistwidget.h) + +add_library(rime-config + MODULE ${RIME_CONFIG_SRCS}) + +target_include_directories(rime-config PRIVATE ${PROJECT_SOURCE_DIR}/gui) + +set_target_properties(rime-config PROPERTIES + LINK_FLAGS "-Wl,--no-undefined" + AUTOMOC TRUE + AUTOUIC TRUE + AUTOUIC_OPTIONS "-tr=fcitx::tr2fcitx;--include=fcitxqti18nhelper.h") + +target_link_libraries(rime-config + Qt5::Widgets + Fcitx5::Core + Fcitx5Qt5::WidgetsAddons + PkgConfig::Rime) + +install(TARGETS rime-config DESTINATION ${CMAKE_INSTALL_LIBDIR}/fcitx5/qt5) diff --git a/gui/ConfigMain.cpp b/gui/ConfigMain.cpp new file mode 100644 index 0000000..81ed080 --- /dev/null +++ b/gui/ConfigMain.cpp @@ -0,0 +1,503 @@ +/* + * SPDX-FileCopyrightText: 2020~2020 xzhao9 + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ +#include "ConfigMain.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fcitx::rime { + +ConfigMain::ConfigMain(QWidget *parent) + : FcitxQtConfigUIWidget(parent), + model_(std::make_unique()), inError_(false) { + // Setup UI + setupUi(this); + + // listViews for currentIM and availIM + activeIMModel_ = std::make_unique(this); + currentIMView->setModel(activeIMModel_.get()); + availIMModel_ = std::make_unique(this); + availIMView->setModel(availIMModel_.get()); + + // Shortcuts Tab + connect(candidateWordNumber, QOverload::of(&QSpinBox::valueChanged), + this, &ConfigMain::stateChanged); + connect(shiftLeftCombo, QOverload::of(&QComboBox::currentIndexChanged), + this, &ConfigMain::stateChanged); + connect(shiftRightCombo, + QOverload::of(&QComboBox::currentIndexChanged), this, + &ConfigMain::stateChanged); + auto keywgts = shortCutTab->findChildren(); + for (auto &keywgt : keywgts) { + connect(keywgt, &kcm::KeyListWidget::keyChanged, this, + &ConfigMain::keytoggleChanged); + } + + // Schemas Tab + connect(removeIMButton, &QPushButton::clicked, this, &ConfigMain::removeIM); + connect(addIMButton, &QPushButton::clicked, this, &ConfigMain::addIM); + connect(moveUpButton, &QPushButton::clicked, this, &ConfigMain::moveUpIM); + connect(moveDownButton, &QPushButton::clicked, this, + &ConfigMain::moveDownIM); + connect(availIMView->selectionModel(), &QItemSelectionModel::currentChanged, + this, &ConfigMain::availIMSelectionChanged); + connect(currentIMView->selectionModel(), + &QItemSelectionModel::currentChanged, this, + &ConfigMain::activeIMSelectionChanged); + + if (!yamlToModel()) { // Load data from yaml + disableUi(_("Failed to load Rime config or api. Please check your Rime " + "config or installation.")); + return; + } + + modelToUi(); +} + +ConfigMain::~ConfigMain() {} + +void ConfigMain::keytoggleChanged() { stateChanged(); } + +// SLOTs +void ConfigMain::stateChanged() { emit changed(true); } + +void ConfigMain::focusSelectedIM(const QString im_name) { + // search enabled IM first + int sz = activeIMModel_->rowCount(); + for (int i = 0; i < sz; i++) { + QModelIndex ind = activeIMModel_->index(i, 0); + const QString name = + activeIMModel_->data(ind, Qt::DisplayRole).toString(); + if (name == im_name) { + currentIMView->setCurrentIndex(ind); + currentIMView->setFocus(); + return; + } + } + // if not found, search avali IM list + sz = availIMModel_->rowCount(); + for (int i = 0; i < sz; i++) { + QModelIndex ind = availIMModel_->index(i, 0); + const QString name = + availIMModel_->data(ind, Qt::DisplayRole).toString(); + if (name == im_name) { + availIMView->setCurrentIndex(ind); + availIMView->setFocus(); + return; + } + } +} + +void ConfigMain::addIM() { + if (availIMView->currentIndex().isValid()) { + const QString uniqueName = + availIMView->currentIndex().data(Qt::DisplayRole).toString(); + int largest = 0; + int find = -1; + for (int i = 0; i < model_->schemas_.size(); i++) { + if (model_->schemas_[i].name == uniqueName) { + find = i; + } + if (model_->schemas_[i].index > largest) { + largest = model_->schemas_[i].index; + } + } + if (find != -1) { + model_->schemas_[find].active = true; + model_->schemas_[find].index = largest + 1; + } + + model_->sortSchemas(); + updateIMList(); + focusSelectedIM(uniqueName); + stateChanged(); + } +} + +void ConfigMain::removeIM() { + if (currentIMView->currentIndex().isValid()) { + const QString uniqueName = + currentIMView->currentIndex().data(Qt::DisplayRole).toString(); + for (int i = 0; i < model_->schemas_.size(); i++) { + if (model_->schemas_[i].name == uniqueName) { + model_->schemas_[i].active = false; + model_->schemas_[i].index = 0; + } + } + model_->sortSchemas(); + updateIMList(); + focusSelectedIM(uniqueName); + stateChanged(); + } +} + +void ConfigMain::moveUpIM() { + if (currentIMView->currentIndex().isValid()) { + const QString uniqueName = + currentIMView->currentIndex().data(Qt::DisplayRole).toString(); + int curIndex = -1; + for (int i = 0; i < model_->schemas_.size(); i++) { + if (model_->schemas_[i].name == uniqueName) { + curIndex = model_->schemas_[i].index; + Q_ASSERT(curIndex == (i + 1)); // make sure the schema is sorted + } + } + // can't move up the top schema because the button should be grey + if (curIndex == -1 || curIndex == 0) { + return; + } + + int temp; + temp = model_->schemas_[curIndex - 1].index; + model_->schemas_[curIndex - 1].index = + model_->schemas_[curIndex - 2].index; + model_->schemas_[curIndex - 2].index = temp; + model_->sortSchemas(); + updateIMList(); + focusSelectedIM(uniqueName); + stateChanged(); + } +} + +void ConfigMain::moveDownIM() { + if (currentIMView->currentIndex().isValid()) { + const QString uniqueName = + currentIMView->currentIndex().data(Qt::DisplayRole).toString(); + int curIndex = -1; + int temp; + + for (int i = 0; i < model_->schemas_.size(); i++) { + if (model_->schemas_[i].name == uniqueName) { + curIndex = model_->schemas_[i].index; + Q_ASSERT(curIndex == (i + 1)); // make sure the schema is sorted + } + } + // can't move down the bottom schema because the button should be grey + if (curIndex == -1 || curIndex == 0) { + return; + } + temp = model_->schemas_[curIndex - 1].index; + model_->schemas_[curIndex - 1].index = model_->schemas_[curIndex].index; + model_->schemas_[curIndex].index = temp; + model_->sortSchemas(); + updateIMList(); + focusSelectedIM(uniqueName); + stateChanged(); + } +} + +void ConfigMain::availIMSelectionChanged() { + if (!availIMView->currentIndex().isValid()) { + addIMButton->setEnabled(false); + } else { + addIMButton->setEnabled(true); + } +} + +void ConfigMain::activeIMSelectionChanged() { + if (!currentIMView->currentIndex().isValid()) { + removeIMButton->setEnabled(false); + moveUpButton->setEnabled(false); + moveDownButton->setEnabled(false); + // configureButton->setEnabled(false); + } else { + removeIMButton->setEnabled(true); + // configureButton->setEnabled(true); + if (currentIMView->currentIndex().row() == 0) { + moveUpButton->setEnabled(false); + } else { + moveUpButton->setEnabled(true); + } + if (currentIMView->currentIndex().row() == + activeIMModel_->rowCount() - 1) { + moveDownButton->setEnabled(false); + } else { + moveDownButton->setEnabled(true); + } + } +} +// end of SLOTs + +QString ConfigMain::icon() { return "fcitx-rime"; } + +QString ConfigMain::title() { return _("Fcitx5 Rime Config Tool"); } + +void ConfigMain::load() { + if (inError_) { + return; + } + modelToUi(); +} + +void ConfigMain::disableUi(const QString &message) { + setEnabled(false); + QMetaObject::invokeMethod( + this, + [this, message]() { QMessageBox::critical(this, _("Error"), message); }, + Qt::QueuedConnection); + inError_ = true; +} + +void ConfigMain::uiToModel() { + QModelIndex parent; + int seqno = 1; + + model_->candidatePerWord_ = candidateWordNumber->value(); + + model_->toggleKeys_ = toggle_shortcut->keys(); + model_->asciiKeys_ = hotkey_ascii->keys(); + model_->pgdownKeys_ = hotkey_pagedown->keys(); + model_->pgupKeys_ = hotkey_pageup->keys(); + model_->trasimKeys_ = hotkey_transim->keys(); + model_->halffullKeys_ = hotkey_hfshape->keys(); + + if (model_->switchKeys_.size() >= 2) { + model_->switchKeys_[0] = + textToSwitchKey(shiftLeftCombo->currentIndex()); + model_->switchKeys_[1] = + textToSwitchKey(shiftRightCombo->currentIndex()); + } + + // clear cuurent model and save from the ui + for (int i = 0; i < model_->schemas_.size(); i++) { + model_->schemas_[i].index = 0; + model_->schemas_[i].active = false; + } + + for (int r = 0; r < activeIMModel_->rowCount(parent); ++r) { + QModelIndex index = activeIMModel_->index(r, 0, parent); + QVariant name = activeIMModel_->data(index); + for (int i = 0; i < model_->schemas_.size(); i++) { + if (model_->schemas_[i].name == name) { + model_->schemas_[i].index = seqno++; + model_->schemas_[i].active = true; + } + } + } + model_->sortSchemas(); +} + +void ConfigMain::save() { + if (inError_) { + return; + } + uiToModel(); + + modelToYaml(); + emit changed(false); + emit saveFinished(); +} + +void ConfigMain::setSwitchKey(QComboBox *box, SwitchKeyFunction switchKey) { + int index = -1; + switch (switchKey) { + case SwitchKeyFunction::Noop: + index = 0; + break; + case SwitchKeyFunction::InlineASCII: + index = 1; + break; + case SwitchKeyFunction::CommitText: + index = 2; + break; + case SwitchKeyFunction::CommitCode: + index = 3; + break; + case SwitchKeyFunction::Clear: + index = 4; + break; + }; + box->setCurrentIndex(index); +} + +SwitchKeyFunction ConfigMain::textToSwitchKey(int currentIndex) { + switch (currentIndex) { + case 0: + return SwitchKeyFunction::Noop; + case 1: + return SwitchKeyFunction::InlineASCII; + case 2: + return SwitchKeyFunction::CommitText; + case 3: + return SwitchKeyFunction::CommitCode; + case 4: + return SwitchKeyFunction::Clear; + default: + return SwitchKeyFunction::Noop; + } +} + +void ConfigMain::modelToUi() { + candidateWordNumber->setValue(model_->candidatePerWord_); + + // set shortcut keys + toggle_shortcut->setKeys(model_->toggleKeys_); + hotkey_pagedown->setKeys(model_->pgdownKeys_); + hotkey_pageup->setKeys(model_->pgupKeys_); + hotkey_ascii->setKeys(model_->asciiKeys_); + hotkey_transim->setKeys(model_->trasimKeys_); + hotkey_hfshape->setKeys(model_->halffullKeys_); + + // set switch keys + if (model_->switchKeys_.size() >= 2) { + setSwitchKey(shiftLeftCombo, model_->switchKeys_[0]); + setSwitchKey(shiftRightCombo, model_->switchKeys_[1]); + } + + // Clear both models + activeIMModel_->clear(); + availIMModel_->clear(); + // Set available and enabled input methods + for (int i = 0; i < model_->schemas_.size(); i++) { + auto &schema = model_->schemas_[i]; + if (schema.active) { + QStandardItem *activeSchema = new QStandardItem(schema.name); + activeSchema->setEditable(false); + activeIMModel_->appendRow(activeSchema); + } else { + QStandardItem *inactiveSchema = new QStandardItem(schema.name); + inactiveSchema->setEditable(false); + availIMModel_->appendRow(inactiveSchema); + } + } +} + +void ConfigMain::updateIMList() { + availIMModel_->removeRows(0, availIMModel_->rowCount()); + activeIMModel_->removeRows(0, activeIMModel_->rowCount()); + for (int i = 0; i < model_->schemas_.size(); i++) { + auto &schema = model_->schemas_[i]; + if (schema.active) { + QStandardItem *activeSchema = new QStandardItem(schema.name); + activeSchema->setEditable(false); + activeIMModel_->appendRow(activeSchema); + } else { + QStandardItem *inactiveSchema = new QStandardItem(schema.name); + inactiveSchema->setEditable(false); + availIMModel_->appendRow(inactiveSchema); + } + } +} + +void ConfigMain::modelToYaml() { + std::vector toggleKeys; + std::vector schemaNames; + + config_.setPageSize(model_->candidatePerWord_); + + for (int i = 0; i < model_->toggleKeys_.size(); i++) { + toggleKeys.push_back(model_->toggleKeys_[i].toString()); + } + + config_.setToggleKeys(toggleKeys); + config_.setKeybindings(model_->getKeybindings()); + config_.setSwitchKeys(std::vector( + model_->switchKeys_.begin(), model_->switchKeys_.end())); + + // set active schema list + schemaNames.reserve(model_->schemas_.size()); + for (int i = 0; i < model_->schemas_.size(); i++) { + if (model_->schemas_[i].index == 0) { + break; + } else { + schemaNames.push_back(model_->schemas_[i].id.toStdString()); + } + } + config_.setSchemas(schemaNames); + + inError_ = !(config_.sync()); + return; +} + +bool ConfigMain::yamlToModel() { + if (config_.isError()) { + return false; + } + + // load page size + int pageSize = 0; + if (config_.getPageSize(&pageSize)) { + model_->candidatePerWord_ = pageSize; + } else { + model_->candidatePerWord_ = defaultPageSize; + } + + // load toggle keys + auto toggleKeys = config_.getToggleKeys(); + for (const auto &toggleKey : toggleKeys) { + if (!toggleKey.empty()) { // skip the empty keys + Key data = Key(toggleKey.data()); + model_->toggleKeys_.push_back(std::move(data)); + } + } + + // load keybindings + auto bindings = config_.getKeybindings(); + model_->setKeybindings(std::move(bindings)); + + // load switchkeys + auto switchKeys = config_.getSwitchKeys(); + model_->switchKeys_ = + QList(switchKeys.begin(), switchKeys.end()); + + // load schemas + getAvailableSchemas(); + return true; +} + +void ConfigMain::getAvailableSchemas() { + const char *userPath = RimeGetUserDataDir(); + const char *sysPath = RimeGetSharedDataDir(); + + QSet files; + for (auto path : {sysPath, userPath}) { + if (!path) { + continue; + } + QDir dir(path); + QList entryList = dir.entryList(QStringList("*.schema.yaml"), + QDir::Files | QDir::Readable); + files.unite(QSet(entryList.begin(), entryList.end())); + } + + auto filesList = files.values(); + filesList.sort(); + + for (const auto &file : filesList) { + auto schema = FcitxRimeSchema(); + QString fullPath; + for (auto path : {userPath, sysPath}) { + QDir dir(path); + if (dir.exists(file)) { + fullPath = dir.filePath(file); + break; + } + } + schema.path = fullPath; + QFile fd(fullPath); + if (!fd.open(QIODevice::ReadOnly)) { + continue; + } + auto yamlData = fd.readAll(); + auto name = config_.stringFromYAML(yamlData.constData(), "schema/name"); + auto id = + config_.stringFromYAML(yamlData.constData(), "schema/schema_id"); + schema.name = QString::fromStdString(name); + schema.id = QString::fromStdString(id); + schema.index = config_.schemaIndex(id.data()); + schema.active = static_cast(schema.index); + model_->schemas_.push_back(schema); + } + model_->sortSchemas(); +} + +} // namespace fcitx::rime diff --git a/gui/ConfigMain.h b/gui/ConfigMain.h new file mode 100644 index 0000000..f6d4e44 --- /dev/null +++ b/gui/ConfigMain.h @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2020~2020 xzhao9 + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ +#ifndef _RIME_GUI_CONFIGMAIN_H +#define _RIME_GUI_CONFIGMAIN_H + +#include +#include +#include +#include + +#include "Model.h" +#include "RimeConfigParser.h" +#include "ui_ConfigMain.h" + +namespace fcitx::rime { + +class ConfigMain : public FcitxQtConfigUIWidget, private Ui::MainUI { + Q_OBJECT +public: + explicit ConfigMain(QWidget *parent = 0); + QString title() override; + ~ConfigMain(); + void load() override; + void save() override; + bool asyncSave() override { return true; } + + QString icon() override; +public slots: + void keytoggleChanged(); + void stateChanged(); + void addIM(); + void removeIM(); + void moveUpIM(); + void moveDownIM(); + void availIMSelectionChanged(); + void activeIMSelectionChanged(); + +private: + void disableUi(const QString &message); + bool yamlToModel(); + void uiToModel(); + void modelToUi(); + void modelToYaml(); + void getAvailableSchemas(); + void updateIMList(); + void focusSelectedIM(const QString im_name); + void setSwitchKey(QComboBox *box, SwitchKeyFunction switchKey); + SwitchKeyFunction textToSwitchKey(int currentIndex); + + RimeConfigParser config_; + std::unique_ptr model_; + std::unique_ptr activeIMModel_; + std::unique_ptr availIMModel_; + + bool inError_; +}; + +} // namespace fcitx::rime + +#endif // _RIME_GUI_CONFIGMAIN_H diff --git a/gui/ConfigMain.ui b/gui/ConfigMain.ui new file mode 100644 index 0000000..2eff6dc --- /dev/null +++ b/gui/ConfigMain.ui @@ -0,0 +1,443 @@ + + + MainUI + + + + 0 + 0 + 680 + 626 + + + + + 680 + 500 + + + + MainWindow + + + + .. + + + + + + 0 + + + + Shortcut + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + true + + + + + 0 + 0 + 648 + 557 + + + + + + + Call-out Menu + + + + + + + + 100 + 0 + + + + + + + + Page Up + + + + + + + Page Down + + + + + + + Half/Full Shape + + + + + + + Western/Eastern + + + + + + + Traditional/Simplified + + + + + + + + 100 + 0 + + + + + + + + + 100 + 0 + + + + + + + + + 100 + 0 + + + + + + + + + 100 + 0 + + + + + + + + + 100 + 0 + + + + + + + + Candidate Word Number + + + + + + + Left Shift Key + + + + + + + Right Shift Key + + + + + + + + Noop + + + + + Inline ASCII + + + + + Commit Text + + + + + Commit Code + + + + + Clear + + + + + + + + + Noop + + + + + Inline ASCII + + + + + Commit Text + + + + + Commit Code + + + + + Clear + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + Schema + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + + + + Available Input Schemas: + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + false + + + + .. + + + + + + + false + + + + .. + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Active Input Schemas: + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + false + + + + .. + + + + + + + false + + + + .. + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + <b>Select Input Schema:</b> + + + + + + + + + + + + fcitx::kcm::KeyListWidget + QWidget +
keylistwidget.h
+
+
+ + +
diff --git a/gui/Main.cpp b/gui/Main.cpp new file mode 100644 index 0000000..1b2c351 --- /dev/null +++ b/gui/Main.cpp @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2020~2020 xzhao9 + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "Main.h" +#include "ConfigMain.h" +#include +#include + +namespace fcitx::rime { + +// FcitxQtConfigUIPlugin : QObject, FcitxQtConfigUIFactoryInterface +RimeConfigPlugin::RimeConfigPlugin(QObject *parent) + : FcitxQtConfigUIPlugin(parent) {} + +FcitxQtConfigUIWidget *RimeConfigPlugin::create(const QString &key) { + Q_UNUSED(key); + return new ConfigMain; +} + +} // namespace fcitx::rime diff --git a/gui/Main.h b/gui/Main.h new file mode 100644 index 0000000..a8424fe --- /dev/null +++ b/gui/Main.h @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2020~2020 xzhao9 + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ +#ifndef _RIME_GUI_MAIN_H +#define _RIME_GUI_MAIN_H + +#include + +namespace fcitx::rime { + +class RimeConfigPlugin : public FcitxQtConfigUIPlugin { + Q_OBJECT +public: + Q_PLUGIN_METADATA(IID FcitxQtConfigUIFactoryInterface_iid FILE + "rime-config.json") + explicit RimeConfigPlugin(QObject *parent = nullptr); + FcitxQtConfigUIWidget *create(const QString &key) override; +}; + +} // namespace fcitx::rime + +#endif // _RIME_GUI_MAIN_H diff --git a/gui/Model.cpp b/gui/Model.cpp new file mode 100644 index 0000000..1748494 --- /dev/null +++ b/gui/Model.cpp @@ -0,0 +1,121 @@ +/* + * SPDX-FileCopyrightText: 2020~2020 xzhao9 + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ +#include "Model.h" +#include +#include +#include + +namespace fcitx::rime { + +void RimeConfigDataModel::sortSchemas() { + std::sort(schemas_.begin(), schemas_.end(), + [](const FcitxRimeSchema &a, const FcitxRimeSchema &b) -> bool { + // if both inactive, sort by id + if (a.index == 0 && b.index == 0) { + return a.id < b.id; + } + if (a.index == 0) { + return false; + } + if (b.index == 0) { + return true; + } + return (a.index < b.index); + }); +} + +void RimeConfigDataModel::sortKeys() { + sortSingleKeySet(toggleKeys_); + sortSingleKeySet(asciiKeys_); + sortSingleKeySet(trasimKeys_); + sortSingleKeySet(halffullKeys_); + sortSingleKeySet(pgupKeys_); + sortSingleKeySet(pgdownKeys_); +} + +void RimeConfigDataModel::sortSingleKeySet(QList &keys) { + std::sort(keys.begin(), keys.end(), [](const Key &a, const Key &b) -> bool { + return a.toString() < b.toString(); + }); + std::unique(keys.begin(), keys.end()); +} + +void RimeConfigDataModel::setKeybindings(std::vector bindings) { + for (const auto &binding : bindings) { + if (binding.accept.empty()) { + continue; + } + if (binding.action == "ascii_mode") { + Key seq(binding.accept); + asciiKeys_.push_back(seq); + } else if (binding.action == "full_shape") { + Key seq(binding.accept); + halffullKeys_.push_back(seq); + } else if (binding.action == "simplification") { + Key seq(binding.accept); + trasimKeys_.push_back(seq); + } else if (binding.action == "Page_Up") { + Key seq(binding.accept); + pgupKeys_.push_back(seq); + } else if (binding.action == "Page_Down") { + Key seq(binding.accept); + pgdownKeys_.push_back(seq); + } + } + sortKeys(); +} + +std::vector RimeConfigDataModel::getKeybindings() { + std::vector out; + // Fill ascii_key + for (auto &ascii : asciiKeys_) { + KeyBinding binding; + binding.action = "ascii_mode"; + binding.when = KeyBindingCondition::Always; + binding.type = KeyBindingType::Toggle; + binding.accept = ascii.toString(); + out.push_back(binding); + } + // Fill trasim_key + for (auto &trasim : trasimKeys_) { + KeyBinding binding; + binding.action = "simplification"; + binding.when = KeyBindingCondition::Always; + binding.type = KeyBindingType::Toggle; + binding.accept = trasim.toString(); + out.push_back(binding); + } + // Fill halffull_key + for (auto &halffull : halffullKeys_) { + KeyBinding binding; + binding.action = "full_shape"; + binding.when = KeyBindingCondition::Always; + binding.type = KeyBindingType::Toggle; + binding.accept = halffull.toString(); + out.push_back(binding); + } + // Fill pgup_key + for (auto &pgup : pgupKeys_) { + KeyBinding binding; + binding.action = "Page_Up"; + binding.when = KeyBindingCondition::HasMenu; + binding.type = KeyBindingType::Send; + binding.accept = pgup.toString(); + out.push_back(binding); + } + // Fill pgdown_key + for (auto &pgup : pgdownKeys_) { + KeyBinding binding; + binding.action = "Page_Down"; + binding.when = KeyBindingCondition::HasMenu; + binding.type = KeyBindingType::Send; + binding.accept = pgup.toString(); + out.push_back(binding); + } + return out; +} + +} // namespace fcitx::rime diff --git a/gui/Model.h b/gui/Model.h new file mode 100644 index 0000000..3ff55a3 --- /dev/null +++ b/gui/Model.h @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: 2020~2020 xzhao9 + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ +#ifndef _RIME_GUI_MODEL_H +#define _RIME_GUI_MODEL_H + +#include "RimeConfigParser.h" +#include +#include +#include + +static constexpr int defaultPageSize = 5; + +namespace fcitx::rime { + +struct FcitxRimeSchema { + QString path; + QString id; + QString name; + int index; // index starts from 1, 0 means not enabled + bool active; +}; + +class RimeConfigDataModel { +public: + int candidatePerWord_; + QList switchKeys_; + QList schemas_; + QList toggleKeys_; + QList asciiKeys_; + QList trasimKeys_; + QList halffullKeys_; + QList pgupKeys_; + QList pgdownKeys_; + + void setKeybindings(const std::vector bindings); + std::vector getKeybindings(); + + void sortSchemas(); + void sortKeys(); + +private: + void sortSingleKeySet(QList &keys); +}; + +} // namespace fcitx::rime + +#endif // _RIME_GUI_MODEL_H diff --git a/gui/RimeConfigParser.cpp b/gui/RimeConfigParser.cpp new file mode 100644 index 0000000..4ea6c96 --- /dev/null +++ b/gui/RimeConfigParser.cpp @@ -0,0 +1,368 @@ +/* + * SPDX-FileCopyrightText: 2020~2020 xzhao9 + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ +#include "RimeConfigParser.h" +#include +#include +#include +#include +#include + +namespace fcitx::rime { + +RimeConfigParser::RimeConfigParser() + : api_(rime_get_api()), defaultConf_({0}), inError_(false) { + RimeModule *module = api_->find_module("levers"); + if (!module) { + inError_ = true; + return; + } + levers_ = (RimeLeversApi *)module->get_api(); + start(true); +} + +RimeConfigParser::~RimeConfigParser() { api_->finalize(); } + +bool RimeConfigParser::isError() { return inError_; } + +bool RimeConfigParser::start(bool firstRun) { + std::string userPath; + + userPath = stringutils::joinPath( + StandardPath::global().userDirectory(StandardPath::Type::PkgData), + "rime"); + + RIME_STRUCT(RimeTraits, fcitx_rime_traits); + fcitx_rime_traits.shared_data_dir = RIME_DATA_DIR; + fcitx_rime_traits.user_data_dir = userPath.c_str(); + fcitx_rime_traits.distribution_name = "Rime"; + fcitx_rime_traits.distribution_code_name = "fcitx-rime-config"; + fcitx_rime_traits.distribution_version = FCITX_RIME_VERSION; + fcitx_rime_traits.app_name = "rime.fcitx-rime-config"; + if (firstRun) { + api_->setup(&fcitx_rime_traits); + } + defaultConf_ = {0}; + api_->initialize(&fcitx_rime_traits); + settings_ = levers_->custom_settings_init("default", "rime_patch"); + levers_->load_settings(settings_); + levers_->settings_get_config(settings_, &defaultConf_); + return true; +} + +void RimeConfigParser::setToggleKeys(const std::vector &keys) { + api_->config_clear(&defaultConf_, "switcher/hotkeys"); + api_->config_create_list(&defaultConf_, "switcher/hotkeys"); + RimeConfigIterator iterator; + api_->config_begin_list(&iterator, &defaultConf_, "switcher/hotkeys"); + api_->config_next(&iterator); + for (size_t i = 0; i < keys.size(); i++) { + api_->config_next(&iterator); + api_->config_set_string(&defaultConf_, iterator.path, keys[i].data()); + } + api_->config_end(&iterator); +} + +std::vector RimeConfigParser::getToggleKeys() { + std::vector result; + listForeach(&defaultConf_, "switcher/hotkeys", + [=, &result](RimeConfig *config, const char *path) { + auto str = api_->config_get_cstring(config, path); + if (str) { + result.push_back(str); + } + return true; + }); + return result; +} + +static const char *keyBindingConditionToString(KeyBindingCondition condition) { + switch (condition) { + case KeyBindingCondition::Composing: + return "composing"; + case KeyBindingCondition::HasMenu: + return "has_menu"; + case KeyBindingCondition::Always: + return "always"; + case KeyBindingCondition::Paging: + return "paging"; + } + return ""; +} + +static KeyBindingCondition keyBindingConditionFromString(std::string_view str) { + if (str == "composing") { + return KeyBindingCondition::Composing; + } else if (str == "has_menu") { + return KeyBindingCondition::HasMenu; + } else if (str == "paging") { + return KeyBindingCondition::Paging; + } else if (str == "always") { + return KeyBindingCondition::Always; + } + return KeyBindingCondition::Composing; +} + +static const char *keybindingTypeToString(KeyBindingType type) { + switch (type) { + case KeyBindingType::Send: + return "send"; + case KeyBindingType::Select: + return "select"; + case KeyBindingType::Toggle: + return "toggle"; + } + return ""; +} + +static const char *switchKeyFunctionToString(SwitchKeyFunction type) { + switch (type) { + case SwitchKeyFunction::Noop: + return "noop"; + case SwitchKeyFunction::InlineASCII: + return "inline_ascii"; + case SwitchKeyFunction::CommitText: + return "commit_text"; + case SwitchKeyFunction::CommitCode: + return "commit_code"; + case SwitchKeyFunction::Clear: + return "clear"; + } + return ""; +} + +static SwitchKeyFunction switchKeyFunctionFromString(std::string_view str) { + if (str == "noop") { + return SwitchKeyFunction::Noop; + } else if (str == "inline_ascii") { + return SwitchKeyFunction::InlineASCII; + } else if (str == "commit_text") { + return SwitchKeyFunction::CommitText; + } else if (str == "commit_code") { + return SwitchKeyFunction::CommitCode; + } else if (str == "clear") { + return SwitchKeyFunction::Clear; + } + return SwitchKeyFunction::Noop; +} + +void RimeConfigParser::setKeybindings(const std::vector &bindings) { + RimeConfig copyConfig = {0}; + RimeConfig copyConfigMap = {0}; + RimeConfigIterator iterator; + RimeConfigIterator copyIterator; + api_->config_init(©Config); + api_->config_create_list(©Config, "key_binder/bindings"); + api_->config_begin_list(&iterator, &defaultConf_, "key_binder/bindings"); + api_->config_begin_list(©Iterator, ©Config, "key_binder/bindings"); + while (!copyIterator.path) { + api_->config_next(©Iterator); + } + while (api_->config_next(&iterator)) { + RimeConfig map = {0}; + const char *sendKey = nullptr; + api_->config_get_item(&defaultConf_, iterator.path, &map); + sendKey = api_->config_get_cstring(&map, "send"); + if (!sendKey) { + sendKey = api_->config_get_cstring(&map, "toggle"); + } + if (!sendKey) { + sendKey = api_->config_get_cstring(&map, "select"); + } + if (strcmp(sendKey, "Page_Up") && strcmp(sendKey, "Page_Down") && + strcmp(sendKey, "ascii_mode") && strcmp(sendKey, "full_shape") && + strcmp(sendKey, "simplification")) { + api_->config_set_item(©Config, copyIterator.path, &map); + api_->config_next(©Iterator); + } + }; + api_->config_end(&iterator); + for (auto &binding : bindings) { + RimeConfig map = {0}; + api_->config_init(&map); + api_->config_set_string(&map, "accept", binding.accept.data()); + api_->config_set_string(&map, "when", + keyBindingConditionToString(binding.when)); + api_->config_set_string(&map, keybindingTypeToString(binding.type), + binding.action.data()); + api_->config_set_item(©Config, copyIterator.path, &map); + api_->config_next(©Iterator); + } + api_->config_end(©Iterator); + api_->config_get_item(©Config, "key_binder/bindings", ©ConfigMap); + api_->config_set_item(&defaultConf_, "key_binder/bindings", ©ConfigMap); +} + +void RimeConfigParser::setPageSize(int pageSize) { + api_->config_set_int(&defaultConf_, "menu/page_size", pageSize); +} + +bool RimeConfigParser::getPageSize(int *pageSize) { + return api_->config_get_int(&defaultConf_, "menu/page_size", pageSize); +} + +std::vector RimeConfigParser::getKeybindings() { + std::vector result; + listForeach(&defaultConf_, "key_binder/bindings", + [=, &result](RimeConfig *config, const char *path) { + RimeConfig map = {0}; + api_->config_get_item(config, path, &map); + auto when = api_->config_get_cstring(&map, "when"); + if (!when) { + return false; + } + KeyBinding binding; + binding.when = keyBindingConditionFromString(when); + auto accept = api_->config_get_cstring(&map, "accept"); + if (!accept) { + return false; + } + binding.accept = accept; + auto action = api_->config_get_cstring(&map, "send"); + if (action) { + binding.type = KeyBindingType::Send; + } else { + action = api_->config_get_cstring(&map, "toggle"); + } + if (action) { + binding.type = KeyBindingType::Toggle; + } else { + action = api_->config_get_cstring(&map, "select"); + binding.type = KeyBindingType::Select; + } + if (!action) { + return false; + } + binding.action = action; + result.push_back(std::move(binding)); + return true; + }); + return result; +} + +void RimeConfigParser::listForeach( + RimeConfig *config, const char *key, + std::function callback) { + size_t size = RimeConfigListSize(config, key); + if (!size) { + return; + } + + RimeConfigIterator iterator; + RimeConfigBeginList(&iterator, config, key); + for (size_t i = 0; i < size; i++) { + RimeConfigNext(&iterator); + if (!callback(config, iterator.path)) { + break; + } + } + RimeConfigEnd(&iterator); +} + +bool RimeConfigParser::sync() { + bool suc; + RimeConfig hotkeys = {0}; + RimeConfig keybindings = {0}; + RimeConfig schema_list = {0}; + std::string yaml; + + int pageSize; + api_->config_get_int(&defaultConf_, "menu/page_size", &pageSize); + levers_->customize_int(settings_, "menu/page_size", pageSize); + api_->config_get_item(&defaultConf_, "switcher/hotkeys", &hotkeys); + levers_->customize_item(settings_, "switcher/hotkeys", &hotkeys); + api_->config_get_item(&defaultConf_, "key_binder/bindings", &keybindings); + levers_->customize_item(settings_, "key_binder/bindings", &keybindings); + levers_->customize_string( + settings_, "ascii_composer/switch_key/Shift_L", + api_->config_get_cstring(&defaultConf_, + "ascii_composer/switch_key/Shift_L")); + levers_->customize_string( + settings_, "ascii_composer/switch_key/Shift_R", + api_->config_get_cstring(&defaultConf_, + "ascii_composer/switch_key/Shift_R")); + + /* Concatenate all active schemas */ + for (const auto &schema : schemaIdList_) { + yaml += "- { schema: " + schema + " } \n"; + } + api_->config_load_string(&schema_list, yaml.c_str()); + levers_->customize_item(settings_, "schema_list", &schema_list); + suc = levers_->save_settings(settings_); + if (!suc) { + return false; + } + levers_->custom_settings_destroy(settings_); + suc = api_->start_maintenance(true); // Full check mode + if (!suc) { + return false; + } + api_->finalize(); + return start(false); +} + +std::string RimeConfigParser::stringFromYAML(const char *yaml, + const char *attr) { + RimeConfig rimeSchemaConfig = {0}; + api_->config_load_string(&rimeSchemaConfig, yaml); + auto str = api_->config_get_cstring(&rimeSchemaConfig, attr); + std::string result; + if (str) { + result = str; + } + return result; +} + +void RimeConfigParser::setSchemas(const std::vector &schemas) { + schemaIdList_ = schemas; + return; +} + +int RimeConfigParser::schemaIndex(const char *schema_id) { + int idx = 0; + bool found = false; + listForeach(&defaultConf_, "schema_list", + [=, &idx, &found](RimeConfig *config, const char *path) { + RimeConfig map = {0}; + this->api_->config_get_item(config, path, &map); + auto schema = + this->api_->config_get_cstring(&map, "schema"); + /* This schema is enabled in default */ + if (schema && strcmp(schema, schema_id) == 0) { + found = true; + return false; + } + idx++; + return true; + }); + + return found ? (idx + 1) : 0; +} + +std::vector RimeConfigParser::getSwitchKeys() { + std::vector out; + const char *shiftL = nullptr, *shiftR = nullptr; + shiftL = api_->config_get_cstring(&defaultConf_, + "ascii_composer/switch_key/Shift_L"); + shiftR = api_->config_get_cstring(&defaultConf_, + "ascii_composer/switch_key/Shift_R"); + out.push_back(switchKeyFunctionFromString(shiftL)); + out.push_back(switchKeyFunctionFromString(shiftR)); + return out; +} + +void RimeConfigParser::setSwitchKeys( + const std::vector &switch_keys) { + if (switch_keys.size() < 2) { + return; + } + api_->config_set_string(&defaultConf_, "ascii_composer/switch_key/Shift_L", + switchKeyFunctionToString(switch_keys[0])); + api_->config_set_string(&defaultConf_, "ascii_composer/switch_key/Shift_R", + switchKeyFunctionToString(switch_keys[1])); + return; +} + +} // namespace fcitx::rime diff --git a/gui/RimeConfigParser.h b/gui/RimeConfigParser.h new file mode 100644 index 0000000..64d656e --- /dev/null +++ b/gui/RimeConfigParser.h @@ -0,0 +1,84 @@ +/* + * SPDX-FileCopyrightText: 2020~2020 xzhao9 + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ +#ifndef _RIME_GUI_CONFIG_PARSER_H +#define _RIME_GUI_CONFIG_PARSER_H + +#include +#include +#include +#include + +namespace fcitx::rime { + +enum class KeyBindingCondition { + Composing, + HasMenu, + Paging, + Always, +}; + +enum class KeyBindingType { + Send, + Toggle, + Select, +}; + +enum class SwitchKeyFunction { + Noop, + InlineASCII, + CommitText, + CommitCode, + Clear, +}; + +struct KeyBinding { + KeyBindingCondition when; + std::string accept; + KeyBindingType type; + std::string action; +}; + +class RimeConfigParser { +public: + RimeConfigParser(); + ~RimeConfigParser(); + + bool isError(); + bool sync(); + + void setSwitchKeys(const std::vector &switch_keys); + std::vector getSwitchKeys(); + + void setToggleKeys(const std::vector &keys); + std::vector getToggleKeys(); + + void setKeybindings(const std::vector &bindings); + std::vector getKeybindings(); + + void setPageSize(int page_size); + bool getPageSize(int *page_size); + + std::string stringFromYAML(const char *yaml, const char *attr); + void setSchemas(const std::vector &schemas); + int schemaIndex(const char *schema); + +private: + bool start(bool firstRun = false); + static void + listForeach(RimeConfig *config, const char *key, + std::function); + + RimeApi *api_; + RimeLeversApi *levers_; + RimeCustomSettings *settings_; + RimeConfig defaultConf_; + std::vector schemaIdList_; + bool inError_; +}; + +} // namespace fcitx::rime + +#endif // _RIME_GUI_CONFIG_PARSER_H diff --git a/gui/keylistwidget.cpp b/gui/keylistwidget.cpp new file mode 100644 index 0000000..9003cb2 --- /dev/null +++ b/gui/keylistwidget.cpp @@ -0,0 +1,190 @@ +/* + * SPDX-FileCopyrightText: 2017~2017 CSSlayer + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + */ + +#include "keylistwidget.h" +#include +#include +#include +#include +#include + +namespace fcitx { +namespace kcm { + +KeyListWidget::KeyListWidget(QWidget *parent) : QWidget(parent) { + auto layout = new QHBoxLayout; + layout->setMargin(0); + keysLayout_ = new QVBoxLayout; + keysLayout_->setMargin(0); + auto subLayout = new QVBoxLayout; + + addButton_ = new QToolButton; + addButton_->setAutoRaise(true); + addButton_->setIcon(QIcon::fromTheme("list-add-symbolic")); + addButton_->setText(_("Add")); + addButton_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + connect(addButton_, &QToolButton::clicked, this, [this]() { + addKey(Key()); + emit keyChanged(); + }); + + layout->addLayout(keysLayout_); + subLayout->addWidget(addButton_, 0, Qt::AlignTop); + // subLayout->addStretch(1); + layout->addLayout(subLayout); + + setLayout(layout); + + // Add an empty one. + addKey(); +} + +void KeyListWidget::addKey(fcitx::Key key) { + auto keyWidget = new FcitxQtKeySequenceWidget; + keyWidget->setClearButtonShown(false); + keyWidget->setKeySequence({key}); + keyWidget->setModifierlessAllowed(modifierLess_); + keyWidget->setModifierOnlyAllowed(modifierOnly_); + auto widget = new QWidget; + auto layout = new QHBoxLayout; + layout->setMargin(0); + layout->addWidget(keyWidget); + auto removeButton = new QToolButton; + removeButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + removeButton->setIcon(QIcon::fromTheme("list-remove-symbolic")); + removeButton->setText(_("Remove")); + removeButton->setVisible(showRemoveButton()); + layout->addWidget(removeButton); + widget->setLayout(layout); + connect(removeButton, &QPushButton::clicked, widget, [widget, this]() { + auto idx = keysLayout_->indexOf(widget); + if (removeKeyAt(idx)) { + emit keyChanged(); + } + }); + connect(keyWidget, &FcitxQtKeySequenceWidget::keySequenceChanged, this, + &KeyListWidget::keyChanged); + connect(this, &KeyListWidget::keyChanged, removeButton, + [this, removeButton]() { + removeButton->setVisible(showRemoveButton()); + }); + keysLayout_->addWidget(widget); +} + +void KeyListWidget::setKeys(const QList &keys) { + while (keysLayout_->count() > 1) { + removeKeyAt(0); + } + removeKeyAt(0); + + bool first = true; + for (auto key : keys) { + if (first) { + first = false; + keysLayout_->itemAt(0) + ->widget() + ->findChild() + ->setKeySequence({key}); + } else { + addKey(key); + } + } + emit keyChanged(); +} + +QList KeyListWidget::keys() const { + QList result; + for (int i = 0; i < keysLayout_->count(); i++) { + if (auto keyWidget = keysLayout_->itemAt(i) + ->widget() + ->findChild()) { + if (keyWidget->keySequence().isEmpty()) { + continue; + } + auto &key = keyWidget->keySequence()[0]; + if (key.isValid() && !result.contains(key)) { + result << keyWidget->keySequence()[0]; + } + } + } + return result; +} + +void KeyListWidget::setAllowModifierLess(bool value) { + if (value == modifierLess_) { + return; + } + + modifierLess_ = value; + + for (int i = 0; i < keysLayout_->count(); i++) { + if (auto keyWidget = keysLayout_->itemAt(i) + ->widget() + ->findChild()) { + keyWidget->setModifierlessAllowed(modifierLess_); + } + } +} + +void KeyListWidget::setAllowModifierOnly(bool value) { + if (value == modifierOnly_) { + return; + } + + modifierOnly_ = value; + + for (int i = 0; i < keysLayout_->count(); i++) { + if (auto keyWidget = keysLayout_->itemAt(i) + ->widget() + ->findChild()) { + keyWidget->setModifierOnlyAllowed(modifierOnly_); + } + } +} + +bool KeyListWidget::removeKeyAt(int idx) { + if (idx < 0 || idx > keysLayout_->count()) { + return false; + } + auto widget = keysLayout_->itemAt(idx)->widget(); + if (keysLayout_->count() == 1) { + keysLayout_->itemAt(0) + ->widget() + ->findChild() + ->setKeySequence(QList()); + } else { + keysLayout_->removeWidget(widget); + delete widget; + } + return true; +} + +bool KeyListWidget::showRemoveButton() const { + return keysLayout_->count() > 1 || + (keysLayout_->count() == 1 && + keysLayout_->itemAt(0) + ->widget() + ->findChild() + ->keySequence() + .size()); +} + +void KeyListWidget::resizeEvent(QResizeEvent *event) { + if (keysLayout_->count() > 0) { + addButton_->setMinimumHeight( + keysLayout_->itemAt(0) + ->widget() + ->findChild() + ->height()); + addButton_->setMaximumHeight(addButton_->minimumHeight()); + } + + QWidget::resizeEvent(event); +} + +} // namespace kcm +} // namespace fcitx diff --git a/gui/keylistwidget.h b/gui/keylistwidget.h new file mode 100644 index 0000000..4ef8ad7 --- /dev/null +++ b/gui/keylistwidget.h @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: 2017~2017 CSSlayer + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + */ +#ifndef _KCM_FCITX_KEYLISTWIDGET_H_ +#define _KCM_FCITX_KEYLISTWIDGET_H_ + +#include +#include + +class QToolButton; +class QBoxLayout; + +namespace fcitx { +namespace kcm { + +class KeyListWidget : public QWidget { + Q_OBJECT +public: + explicit KeyListWidget(QWidget *parent = 0); + + QList keys() const; + void setKeys(const QList &keys); + void setAllowModifierLess(bool); + void setAllowModifierOnly(bool); + +signals: + void keyChanged(); + +protected: + void resizeEvent(QResizeEvent *) override; + +private: + void addKey(Key key = Key()); + bool removeKeyAt(int idx); + bool showRemoveButton() const; + + QToolButton *addButton_; + QBoxLayout *keysLayout_; + bool modifierLess_ = false; + bool modifierOnly_ = false; +}; + +} // namespace kcm +} // namespace fcitx + +#endif // _KCM_FCITX_KEYLISTWIDGET_H_ diff --git a/gui/rime-config.json b/gui/rime-config.json new file mode 100644 index 0000000..5be41e0 --- /dev/null +++ b/gui/rime-config.json @@ -0,0 +1,4 @@ +{ + "addon": "rime", + "files": ["custom_config"] +} diff --git a/src/rimeengine.cpp b/src/rimeengine.cpp index ca397b6..335ed82 100644 --- a/src/rimeengine.cpp +++ b/src/rimeengine.cpp @@ -187,6 +187,8 @@ void RimeEngine::setSubConfig(const std::string &path, const RawConfig &) { deploy(); } else if (path == "sync") { sync(); + } else if (path == "custom_config") { + deploy(); } } diff --git a/src/rimeengine.h b/src/rimeengine.h index 7b93bcd..4c4317c 100644 --- a/src/rimeengine.h +++ b/src/rimeengine.h @@ -40,7 +40,9 @@ FCITX_CONFIGURATION( Option> plugins{this, "Plugins", _("Plugins"), std::vector()}; Option> modules{this, "Modules", _("Modules"), - std::vector()};); + std::vector()}; + ExternalOption customConfig{this, "Custom Config", _("Custom Config"), + "fcitx://config/addon/rime/custom_config"};); class RimeEngine final : public InputMethodEngine { public: