init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled

This commit is contained in:
allhaileris
2026-02-16 15:50:16 +03:00
commit afb81b8278
13816 changed files with 3689732 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
find_package(Qt6Concurrent REQUIRED)
set(QUICKPHRASE_EDITOR_SRCS
main.cpp
model.cpp
editor.cpp
editordialog.cpp
batchdialog.cpp
filelistmodel.cpp
)
add_library(fcitx-quickphrase-editor5
MODULE ${QUICKPHRASE_EDITOR_SRCS})
set_target_properties(fcitx-quickphrase-editor5 PROPERTIES
AUTOMOC TRUE AUTOUIC TRUE AUTOUIC_OPTIONS "-tr=fcitx::tr2fcitx;--include=fcitxqti18nhelper.h"
)
target_link_libraries(fcitx-quickphrase-editor5
Fcitx5::Utils
Qt6::Core
Qt6::Gui
Qt6::Concurrent
Fcitx5Qt6::WidgetsAddons
)
install(TARGETS fcitx-quickphrase-editor5 DESTINATION ${CMAKE_INSTALL_LIBDIR}/fcitx5/qt6)

View File

@@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: 2013~2017 CSSlayer <wengxt@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
*/
#include "batchdialog.h"
#include "ui_batchdialog.h"
#include <QIcon>
#include <fcitx-utils/i18n.h>
namespace fcitx {
BatchDialog::BatchDialog(QWidget *parent) : QDialog(parent) {
setupUi(this);
iconLabel->setPixmap(QIcon::fromTheme("dialog-information").pixmap(22, 22));
}
BatchDialog::~BatchDialog() {}
void BatchDialog::setText(const QString &s) { plainTextEdit->setPlainText(s); }
QString BatchDialog::text() const { return plainTextEdit->toPlainText(); }
} // namespace fcitx

View File

@@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2013~2017 CSSlayer <wengxt@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
*/
#ifndef _QUICKPHRASE_EDITOR_BATCHDIALOG_H_
#define _QUICKPHRASE_EDITOR_BATCHDIALOG_H_
#include "ui_batchdialog.h"
#include <QDialog>
namespace fcitx {
class BatchDialog : public QDialog, public Ui::BatchDialog {
Q_OBJECT
public:
explicit BatchDialog(QWidget *parent = 0);
virtual ~BatchDialog();
QString text() const;
void setText(const QString &s);
};
} // namespace fcitx
#endif // _QUICKPHRASE_EDITOR_BATCHDIALOG_H_

View File

@@ -0,0 +1,106 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>BatchDialog</class>
<widget class="QDialog" name="BatchDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>473</width>
<height>344</height>
</rect>
</property>
<property name="windowTitle">
<string>Batch editing</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPlainTextEdit" name="plainTextEdit"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="iconLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="infoLabel">
<property name="text">
<string>Use &lt;Keyword&gt; &lt;Phrase&gt; format on every line.</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>buttonBox</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>BatchDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>BatchDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -0,0 +1,286 @@
/*
* SPDX-FileCopyrightText: 2012~2017 CSSlayer <wengxt@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
*/
#include "editor.h"
#include "batchdialog.h"
#include "editordialog.h"
#include "filelistmodel.h"
#include "model.h"
#include <QCloseEvent>
#include <QDebug>
#include <QFileDialog>
#include <QInputDialog>
#include <QMenu>
#include <QMessageBox>
#include <QtConcurrentRun>
#include <fcitx-utils/i18n.h>
#include <fcitx-utils/standardpath.h>
namespace fcitx {
ListEditor::ListEditor(QWidget *parent)
: FcitxQtConfigUIWidget(parent), model_(new QuickPhraseModel(this)),
fileListModel_(new FileListModel(this)) {
setupUi(this);
macroTableView->setModel(model_);
fileListComboBox->setModel(fileListModel_);
operationMenu_ = new QMenu(this);
operationMenu_->addAction(_("Add File"), this,
&ListEditor::addFileTriggered);
operationMenu_->addAction(_("Remove File"), this,
&ListEditor::removeFileTriggered);
operationMenu_->addAction(_("Refresh List"), this,
&ListEditor::refreshListTriggered);
operationButton->setMenu(operationMenu_);
loadFileList();
itemFocusChanged();
connect(addButton, &QPushButton::clicked, this, &ListEditor::addWord);
connect(batchEditButton, &QPushButton::clicked, this,
&ListEditor::batchEditWord);
connect(deleteButton, &QPushButton::clicked, this, &ListEditor::deleteWord);
connect(clearButton, &QPushButton::clicked, this,
&ListEditor::deleteAllWord);
connect(importButton, &QPushButton::clicked, this, &ListEditor::importData);
connect(exportButton, &QPushButton::clicked, this, &ListEditor::exportData);
connect(fileListComboBox, qOverload<int>(&QComboBox::activated), this,
&ListEditor::changeFile);
connect(macroTableView->selectionModel(),
&QItemSelectionModel::selectionChanged, this,
&ListEditor::itemFocusChanged);
connect(model_, &QuickPhraseModel::needSaveChanged, this,
&ListEditor::changed);
}
void ListEditor::load() {
lastFile_ = currentFile();
model_->load(currentFile(), false);
}
void ListEditor::load(const QString &file) { model_->load(file, true); }
void ListEditor::save(const QString &file) { model_->save(file); }
void ListEditor::save() {
// QFutureWatcher< bool >* futureWatcher =
// m_model->save("data/QuickPhrase.mb");
QFutureWatcher<bool> *futureWatcher = model_->save(currentFile());
connect(futureWatcher, &QFutureWatcherBase::finished, this,
&ListEditor::saveFinished);
}
bool ListEditor::asyncSave() { return true; }
void ListEditor::changeFile(int) {
if (model_->needSave()) {
int ret = QMessageBox::question(
this, _("Save Changes"),
_("The content has changed.\n"
"Do you want to save the changes or discard them?"),
QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
if (ret == QMessageBox::Save) {
// save(fileListComboBox->itemText(lastFileIndex));
save(lastFile_);
} else if (ret == QMessageBox::Cancel) {
fileListComboBox->setCurrentIndex(
fileListModel_->findFile(lastFile_));
return;
}
}
load();
}
QString ListEditor::title() { return _("Quick Phrase Editor"); }
void ListEditor::itemFocusChanged() {
deleteButton->setEnabled(macroTableView->currentIndex().isValid());
}
void ListEditor::deleteWord() {
if (!macroTableView->currentIndex().isValid())
return;
int row = macroTableView->currentIndex().row();
model_->deleteItem(row);
}
void ListEditor::deleteAllWord() { model_->deleteAllItem(); }
void ListEditor::addWord() {
EditorDialog *dialog = new EditorDialog(this);
dialog->setAttribute(Qt::WA_DeleteOnClose, true);
dialog->open();
connect(dialog, &QDialog::accepted, this, &ListEditor::addWordAccepted);
}
void ListEditor::batchEditWord() {
BatchDialog *dialog = new BatchDialog(this);
QString text;
QTextStream stream(&text);
model_->saveDataToStream(stream);
dialog->setAttribute(Qt::WA_DeleteOnClose, true);
dialog->setText(text);
dialog->open();
connect(dialog, &QDialog::accepted, this, &ListEditor::batchEditAccepted);
}
void ListEditor::addWordAccepted() {
const EditorDialog *dialog =
qobject_cast<const EditorDialog *>(QObject::sender());
model_->addItem(dialog->key(), dialog->value());
QModelIndex last = model_->index(model_->rowCount() - 1, 0);
macroTableView->setCurrentIndex(last);
macroTableView->scrollTo(last);
}
void ListEditor::batchEditAccepted() {
const BatchDialog *dialog =
qobject_cast<const BatchDialog *>(QObject::sender());
QString s = dialog->text();
QTextStream stream(&s);
model_->loadData(stream);
QModelIndex last = model_->index(model_->rowCount() - 1, 0);
macroTableView->setCurrentIndex(last);
macroTableView->scrollTo(last);
}
void ListEditor::importData() {
QFileDialog *dialog = new QFileDialog(this);
dialog->setAttribute(Qt::WA_DeleteOnClose, true);
dialog->setFileMode(QFileDialog::ExistingFile);
dialog->setAcceptMode(QFileDialog::AcceptOpen);
dialog->open();
connect(dialog, &QDialog::accepted, this, &ListEditor::importFileSelected);
}
void ListEditor::exportData() {
QFileDialog *dialog = new QFileDialog(this);
dialog->setAttribute(Qt::WA_DeleteOnClose, true);
dialog->setAcceptMode(QFileDialog::AcceptSave);
dialog->open();
connect(dialog, &QDialog::accepted, this, &ListEditor::exportFileSelected);
}
void ListEditor::importFileSelected() {
const QFileDialog *dialog =
qobject_cast<const QFileDialog *>(QObject::sender());
if (dialog->selectedFiles().length() <= 0)
return;
QString file = dialog->selectedFiles()[0];
load(file);
}
void ListEditor::exportFileSelected() {
const QFileDialog *dialog =
qobject_cast<const QFileDialog *>(QObject::sender());
if (dialog->selectedFiles().length() <= 0)
return;
QString file = dialog->selectedFiles()[0];
save(file);
}
void ListEditor::loadFileList() {
int row = fileListComboBox->currentIndex();
int col = fileListComboBox->modelColumn();
QString lastFileName =
fileListModel_->data(fileListModel_->index(row, col), Qt::UserRole)
.toString();
fileListModel_->loadFileList();
fileListComboBox->setCurrentIndex(fileListModel_->findFile(lastFileName));
load();
}
QString ListEditor::currentFile() {
int row = fileListComboBox->currentIndex();
int col = fileListComboBox->modelColumn();
return fileListModel_->data(fileListModel_->index(row, col), Qt::UserRole)
.toString();
}
QString ListEditor::currentName() {
int row = fileListComboBox->currentIndex();
int col = fileListComboBox->modelColumn();
return fileListModel_
->data(fileListModel_->index(row, col), Qt::DisplayRole)
.toString();
}
void ListEditor::addFileTriggered() {
bool ok;
QString filename = QInputDialog::getText(
this, _("Create new file"), _("Please input a filename for newfile"),
QLineEdit::Normal, "newfile", &ok);
if (filename.contains('/')) {
QMessageBox::warning(this, _("Invalid filename"),
_("File name should not contain '/'."));
return;
}
filename.append(".mb");
if (!StandardPath::global().safeSave(
StandardPath::Type::PkgData,
stringutils::joinPath(QUICK_PHRASE_CONFIG_DIR,
filename.toLocal8Bit().constData()),
[](int) { return true; })) {
QMessageBox::warning(
this, _("File Operation Failed"),
QString(_("Cannot create file %1.")).arg(filename));
return;
}
fileListModel_->loadFileList();
fileListComboBox->setCurrentIndex(fileListModel_->findFile(
filename.prepend(QUICK_PHRASE_CONFIG_DIR "/")));
load();
}
void ListEditor::refreshListTriggered() { loadFileList(); }
void ListEditor::removeFileTriggered() {
QString filename = currentFile();
QString curName = currentName();
auto fullname = stringutils::joinPath(
StandardPath::global().userDirectory(StandardPath::Type::PkgData),
filename.toLocal8Bit().constData());
QFile f(fullname.data());
if (!f.exists()) {
int ret = QMessageBox::question(
this, _("Cannot remove system file"),
QString(_("%1 is a system file, do you want to delete all phrases "
"instead?"))
.arg(curName),
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
if (ret == QMessageBox::Yes) {
deleteAllWord();
}
return;
}
int ret = QMessageBox::question(
this, _("Confirm deletion"),
QString(_("Are you sure to delete %1?")).arg(curName),
QMessageBox::Ok | QMessageBox::Cancel);
if (ret == QMessageBox::Ok) {
bool ok = f.remove();
if (!ok) {
QMessageBox::warning(
this, _("File Operation Failed"),
QString(_("Error while deleting %1.")).arg(curName));
}
}
loadFileList();
load();
}
} // namespace fcitx

View File

@@ -0,0 +1,66 @@
/*
* SPDX-FileCopyrightText: 2012~2017 CSSlayer <wengxt@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
*/
#ifndef _QUICKPHRASE_EDITOR_EDITOR_H_
#define _QUICKPHRASE_EDITOR_EDITOR_H_
#include "fcitxqtconfiguiwidget.h"
#include "model.h"
#include "ui_editor.h"
#include <QDir>
#include <QMainWindow>
#include <QMutex>
class QAbstractItemModel;
namespace fcitx {
class FileListModel;
class ListEditor final : public FcitxQtConfigUIWidget, Ui::Editor {
Q_OBJECT
public:
explicit ListEditor(QWidget *parent = 0);
void load() override;
void save() override;
QString title() override;
bool asyncSave() override;
void loadFileList();
public Q_SLOTS:
void batchEditAccepted();
void removeFileTriggered();
void addFileTriggered();
void refreshListTriggered();
void changeFile(int);
private Q_SLOTS:
void addWord();
void batchEditWord();
void deleteWord();
void deleteAllWord();
void itemFocusChanged();
void addWordAccepted();
void importData();
void exportData();
void importFileSelected();
void exportFileSelected();
private:
void load(const QString &file);
void save(const QString &file);
QString currentFile();
QString currentName();
QuickPhraseModel *model_;
FileListModel *fileListModel_;
QMenu *operationMenu_;
QString lastFile_;
};
} // namespace fcitx
#endif // _QUICKPHRASE_EDITOR_EDITOR_H_

View File

@@ -0,0 +1,141 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Editor</class>
<widget class="QWidget" name="Editor">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>358</width>
<height>389</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QComboBox" name="fileListComboBox"/>
</item>
<item>
<widget class="QTableView" name="macroTableView">
<property name="editTriggers">
<set>QAbstractItemView::DoubleClicked</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPushButton" name="operationButton">
<property name="text">
<string>&amp;Operation</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="addButton">
<property name="text">
<string>&amp;Add</string>
</property>
<property name="icon">
<iconset theme="list-add">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="batchEditButton">
<property name="text">
<string>&amp;Batch Edit</string>
</property>
<property name="icon">
<iconset theme="document-edit">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="deleteButton">
<property name="text">
<string>&amp;Delete</string>
</property>
<property name="icon">
<iconset theme="list-remove">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="clearButton">
<property name="text">
<string>De&amp;lete All</string>
</property>
<property name="icon">
<iconset theme="edit-delete">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="importButton">
<property name="text">
<string>&amp;Import</string>
</property>
<property name="icon">
<iconset theme="document-import">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="exportButton">
<property name="text">
<string>E&amp;xport</string>
</property>
<property name="icon">
<iconset theme="document-export">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,23 @@
/*
* SPDX-FileCopyrightText: 2017~2017 CSSlayer <wengxt@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
*/
#include "editordialog.h"
#include <fcitx-utils/i18n.h>
namespace fcitx {
EditorDialog::EditorDialog(QWidget *parent) : QDialog(parent) { setupUi(this); }
EditorDialog::~EditorDialog() {}
void EditorDialog::setKey(const QString &s) { keyLineEdit->setText(s); }
void EditorDialog::setValue(const QString &s) { valueLineEdit->setText(s); }
QString EditorDialog::key() const { return keyLineEdit->text(); }
QString EditorDialog::value() const { return valueLineEdit->text(); }
} // namespace fcitx

View File

@@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: 2012~2017 CSSlayer <wengxt@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
*/
#ifndef _QUICKPHRASE_EDITOR_EDITORDIALOG_H_
#define _QUICKPHRASE_EDITOR_EDITORDIALOG_H_
#include "ui_editordialog.h"
#include <QDialog>
namespace fcitx {
class EditorDialog : public QDialog, public Ui::EditorDialog {
Q_OBJECT
public:
explicit EditorDialog(QWidget *parent = 0);
virtual ~EditorDialog();
QString key() const;
QString value() const;
void setValue(const QString &s);
void setKey(const QString &s);
};
} // namespace fcitx
#endif // _QUICKPHRASE_EDITOR_EDITORDIALOG_H_

View File

@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>EditorDialog</class>
<widget class="QDialog" name="EditorDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>334</width>
<height>133</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="1">
<widget class="QLineEdit" name="keyLineEdit"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="keyLabel">
<property name="text">
<string notr="true">Keyword:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="valueLineEdit"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="valueLabel">
<property name="text">
<string notr="true">Phrase:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>keyLineEdit</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>EditorDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>EditorDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -0,0 +1,67 @@
/*
* SPDX-FileCopyrightText: 2013~2017 CSSlayer <wengxt@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
*/
#include "filelistmodel.h"
#include <fcitx-utils/i18n.h>
#include <fcitx-utils/standardpath.h>
#include <fcntl.h>
fcitx::FileListModel::FileListModel(QObject *parent)
: QAbstractListModel(parent) {}
fcitx::FileListModel::~FileListModel() {}
int fcitx::FileListModel::rowCount(const QModelIndex &parent) const {
return parent.isValid() ? 0 : fileList_.size();
}
QVariant fcitx::FileListModel::data(const QModelIndex &index, int role) const {
if (!index.isValid() || index.row() >= fileList_.size())
return QVariant();
switch (role) {
case Qt::DisplayRole:
if (fileList_[index.row()] == QUICK_PHRASE_CONFIG_FILE) {
return _("Default");
} else {
// remove "data/quickphrase.d/"
const size_t length = strlen(QUICK_PHRASE_CONFIG_DIR);
return fileList_[index.row()].mid(length + 1,
fileList_[index.row()].size() -
length - strlen(".mb") - 1);
}
case Qt::UserRole:
return fileList_[index.row()];
default:
break;
}
return QVariant();
}
void fcitx::FileListModel::loadFileList() {
beginResetModel();
fileList_.clear();
fileList_.append(QUICK_PHRASE_CONFIG_FILE);
auto files = StandardPath::global().multiOpen(
StandardPath::Type::PkgData, QUICK_PHRASE_CONFIG_DIR, O_RDONLY,
filter::Suffix(".mb"));
for (auto &file : files) {
fileList_.append(QString::fromLocal8Bit(
stringutils::joinPath(QUICK_PHRASE_CONFIG_DIR, file.first).data()));
}
endResetModel();
}
int fcitx::FileListModel::findFile(const QString &lastFileName) {
int idx = fileList_.indexOf(lastFileName);
if (idx < 0) {
return 0;
}
return idx;
}

View File

@@ -0,0 +1,36 @@
/*
* SPDX-FileCopyrightText: 2017~2017 CSSlayer <wengxt@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
*/
#ifndef _QUICKPHRASE_EDITOR_FILELISTMODEL_H_
#define _QUICKPHRASE_EDITOR_FILELISTMODEL_H_
#include <QAbstractListModel>
#include <QStringList>
#define QUICK_PHRASE_CONFIG_DIR "data/quickphrase.d"
#define QUICK_PHRASE_CONFIG_FILE "data/QuickPhrase.mb"
namespace fcitx {
class FileListModel : public QAbstractListModel {
Q_OBJECT
public:
explicit FileListModel(QObject *parent = 0);
virtual ~FileListModel();
QVariant data(const QModelIndex &index,
int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
void loadFileList();
int findFile(const QString &lastFileName);
private:
QStringList fileList_;
};
} // namespace fcitx
#endif // _QUICKPHRASE_EDITOR_FILELISTMODEL_H_

View File

@@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: 2012~2017 CSSlayer <wengxt@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
*/
#include "main.h"
#include "editor.h"
#include "model.h"
QuickPhraseEditorPlugin::QuickPhraseEditorPlugin(QObject *parent)
: fcitx::FcitxQtConfigUIPlugin(parent) {}
fcitx::FcitxQtConfigUIWidget *
QuickPhraseEditorPlugin::create(const QString &key) {
Q_UNUSED(key);
return new fcitx::ListEditor;
}

View File

@@ -0,0 +1,21 @@
/*
* SPDX-FileCopyrightText: 2012~2017 CSSlayer <wengxt@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
*/
#ifndef _QUICKPHRASE_EDITOR_MAIN_H_
#define _QUICKPHRASE_EDITOR_MAIN_H_
#include "fcitxqtconfiguiplugin.h"
class QuickPhraseEditorPlugin : public fcitx::FcitxQtConfigUIPlugin {
Q_OBJECT
public:
Q_PLUGIN_METADATA(IID FcitxQtConfigUIFactoryInterface_iid FILE
"quickphrase-editor.json")
explicit QuickPhraseEditorPlugin(QObject *parent = 0);
fcitx::FcitxQtConfigUIWidget *create(const QString &key) override;
};
#endif // _QUICKPHRASE_EDITOR_MAIN_H_

View File

@@ -0,0 +1,293 @@
/*
* SPDX-FileCopyrightText: 2012~2017 CSSlayer <wengxt@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
*/
#include "model.h"
#include "editor.h"
#include "filelistmodel.h"
#include <QApplication>
#include <QFile>
#include <QFutureWatcher>
#include <QtConcurrentRun>
#include <fcitx-utils/i18n.h>
#include <fcitx-utils/standardpath.h>
#include <fcitx-utils/utf8.h>
#include <fcntl.h>
namespace fcitx {
namespace {
std::optional<std::pair<std::string, std::string>>
parseLine(const std::string &strBuf) {
auto [start, end] = stringutils::trimInplace(strBuf);
if (start == end) {
return std::nullopt;
}
std::string_view text(strBuf.data() + start, end - start);
if (!utf8::validate(text)) {
return std::nullopt;
}
auto pos = text.find_first_of(FCITX_WHITESPACE);
if (pos == std::string::npos) {
return std::nullopt;
}
auto word = text.find_first_not_of(FCITX_WHITESPACE, pos);
if (word == std::string::npos) {
return std::nullopt;
}
std::string key(text.begin(), text.begin() + pos);
auto wordString =
stringutils::unescapeForValue(std::string_view(text).substr(word));
if (!wordString) {
return std::nullopt;
}
return std::make_pair(key, *wordString);
}
QString escapeValue(const QString &v) {
return QString::fromStdString(stringutils::escapeForValue(v.toStdString()));
}
} // namespace
typedef QPair<QString, QString> ItemType;
QuickPhraseModel::QuickPhraseModel(QObject *parent)
: QAbstractTableModel(parent), needSave_(false), futureWatcher_(0) {}
QuickPhraseModel::~QuickPhraseModel() {}
bool QuickPhraseModel::needSave() { return needSave_; }
QVariant QuickPhraseModel::headerData(int section, Qt::Orientation orientation,
int role) const {
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
if (section == 0)
return _("Keyword");
else if (section == 1)
return _("Phrase");
}
return QVariant();
}
int QuickPhraseModel::rowCount(const QModelIndex &parent) const {
Q_UNUSED(parent);
return list_.count();
}
int QuickPhraseModel::columnCount(const QModelIndex &parent) const {
Q_UNUSED(parent);
return 2;
}
QVariant QuickPhraseModel::data(const QModelIndex &index, int role) const {
do {
if ((role == Qt::DisplayRole || role == Qt::EditRole) &&
index.row() < list_.count()) {
if (index.column() == 0) {
return list_[index.row()].first;
} else if (index.column() == 1) {
return list_[index.row()].second;
}
}
} while (0);
return QVariant();
}
void QuickPhraseModel::addItem(const QString &macro, const QString &word) {
beginInsertRows(QModelIndex(), list_.size(), list_.size());
list_.append(QPair<QString, QString>(macro, word));
endInsertRows();
setNeedSave(true);
}
void QuickPhraseModel::deleteItem(int row) {
if (row >= list_.count())
return;
QPair<QString, QString> item = list_.at(row);
QString key = item.first;
beginRemoveRows(QModelIndex(), row, row);
list_.removeAt(row);
endRemoveRows();
setNeedSave(true);
}
void QuickPhraseModel::deleteAllItem() {
if (list_.count())
setNeedSave(true);
beginResetModel();
list_.clear();
endResetModel();
}
Qt::ItemFlags QuickPhraseModel::flags(const QModelIndex &index) const {
if (!index.isValid())
return {};
return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
bool QuickPhraseModel::setData(const QModelIndex &index, const QVariant &value,
int role) {
if (role != Qt::EditRole)
return false;
if (index.column() == 0) {
list_[index.row()].first = value.toString();
Q_EMIT dataChanged(index, index);
setNeedSave(true);
return true;
} else if (index.column() == 1) {
list_[index.row()].second = value.toString();
Q_EMIT dataChanged(index, index);
setNeedSave(true);
return true;
} else
return false;
}
void QuickPhraseModel::load(const QString &file, bool append) {
if (futureWatcher_) {
return;
}
beginResetModel();
if (!append) {
list_.clear();
setNeedSave(false);
} else
setNeedSave(true);
futureWatcher_ = new QFutureWatcher<QStringPairList>(this);
futureWatcher_->setFuture(
QtConcurrent::run([this, file]() { return parse(file); }));
connect(futureWatcher_, &QFutureWatcherBase::finished, this,
&QuickPhraseModel::loadFinished);
}
QStringPairList QuickPhraseModel::parse(const QString &file) {
QByteArray fileNameArray = file.toLocal8Bit();
QStringPairList list;
do {
auto fp = fcitx::StandardPath::global().open(
fcitx::StandardPath::Type::PkgData, fileNameArray.constData(),
O_RDONLY);
if (fp.fd() < 0)
break;
QFile file;
if (!file.open(fp.fd(), QFile::ReadOnly)) {
break;
}
QByteArray line;
while (!(line = file.readLine()).isNull()) {
auto l = line.toStdString();
auto parsed = parseLine(l);
if (!parsed)
continue;
auto [key, value] = *parsed;
if (key.empty() || value.empty()) {
continue;
}
list_.append(
{QString::fromStdString(key), QString::fromStdString(value)});
}
file.close();
} while (0);
return list;
}
void QuickPhraseModel::loadFinished() {
list_.append(futureWatcher_->future().result());
endResetModel();
futureWatcher_->deleteLater();
futureWatcher_ = 0;
}
QFutureWatcher<bool> *QuickPhraseModel::save(const QString &file) {
auto *futureWatcher = new QFutureWatcher<bool>(this);
futureWatcher->setFuture(QtConcurrent::run(
[this, file, list = list_]() { return saveData(file, list); }));
connect(futureWatcher, &QFutureWatcherBase::finished, this,
&QuickPhraseModel::saveFinished);
return futureWatcher;
}
void QuickPhraseModel::saveDataToStream(QTextStream &dev) {
for (int i = 0; i < list_.size(); i++) {
dev << list_[i].first << "\t" << escapeValue(list_[i].second) << "\n";
}
}
void QuickPhraseModel::loadData(QTextStream &stream) {
beginResetModel();
list_.clear();
setNeedSave(true);
QString s;
while (!(s = stream.readLine()).isNull()) {
auto line = s.toStdString();
auto parsed = parseLine(line);
if (!parsed)
continue;
auto [key, value] = *parsed;
if (key.empty() || value.empty()) {
continue;
}
list_.append(
{QString::fromStdString(key), QString::fromStdString(value)});
}
endResetModel();
}
bool QuickPhraseModel::saveData(const QString &file,
const QStringPairList &list) {
QByteArray filenameArray = file.toLocal8Bit();
fs::makePath(stringutils::joinPath(
StandardPath::global().userDirectory(StandardPath::Type::PkgData),
QUICK_PHRASE_CONFIG_DIR));
return StandardPath::global().safeSave(
StandardPath::Type::PkgData, filenameArray.constData(),
[&list](int fd) {
QFile tempFile;
if (!tempFile.open(fd, QIODevice::WriteOnly)) {
return false;
}
for (int i = 0; i < list.size(); i++) {
tempFile.write(list[i].first.toUtf8());
tempFile.write("\t");
tempFile.write(escapeValue(list[i].second).toUtf8());
tempFile.write("\n");
}
tempFile.close();
return true;
});
}
void QuickPhraseModel::saveFinished() {
QFutureWatcher<bool> *watcher =
static_cast<QFutureWatcher<bool> *>(sender());
QFuture<bool> future = watcher->future();
if (future.result()) {
setNeedSave(false);
}
watcher->deleteLater();
}
void QuickPhraseModel::setNeedSave(bool needSave) {
if (needSave_ != needSave) {
needSave_ = needSave;
Q_EMIT needSaveChanged(needSave_);
}
}
} // namespace fcitx

View File

@@ -0,0 +1,61 @@
/*
* SPDX-FileCopyrightText: 2012~2017 CSSlayer <wengxt@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
*/
#ifndef _QUICKPHRASE_EDITOR_MODEL_H_
#define _QUICKPHRASE_EDITOR_MODEL_H_
#include <QAbstractTableModel>
#include <QFutureWatcher>
#include <QSet>
#include <QTextStream>
class QFile;
namespace fcitx {
typedef QList<QPair<QString, QString>> QStringPairList;
class QuickPhraseModel : public QAbstractTableModel {
Q_OBJECT
public:
explicit QuickPhraseModel(QObject *parent = 0);
virtual ~QuickPhraseModel();
Qt::ItemFlags flags(const QModelIndex &index) const override;
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole) override;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index,
int role = Qt::DisplayRole) const override;
void load(const QString &file, bool append);
void loadData(QTextStream &stream);
void addItem(const QString &macro, const QString &word);
void deleteItem(int row);
void deleteAllItem();
QFutureWatcher<bool> *save(const QString &file);
void saveDataToStream(QTextStream &dev);
bool needSave();
Q_SIGNALS:
void needSaveChanged(bool needSave);
private Q_SLOTS:
void loadFinished();
void saveFinished();
private:
QStringPairList parse(const QString &file);
bool saveData(const QString &file, const fcitx::QStringPairList &list);
void setNeedSave(bool needSave);
bool needSave_;
QStringPairList list_;
QFutureWatcher<QStringPairList> *futureWatcher_;
};
} // namespace fcitx
#endif // _QUICKPHRASE_EDITOR_MODEL_H_

View File

@@ -0,0 +1,4 @@
{
"addon": "quickphrase",
"files": ["editor"]
}