Feature Task System (#1947)
* add task system code * enable ci build * Revert "enable ci build" This reverts commit 8c457a22e44e64c7d87804fc3c76ee778c1c3b6f.
@ -227,6 +227,12 @@ bool ConfigMgr::checkAppConfig()
|
||||
FileUtils::copyDir(extraDataRoot + QStringLiteral("/themes"),
|
||||
appConfigDir.filePath(QStringLiteral("themes")));
|
||||
|
||||
// Copy tasks.
|
||||
qApp->processEvents();
|
||||
splash->showMessage("Copying tasks");
|
||||
FileUtils::copyDir(extraDataRoot + QStringLiteral("/tasks"),
|
||||
appConfigDir.filePath(QStringLiteral("tasks")));
|
||||
|
||||
// Copy docs.
|
||||
qApp->processEvents();
|
||||
splash->showMessage("Copying docs");
|
||||
@ -377,6 +383,18 @@ QString ConfigMgr::getUserThemeFolder() const
|
||||
return folderPath;
|
||||
}
|
||||
|
||||
QString ConfigMgr::getAppTaskFolder() const
|
||||
{
|
||||
return PathUtils::concatenateFilePath(m_appConfigFolderPath, QStringLiteral("tasks"));
|
||||
}
|
||||
|
||||
QString ConfigMgr::getUserTaskFolder() const
|
||||
{
|
||||
auto folderPath = PathUtils::concatenateFilePath(m_userConfigFolderPath, QStringLiteral("tasks"));
|
||||
QDir().mkpath(folderPath);
|
||||
return folderPath;
|
||||
}
|
||||
|
||||
QString ConfigMgr::getAppWebStylesFolder() const
|
||||
{
|
||||
return PathUtils::concatenateFilePath(m_appConfigFolderPath, QStringLiteral("web-styles"));
|
||||
|
@ -74,6 +74,10 @@ namespace vnotex
|
||||
|
||||
QString getUserThemeFolder() const;
|
||||
|
||||
QString getAppTaskFolder() const;
|
||||
|
||||
QString getUserTaskFolder() const;
|
||||
|
||||
QString getAppWebStylesFolder() const;
|
||||
|
||||
QString getUserWebStylesFolder() const;
|
||||
|
@ -29,6 +29,11 @@ SOURCES += \
|
||||
$$PWD/texteditorconfig.cpp \
|
||||
$$PWD/vnotex.cpp \
|
||||
$$PWD/thememgr.cpp \
|
||||
$$PWD/task.cpp \
|
||||
$$PWD/taskhelper.cpp \
|
||||
$$PWD/taskmgr.cpp \
|
||||
$$PWD/taskvariablemgr.cpp \
|
||||
$$PWD/shellexecution.cpp \
|
||||
$$PWD/notebookmgr.cpp \
|
||||
$$PWD/theme.cpp \
|
||||
$$PWD/sessionconfig.cpp \
|
||||
@ -60,6 +65,11 @@ HEADERS += \
|
||||
$$PWD/texteditorconfig.h \
|
||||
$$PWD/vnotex.h \
|
||||
$$PWD/thememgr.h \
|
||||
$$PWD/task.h \
|
||||
$$PWD/taskhelper.h \
|
||||
$$PWD/taskmgr.h \
|
||||
$$PWD/taskvariablemgr.h \
|
||||
$$PWD/shellexecution.h \
|
||||
$$PWD/global.h \
|
||||
$$PWD/namebasedserver.h \
|
||||
$$PWD/exception.h \
|
||||
|
102
src/core/shellexecution.cpp
Normal file
@ -0,0 +1,102 @@
|
||||
#include "shellexecution.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
ShellExecution::ShellExecution()
|
||||
{
|
||||
auto shell = defaultShell();
|
||||
setShellExecutable(shell);
|
||||
setShellArguments(defaultShellArguments(shell));
|
||||
}
|
||||
|
||||
void ShellExecution::setProgram(const QString &p_prog)
|
||||
{
|
||||
m_program = p_prog;
|
||||
}
|
||||
|
||||
void ShellExecution::setArguments(const QStringList &p_args)
|
||||
{
|
||||
m_args = p_args;
|
||||
}
|
||||
|
||||
void ShellExecution::setShellExecutable(const QString &p_exec)
|
||||
{
|
||||
m_shell_executable = p_exec;
|
||||
}
|
||||
|
||||
void ShellExecution::setShellArguments(const QStringList &p_args)
|
||||
{
|
||||
m_shell_args = p_args;
|
||||
}
|
||||
|
||||
void ShellExecution::setupProcess(QProcess *p_process,
|
||||
const QString &p_program,
|
||||
const QStringList &p_args,
|
||||
const QString &p_shell_exec,
|
||||
const QStringList &p_shell_args)
|
||||
{
|
||||
auto shell_exec = p_shell_exec.isNull() ? defaultShell() : p_shell_exec;
|
||||
auto shell_args = p_shell_args.isEmpty() ? defaultShellArguments(shell_exec)
|
||||
: p_shell_args;
|
||||
p_process->setProgram(shell_exec);
|
||||
auto args = p_args;
|
||||
|
||||
auto shell = shellBasename(shell_exec);
|
||||
if (!p_program.isEmpty() && !p_args.isEmpty()) {
|
||||
args = shellQuote(args, shell_exec);
|
||||
}
|
||||
QStringList allArgs(shell_args);
|
||||
if (shell == "bash") {
|
||||
allArgs << (QStringList() << p_program << args).join(' ');
|
||||
} else {
|
||||
allArgs << p_program << args;
|
||||
}
|
||||
p_process->setArguments(allArgs);
|
||||
}
|
||||
|
||||
QString ShellExecution::shellBasename(const QString &p_shell)
|
||||
{
|
||||
return QFileInfo(p_shell).baseName().toLower();
|
||||
}
|
||||
|
||||
QString ShellExecution::defaultShell()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
return "PowerShell.exe";
|
||||
#else
|
||||
return "/bin/bash";
|
||||
#endif
|
||||
}
|
||||
|
||||
QStringList ShellExecution::defaultShellArguments(const QString &p_shell)
|
||||
{
|
||||
auto shell = shellBasename(p_shell);
|
||||
if (shell == "cmd") {
|
||||
return {"/C"};
|
||||
} else if (shell == "powershell" || p_shell == "pwsh") {
|
||||
return {"-Command"};
|
||||
} else if (shell == "bash") {
|
||||
return {"-c"};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QString ShellExecution::shellQuote(const QString &p_text, const QString &)
|
||||
{
|
||||
if (p_text.contains(' ')) {
|
||||
return QString("\"%1\"").arg(p_text);
|
||||
}
|
||||
return p_text;
|
||||
}
|
||||
|
||||
QStringList ShellExecution::shellQuote(const QStringList &p_list, const QString &p_shell)
|
||||
{
|
||||
auto shell = shellBasename(p_shell);
|
||||
QStringList list;
|
||||
for (const auto &s : p_list) {
|
||||
list << shellQuote(s, shell);
|
||||
}
|
||||
return list;
|
||||
}
|
42
src/core/shellexecution.h
Normal file
@ -0,0 +1,42 @@
|
||||
#ifndef SHELLEXECUTION_H
|
||||
#define SHELLEXECUTION_H
|
||||
|
||||
#include <QProcess>
|
||||
|
||||
namespace vnotex {
|
||||
|
||||
|
||||
class ShellExecution : public QProcess
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ShellExecution();
|
||||
void setProgram(const QString &p_prog);
|
||||
void setArguments(const QStringList &p_args);
|
||||
void setShellExecutable(const QString &p_exec);
|
||||
void setShellArguments(const QStringList &p_args);
|
||||
|
||||
static void setupProcess(QProcess *p_process,
|
||||
const QString &p_program,
|
||||
const QStringList &p_args = QStringList(),
|
||||
const QString &p_shell_exec = QString(),
|
||||
const QStringList &p_shell_args = QStringList());
|
||||
static QString defaultShell();
|
||||
static QStringList defaultShellArguments(const QString &p_shell);
|
||||
static QString shellQuote(const QString &p_text,
|
||||
const QString &p_shell);
|
||||
static QStringList shellQuote(const QStringList &p_list,
|
||||
const QString &p_shell);
|
||||
|
||||
private:
|
||||
QString m_shell_executable;
|
||||
QStringList m_shell_args;
|
||||
QString m_program;
|
||||
QStringList m_args;
|
||||
|
||||
static QString shellBasename(const QString &p_shell);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // SHELLEXECUTION_H
|
557
src/core/task.cpp
Normal file
@ -0,0 +1,557 @@
|
||||
#include "task.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QVersionNumber>
|
||||
#include <QDebug>
|
||||
#include <QJsonValue>
|
||||
#include <QJsonArray>
|
||||
#include <QAction>
|
||||
#include <QRegularExpression>
|
||||
#include <QInputDialog>
|
||||
#include <QTextCodec>
|
||||
#include <QRandomGenerator>
|
||||
|
||||
#include "utils/fileutils.h"
|
||||
#include "utils/pathutils.h"
|
||||
#include "vnotex.h"
|
||||
#include "exception.h"
|
||||
#include "taskhelper.h"
|
||||
#include "notebook/notebook.h"
|
||||
#include "shellexecution.h"
|
||||
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
QString Task::s_latestVersion = "0.1.3";
|
||||
TaskVariableMgr Task::s_vars;
|
||||
|
||||
Task *Task::fromFile(const QString &p_file,
|
||||
const QJsonDocument &p_json,
|
||||
const QString &p_locale,
|
||||
QObject *p_parent)
|
||||
{
|
||||
auto task = new Task(p_locale, p_file, p_parent);
|
||||
return fromJson(task, p_json.object());
|
||||
}
|
||||
|
||||
Task* Task::fromJson(Task *p_task,
|
||||
const QJsonObject &p_obj)
|
||||
{
|
||||
if (p_obj.contains("version")) {
|
||||
p_task->dto.version = p_obj["version"].toString();
|
||||
}
|
||||
|
||||
auto version = QVersionNumber::fromString(p_task->getVersion());
|
||||
if (version < QVersionNumber(1, 0, 0)) {
|
||||
return fromJsonV0(p_task, p_obj);
|
||||
}
|
||||
qWarning() << "Unknow Task Version" << version << p_task->dto._source;
|
||||
return p_task;
|
||||
}
|
||||
|
||||
Task *Task::fromJsonV0(Task *p_task,
|
||||
const QJsonObject &p_obj,
|
||||
bool mergeTasks)
|
||||
{
|
||||
if (p_obj.contains("type")) {
|
||||
p_task->dto.type = p_obj["type"].toString();
|
||||
}
|
||||
|
||||
if (p_obj.contains("icon")) {
|
||||
QString path = p_obj["icon"].toString();
|
||||
QDir iconPath(path);
|
||||
if (iconPath.isRelative()) {
|
||||
QDir taskDir(p_task->dto._source);
|
||||
taskDir.cdUp();
|
||||
path = QDir(taskDir.filePath(path)).absolutePath();
|
||||
}
|
||||
if (QFile::exists(path)) {
|
||||
p_task->dto.icon = path;
|
||||
} else {
|
||||
qWarning() << "task icon not exists" << path;
|
||||
}
|
||||
}
|
||||
|
||||
if (p_obj.contains("shortcut")) {
|
||||
p_task->dto.shortcut = p_obj["shortcut"].toString();
|
||||
}
|
||||
|
||||
if (p_obj.contains("type")) {
|
||||
p_task->dto.type = p_obj["type"].toString();
|
||||
}
|
||||
|
||||
if (p_obj.contains("command")) {
|
||||
p_task->dto.command = getLocaleString(p_obj["command"], p_task->m_locale);
|
||||
}
|
||||
|
||||
if (p_obj.contains("args")) {
|
||||
p_task->dto.args = getLocaleStringList(p_obj["args"], p_task->m_locale);
|
||||
}
|
||||
|
||||
if (p_obj.contains("label")) {
|
||||
p_task->dto.label = getLocaleString(p_obj["label"], p_task->m_locale);
|
||||
} else if (p_task->dto.label.isNull() && !p_task->dto.command.isNull()) {
|
||||
p_task->dto.label = p_task->dto.command;
|
||||
}
|
||||
|
||||
if (p_obj.contains("options")) {
|
||||
auto options = p_obj["options"].toObject();
|
||||
|
||||
if (options.contains("cwd")) {
|
||||
p_task->dto.options.cwd = options["cwd"].toString();
|
||||
}
|
||||
|
||||
if (options.contains("env")) {
|
||||
p_task->dto.options.env.clear();
|
||||
auto env = options["env"].toObject();
|
||||
for (auto i = env.begin(); i != env.end(); i++) {
|
||||
auto key = i.key();
|
||||
auto value = getLocaleString(i.value(), p_task->m_locale);
|
||||
p_task->dto.options.env.insert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.contains("shell") && p_task->getType() == "shell") {
|
||||
auto shell = options["shell"].toObject();
|
||||
|
||||
if (shell.contains("executable")) {
|
||||
p_task->dto.options.shell.executable = shell["executable"].toString();
|
||||
}
|
||||
|
||||
if (shell.contains("args")) {
|
||||
p_task->dto.options.shell.args.clear();
|
||||
|
||||
for (auto arg : shell["args"].toArray()) {
|
||||
p_task->dto.options.shell.args << arg.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (p_obj.contains("tasks")) {
|
||||
if (!mergeTasks) p_task->m_tasks.clear();
|
||||
auto tasks = p_obj["tasks"].toArray();
|
||||
|
||||
for (const auto &task : tasks) {
|
||||
auto t = new Task(p_task->m_locale,
|
||||
p_task->getFile(),
|
||||
p_task);
|
||||
p_task->m_tasks.append(fromJson(t, task.toObject()));
|
||||
}
|
||||
}
|
||||
|
||||
if (p_obj.contains("inputs")) {
|
||||
p_task->dto.inputs.clear();
|
||||
auto inputs = p_obj["inputs"].toArray();
|
||||
|
||||
for (const auto &input : inputs) {
|
||||
auto in = input.toObject();
|
||||
InputDTO i;
|
||||
if (in.contains("id")) {
|
||||
i.id = in["id"].toString();
|
||||
} else {
|
||||
qWarning() << "Input configuration not contain id";
|
||||
}
|
||||
|
||||
if (in.contains("type")) {
|
||||
i.type = in["type"].toString();
|
||||
} else {
|
||||
i.type = "promptString";
|
||||
}
|
||||
|
||||
if (in.contains("description")) {
|
||||
i.description = getLocaleString(in["description"], p_task->m_locale);
|
||||
}
|
||||
|
||||
if (in.contains("default")) {
|
||||
i.default_ = getLocaleString(in["default"], p_task->m_locale);
|
||||
}
|
||||
|
||||
if (i.type == "promptString" && in.contains("password")) {
|
||||
i.password = in["password"].toBool();
|
||||
} else {
|
||||
i.password = false;
|
||||
}
|
||||
|
||||
if (i.type == "pickString" && in.contains("options")) {
|
||||
i.options = getLocaleStringList(in["options"], p_task->m_locale);
|
||||
}
|
||||
|
||||
if (i.type == "pickString" && !i.default_.isNull() && !i.options.contains(i.default_)) {
|
||||
qWarning() << "default must be one of the option values";
|
||||
}
|
||||
|
||||
p_task->dto.inputs << i;
|
||||
}
|
||||
}
|
||||
|
||||
if (p_obj.contains("messages")) {
|
||||
p_task->dto.messages.clear();
|
||||
auto messages = p_obj["messages"].toArray();
|
||||
|
||||
for (const auto &message : messages) {
|
||||
auto msg = message.toObject();
|
||||
MessageDTO m;
|
||||
if (msg.contains("id")) {
|
||||
m.id = msg["id"].toString();
|
||||
} else {
|
||||
qWarning() << "Message configuration not contain id";
|
||||
}
|
||||
|
||||
if (msg.contains("type")) {
|
||||
m.type = msg["type"].toString();
|
||||
} else {
|
||||
m.type = "information";
|
||||
}
|
||||
|
||||
if (msg.contains("title")) {
|
||||
m.title = getLocaleString(msg["title"], p_task->m_locale);
|
||||
}
|
||||
|
||||
if (msg.contains("text")) {
|
||||
m.text = getLocaleString(msg["text"], p_task->m_locale);
|
||||
}
|
||||
|
||||
if (msg.contains("detailedText")) {
|
||||
m.detailedText = getLocaleString(msg["detailedText"], p_task->m_locale);
|
||||
}
|
||||
|
||||
if (msg.contains("buttons")) {
|
||||
auto buttons = msg["buttons"].toArray();
|
||||
for (auto button : buttons) {
|
||||
auto btn = button.toObject();
|
||||
ButtonDTO b;
|
||||
b.text = getLocaleString(btn["text"], p_task->m_locale);
|
||||
m.buttons << b;
|
||||
}
|
||||
}
|
||||
p_task->dto.messages << m;
|
||||
}
|
||||
}
|
||||
|
||||
// OS-specific task configuration
|
||||
#if defined (Q_OS_WIN)
|
||||
#define OS_SPEC "windows"
|
||||
#endif
|
||||
#if defined (Q_OS_MACOS)
|
||||
#define OS_SPEC "osx"
|
||||
#endif
|
||||
#if defined (Q_OS_LINUX)
|
||||
#define OS_SPEC "linux"
|
||||
#endif
|
||||
if (p_obj.contains(OS_SPEC)) {
|
||||
auto os = p_obj[OS_SPEC].toObject();
|
||||
fromJsonV0(p_task, os, true);
|
||||
}
|
||||
#undef OS_SPEC
|
||||
|
||||
return p_task;
|
||||
}
|
||||
|
||||
QString Task::getVersion() const
|
||||
{
|
||||
return dto.version;
|
||||
}
|
||||
|
||||
QString Task::getType() const
|
||||
{
|
||||
return dto.type;
|
||||
}
|
||||
|
||||
QString Task::getCommand() const
|
||||
{
|
||||
return s_vars.evaluate(dto.command, this);
|
||||
}
|
||||
|
||||
QStringList Task::getArgs() const
|
||||
{
|
||||
return s_vars.evaluate(dto.args, this);
|
||||
}
|
||||
|
||||
QString Task::getLabel() const
|
||||
{
|
||||
return dto.label;
|
||||
}
|
||||
|
||||
QString Task::getIcon() const
|
||||
{
|
||||
return dto.icon;
|
||||
}
|
||||
|
||||
QString Task::getShortcut() const
|
||||
{
|
||||
return dto.shortcut;
|
||||
}
|
||||
|
||||
QString Task::getOptionsCwd() const
|
||||
{
|
||||
auto cwd = dto.options.cwd;
|
||||
if (!cwd.isNull()) {
|
||||
return s_vars.evaluate(cwd, this);
|
||||
}
|
||||
auto notebook = TaskHelper::getCurrentNotebook();
|
||||
if (notebook) cwd = notebook->getRootFolderAbsolutePath();
|
||||
if (!cwd.isNull()) {
|
||||
return cwd;
|
||||
}
|
||||
cwd = TaskHelper::getCurrentFile();
|
||||
if (!cwd.isNull()) {
|
||||
return QFileInfo(cwd).dir().absolutePath();
|
||||
}
|
||||
return QFileInfo(dto._source).dir().absolutePath();
|
||||
}
|
||||
|
||||
const QMap<QString, QString> &Task::getOptionsEnv() const
|
||||
{
|
||||
return dto.options.env;
|
||||
}
|
||||
|
||||
QString Task::getOptionsShellExecutable() const
|
||||
{
|
||||
return dto.options.shell.executable;
|
||||
}
|
||||
|
||||
QStringList Task::getOptionsShellArgs() const
|
||||
{
|
||||
if (dto.options.shell.args.isEmpty()) {
|
||||
return ShellExecution::defaultShellArguments(dto.options.shell.executable);
|
||||
} else {
|
||||
return s_vars.evaluate(dto.options.shell.args, this);
|
||||
}
|
||||
}
|
||||
|
||||
const QVector<Task *> &Task::getTasks() const
|
||||
{
|
||||
return m_tasks;
|
||||
}
|
||||
|
||||
const QVector<InputDTO> &Task::getInputs() const
|
||||
{
|
||||
return dto.inputs;
|
||||
}
|
||||
|
||||
InputDTO Task::getInput(const QString &p_id) const
|
||||
{
|
||||
for (auto i : dto.inputs) {
|
||||
if (i.id == p_id) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
qDebug() << getLabel();
|
||||
qWarning() << "input" << p_id << "not found";
|
||||
throw "Input variable can not found";
|
||||
}
|
||||
|
||||
MessageDTO Task::getMessage(const QString &p_id) const
|
||||
{
|
||||
for (auto msg : dto.messages) {
|
||||
if (msg.id == p_id) {
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
qDebug() << getLabel();
|
||||
qWarning() << "message" << p_id << "not found";
|
||||
throw "Message can not found";
|
||||
}
|
||||
|
||||
QString Task::getFile() const
|
||||
{
|
||||
return dto._source;
|
||||
}
|
||||
|
||||
Task::Task(const QString &p_locale,
|
||||
const QString &p_file,
|
||||
QObject *p_parent)
|
||||
: QObject(p_parent)
|
||||
{
|
||||
dto._source = p_file;
|
||||
dto.version = s_latestVersion;
|
||||
dto.type = "shell";
|
||||
dto.options.shell.executable = ShellExecution::defaultShell();
|
||||
|
||||
// inherit configuration
|
||||
m_parent = qobject_cast<Task*>(p_parent);
|
||||
if (m_parent) {
|
||||
dto.version = m_parent->dto.version;
|
||||
dto.type = m_parent->dto.type;
|
||||
dto.command = m_parent->dto.command;
|
||||
dto.args = m_parent->dto.args;
|
||||
dto.options.cwd = m_parent->dto.options.cwd;
|
||||
dto.options.env = m_parent->dto.options.env;
|
||||
dto.options.shell.executable = m_parent->dto.options.shell.executable;
|
||||
dto.options.shell.args = m_parent->dto.options.shell.args;
|
||||
// not inherit label/inputs/tasks
|
||||
} else {
|
||||
dto.label = QFileInfo(p_file).baseName();
|
||||
}
|
||||
|
||||
if (!p_locale.isNull()) {
|
||||
m_locale = p_locale;
|
||||
}
|
||||
}
|
||||
|
||||
QProcess *Task::setupProcess() const
|
||||
{
|
||||
// Set process property
|
||||
auto command = getCommand();
|
||||
if (command.isEmpty()) return nullptr;
|
||||
auto process = new QProcess(this->parent());
|
||||
process->setWorkingDirectory(getOptionsCwd());
|
||||
|
||||
auto options_env = getOptionsEnv();
|
||||
if (!options_env.isEmpty()) {
|
||||
auto env = QProcessEnvironment::systemEnvironment();
|
||||
for (auto i = options_env.begin(); i != options_env.end(); i++) {
|
||||
env.insert(i.key(), i.value());
|
||||
}
|
||||
process->setProcessEnvironment(env);
|
||||
}
|
||||
|
||||
auto args = getArgs();
|
||||
auto type = getType();
|
||||
|
||||
// set program and args
|
||||
if (type == "shell") {
|
||||
ShellExecution::setupProcess(process,
|
||||
command,
|
||||
args,
|
||||
getOptionsShellExecutable(),
|
||||
getOptionsShellArgs());
|
||||
} else if (getType() == "process") {
|
||||
process->setProgram(command);
|
||||
process->setArguments(args);
|
||||
}
|
||||
|
||||
// connect signal and slot
|
||||
connect(process, &QProcess::started,
|
||||
this, [this]() {
|
||||
emit showOutput(tr("[Task %1 started]\n").arg(getLabel()));
|
||||
});
|
||||
connect(process, &QProcess::readyReadStandardOutput,
|
||||
this, [process, this]() {
|
||||
auto text = textDecode(process->readAllStandardOutput());
|
||||
text = TaskHelper::handleCommand(text, process, this);
|
||||
emit showOutput(text);
|
||||
});
|
||||
connect(process, &QProcess::readyReadStandardError,
|
||||
this, [process, this]() {
|
||||
auto text = process->readAllStandardError();
|
||||
emit showOutput(textDecode(text));
|
||||
});
|
||||
connect(process, &QProcess::errorOccurred,
|
||||
this, [this](QProcess::ProcessError error) {
|
||||
emit showOutput(tr("[Task %1 error occurred with code %2]\n").arg(getLabel(), QString::number(error)));
|
||||
});
|
||||
connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
|
||||
this, [this, process](int exitCode) {
|
||||
emit showOutput(tr("\n[Task %1 finished with exit code %2]\n")
|
||||
.arg(getLabel(), QString::number(exitCode)));
|
||||
process->deleteLater();
|
||||
});
|
||||
return process;
|
||||
}
|
||||
|
||||
void Task::run() const
|
||||
{
|
||||
QProcess *process;
|
||||
try {
|
||||
process = setupProcess();
|
||||
} catch (const char *msg) {
|
||||
qDebug() << msg;
|
||||
return ;
|
||||
}
|
||||
if (process) {
|
||||
// start process
|
||||
qInfo() << "run task" << process->program() << process->arguments();
|
||||
process->start();
|
||||
}
|
||||
}
|
||||
|
||||
TaskDTO Task::getDTO() const
|
||||
{
|
||||
return dto;
|
||||
}
|
||||
|
||||
QString Task::textDecode(const QByteArray &p_text)
|
||||
{
|
||||
static QByteArrayList codecNames = {
|
||||
"UTF-8",
|
||||
"System",
|
||||
"UTF-16",
|
||||
"GB18030"
|
||||
};
|
||||
for (auto name : codecNames) {
|
||||
auto text = textDecode(p_text, name);
|
||||
if (!text.isNull()) return text;
|
||||
}
|
||||
return p_text;
|
||||
}
|
||||
|
||||
QString Task::textDecode(const QByteArray &p_text, const QByteArray &name)
|
||||
{
|
||||
auto codec = QTextCodec::codecForName(name);
|
||||
if (codec) {
|
||||
QTextCodec::ConverterState state;
|
||||
auto text = codec->toUnicode(p_text.data(), p_text.size(), &state);
|
||||
if (state.invalidChars > 0) return QString();
|
||||
return text;
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
|
||||
bool Task::isValidTaskFile(const QString &p_file,
|
||||
QJsonDocument &p_json)
|
||||
{
|
||||
QFile file(p_file);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
return false;
|
||||
}
|
||||
QJsonParseError error;
|
||||
p_json = QJsonDocument::fromJson(file.readAll(), &error);
|
||||
file.close();
|
||||
if (p_json.isNull()) {
|
||||
qDebug() << "load task" << p_file << "failed: " << error.errorString()
|
||||
<< "at offset" << error.offset;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QString Task::getLocaleString(const QJsonValue &p_value, const QString &p_locale)
|
||||
{
|
||||
if (p_value.isObject()) {
|
||||
auto obj = p_value.toObject();
|
||||
if (obj.contains(p_locale)) {
|
||||
return obj.value(p_locale).toString();
|
||||
} else {
|
||||
qWarning() << "current locale" << p_locale << "not found";
|
||||
if (!obj.isEmpty()){
|
||||
return obj.begin().value().toString();
|
||||
} else {
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return p_value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
QStringList Task::getLocaleStringList(const QJsonValue &p_value, const QString &p_locale)
|
||||
{
|
||||
QStringList list;
|
||||
for (auto value : p_value.toArray()) {
|
||||
list << getLocaleString(value, p_locale);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
QStringList Task::getStringList(const QJsonValue &p_value)
|
||||
{
|
||||
QStringList list;
|
||||
for (auto value : p_value.toArray()) {
|
||||
list << value.toString();
|
||||
}
|
||||
return list;
|
||||
}
|
185
src/core/task.h
Normal file
@ -0,0 +1,185 @@
|
||||
#ifndef TASK_H
|
||||
#define TASK_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QJsonObject>
|
||||
#include <QVector>
|
||||
#include <QMap>
|
||||
#include <QProcess>
|
||||
|
||||
#include "taskvariablemgr.h"
|
||||
|
||||
class QAction;
|
||||
|
||||
namespace vnotex {
|
||||
|
||||
struct ButtonDTO {
|
||||
QString text;
|
||||
};
|
||||
|
||||
struct InputDTO {
|
||||
QString id;
|
||||
|
||||
QString type;
|
||||
|
||||
QString description;
|
||||
|
||||
QString default_;
|
||||
|
||||
bool password;
|
||||
|
||||
QStringList options;
|
||||
};
|
||||
|
||||
struct MessageDTO {
|
||||
QString id;
|
||||
|
||||
QString type;
|
||||
|
||||
QString title;
|
||||
|
||||
QString text;
|
||||
|
||||
QString detailedText;
|
||||
|
||||
QVector<ButtonDTO> buttons;
|
||||
};
|
||||
|
||||
struct ShellOptionsDTO {
|
||||
QString executable;
|
||||
|
||||
QStringList args;
|
||||
};
|
||||
|
||||
struct TaskOptionsDTO {
|
||||
QString cwd;
|
||||
|
||||
QMap<QString, QString> env;
|
||||
|
||||
ShellOptionsDTO shell;
|
||||
};
|
||||
|
||||
struct TaskDTO {
|
||||
QString version;
|
||||
|
||||
QString type;
|
||||
|
||||
QString command;
|
||||
|
||||
QStringList args;
|
||||
|
||||
QString label;
|
||||
|
||||
QString icon;
|
||||
|
||||
QString shortcut;
|
||||
|
||||
QVector<InputDTO> inputs;
|
||||
|
||||
QVector<MessageDTO> messages;
|
||||
|
||||
TaskOptionsDTO options;
|
||||
|
||||
QString _scope;
|
||||
|
||||
QString _source;
|
||||
};
|
||||
|
||||
class Notebook;
|
||||
|
||||
class Task : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
|
||||
static Task* fromFile(const QString &p_file,
|
||||
const QJsonDocument &p_json,
|
||||
const QString &p_locale,
|
||||
QObject *p_parent = nullptr);
|
||||
|
||||
void run() const;
|
||||
|
||||
TaskDTO getDTO() const;
|
||||
|
||||
QString getVersion() const;
|
||||
|
||||
QString getType() const;
|
||||
|
||||
QString getCommand() const;
|
||||
|
||||
QStringList getArgs() const;
|
||||
|
||||
QString getLabel() const;
|
||||
|
||||
QString getIcon() const;
|
||||
|
||||
QString getShortcut() const;
|
||||
|
||||
QString getOptionsCwd() const;
|
||||
|
||||
const QMap<QString, QString> &getOptionsEnv() const;
|
||||
|
||||
QString getOptionsShellExecutable() const;
|
||||
|
||||
QStringList getOptionsShellArgs() const;
|
||||
|
||||
const QVector<Task*> &getTasks() const;
|
||||
|
||||
const QVector<InputDTO> &getInputs() const;
|
||||
|
||||
InputDTO getInput(const QString &p_id) const;
|
||||
|
||||
MessageDTO getMessage(const QString &p_id) const;
|
||||
|
||||
QString getFile() const;
|
||||
|
||||
static QString s_latestVersion;
|
||||
|
||||
static bool isValidTaskFile(const QString &p_file,
|
||||
QJsonDocument &p_json);
|
||||
|
||||
static QString getLocaleString(const QJsonValue &p_value,
|
||||
const QString &p_locale);
|
||||
|
||||
static QStringList getLocaleStringList(const QJsonValue &p_value,
|
||||
const QString &p_locale);
|
||||
|
||||
static QStringList getStringList(const QJsonValue &p_value);
|
||||
|
||||
signals:
|
||||
void showOutput(const QString &p_text) const;
|
||||
|
||||
private:
|
||||
static Task* fromJson(Task *p_task,
|
||||
const QJsonObject &p_obj);
|
||||
|
||||
static Task* fromJsonV0(Task *p_task,
|
||||
const QJsonObject &p_obj,
|
||||
bool mergeTasks = false);
|
||||
|
||||
explicit Task(const QString &p_locale,
|
||||
const QString &p_file = QString(),
|
||||
QObject *p_parent = nullptr);
|
||||
|
||||
QProcess *setupProcess() const;
|
||||
|
||||
static QString textDecode(const QByteArray &p_text);
|
||||
|
||||
static QString textDecode(const QByteArray &p_text, const QByteArray &name);
|
||||
|
||||
TaskDTO dto;
|
||||
|
||||
Task *m_parent = nullptr;
|
||||
|
||||
QVector<Task*> m_tasks;
|
||||
|
||||
QString m_locale;
|
||||
|
||||
static TaskVariableMgr s_vars;
|
||||
|
||||
};
|
||||
|
||||
} // ns vnotex
|
||||
|
||||
#endif // TASK_H
|
233
src/core/taskhelper.cpp
Normal file
@ -0,0 +1,233 @@
|
||||
#include "taskhelper.h"
|
||||
|
||||
#include <QRegularExpression>
|
||||
#include <QJsonArray>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QInputDialog>
|
||||
|
||||
#include "vnotex.h"
|
||||
#include "notebookmgr.h"
|
||||
#include <widgets/mainwindow.h>
|
||||
#include <widgets/viewarea.h>
|
||||
#include <widgets/viewwindow.h>
|
||||
#include <widgets/dialogs/selectdialog.h>
|
||||
#include <widgets/markdownviewwindow.h>
|
||||
#include <widgets/textviewwindow.h>
|
||||
#include <utils/pathutils.h>
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
QString TaskHelper::normalPath(const QString &p_text)
|
||||
{
|
||||
#if defined (Q_OS_WIN)
|
||||
return QString(p_text).replace('/', '\\');
|
||||
#endif
|
||||
return p_text;
|
||||
}
|
||||
|
||||
QString TaskHelper::getCurrentFile()
|
||||
{
|
||||
auto win = VNoteX::getInst().getMainWindow()->getViewArea()->getCurrentViewWindow();
|
||||
QString file;
|
||||
if (win && win->getBuffer()) {
|
||||
file = win->getBuffer()->getPath();
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
QSharedPointer<Notebook> TaskHelper::getCurrentNotebook()
|
||||
{
|
||||
const auto ¬ebookMgr = VNoteX::getInst().getNotebookMgr();
|
||||
auto id = notebookMgr.getCurrentNotebookId();
|
||||
if (id == Notebook::InvalidId) return nullptr;
|
||||
return notebookMgr.findNotebookById(id);
|
||||
}
|
||||
|
||||
QString TaskHelper::getFileNotebookFolder(const QString p_currentFile)
|
||||
{
|
||||
const auto ¬ebookMgr = VNoteX::getInst().getNotebookMgr();
|
||||
const auto ¬ebooks = notebookMgr.getNotebooks();
|
||||
for (auto notebook : notebooks) {
|
||||
auto rootPath = notebook->getRootFolderAbsolutePath();
|
||||
if (PathUtils::pathContains(rootPath, p_currentFile)) {
|
||||
return rootPath;
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString TaskHelper::getSelectedText()
|
||||
{
|
||||
auto window = VNoteX::getInst().getMainWindow()->getViewArea()->getCurrentViewWindow();
|
||||
{
|
||||
auto win = dynamic_cast<MarkdownViewWindow*>(window);
|
||||
if (win) {
|
||||
return win->selectedText();
|
||||
}
|
||||
}
|
||||
{
|
||||
auto win = dynamic_cast<TextViewWindow*>(window);
|
||||
if (win) {
|
||||
return win->selectedText();
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QStringList TaskHelper::getAllSpecialVariables(const QString &p_name, const QString &p_text)
|
||||
{
|
||||
QStringList list;
|
||||
QRegularExpression re(QString(R"(\$\{[\t ]*%1[\t ]*:[\t ]*(.*?)[\t ]*\})").arg(p_name));
|
||||
auto it = re.globalMatch(p_text);
|
||||
while (it.hasNext()) {
|
||||
list << it.next().captured(1);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
QString TaskHelper::replaceAllSepcialVariables(const QString &p_name,
|
||||
const QString &p_text,
|
||||
const QMap<QString, QString> &p_map)
|
||||
{
|
||||
auto text = p_text;
|
||||
for (auto i = p_map.begin(); i != p_map.end(); i++) {
|
||||
auto key = QString(i.key()).replace(".", "\\.").replace("[", "\\[").replace("]", "\\]");
|
||||
auto pattern = QRegularExpression(QString(R"(\$\{[\t ]*%1[\t ]*:[\t ]*%2[\t ]*\})").arg(p_name, key));
|
||||
text = text.replace(pattern, i.value());
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
QString TaskHelper::evaluateJsonExpr(const QJsonObject &p_obj, const QString &p_expr)
|
||||
{
|
||||
QJsonValue value = p_obj;
|
||||
for (auto token : p_expr.split('.')) {
|
||||
auto pos = token.indexOf('[');
|
||||
auto name = token.mid(0, pos);
|
||||
value = value.toObject().value(name);
|
||||
if (pos == -1) continue;
|
||||
if (token.back() == ']') {
|
||||
for (auto idx : token.mid(pos+1, token.length()-pos-2).split("][")) {
|
||||
bool ok;
|
||||
auto index = idx.toInt(&ok);
|
||||
if (!ok) throw "Config variable syntax error!";
|
||||
value = value.toArray().at(index);
|
||||
}
|
||||
} else {
|
||||
throw "Config variable syntax error!";
|
||||
}
|
||||
}
|
||||
if (value.isBool()) {
|
||||
if (value.toBool()) return "true";
|
||||
else return "false";
|
||||
} else if (value.isDouble()) {
|
||||
return QString::number(value.toDouble());
|
||||
} else if (value.isNull()) {
|
||||
return "null";
|
||||
} else if (value.isUndefined()) {
|
||||
return "undefined";
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
QString TaskHelper::getPathSeparator()
|
||||
{
|
||||
#if defined (Q_OS_WIN)
|
||||
return "\\";
|
||||
#else
|
||||
return "/";
|
||||
#endif
|
||||
}
|
||||
|
||||
QString TaskHelper::handleCommand(const QString &p_text,
|
||||
QProcess *p_process,
|
||||
const Task *p_task)
|
||||
{
|
||||
QRegularExpression re(R"(^::([a-zA-Z-]+)(.*?)?::(.*?)$)",
|
||||
QRegularExpression::MultilineOption);
|
||||
auto i = re.globalMatch(p_text);
|
||||
while (i.hasNext()) {
|
||||
auto match = i.next();
|
||||
auto cmd = match.captured(1).toLower();
|
||||
auto args = match.captured(2).trimmed().split(',');
|
||||
auto value = match.captured(3);
|
||||
|
||||
QMap<QString, QString> arg;
|
||||
for (const auto &i : args) {
|
||||
auto s = i.trimmed();
|
||||
auto p = s.indexOf('=');
|
||||
auto name = s.mid(0, p);
|
||||
QString val;
|
||||
if (p != -1) {
|
||||
val = s.mid(p+1);
|
||||
}
|
||||
arg.insert(name, val);
|
||||
}
|
||||
if (cmd == "show-message") {
|
||||
QMessageBox box;
|
||||
// fill message dto
|
||||
auto msgId = arg.value("id");
|
||||
QVector<QPushButton*> buttons;
|
||||
if (!msgId.isEmpty()) {
|
||||
MessageDTO msgd = p_task->getMessage(msgId);
|
||||
box.setWindowTitle(msgd.title);
|
||||
box.setText(msgd.text);
|
||||
box.setDetailedText(msgd.detailedText);
|
||||
for (auto button : msgd.buttons) {
|
||||
buttons.append(box.addButton(button.text, QMessageBox::ActionRole));
|
||||
}
|
||||
}
|
||||
// fill args
|
||||
if (arg.contains("title")) box.setWindowTitle(arg["title"]);
|
||||
if (arg.contains("text")) box.setText(arg["text"]);
|
||||
if (arg.contains("detailedText")) box.setWindowTitle(arg["detailedText"]);
|
||||
if (arg.contains("buttons")) {
|
||||
buttons.clear();
|
||||
for (auto button : arg["buttons"].split('|')) {
|
||||
buttons.append(box.addButton(button, QMessageBox::ActionRole));
|
||||
}
|
||||
}
|
||||
box.exec();
|
||||
int clickedBtnId;
|
||||
for (clickedBtnId = 0; clickedBtnId < buttons.size(); clickedBtnId++) {
|
||||
if (box.clickedButton() == buttons.at(clickedBtnId)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (p_process) {
|
||||
if (p_process->state() == QProcess::Running) {
|
||||
p_process->write(QByteArray::number(clickedBtnId)+"\n");
|
||||
}
|
||||
} else {
|
||||
qWarning() << "process finished!";
|
||||
}
|
||||
} else if (cmd == "show-inputdialog") {
|
||||
QInputDialog dialog;
|
||||
dialog.setWindowTitle(arg.value("title"));
|
||||
|
||||
} else if (cmd == "show-info") {
|
||||
QMessageBox::information(VNoteX::getInst().getMainWindow(),
|
||||
arg.value("title"),
|
||||
value);
|
||||
} else if (cmd == "show-question") {
|
||||
auto ret = QMessageBox::question(VNoteX::getInst().getMainWindow(),
|
||||
arg.value("title"),
|
||||
value);
|
||||
if (p_process) {
|
||||
if (p_process->state() == QProcess::Running) {
|
||||
p_process->write(QByteArray::number(ret)+"\n");
|
||||
}
|
||||
} else {
|
||||
qWarning() << "process finished!";
|
||||
}
|
||||
}
|
||||
}
|
||||
auto text = p_text;
|
||||
return text.replace(re, "");
|
||||
}
|
||||
|
||||
TaskHelper::TaskHelper()
|
||||
{
|
||||
|
||||
}
|
50
src/core/taskhelper.h
Normal file
@ -0,0 +1,50 @@
|
||||
#ifndef TASKHELPER_H
|
||||
#define TASKHELPER_H
|
||||
|
||||
#include <QString>
|
||||
#include <QSharedPointer>
|
||||
|
||||
class QProcess;
|
||||
|
||||
namespace vnotex {
|
||||
|
||||
class Notebook;
|
||||
class Task;
|
||||
|
||||
class TaskHelper
|
||||
{
|
||||
public:
|
||||
// helper functions
|
||||
|
||||
static QString normalPath(const QString &p_text);
|
||||
|
||||
static QString getCurrentFile();
|
||||
|
||||
static QSharedPointer<Notebook> getCurrentNotebook();
|
||||
|
||||
static QString getFileNotebookFolder(const QString p_currentFile);
|
||||
|
||||
static QString getSelectedText();
|
||||
|
||||
static QStringList getAllSpecialVariables(const QString &p_name, const QString &p_text);
|
||||
|
||||
static QString replaceAllSepcialVariables(const QString &p_name,
|
||||
const QString &p_text,
|
||||
const QMap<QString, QString> &p_map);
|
||||
|
||||
static QString evaluateJsonExpr(const QJsonObject &p_obj,
|
||||
const QString &p_expr);
|
||||
|
||||
static QString getPathSeparator();
|
||||
|
||||
static QString handleCommand(const QString &p_text,
|
||||
QProcess *p_process,
|
||||
const Task *p_task);
|
||||
|
||||
private:
|
||||
TaskHelper();
|
||||
};
|
||||
|
||||
} // ns vnotex
|
||||
|
||||
#endif // TASKHELPER_H
|
192
src/core/taskmgr.cpp
Normal file
@ -0,0 +1,192 @@
|
||||
#include "taskmgr.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QDebug>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QJsonDocument>
|
||||
|
||||
#include "configmgr.h"
|
||||
#include "coreconfig.h"
|
||||
#include "utils/pathutils.h"
|
||||
#include "utils/fileutils.h"
|
||||
#include "vnotex.h"
|
||||
#include "notebookmgr.h"
|
||||
#include "notebookconfigmgr/bundlenotebookconfigmgr.h"
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
QStringList TaskMgr::s_searchPaths;
|
||||
|
||||
TaskMgr::TaskMgr(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
m_watcher = new QFileSystemWatcher(this);
|
||||
}
|
||||
|
||||
void TaskMgr::init()
|
||||
{
|
||||
// load all tasks and watch them
|
||||
loadAllTask();
|
||||
|
||||
connect(&VNoteX::getInst().getNotebookMgr(), &NotebookMgr::currentNotebookChanged,
|
||||
this, &TaskMgr::loadAllTask);
|
||||
connect(m_watcher, &QFileSystemWatcher::directoryChanged,
|
||||
this, &TaskMgr::loadAllTask);
|
||||
connect(m_watcher, &QFileSystemWatcher::fileChanged,
|
||||
this, &TaskMgr::loadAllTask);
|
||||
}
|
||||
|
||||
void TaskMgr::refresh()
|
||||
{
|
||||
loadAvailableTasks();
|
||||
}
|
||||
|
||||
QVector<Task*> TaskMgr::getAllTasks() const
|
||||
{
|
||||
QVector<Task*> tasks;
|
||||
tasks.append(m_appTasks);
|
||||
tasks.append(m_userTasks);
|
||||
tasks.append(m_notebookTasks);
|
||||
return tasks;
|
||||
}
|
||||
|
||||
const QVector<Task *> &TaskMgr::getAppTasks() const
|
||||
{
|
||||
return m_appTasks;
|
||||
}
|
||||
|
||||
const QVector<Task *> &TaskMgr::getUserTasks() const
|
||||
{
|
||||
return m_userTasks;
|
||||
}
|
||||
|
||||
const QVector<Task *> &TaskMgr::getNotebookTasks() const
|
||||
{
|
||||
return m_notebookTasks;
|
||||
}
|
||||
|
||||
void TaskMgr::addSearchPath(const QString &p_path)
|
||||
{
|
||||
s_searchPaths << p_path;
|
||||
}
|
||||
|
||||
QString TaskMgr::getNotebookTaskFolder()
|
||||
{
|
||||
const auto ¬ebookMgr = VNoteX::getInst().getNotebookMgr();
|
||||
auto id = notebookMgr.getCurrentNotebookId();
|
||||
if (id == Notebook::InvalidId) return QString();
|
||||
auto notebook = notebookMgr.findNotebookById(id);
|
||||
if (!notebook) return QString();
|
||||
auto configMgr = notebook->getConfigMgr();
|
||||
if (!configMgr) return QString();
|
||||
auto configMgrName = configMgr->getName();
|
||||
if (configMgrName == "vx.vnotex") {
|
||||
QDir dir(notebook->getRootFolderAbsolutePath());
|
||||
dir.cd(BundleNotebookConfigMgr::getConfigFolderName());
|
||||
if (!dir.cd("tasks")) return QString();
|
||||
return dir.absolutePath();
|
||||
} else {
|
||||
qWarning() << "Unknow notebook config type"<< configMgrName <<"task will not be load.";
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
void TaskMgr::addWatchPaths(const QStringList &list)
|
||||
{
|
||||
if (list.isEmpty()) return ;
|
||||
qDebug() << "addWatchPaths" << list;
|
||||
m_watcher->addPaths(list);
|
||||
}
|
||||
|
||||
void TaskMgr::clearWatchPaths()
|
||||
{
|
||||
auto entrys = m_watcher->files();
|
||||
if (!entrys.isEmpty()) {
|
||||
m_watcher->removePaths(entrys);
|
||||
}
|
||||
entrys = m_watcher->directories();
|
||||
if (!entrys.isEmpty()) {
|
||||
m_watcher->removePaths(entrys);
|
||||
}
|
||||
}
|
||||
|
||||
void TaskMgr::clearTasks()
|
||||
{
|
||||
m_appTasks.clear();
|
||||
m_userTasks.clear();
|
||||
m_notebookTasks.clear();
|
||||
m_files.clear();
|
||||
}
|
||||
|
||||
void TaskMgr::addAllTaskFolder()
|
||||
{
|
||||
s_searchPaths.clear();
|
||||
auto &configMgr = ConfigMgr::getInst();
|
||||
// App scope task folder
|
||||
addSearchPath(configMgr.getAppTaskFolder());
|
||||
// User scope task folder
|
||||
addSearchPath(configMgr.getUserTaskFolder());
|
||||
// Notebook scope task folder
|
||||
auto path = getNotebookTaskFolder();
|
||||
if (!path.isNull()) addSearchPath(path);
|
||||
}
|
||||
|
||||
void TaskMgr::loadAllTask()
|
||||
{
|
||||
addAllTaskFolder();
|
||||
loadAvailableTasks();
|
||||
watchTaskEntrys();
|
||||
emit taskChanged();
|
||||
}
|
||||
|
||||
void TaskMgr::watchTaskEntrys()
|
||||
{
|
||||
clearWatchPaths();
|
||||
addWatchPaths(s_searchPaths);
|
||||
for (const auto &pa : s_searchPaths) {
|
||||
addWatchPaths(FileUtils::entryListRecursively(pa, QStringList(), QDir::AllDirs));
|
||||
}
|
||||
addWatchPaths(m_files);
|
||||
}
|
||||
|
||||
void TaskMgr::loadAvailableTasks()
|
||||
{
|
||||
m_files.clear();
|
||||
auto &configMgr = ConfigMgr::getInst();
|
||||
loadAvailableTasks(m_appTasks, configMgr.getAppTaskFolder());
|
||||
loadAvailableTasks(m_userTasks, configMgr.getUserTaskFolder());
|
||||
loadAvailableTasks(m_notebookTasks, getNotebookTaskFolder());
|
||||
}
|
||||
|
||||
void TaskMgr::loadAvailableTasks(QVector<Task *> &p_tasks, const QString &p_searchPath)
|
||||
{
|
||||
p_tasks.clear();
|
||||
if (p_searchPath.isEmpty()) return ;
|
||||
const auto taskFiles = FileUtils::entryListRecursively(p_searchPath, {"*.json"}, QDir::Files);
|
||||
for (auto &file : taskFiles) {
|
||||
m_files << file;
|
||||
checkAndAddTaskFile(p_tasks, file);
|
||||
}
|
||||
|
||||
{
|
||||
QStringList list;
|
||||
for (auto task : p_tasks) {
|
||||
list << QFileInfo(task->getFile()).fileName();
|
||||
}
|
||||
if (!p_tasks.isEmpty()) qDebug() << "load tasks" << list;
|
||||
}
|
||||
}
|
||||
|
||||
void TaskMgr::checkAndAddTaskFile(QVector<Task *> &p_tasks, const QString &p_file)
|
||||
{
|
||||
QJsonDocument json;
|
||||
if (Task::isValidTaskFile(p_file, json)) {
|
||||
const auto localeStr = ConfigMgr::getInst().getCoreConfig().getLocaleToUse();
|
||||
p_tasks.push_back(Task::fromFile(p_file, json, localeStr, this));
|
||||
}
|
||||
}
|
||||
|
||||
void TaskMgr::deleteTask(Task *p_task)
|
||||
{
|
||||
Q_UNUSED(p_task);
|
||||
}
|
79
src/core/taskmgr.h
Normal file
@ -0,0 +1,79 @@
|
||||
#ifndef TASKMGR_H
|
||||
#define TASKMGR_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <QVector>
|
||||
|
||||
#include "task.h"
|
||||
|
||||
class QFileSystemWatcher;
|
||||
|
||||
namespace vnotex
|
||||
{
|
||||
class TaskMgr : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
explicit TaskMgr(QObject *parent = nullptr);
|
||||
|
||||
// It will be invoked after MainWindow show
|
||||
void init();
|
||||
|
||||
void refresh();
|
||||
|
||||
QVector<Task*> getAllTasks() const;
|
||||
|
||||
const QVector<Task*> &getAppTasks() const;
|
||||
|
||||
const QVector<Task*> &getUserTasks() const;
|
||||
|
||||
const QVector<Task*> &getNotebookTasks() const;
|
||||
|
||||
void deleteTask(Task *p_task);
|
||||
|
||||
static void addSearchPath(const QString &p_path);
|
||||
|
||||
static QString getNotebookTaskFolder();
|
||||
|
||||
signals:
|
||||
void taskChanged();
|
||||
|
||||
private:
|
||||
void addWatchPaths(const QStringList &list);
|
||||
|
||||
void clearWatchPaths();
|
||||
|
||||
void clearTasks();
|
||||
|
||||
void addAllTaskFolder();
|
||||
|
||||
void loadAllTask();
|
||||
|
||||
void watchTaskEntrys();
|
||||
|
||||
void loadAvailableTasks();
|
||||
|
||||
void loadAvailableTasks(QVector<Task*> &p_tasks, const QString &p_searchPath);
|
||||
|
||||
void loadTasks(const QString &p_path);
|
||||
|
||||
void checkAndAddTaskFile(QVector<Task*> &p_tasks, const QString &p_file);
|
||||
|
||||
QVector<Task*> m_appTasks;
|
||||
QVector<Task*> m_userTasks;
|
||||
QVector<Task*> m_notebookTasks;
|
||||
|
||||
// all json files in task folder
|
||||
// maybe invalid
|
||||
QStringList m_files;
|
||||
|
||||
QFileSystemWatcher *m_watcher;
|
||||
|
||||
// List of path to search for tasks.
|
||||
static QStringList s_searchPaths;
|
||||
};
|
||||
} // ns vnotex
|
||||
|
||||
#endif // TASKMGR_H
|
334
src/core/taskvariablemgr.cpp
Normal file
@ -0,0 +1,334 @@
|
||||
#include "taskvariablemgr.h"
|
||||
|
||||
#include <QRegularExpression>
|
||||
#include <QInputDialog>
|
||||
#include <QApplication>
|
||||
#include <QRandomGenerator>
|
||||
#include <QTimeZone>
|
||||
|
||||
#include "vnotex.h"
|
||||
#include "task.h"
|
||||
#include "taskhelper.h"
|
||||
#include "shellexecution.h"
|
||||
#include "configmgr.h"
|
||||
#include "mainconfig.h"
|
||||
#include "notebook/notebook.h"
|
||||
#include <widgets/mainwindow.h>
|
||||
#include <widgets/dialogs/selectdialog.h>
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
|
||||
TaskVariable::TaskVariable(TaskVariable::Type p_type,
|
||||
const QString &p_name,
|
||||
TaskVariable::Func p_func)
|
||||
: m_type(p_type), m_name(p_name), m_func(p_func)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
TaskVariableMgr::TaskVariableMgr()
|
||||
: m_initialized(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void TaskVariableMgr::refresh()
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
QString TaskVariableMgr::evaluate(const QString &p_text,
|
||||
const Task *p_task) const
|
||||
{
|
||||
auto text = p_text;
|
||||
auto eval = [&text](const QString &p_name, std::function<QString()> p_func) {
|
||||
auto reg = QRegularExpression(QString(R"(\$\{[\t ]*%1[\t ]*\})").arg(p_name));
|
||||
if (text.contains(reg)) {
|
||||
text.replace(reg, p_func());
|
||||
}
|
||||
};
|
||||
|
||||
// current notebook variables
|
||||
{
|
||||
eval("notebookFolder", []() {
|
||||
auto notebook = TaskHelper::getCurrentNotebook();
|
||||
if (notebook) {
|
||||
return TaskHelper::normalPath(notebook->getRootFolderAbsolutePath());
|
||||
} else return QString();
|
||||
});
|
||||
eval("notebookFolderBasename", []() {
|
||||
auto notebook = TaskHelper::getCurrentNotebook();
|
||||
if (notebook) {
|
||||
auto folder = notebook->getRootFolderAbsolutePath();
|
||||
return QDir(folder).dirName();
|
||||
} else return QString();
|
||||
});
|
||||
eval("notebookName", []() {
|
||||
auto notebook = TaskHelper::getCurrentNotebook();
|
||||
if (notebook) {
|
||||
return notebook->getName();
|
||||
} else return QString();
|
||||
});
|
||||
eval("notebookName", []() {
|
||||
auto notebook = TaskHelper::getCurrentNotebook();
|
||||
if (notebook) {
|
||||
return notebook->getDescription();
|
||||
} else return QString();
|
||||
});
|
||||
}
|
||||
|
||||
// current file variables
|
||||
{
|
||||
eval("file", []() {
|
||||
return TaskHelper::normalPath(TaskHelper::getCurrentFile());
|
||||
});
|
||||
eval("fileNotebookFolder", []() {
|
||||
auto file = TaskHelper::getCurrentFile();
|
||||
return TaskHelper::normalPath(TaskHelper::getFileNotebookFolder(file));
|
||||
});
|
||||
eval("relativeFile", []() {
|
||||
auto file = TaskHelper::getCurrentFile();
|
||||
auto folder = TaskHelper::getFileNotebookFolder(file);
|
||||
return QDir(folder).relativeFilePath(file);
|
||||
});
|
||||
eval("fileBasename", []() {
|
||||
auto file = TaskHelper::getCurrentFile();
|
||||
return QFileInfo(file).fileName();
|
||||
});
|
||||
eval("fileBasename", []() {
|
||||
auto file = TaskHelper::getCurrentFile();
|
||||
return QFileInfo(file).completeBaseName();
|
||||
});
|
||||
eval("fileDirname", []() {
|
||||
auto file = TaskHelper::getCurrentFile();
|
||||
return TaskHelper::normalPath(QFileInfo(file).dir().absolutePath());
|
||||
});
|
||||
eval("fileExtname", []() {
|
||||
auto file = TaskHelper::getCurrentFile();
|
||||
return QFileInfo(file).suffix();
|
||||
});
|
||||
eval("selectedText", []() {
|
||||
return TaskHelper::getSelectedText();
|
||||
});
|
||||
eval("cwd", [p_task]() {
|
||||
return TaskHelper::normalPath(p_task->getOptionsCwd());
|
||||
});
|
||||
eval("taskFile", [p_task]() {
|
||||
return TaskHelper::normalPath(p_task->getFile());
|
||||
});
|
||||
eval("taskDirname", [p_task]() {
|
||||
return TaskHelper::normalPath(QFileInfo(p_task->getFile()).dir().absolutePath());
|
||||
});
|
||||
eval("execPath", []() {
|
||||
return TaskHelper::normalPath(qApp->applicationFilePath());
|
||||
});
|
||||
eval("pathSeparator", []() {
|
||||
return TaskHelper::getPathSeparator();
|
||||
});
|
||||
eval("notebookTaskFolder", []() {
|
||||
return TaskHelper::normalPath(TaskMgr::getNotebookTaskFolder());
|
||||
});
|
||||
eval("userTaskFolder", []() {
|
||||
return TaskHelper::normalPath(ConfigMgr::getInst().getUserTaskFolder());
|
||||
});
|
||||
eval("appTaskFolder", []() {
|
||||
return TaskHelper::normalPath(ConfigMgr::getInst().getAppTaskFolder());
|
||||
});
|
||||
eval("userThemeFolder", []() {
|
||||
return TaskHelper::normalPath(ConfigMgr::getInst().getUserThemeFolder());
|
||||
});
|
||||
eval("appThemeFolder", []() {
|
||||
return TaskHelper::normalPath(ConfigMgr::getInst().getAppThemeFolder());
|
||||
});
|
||||
eval("userDocsFolder", []() {
|
||||
return TaskHelper::normalPath(ConfigMgr::getInst().getUserDocsFolder());
|
||||
});
|
||||
eval("appDocsFolder", []() {
|
||||
return TaskHelper::normalPath(ConfigMgr::getInst().getAppDocsFolder());
|
||||
});
|
||||
}
|
||||
|
||||
// Magic variables
|
||||
{
|
||||
auto cDT = QDateTime::currentDateTime();
|
||||
for(auto s : {
|
||||
"d", "dd", "ddd", "dddd", "M", "MM", "MMM", "MMMM",
|
||||
"yy", "yyyy", "h", "hh", "H", "HH", "m", "mm",
|
||||
"s", "ss", "z", "zzz", "AP", "A", "ap", "a"
|
||||
}) eval(QString("magic:%1").arg(s), [s]() {
|
||||
return QDateTime::currentDateTime().toString(s);
|
||||
});
|
||||
eval("magic:random", []() {
|
||||
return QString::number(QRandomGenerator::global()->generate());
|
||||
});
|
||||
eval("magic:random_d", []() {
|
||||
return QString::number(QRandomGenerator::global()->generate());
|
||||
});
|
||||
eval("magic:date", []() {
|
||||
return QDate::currentDate().toString("yyyy-MM-dd");
|
||||
});
|
||||
eval("magic:da", []() {
|
||||
return QDate::currentDate().toString("yyyyMMdd");
|
||||
});
|
||||
eval("magic:time", []() {
|
||||
return QTime::currentTime().toString("hh:mm:ss");
|
||||
});
|
||||
eval("magic:datetime", []() {
|
||||
return QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
|
||||
});
|
||||
eval("magic:dt", []() {
|
||||
return QDateTime::currentDateTime().toString("yyyyMMdd hh:mm:ss");
|
||||
});
|
||||
eval("magic:note", []() {
|
||||
auto file = TaskHelper::getCurrentFile();
|
||||
return QFileInfo(file).fileName();
|
||||
});
|
||||
eval("magic:no", []() {
|
||||
auto file = TaskHelper::getCurrentFile();
|
||||
return QFileInfo(file).completeBaseName();
|
||||
});
|
||||
eval("magic:t", []() {
|
||||
auto dt = QDateTime::currentDateTime();
|
||||
return dt.timeZone().displayName(dt, QTimeZone::ShortName);
|
||||
});
|
||||
eval("magic:w", []() {
|
||||
return QString::number(QDate::currentDate().weekNumber());
|
||||
});
|
||||
eval("magic:att", []() {
|
||||
// TODO
|
||||
return QString();
|
||||
});
|
||||
}
|
||||
|
||||
// environment variables
|
||||
do {
|
||||
QMap<QString, QString> map;
|
||||
auto list = TaskHelper::getAllSpecialVariables("env", p_text);
|
||||
list.erase(std::unique(list.begin(), list.end()), list.end());
|
||||
if (list.isEmpty()) break;
|
||||
for (const auto &name : list) {
|
||||
auto value = QProcessEnvironment::systemEnvironment().value(name);
|
||||
map.insert(name, value);
|
||||
}
|
||||
text = TaskHelper::replaceAllSepcialVariables("env", text, map);
|
||||
} while(0);
|
||||
|
||||
// config variables
|
||||
do {
|
||||
const auto config_obj = ConfigMgr::getInst().getConfig().toJson();
|
||||
QMap<QString, QString> map;
|
||||
auto list = TaskHelper::getAllSpecialVariables("config", p_text);
|
||||
if (list.isEmpty()) break;
|
||||
list.erase(std::unique(list.begin(), list.end()), list.end());
|
||||
for (const auto &name : list) {
|
||||
auto value = TaskHelper::evaluateJsonExpr(config_obj, name);
|
||||
qDebug() << "insert" << name << value;
|
||||
map.insert(name, value);
|
||||
}
|
||||
text = TaskHelper::replaceAllSepcialVariables("config", text, map);
|
||||
} while(0);
|
||||
|
||||
// input variables
|
||||
text = evaluateInputVariables(text, p_task);
|
||||
// shell variables
|
||||
text = evaluateShellVariables(text, p_task);
|
||||
return text;
|
||||
}
|
||||
|
||||
QStringList TaskVariableMgr::evaluate(const QStringList &p_list,
|
||||
const Task *p_task) const
|
||||
{
|
||||
QStringList list;
|
||||
for (const auto &s : p_list) {
|
||||
list << evaluate(s, p_task);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
void TaskVariableMgr::init()
|
||||
{
|
||||
if (m_initialized) return ;
|
||||
m_initialized = true;
|
||||
|
||||
add(TaskVariable::FunctionBased,
|
||||
"file",
|
||||
[](const TaskVariable *, const Task *) {
|
||||
return QString();
|
||||
});
|
||||
}
|
||||
|
||||
void TaskVariableMgr::add(TaskVariable::Type p_type,
|
||||
const QString &p_name,
|
||||
TaskVariable::Func p_func)
|
||||
{
|
||||
m_predefs.insert(p_name, TaskVariable(p_type, p_name, p_func));
|
||||
}
|
||||
|
||||
QString TaskVariableMgr::evaluateInputVariables(const QString &p_text,
|
||||
const Task *p_task) const
|
||||
{
|
||||
QMap<QString, QString> map;
|
||||
auto list = TaskHelper::getAllSpecialVariables("input", p_text);
|
||||
list.erase(std::unique(list.begin(), list.end()), list.end());
|
||||
if (list.isEmpty()) return p_text;
|
||||
for (const auto &id : list) {
|
||||
auto input = p_task->getInput(id);
|
||||
QString text;
|
||||
auto mainwin = VNoteX::getInst().getMainWindow();
|
||||
if (input.type == "promptString") {
|
||||
auto desc = evaluate(input.description, p_task);
|
||||
auto defaultText = evaluate(input.default_, p_task);
|
||||
QInputDialog dialog(mainwin);
|
||||
dialog.setInputMode(QInputDialog::TextInput);
|
||||
if (input.password) dialog.setTextEchoMode(QLineEdit::Password);
|
||||
else dialog.setTextEchoMode(QLineEdit::Normal);
|
||||
dialog.setWindowTitle(p_task->getLabel());
|
||||
dialog.setLabelText(desc);
|
||||
dialog.setTextValue(defaultText);
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
text = dialog.textValue();
|
||||
} else {
|
||||
throw "TaskCancle";
|
||||
}
|
||||
} else if (input.type == "pickString") {
|
||||
// TODO: select description
|
||||
SelectDialog dialog(p_task->getLabel(), input.description, mainwin);
|
||||
for (int i = 0; i < input.options.size(); i++) {
|
||||
dialog.addSelection(input.options.at(i), i);
|
||||
}
|
||||
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
int selection = dialog.getSelection();
|
||||
text = input.options.at(selection);
|
||||
} else {
|
||||
throw "TaskCancle";
|
||||
}
|
||||
}
|
||||
map.insert(input.id, text);
|
||||
}
|
||||
return TaskHelper::replaceAllSepcialVariables("input", p_text, map);
|
||||
}
|
||||
|
||||
QString TaskVariableMgr::evaluateShellVariables(const QString &p_text,
|
||||
const Task *p_task) const
|
||||
{
|
||||
QMap<QString, QString> map;
|
||||
auto list = TaskHelper::getAllSpecialVariables("shell", p_text);
|
||||
list.erase(std::unique(list.begin(), list.end()), list.end());
|
||||
if (list.isEmpty()) return p_text;
|
||||
for (const auto &cmd : list) {
|
||||
QProcess process;
|
||||
process.setWorkingDirectory(p_task->getOptionsCwd());
|
||||
ShellExecution::setupProcess(&process, cmd);
|
||||
process.start();
|
||||
if (!process.waitForStarted(1000) || !process.waitForFinished(1000)) {
|
||||
throw "Shell variable execution timeout";
|
||||
}
|
||||
auto res = process.readAllStandardOutput().trimmed();
|
||||
map.insert(cmd, res);
|
||||
}
|
||||
return TaskHelper::replaceAllSepcialVariables("shell", p_text, map);
|
||||
}
|
||||
|
73
src/core/taskvariablemgr.h
Normal file
@ -0,0 +1,73 @@
|
||||
#ifndef TASKVARIABLEMGR_H
|
||||
#define TASKVARIABLEMGR_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <QHash>
|
||||
#include <QString>
|
||||
#include <QSharedPointer>
|
||||
|
||||
namespace vnotex {
|
||||
|
||||
class Task;
|
||||
class TaskVariable;
|
||||
class TaskVariableMgr;
|
||||
class Notebook;
|
||||
|
||||
class TaskVariable {
|
||||
public:
|
||||
enum Type
|
||||
{
|
||||
// Definition is plain text.
|
||||
Simple = 0,
|
||||
|
||||
// Definition is a function call to get the value.
|
||||
FunctionBased,
|
||||
|
||||
// Like FunctionBased, but should re-evaluate the value for each occurence.
|
||||
Dynamic,
|
||||
|
||||
Invalid
|
||||
};
|
||||
typedef std::function<QString(const TaskVariable *,
|
||||
const Task*)> Func;
|
||||
TaskVariable(Type p_type,
|
||||
const QString &p_name,
|
||||
Func p_func = nullptr);
|
||||
private:
|
||||
Type m_type;
|
||||
QString m_name;
|
||||
Func m_func;
|
||||
};
|
||||
|
||||
class TaskVariableMgr
|
||||
{
|
||||
public:
|
||||
TaskVariableMgr();
|
||||
void refresh();
|
||||
QString evaluate(const QString &p_text,
|
||||
const Task *p_task) const;
|
||||
|
||||
QStringList evaluate(const QStringList &p_list,
|
||||
const Task *p_task) const;
|
||||
|
||||
private:
|
||||
void init();
|
||||
|
||||
void add(TaskVariable::Type p_type,
|
||||
const QString &p_name,
|
||||
TaskVariable::Func p_func = nullptr);
|
||||
|
||||
QString evaluateInputVariables(const QString &p_text,
|
||||
const Task *p_task) const;
|
||||
|
||||
QString evaluateShellVariables(const QString &p_text,
|
||||
const Task *p_task) const;
|
||||
|
||||
QHash<QString, TaskVariable> m_predefs;
|
||||
bool m_initialized;
|
||||
};
|
||||
|
||||
} // ns vnotex
|
||||
|
||||
#endif // TASKVARIABLEMGR_H
|
@ -27,6 +27,8 @@ VNoteX::VNoteX(QObject *p_parent)
|
||||
|
||||
initThemeMgr();
|
||||
|
||||
initTaskMgr();
|
||||
|
||||
initNotebookMgr();
|
||||
|
||||
initBufferMgr();
|
||||
@ -40,6 +42,7 @@ void VNoteX::initLoad()
|
||||
{
|
||||
qDebug() << "start init which may take a while";
|
||||
m_notebookMgr->loadNotebooks();
|
||||
m_taskMgr->init();
|
||||
}
|
||||
|
||||
void VNoteX::initThemeMgr()
|
||||
@ -56,11 +59,22 @@ void VNoteX::initThemeMgr()
|
||||
m_themeMgr = new ThemeMgr(configMgr.getCoreConfig().getTheme(), this);
|
||||
}
|
||||
|
||||
void VNoteX::initTaskMgr()
|
||||
{
|
||||
Q_ASSERT(!m_taskMgr);
|
||||
m_taskMgr = new TaskMgr(this);
|
||||
}
|
||||
|
||||
ThemeMgr &VNoteX::getThemeMgr() const
|
||||
{
|
||||
return *m_themeMgr;
|
||||
}
|
||||
|
||||
TaskMgr &VNoteX::getTaskMgr() const
|
||||
{
|
||||
return *m_taskMgr;
|
||||
}
|
||||
|
||||
void VNoteX::setMainWindow(MainWindow *p_mainWindow)
|
||||
{
|
||||
Q_ASSERT(!m_mainWindow);
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include "noncopyable.h"
|
||||
#include "thememgr.h"
|
||||
#include "taskmgr.h"
|
||||
#include "global.h"
|
||||
|
||||
namespace vnotex
|
||||
@ -35,6 +36,8 @@ namespace vnotex
|
||||
|
||||
ThemeMgr &getThemeMgr() const;
|
||||
|
||||
TaskMgr &getTaskMgr() const;
|
||||
|
||||
void setMainWindow(MainWindow *p_mainWindow);
|
||||
MainWindow *getMainWindow() const;
|
||||
|
||||
@ -79,6 +82,9 @@ namespace vnotex
|
||||
// Requested to new a folder in current notebook.
|
||||
void newFolderRequested();
|
||||
|
||||
// Requested to show output message.
|
||||
void showOutputRequested(const QString &p_text);
|
||||
|
||||
// Requested to show status message.
|
||||
void statusMessageRequested(const QString &p_message, int p_timeoutMilliseconds);
|
||||
|
||||
@ -116,6 +122,8 @@ namespace vnotex
|
||||
|
||||
void initThemeMgr();
|
||||
|
||||
void initTaskMgr();
|
||||
|
||||
void initNotebookMgr();
|
||||
|
||||
void initBufferMgr();
|
||||
@ -129,6 +137,9 @@ namespace vnotex
|
||||
// QObject managed.
|
||||
ThemeMgr *m_themeMgr;
|
||||
|
||||
// QObject managed.
|
||||
TaskMgr *m_taskMgr;
|
||||
|
||||
// QObject managed.
|
||||
NotebookMgr *m_notebookMgr;
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
<file>icons/import_notebook_of_vnote2.svg</file>
|
||||
<file>icons/new_notebook.svg</file>
|
||||
<file>icons/notebook_menu.svg</file>
|
||||
<file>icons/task_menu.svg</file>
|
||||
<file>icons/advanced_settings.svg</file>
|
||||
<file>icons/new_notebook_from_folder.svg</file>
|
||||
<file>icons/discard_editor.svg</file>
|
||||
|
1
src/data/core/icons/task_menu.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1611462360764" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2562" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M585.142857 804.571429h365.714286v-73.142858H585.142857v73.142858z m-219.428571-292.571429h585.142857v-73.142857H365.714286v73.142857z m365.714285-292.571429h219.428572V146.285714h-219.428572v73.142857z m292.571429 475.428572v146.285714c0 20.004571-16.566857 36.571429-36.571429 36.571429H36.571429c-20.004571 0-36.571429-16.566857-36.571429-36.571429v-146.285714c0-20.004571 16.566857-36.571429 36.571429-36.571429h950.857142c20.004571 0 36.571429 16.566857 36.571429 36.571429z m0-292.571429v146.285715c0 20.004571-16.566857 36.571429-36.571429 36.571428H36.571429c-20.004571 0-36.571429-16.566857-36.571429-36.571428v-146.285715c0-20.004571 16.566857-36.571429 36.571429-36.571428h950.857142c20.004571 0 36.571429 16.566857 36.571429 36.571428z m0-292.571428v146.285714c0 20.004571-16.566857 36.571429-36.571429 36.571429H36.571429c-20.004571 0-36.571429-16.566857-36.571429-36.571429V109.714286c0-20.004571 16.566857-36.571429 36.571429-36.571429h950.857142c20.004571 0 36.571429 16.566857 36.571429 36.571429z" fill="" p-id="2563"></path></svg>
|
After Width: | Height: | Size: 1.4 KiB |
@ -150,6 +150,14 @@
|
||||
<file>themes/pure/up.svg</file>
|
||||
<file>themes/pure/up_disabled.svg</file>
|
||||
<file>themes/pure/web.css</file>
|
||||
<file>tasks/git/git.json</file>
|
||||
<file>tasks/git/git.svg</file>
|
||||
<file>tasks/git/commit.svg</file>
|
||||
<file>tasks/git/history.svg</file>
|
||||
<file>tasks/git/initialization.svg</file>
|
||||
<file>tasks/git/pull.svg</file>
|
||||
<file>tasks/git/push.svg</file>
|
||||
<file>tasks/git/status.svg</file>
|
||||
<file>syntax-highlighting/themes/markdown-default.theme</file>
|
||||
<file>syntax-highlighting/themes/markdown-breeze-dark.theme</file>
|
||||
<file>syntax-highlighting/themes/default.theme</file>
|
||||
|
1
src/data/extra/tasks/git/commit.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1612349138300" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4255" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M960 576H692.224c-26.432 74.432-96.704 128-180.224 128-83.456 0-153.792-53.568-180.224-128H64a64 64 0 1 1 0-128h267.776c26.432-74.368 96.768-128 180.224-128 83.52 0 153.792 53.632 180.224 128H960c35.392 0 64 28.608 64 64s-28.608 64-64 64z m-448-128a64 64 0 1 0 0 128c35.392 0 64-28.608 64-64s-28.608-64-64-64z" fill="" p-id="4256"></path></svg>
|
After Width: | Height: | Size: 721 B |
80
src/data/extra/tasks/git/git.json
Normal file
@ -0,0 +1,80 @@
|
||||
{
|
||||
"version": "0.1.4",
|
||||
"label": "Git",
|
||||
"icon": "git.svg",
|
||||
"tasks": [
|
||||
{
|
||||
"label": {
|
||||
"en_US": "Initialize",
|
||||
"zh_CN": "初始化",
|
||||
"ja_JP": "イニシャライズ"
|
||||
},
|
||||
"icon": "initialization.svg",
|
||||
"command": "git init -b main"
|
||||
},
|
||||
{
|
||||
"label": {
|
||||
"en_US": "Status",
|
||||
"zh_CN": "状态",
|
||||
"ja_JP": "ステータス"
|
||||
},
|
||||
"icon": "status.svg",
|
||||
"command": "git status"
|
||||
},
|
||||
{
|
||||
"label": {
|
||||
"en_US": "Commit",
|
||||
"zh_CN": "提交",
|
||||
"ja_JP": "全てコミット"
|
||||
},
|
||||
"icon": "commit.svg",
|
||||
"windows" :{
|
||||
"command": "git add -A -- . ; if ($?) { git commit --message=\"${input:msg}\" }"
|
||||
},
|
||||
"command": "git add -A -- . && git commit --message=\"${input:msg}\"",
|
||||
"inputs": [
|
||||
{
|
||||
"id": "msg",
|
||||
"type": "promptString",
|
||||
"description": {
|
||||
"en_US": "Please provide a commit message",
|
||||
"zh_CN": "请输入提交信息",
|
||||
"ja_JP": "コミットメッセージを提供してください"
|
||||
},
|
||||
"default": {
|
||||
"en_US": "Update note on ${magic:datetime}",
|
||||
"zh_CN": "更新笔记于 ${magic:datetime}",
|
||||
"ja_JP": "アップデート ${magic:datetime}"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": {
|
||||
"en_US": "Push",
|
||||
"zh_CN": "上传",
|
||||
"ja_JP": "プッシュ"
|
||||
},
|
||||
"icon": "push.svg",
|
||||
"command": "git push"
|
||||
},
|
||||
{
|
||||
"label": {
|
||||
"en_US": "Pull",
|
||||
"zh_CN": "下载",
|
||||
"ja_JP": "プル"
|
||||
},
|
||||
"icon": "pull.svg",
|
||||
"command": "git pull --no-rebase"
|
||||
},
|
||||
{
|
||||
"label": {
|
||||
"en_US": "Log",
|
||||
"zh_CN": "日志",
|
||||
"ja_JP": "ログ"
|
||||
},
|
||||
"icon": "history.svg",
|
||||
"command": "git log -10 --graph --pretty=format:'%h -%d %s (%cr) <%an>' --abbrev-commit"
|
||||
}
|
||||
]
|
||||
}
|
1
src/data/extra/tasks/git/git.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1612348787730" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2148" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M1004.628512 466.345889L557.525257 19.285301a66.133223 66.133223 0 0 0-93.354511 0L371.541567 112.085147l117.759804 117.759803a78.421203 78.421203 0 0 1 99.285168 99.8825l113.407811 113.493145a78.421203 78.421203 0 0 1 81.066532 129.663784 78.378536 78.378536 0 0 1-110.933149 0 78.762535 78.762535 0 0 1-17.237304-85.162525L548.693272 382.079363v278.399536c7.509321 3.669327 14.591976 8.661319 20.821299 14.847975a78.847869 78.847869 0 0 1 0 110.933149 78.677202 78.677202 0 0 1-111.317148 0 78.250536 78.250536 0 0 1 0-110.847815c7.76532-7.679987 16.511972-13.482644 25.81329-17.322638V376.959372a78.250536 78.250536 0 0 1-42.495929-102.826496L325.802977 157.866404 19.200821 464.255226c-25.599957 25.81329-25.599957 67.583887 0 93.397178l447.145922 447.017922a65.91989 65.91989 0 0 0 93.269178 0l445.012591-445.012592a65.877224 65.877224 0 0 0 0-93.311845" fill="" p-id="2149"></path></svg>
|
After Width: | Height: | Size: 1.2 KiB |
1
src/data/extra/tasks/git/history.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1612348938376" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2143" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M554.453333 128c-212.266667 0-383.786667 171.946667-383.786667 384l-128 0 166.186667 166.186667 2.986667 6.186667 172.16-172.373333-128 0c0-164.906667 133.76-298.666667 298.666667-298.666667s298.666667 133.76 298.666667 298.666667-133.76 298.666667-298.666667 298.666667c-82.56 0-157.013333-33.706667-210.986667-87.68l-60.373333 60.373333c69.333333 69.546667 165.12 112.64 271.146667 112.64 212.266667 0 384.213333-171.946667 384.213333-384s-171.946667-384-384.213333-384zM512 341.333333l0 213.333333 182.613333 108.373333 30.72-51.84-149.333333-88.533333 0-181.333333-64 0z" fill="" p-id="2144"></path></svg>
|
After Width: | Height: | Size: 986 B |
1
src/data/extra/tasks/git/initialization.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1612619426558" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2127" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M885.333333 917.333333h-746.666666c-17.066667 0-32-14.933333-32-32S121.6 853.333333 138.666667 853.333333h746.666666c17.066667 0 32 14.933333 32 32s-14.933333 32-32 32zM546.133333 785.066667c-64 0-128-19.2-183.466666-53.333334-14.933333-8.533333-19.2-29.866667-10.666667-44.8 8.533333-14.933333 29.866667-19.2 44.8-10.666666 64 40.533333 140.8 53.333333 215.466667 36.266666 149.333333-36.266667 241.066667-185.6 204.8-334.933333-17.066667-72.533333-61.866667-132.266667-125.866667-170.666667-64-38.4-138.666667-49.066667-209.066667-32-89.6 21.333333-168.533333 96-200.533333 187.733334-6.4 17.066667-23.466667 25.6-40.533333 19.2-17.066667-6.4-25.6-23.466667-19.2-40.533334 38.4-110.933333 134.4-202.666667 247.466666-228.266666 89.6-21.333333 179.2-6.4 258.133334 40.533333 78.933333 46.933333 132.266667 121.6 153.6 211.2 44.8 183.466667-68.266667 366.933333-251.733334 411.733333-27.733333 6.4-55.466667 8.533333-83.2 8.533334z" fill="" p-id="2128"></path><path d="M230.4 437.333333c-29.866667 0-57.6-21.333333-64-51.2l-29.866667-123.733333c-4.266667-17.066667 6.4-34.133333 23.466667-38.4 17.066667-4.266667 34.133333 6.4 38.4 23.466667l29.866667 123.733333c0 2.133333 2.133333 2.133333 4.266666 2.133333l123.733334-29.866666c17.066667-4.266667 34.133333 6.4 38.4 23.466666 4.266667 17.066667-6.4 34.133333-23.466667 38.4l-123.733333 29.866667c-6.4 0-10.666667 2.133333-17.066667 2.133333z" fill="" p-id="2129"></path></svg>
|
After Width: | Height: | Size: 1.8 KiB |
1
src/data/extra/tasks/git/pull.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1612349304217" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5782" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M980.76306 840.106926c0 101.570274-82.384097 183.893074-183.893073 183.893074s-183.893074-82.322799-183.893074-183.893074c0-79.993487 51.367465-147.298352 122.595382-172.614298V349.725396c0-33.897623-27.400068-61.297691-61.297691-61.297691v61.236393a61.338556 61.338556 0 0 1-104.635159 43.398766l-122.595382-122.656681A61.032068 61.032068 0 0 1 429.083839 227.068716c0-15.630911 6.007174-31.32312 17.960224-43.27617l122.595382-122.595383A61.256826 61.256826 0 1 1 674.274604 104.473333V165.832322c101.508977 0 183.893074 82.322799 183.893074 183.893074v317.767232c71.227917 25.315946 122.595383 92.620812 122.595382 172.614298z m-183.893073-61.297691c-33.897623 0-61.297691 27.400068-61.297692 61.297691s27.400068 61.297691 61.297692 61.297691 61.297691-27.400068 61.297691-61.297691-27.400068-61.297691-61.297691-61.297691z m-429.083839 61.297691c0 101.570274-82.322799 183.893074-183.893074 183.893074s-183.893074-82.322799-183.893074-183.893074c0-79.993487 51.306168-147.298352 122.595383-172.614298V399.744312C51.306168 374.428366 0 307.1235 0 227.130013c0-101.570274 82.322799-183.893074 183.893074-183.893073s183.893074 82.322799 183.893074 183.893073c0 79.993487-51.306168 147.298352-122.595383 172.614299v267.687018c71.289215 25.377244 122.595383 92.682109 122.595383 172.675596zM183.893074 165.832322a61.297691 61.297691 0 1 0 0 122.595383 61.297691 61.297691 0 1 0 0-122.595383z m0 612.976913a61.297691 61.297691 0 1 0 0 122.595382 61.297691 61.297691 0 1 0 0-122.595382z" fill="" p-id="5783"></path></svg>
|
After Width: | Height: | Size: 1.8 KiB |
1
src/data/extra/tasks/git/push.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1612349231943" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5014" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M1008.57728 218.21568l0.13952-1.12-0.17024-0.84736 0.06656-0.53504 0.0704-0.55936 0.03456-0.28032-0.21504-0.28544 0.0384-0.30464 0.0704-0.56064-0.47616-0.6016 0.06912-0.55936 0.06656-0.53504-0.20096-0.59392-0.24704-0.03072 0.13696-1.0944-0.4736-0.6272 0.0704-0.55936 0.03456-0.28032 0.03456-0.28032-0.47616-0.6016-0.512-0.34816 0.03584-0.27904-0.92416-1.22624 0.06912-0.56064-0.47232-0.6272-0.45184-0.59904-0.4736-0.6272 0.06784-0.53504-0.44928-0.62464-0.29568-0.03584-0.27136-0.03456-0.18048-0.56448-0.1984-0.61952-0.27136-0.03328-0.23552-0.31488 0.03072-0.256-0.512-0.06528 0.03456-0.27904-0.54272-0.06784 0.03456-0.28032 0.03456-0.28032-0.512-0.064-0.50048-0.60544-0.512-0.064 0.03456-0.28032-0.54272-0.06784-0.99072-0.69248-0.2048-0.56832-0.27136-0.03328-0.512-0.064-0.27136-0.03456-0.1984-0.61952-0.24704-0.03072-0.29568-0.03584-0.54272-0.06784-0.45184-0.59904-0.54272-0.06784 0.0704-0.56064-0.512-0.064-0.54272-0.06784-0.512-0.064-0.87296-0.10496-0.1792-0.56576-0.27136-0.03328-0.27136-0.03328-0.27136-0.03456-0.24704-0.03072-0.27136-0.03328-0.54272-0.06784-0.24704-0.03072-0.54272-0.06784-1.62816-0.20352-0.512-0.064-0.54272-0.06784-0.512-0.064-0.27136-0.03456-0.78976-0.09856-0.29568-0.03584-0.85632 0.4352-0.27136-0.03328-0.27136-0.03456-0.512-0.064-0.06912 0.55936-0.54272-0.06784-0.512-0.064-0.60928 0.46592-0.56704-0.0704-0.512-0.064L32.83328 437.59104a24.11136 24.11136 0 0 0-17.12768 28.672 23.552 23.552 0 0 0 12.22912 15.24736l257.60128 145.79456 7.6672 164.77312a22.87488 22.87488 0 0 0 23.808 22.2464 21.78688 21.78688 0 0 0 13.81376-6.10816l113.43104-91.1168 194.688 110.5088a22.64576 22.64576 0 0 0 30.92992-8.7552L1005.01248 229.5296l0.61184-0.49152 0.03584-0.28032 0.03072-0.256 0.24704 0.03072 0.0704-0.55936 0.27136 0.03328 0.03456-0.28032 0.06656-0.53376 0.03456-0.28032 0.30592-0.24576 0.30976-0.27136 0.06656-0.53504 0.31744-0.52864 0.06656-0.53376 0.06912-0.56064 0.06656-0.53376 0.0704-0.56064 0.37504-0.80512 0.34176-0.52608 0.44416-3.56352-0.27136-0.03328z m-700.544 367.90912L100.04864 468.94464l730.19648-187.5776L308.03328 586.1248z m28.31872 158.12096l-3.712-90.08384 68.4672 38.79808-64.768 51.28576z m305.8048 31.40608L354.10944 612.6208l569.64864-332.18048-281.6 495.21152z" fill="" p-id="5015"></path></svg>
|
After Width: | Height: | Size: 2.6 KiB |
1
src/data/extra/tasks/git/status.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1612619602951" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3508" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M849.938174 262.216074c-61.665455 0-111.670336 49.713238-111.670336 111.01235 0 21.939685 6.40385 42.389436 17.449417 59.607586l-76.68452 76.68452c-22.715351-15.924691-50.424436-25.28078-80.340793-25.28078-34.92851 0-66.858734 12.749372-91.339288 33.821293l-34.43016-34.429137c7.966439-14.18814 12.512988-30.531364 12.512988-47.933709 0-54.42147-44.395116-98.547457-99.132788-98.547457s-99.109252 44.12701-99.109252 98.547457c0 12.93152 2.51017 25.278733 7.064906 36.592406l-58.317197 58.30901c-17.715477-11.746532-38.993083-18.600637-61.881372-18.600637-61.688991 0-111.670336 49.714262-111.670336 111.01235 0 61.324694 49.981345 111.01235 111.670336 111.01235 61.665455 0 111.671359-49.688679 111.671359-111.01235 0-18.445094-4.533246-35.837206-12.543687-51.145867l55.787584-55.787584c16.182564 11.426237 35.96512 18.144242 57.32766 18.144242 16.989953 0 32.981159-4.251837 46.960545-11.739369l39.85266 39.847543c-8.980535 18.331507-14.024411 38.920428-14.024411 60.682058 0 76.641541 62.494333 138.771577 139.600455 138.771577S738.267838 699.653891 738.267838 623.01235c0-26.885324-7.696286-51.980885-21.00643-73.240071l79.122035-79.110779c15.901155 8.655124 34.148751 13.579273 53.55473 13.579273 61.690014 0 111.671359-49.688679 111.671359-111.011326C961.609533 311.929312 911.628188 262.216074 849.938174 262.216074z" fill="" p-id="3509"></path></svg>
|
After Width: | Height: | Size: 1.7 KiB |
@ -357,3 +357,22 @@ void FileUtils::removeEmptyDir(const QString &p_dirPath)
|
||||
removeDirIfEmpty(childPath);
|
||||
}
|
||||
}
|
||||
|
||||
QStringList FileUtils::entryListRecursively(const QString &p_dirPath,
|
||||
const QStringList &p_nameFilters,
|
||||
QDir::Filters p_filters)
|
||||
{
|
||||
QDir dir(p_dirPath);
|
||||
if (dir.isEmpty()) return {};
|
||||
QStringList entrys;
|
||||
const auto curEntrys = dir.entryList(p_nameFilters, p_filters | QDir::NoDotAndDotDot);
|
||||
for (const auto &e : curEntrys) {
|
||||
entrys.append(PathUtils::concatenateFilePath(p_dirPath, e));
|
||||
}
|
||||
auto subdirs = dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot);
|
||||
for (const auto &subdir : subdirs) {
|
||||
const auto dirPath = PathUtils::concatenateFilePath(p_dirPath, subdir);
|
||||
entrys.append(entryListRecursively(dirPath, p_nameFilters, p_filters));
|
||||
}
|
||||
return entrys;
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <QImage>
|
||||
#include <QPixmap>
|
||||
#include <QJsonObject>
|
||||
#include <QDir>
|
||||
|
||||
class QTemporaryFile;
|
||||
|
||||
@ -77,6 +78,12 @@ namespace vnotex
|
||||
// Go through @p_dirPath recursively and delete all empty dirs.
|
||||
// @p_dirPath itself is not deleted.
|
||||
static void removeEmptyDir(const QString &p_dirPath);
|
||||
|
||||
// Go through @p_dirPath recursively and get all entrys.
|
||||
// @p_nameFilters is for each dir, not for all.
|
||||
static QStringList entryListRecursively(const QString &p_dirPath,
|
||||
const QStringList &p_nameFilters,
|
||||
QDir::Filters p_filters=QDir::NoFilter);
|
||||
};
|
||||
} // ns vnotex
|
||||
|
||||
|
@ -59,8 +59,8 @@ QString IconUtils::replaceForegroundOfIcon(const QString &p_iconContent, const Q
|
||||
return p_iconContent;
|
||||
}
|
||||
|
||||
// Must have a # to avoid fill="none".
|
||||
QRegExp styleRe("(\\s|\"|;)(fill|stroke)(:|(=\"))#[^#\"\\s]+");
|
||||
// Negative lookahead to avoid fill="none".
|
||||
QRegExp styleRe(R"((\s|"|;)(fill|stroke)(:|(="))(?!none)[^;"]*)");
|
||||
if (p_iconContent.indexOf(styleRe) > -1) {
|
||||
auto newContent(p_iconContent);
|
||||
newContent.replace(styleRe, QString("\\1\\2\\3%1").arg(p_foreground));
|
||||
|
@ -22,11 +22,28 @@ SelectDialog::SelectDialog(const QString &p_title, QWidget *p_parent)
|
||||
setupUI(p_title);
|
||||
}
|
||||
|
||||
void SelectDialog::setupUI(const QString &p_title)
|
||||
SelectDialog::SelectDialog(const QString &p_title,
|
||||
const QString &p_text,
|
||||
QWidget *p_parent)
|
||||
: QDialog(p_parent)
|
||||
{
|
||||
const auto &themeMgr = VNoteX::getInst().getThemeMgr();
|
||||
m_shortcutIconForeground = themeMgr.paletteColor(QStringLiteral("widgets#quickselector#item_icon#fg"));
|
||||
m_shortcutIconBorder = themeMgr.paletteColor(QStringLiteral("widgets#quickselector#item_icon#border"));
|
||||
|
||||
setupUI(p_title, p_text);
|
||||
}
|
||||
|
||||
void SelectDialog::setupUI(const QString &p_title, const QString &p_text)
|
||||
{
|
||||
auto mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
if (!p_text.isNull()) {
|
||||
m_label = new QLabel(p_text, this);
|
||||
mainLayout->addWidget(m_label);
|
||||
}
|
||||
|
||||
m_list = new QListWidget(this);
|
||||
m_list->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
m_list->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
|
@ -10,6 +10,7 @@ class QListWidget;
|
||||
class QListWidgetItem;
|
||||
class QShowEvent;
|
||||
class QKeyEvent;
|
||||
class QLabel;
|
||||
|
||||
namespace vnotex
|
||||
{
|
||||
@ -19,6 +20,10 @@ namespace vnotex
|
||||
public:
|
||||
SelectDialog(const QString &p_title, QWidget *p_parent = nullptr);
|
||||
|
||||
SelectDialog(const QString &p_title,
|
||||
const QString &p_text,
|
||||
QWidget *p_parent = nullptr);
|
||||
|
||||
// @p_selectID should >= 0.
|
||||
void addSelection(const QString &p_selectStr, int p_selectID);
|
||||
|
||||
@ -37,12 +42,14 @@ namespace vnotex
|
||||
private:
|
||||
enum { CANCEL_ID = -1 };
|
||||
|
||||
void setupUI(const QString &p_title);
|
||||
void setupUI(const QString &p_title, const QString &p_text = QString());
|
||||
|
||||
void updateSize();
|
||||
|
||||
int m_choice = CANCEL_ID;
|
||||
|
||||
QLabel *m_label = nullptr;
|
||||
|
||||
QListWidget *m_list = nullptr;
|
||||
|
||||
QMap<QChar, QListWidgetItem *> m_shortcuts;
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <QHelpEvent>
|
||||
#include <QToolTip>
|
||||
#include <QShortcut>
|
||||
#include <QTextEdit>
|
||||
|
||||
#include <core/vnotex.h>
|
||||
#include <core/thememgr.h>
|
||||
@ -112,6 +113,8 @@ void DockWidgetHelper::setupDocks()
|
||||
|
||||
setupOutlineDock();
|
||||
|
||||
setupOutputDock();
|
||||
|
||||
setupLocationListDock();
|
||||
|
||||
setupShortcuts();
|
||||
@ -145,6 +148,19 @@ void DockWidgetHelper::setupOutlineDock()
|
||||
m_mainWindow->addDockWidget(Qt::RightDockWidgetArea, dock);
|
||||
}
|
||||
|
||||
void DockWidgetHelper::setupOutputDock()
|
||||
{
|
||||
auto dock = createDockWidget(DockIndex::OutputDock, tr("Output"), m_mainWindow);
|
||||
|
||||
dock->setObjectName(QStringLiteral("OutputDock.vnotex"));
|
||||
dock->setAllowedAreas(Qt::BottomDockWidgetArea);
|
||||
|
||||
dock->setWidget(m_mainWindow->m_outputViewer);
|
||||
dock->setFocusProxy(m_mainWindow->m_outputViewer);
|
||||
dock->hide();
|
||||
m_mainWindow->addDockWidget(Qt::BottomDockWidgetArea, dock);
|
||||
}
|
||||
|
||||
void DockWidgetHelper::setupSearchDock()
|
||||
{
|
||||
auto dock = createDockWidget(DockIndex::SearchDock, tr("Search"), m_mainWindow);
|
||||
|
@ -29,6 +29,7 @@ namespace vnotex
|
||||
SearchDock,
|
||||
SnippetDock,
|
||||
OutlineDock,
|
||||
OutputDock,
|
||||
LocationListDock,
|
||||
MaxDock
|
||||
};
|
||||
@ -97,6 +98,8 @@ namespace vnotex
|
||||
|
||||
void setupOutlineDock();
|
||||
|
||||
void setupOutputDock();
|
||||
|
||||
void setupSearchDock();
|
||||
|
||||
void setupSnippetDock();
|
||||
|
@ -257,6 +257,8 @@ void MainWindow::setupDocks()
|
||||
|
||||
setupOutlineViewer();
|
||||
|
||||
setupOutputViewer();
|
||||
|
||||
setupHistoryPanel();
|
||||
|
||||
setupSearchPanel();
|
||||
@ -512,11 +514,36 @@ void MainWindow::setupOutlineViewer()
|
||||
this, &MainWindow::focusViewArea);
|
||||
}
|
||||
|
||||
void MainWindow::setupOutputViewer()
|
||||
{
|
||||
m_outputViewer = new QTextEdit(this);
|
||||
m_outputViewer->setObjectName("OutputViewer.vnotex");
|
||||
m_outputViewer->setReadOnly(true);
|
||||
|
||||
connect(&VNoteX::getInst(), &VNoteX::showOutputRequested,
|
||||
m_outputViewer, [this](const QString &p_text) {
|
||||
auto cursor = m_outputViewer->textCursor();
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
m_outputViewer->setTextCursor(cursor);
|
||||
m_outputViewer->insertPlainText(p_text);
|
||||
auto scrollBar = m_outputViewer->verticalScrollBar();
|
||||
if (scrollBar) {
|
||||
scrollBar->setSliderPosition(scrollBar->maximum());
|
||||
}
|
||||
m_dockWidgetHelper.getDock(DockWidgetHelper::OutputDock)->show();
|
||||
});
|
||||
}
|
||||
|
||||
const QVector<QDockWidget *> &MainWindow::getDocks() const
|
||||
{
|
||||
return m_dockWidgetHelper.getDocks();
|
||||
}
|
||||
|
||||
ViewArea *MainWindow::getViewArea() const
|
||||
{
|
||||
return m_viewArea;
|
||||
}
|
||||
|
||||
void MainWindow::focusViewArea()
|
||||
{
|
||||
m_viewArea->focus();
|
||||
|
@ -13,6 +13,7 @@ class QDockWidget;
|
||||
class QSystemTrayIcon;
|
||||
class QTimer;
|
||||
class QLabel;
|
||||
class QTextEdit;
|
||||
|
||||
namespace vnotex
|
||||
{
|
||||
@ -49,6 +50,8 @@ namespace vnotex
|
||||
|
||||
const QVector<QDockWidget *> &getDocks() const;
|
||||
|
||||
ViewArea *getViewArea() const;
|
||||
|
||||
void setContentAreaExpanded(bool p_expanded);
|
||||
// Should be called after MainWindow is shown.
|
||||
bool isContentAreaExpanded() const;
|
||||
@ -103,6 +106,8 @@ namespace vnotex
|
||||
|
||||
void setupOutlineViewer();
|
||||
|
||||
void setupOutputViewer();
|
||||
|
||||
void setupSearchPanel();
|
||||
|
||||
void setupLocationList();
|
||||
@ -162,6 +167,8 @@ namespace vnotex
|
||||
|
||||
OutlineViewer *m_outlineViewer = nullptr;
|
||||
|
||||
QTextEdit *m_outputViewer = nullptr;
|
||||
|
||||
LocationList *m_locationList = nullptr;
|
||||
|
||||
SearchPanel *m_searchPanel = nullptr;
|
||||
|
@ -42,6 +42,8 @@ namespace vnotex
|
||||
|
||||
QString getLatestContent() const Q_DECL_OVERRIDE;
|
||||
|
||||
QString selectedText() const Q_DECL_OVERRIDE;
|
||||
|
||||
void setMode(ViewWindowMode p_mode) Q_DECL_OVERRIDE;
|
||||
|
||||
QSharedPointer<OutlineProvider> getOutlineProvider() Q_DECL_OVERRIDE;
|
||||
@ -101,8 +103,6 @@ namespace vnotex
|
||||
|
||||
QPoint getFloatingWidgetPosition() Q_DECL_OVERRIDE;
|
||||
|
||||
QString selectedText() const Q_DECL_OVERRIDE;
|
||||
|
||||
void updateViewModeMenu(QMenu *p_menu) Q_DECL_OVERRIDE;
|
||||
|
||||
private:
|
||||
|
@ -25,6 +25,8 @@ namespace vnotex
|
||||
|
||||
QString getLatestContent() const Q_DECL_OVERRIDE;
|
||||
|
||||
QString selectedText() const Q_DECL_OVERRIDE;
|
||||
|
||||
void setMode(ViewWindowMode p_mode) Q_DECL_OVERRIDE;
|
||||
|
||||
void openTwice(const QSharedPointer<FileOpenParameters> &p_paras) Q_DECL_OVERRIDE;
|
||||
@ -68,8 +70,6 @@ namespace vnotex
|
||||
|
||||
QPoint getFloatingWidgetPosition() Q_DECL_OVERRIDE;
|
||||
|
||||
QString selectedText() const Q_DECL_OVERRIDE;
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include <core/markdowneditorconfig.h>
|
||||
#include <core/fileopenparameters.h>
|
||||
#include <core/htmltemplatehelper.h>
|
||||
#include <core/exception.h>
|
||||
#include "propertydefs.h"
|
||||
#include "dialogs/settings/settingsdialog.h"
|
||||
#include "dialogs/updater.h"
|
||||
@ -282,6 +283,78 @@ QToolBar *ToolBarHelper::setupQuickAccessToolBar(MainWindow *p_win, QToolBar *p_
|
||||
return tb;
|
||||
}
|
||||
|
||||
void ToolBarHelper::setupTaskMenu(QMenu *p_menu)
|
||||
{
|
||||
p_menu->clear();
|
||||
const auto &taskMgr = VNoteX::getInst().getTaskMgr();
|
||||
for (auto task : taskMgr.getAppTasks()) {
|
||||
addTaskMenu(p_menu, task);
|
||||
}
|
||||
p_menu->addSeparator();
|
||||
for (auto task : taskMgr.getUserTasks()) {
|
||||
addTaskMenu(p_menu, task);
|
||||
}
|
||||
p_menu->addSeparator();
|
||||
for (auto task : taskMgr.getNotebookTasks()) {
|
||||
addTaskMenu(p_menu, task);
|
||||
}
|
||||
}
|
||||
|
||||
void ToolBarHelper::addTaskMenu(QMenu *p_menu, Task *p_task)
|
||||
{
|
||||
MainWindow::connect(p_task, &Task::showOutput,
|
||||
&VNoteX::getInst(), &VNoteX::showOutputRequested);
|
||||
QAction *action = nullptr;
|
||||
const auto &tasks = p_task->getTasks();
|
||||
auto label = p_task->getLabel();
|
||||
label = label.replace("&", "&&");
|
||||
QIcon icon;
|
||||
try {
|
||||
auto taskIcon = p_task->getIcon();
|
||||
if (!taskIcon.isEmpty()) {
|
||||
icon = generateIcon(p_task->getIcon());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (e.m_type != Exception::Type::FailToReadFile) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
if (tasks.isEmpty()) {
|
||||
action = p_menu->addAction(label);
|
||||
} else {
|
||||
auto menu = p_menu->addMenu(label);
|
||||
for (auto task : tasks) {
|
||||
addTaskMenu(menu, task);
|
||||
}
|
||||
action = menu->menuAction();
|
||||
}
|
||||
action->setIcon(icon);
|
||||
WidgetUtils::addActionShortcut(action, p_task->getShortcut());
|
||||
MainWindow::connect(action, &QAction::triggered,
|
||||
p_task, &Task::run);
|
||||
}
|
||||
|
||||
QToolBar *ToolBarHelper::setupTaskToolBar(MainWindow *p_win, QToolBar *p_toolBar)
|
||||
{
|
||||
auto tb = p_toolBar;
|
||||
if (!tb) {
|
||||
tb = createToolBar(p_win, MainWindow::tr("Task"), "TaskToolBar");
|
||||
}
|
||||
|
||||
auto act = tb->addAction(generateIcon("task_menu.svg"), MainWindow::tr("Task"));
|
||||
auto btn = dynamic_cast<QToolButton *>(tb->widgetForAction(act));
|
||||
btn->setPopupMode(QToolButton::InstantPopup);
|
||||
btn->setProperty(PropertyDefs::c_toolButtonWithoutMenuIndicator, true);
|
||||
|
||||
auto taskMenu = WidgetsFactory::createMenu(tb);
|
||||
MainWindow::connect(&VNoteX::getInst().getTaskMgr(), &TaskMgr::taskChanged,
|
||||
taskMenu, [taskMenu]() {
|
||||
setupTaskMenu(taskMenu);
|
||||
});
|
||||
btn->setMenu(taskMenu);
|
||||
return tb;
|
||||
}
|
||||
|
||||
QToolBar *ToolBarHelper::setupSettingsToolBar(MainWindow *p_win, QToolBar *p_toolBar)
|
||||
{
|
||||
auto tb = p_toolBar;
|
||||
@ -344,6 +417,8 @@ void ToolBarHelper::setupToolBars(MainWindow *p_mainWindow)
|
||||
|
||||
setupQuickAccessToolBar(p_mainWindow, nullptr);
|
||||
|
||||
setupTaskToolBar(p_mainWindow, nullptr);
|
||||
|
||||
setupSettingsToolBar(p_mainWindow, nullptr);
|
||||
}
|
||||
|
||||
@ -355,6 +430,7 @@ void ToolBarHelper::setupToolBars(MainWindow *p_mainWindow, QToolBar *p_toolBar)
|
||||
|
||||
setupFileToolBar(p_mainWindow, p_toolBar);
|
||||
setupQuickAccessToolBar(p_mainWindow, p_toolBar);
|
||||
setupTaskToolBar(p_mainWindow, p_toolBar);
|
||||
setupSettingsToolBar(p_mainWindow, p_toolBar);
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ class QMenu;
|
||||
namespace vnotex
|
||||
{
|
||||
class MainWindow;
|
||||
class Task;
|
||||
|
||||
// Tool bar helper for MainWindow.
|
||||
class ToolBarHelper
|
||||
@ -33,6 +34,12 @@ namespace vnotex
|
||||
|
||||
static QToolBar *setupQuickAccessToolBar(MainWindow *p_win, QToolBar *p_toolBar);
|
||||
|
||||
static void setupTaskMenu(QMenu *p_menu);
|
||||
|
||||
static void addTaskMenu(QMenu *p_menu, Task *p_task);
|
||||
|
||||
static QToolBar *setupTaskToolBar(MainWindow *p_win, QToolBar *p_toolBar);
|
||||
|
||||
static QToolBar *setupSettingsToolBar(MainWindow *p_win, QToolBar *p_toolBar);
|
||||
|
||||
static void updateQuickAccessMenu(QMenu *p_menu);
|
||||
|