Feature Task System (#1947)

* add task system code

* enable ci build

* Revert "enable ci build"

This reverts commit 8c457a22e44e64c7d87804fc3c76ee778c1c3b6f.
This commit is contained in:
tootal 2021-12-09 18:40:14 +08:00 committed by GitHub
parent fa238b3e22
commit 911392deab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 2195 additions and 8 deletions

View File

@ -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"));

View File

@ -74,6 +74,10 @@ namespace vnotex
QString getUserThemeFolder() const;
QString getAppTaskFolder() const;
QString getUserTaskFolder() const;
QString getAppWebStylesFolder() const;
QString getUserWebStylesFolder() const;

View File

@ -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
View 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
View 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
View 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
View 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
View 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 &notebookMgr = 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 &notebookMgr = VNoteX::getInst().getNotebookMgr();
const auto &notebooks = 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
View 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
View 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 &notebookMgr = 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
View 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

View 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);
}

View 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

View File

@ -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);

View File

@ -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;

View File

@ -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>

View 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

View File

@ -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>

View 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

View 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"
}
]
}

View 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

View 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

View 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

View 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

View 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

View 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

View File

@ -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;
}

View File

@ -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

View File

@ -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));

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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();

View File

@ -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();

View File

@ -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;

View File

@ -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:

View File

@ -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();

View File

@ -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);
}

View File

@ -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);