Add plugin system

This commit is contained in:
Gary Talent 2017-05-17 00:10:16 -05:00
parent db8ad57828
commit fb52ca6518
15 changed files with 368 additions and 65 deletions

View File

@ -23,6 +23,10 @@ add_library(
core.cpp
)
if(WOMBAT_BUILD_TYPE STREQUAL "Native")
add_subdirectory(studio)
endif()
install(
FILES
core.hpp

View File

@ -0,0 +1,22 @@
cmake_minimum_required(VERSION 2.8.11)
if(WOMBAT_BUILD_TYPE STREQUAL "Native")
add_library(
NostalgiaCore-Studio
SHARED
import_tilesheet_wizard.cpp
plugin.cpp
)
endif()
target_link_libraries(
NostalgiaCore-Studio
Qt5::Core
Qt5::Widgets
NostalgiaStudio
OxFS
OxStd
)
install(TARGETS NostalgiaCore-Studio
LIBRARY DESTINATION lib/nostalgia)

View File

@ -0,0 +1,48 @@
/*
* Copyright 2016-2017 gtalent2@gmail.com
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <QFile>
#include "import_tilesheet_wizard.hpp"
namespace nostalgia {
namespace core {
const QString ImportTilesheetWizardPage::TILESHEET_NAME = "projectName";
const QString ImportTilesheetWizardPage::IMPORT_PATH = "projectPath";
const QString ImportTilesheetWizardPage::BPP = "bpp";
ImportTilesheetWizardPage::ImportTilesheetWizardPage() {
addLineEdit(tr("&Tile Sheet Name:"), TILESHEET_NAME + "*", "", [this](QString) {
auto importPath = field(IMPORT_PATH).toString();
if (QFile(importPath).exists()) {
return 0;
} else {
this->showValidationError(tr("Invalid image file: %1").arg(importPath));
return 1;
}
}
);
auto fileTypes = "(*.png);;(*.bmp);;(*.jpg);;(*.jpeg)";
addPathBrowse(tr("Tile Sheet &Path:"), IMPORT_PATH + "*", "",
QFileDialog::ExistingFile, fileTypes);
addComboBox(tr("Bits Per Pixe&l:"), BPP, {"4", "8"});
}
int ImportTilesheetWizardPage::accept() {
auto tilesheetName = field(TILESHEET_NAME).toString();
auto importPath = field(IMPORT_PATH).toString();
if (QFile(importPath).exists()) {
return 0;
} else {
return 1;
}
}
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2016-2017 gtalent2@gmail.com
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <nostalgia/studio/studio.hpp>
namespace nostalgia {
namespace core {
class ImportTilesheetWizardPage: public studio::WizardFormPage {
private:
static const QString TILESHEET_NAME;
static const QString IMPORT_PATH;
static const QString BPP;
public:
ImportTilesheetWizardPage();
int accept();
};
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2016-2017 gtalent2@gmail.com
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <nostalgia/studio/studio.hpp>
#include "import_tilesheet_wizard.hpp"
#include "plugin.hpp"
using namespace nostalgia::studio;
namespace nostalgia {
namespace core {
Plugin::Plugin() {
addImportWizard(
tr("Tile Sheet"),
[]() {
QVector<QWizardPage*> pgs;
pgs.push_back(new ImportTilesheetWizardPage());
return pgs;
}
);
}
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2016-2017 gtalent2@gmail.com
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <QPluginLoader>
#include <nostalgia/studio/studio.hpp>
namespace nostalgia {
namespace core {
class Plugin: public QObject, studio::Plugin {
Q_OBJECT
Q_PLUGIN_METADATA(IID "net.drinkingtea.nostalgia.studio.Plugin")
Q_INTERFACES(nostalgia::studio::Plugin)
public:
Plugin();
};
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright 2016-2017 gtalent2@gmail.com
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include "plugin.hpp"
namespace nostalgia {
namespace studio {
void Plugin::addNewWizard(QString name, std::function<QVector<QWizardPage*>()> make) {
m_newWizards.push_back({name, make});
}
void Plugin::addImportWizard(QString name, std::function<QVector<QWizardPage*>()> make) {
m_importWizards.push_back({name, make});
}
QVector<WizardMaker> Plugin::newWizards() {
return m_newWizards;
}
QVector<WizardMaker> Plugin::importWizards() {
return m_importWizards;
}
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2016-2017 gtalent2@gmail.com
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <functional>
#include <QMainWindow>
#include <QVector>
#include <QWizardPage>
namespace nostalgia {
namespace studio {
struct WizardMaker {
QString name;
std::function<QVector<QWizardPage*>()> make;
};
class Plugin {
private:
QVector<WizardMaker> m_newWizards;
QVector<WizardMaker> m_importWizards;
public:
void addNewWizard(QString name, std::function<QVector<QWizardPage*>()> make);
void addImportWizard(QString name, std::function<QVector<QWizardPage*>()> make);
QVector<WizardMaker> newWizards();
QVector<WizardMaker> importWizards();
};
}
}
#define PluginInterface_iid "net.drinkingtea.nostalgia.studio.Plugin"
Q_DECLARE_INTERFACE(nostalgia::studio::Plugin, PluginInterface_iid)

View File

@ -132,6 +132,10 @@ WizardFormPage::~WizardFormPage() {
}
}
int WizardFormPage::accept() {
return 0;
}
void WizardFormPage::initializePage() {
for (auto it = m_fields.begin(); it != m_fields.end(); it++) {
auto key = it.key();
@ -223,8 +227,8 @@ void WizardFormPage::addLineEdit(QString displayName, QString fieldName, QString
m_currentLine++;
}
void WizardFormPage::addPathBrowse(QString displayName, QString fieldName,
QString defaultVal, QFileDialog::FileMode fileMode) {
void WizardFormPage::addPathBrowse(QString displayName, QString fieldName, QString defaultVal,
QFileDialog::FileMode fileMode, QString fileExtensions) {
auto layout = new QHBoxLayout();
auto lbl = new QLabel(displayName, this);
auto le = new QLineEdit("", this);
@ -272,14 +276,18 @@ void WizardFormPage::addPathBrowse(QString displayName, QString fieldName,
}
);
connect(btn, &QPushButton::clicked, [this, defaultVal, le, fileMode]() {
connect(btn, &QPushButton::clicked, [this, defaultVal, le, fileMode, fileExtensions]() {
auto dir = defaultVal;
if (dir == "") {
dir = QDir::homePath();
}
if (fileMode == QFileDialog::Directory) {
auto p = QFileDialog::getExistingDirectory(this, tr("Select Directory..."), defaultVal);
auto p = QFileDialog::getExistingDirectory(this, tr("Select Directory..."), dir);
if (p != "") {
le->setText(p);
}
} else if (fileMode == QFileDialog::ExistingFile) {
auto p = QFileDialog::getOpenFileName(this, tr("Select File..."), defaultVal);
auto p = QFileDialog::getOpenFileName(this, tr("Select File..."), dir, fileExtensions);
if (p != "") {
le->setText(p);
}
@ -317,13 +325,15 @@ Wizard::Wizard(QString windowTitle, QWidget *parent): QWizard(parent) {
setModal(true);
}
void Wizard::setAccept(std::function<void()> acceptFunc) {
void Wizard::setAccept(std::function<int()> acceptFunc) {
m_acceptFunc = acceptFunc;
}
void Wizard::accept() {
m_acceptFunc();
QDialog::accept();
auto page = dynamic_cast<WizardFormPage*>(currentPage());
if (page == nullptr || page->accept() == 0) {
QDialog::accept();
}
}
}

View File

@ -69,6 +69,8 @@ class WizardFormPage: public QWizardPage {
~WizardFormPage();
virtual int accept();
void initializePage() override;
bool validatePage() override;
@ -79,8 +81,10 @@ class WizardFormPage: public QWizardPage {
QString defaultVal = "",
std::function<int(QString)> validator = [](QString) { return 0; });
void addPathBrowse(QString displayName, QString fieldName, QString defaultVal = QDir::homePath(),
QFileDialog::FileMode fileMode = QFileDialog::AnyFile);
void addPathBrowse(QString displayName, QString fieldName,
QString defaultVal = QDir::homePath(),
QFileDialog::FileMode fileMode = QFileDialog::AnyFile,
QString fileExtensions = "");
void showValidationError(QString msg);
};
@ -104,14 +108,13 @@ class WizardConclusionPage: public QWizardPage {
class Wizard: public QWizard {
Q_OBJECT
private:
std::function<void()> m_acceptFunc;
std::function<int()> m_acceptFunc;
public:
Wizard(QString windowTitle, QWidget *parent = 0);
void setAccept(std::function<void()> acceptFunc);
void setAccept(std::function<int()> acceptFunc);
void accept();
};

View File

@ -22,18 +22,10 @@ int run(int argc, char **args) {
NostalgiaStudioProfile config;
// load in config file
QFile file(argProfilePath);
if (file.exists()) {
file.open(QIODevice::ReadOnly);
QTextStream in(&file);
readJson(in.readAll(), &config);
}
QApplication app(argc, args);
app.setApplicationName(config.appName);
MainWindow w(config);
MainWindow w(argProfilePath);
app.setApplicationName(w.windowTitle());
w.show();
return app.exec();

View File

@ -8,6 +8,7 @@
#include <QApplication>
#include <QDesktopWidget>
#include <QDebug>
#include <QDialog>
#include <QFileDialog>
#include <QGridLayout>
@ -15,14 +16,16 @@
#include <QLabel>
#include <QLineEdit>
#include <QMenuBar>
#include <QPluginLoader>
#include <QTabBar>
#include <QTextStream>
#include <QVector>
#include "lib/json.hpp"
#include "lib/wizard.hpp"
#include "lib/oxfstreeview.hpp"
#include "lib/project.hpp"
#include "lib/wizard.hpp"
#include "mainwindow.hpp"
namespace nostalgia {
@ -30,7 +33,17 @@ namespace studio {
const QString MainWindow::StateFilePath = "studio_state.json";
MainWindow::MainWindow(NostalgiaStudioProfile config, QWidget *parent) {
MainWindow::MainWindow(QString profilePath) {
m_profilePath = profilePath;
// load in profile file
NostalgiaStudioProfile profile;
QFile file(profilePath);
if (file.exists()) {
file.open(QIODevice::ReadOnly);
QTextStream in(&file);
readJson(in.readAll(), &profile);
}
auto screenSize = QApplication::desktop()->screenGeometry();
// set window to 75% of screen width, and center NostalgiaStudioProfile
@ -39,7 +52,7 @@ MainWindow::MainWindow(NostalgiaStudioProfile config, QWidget *parent) {
move(-x(), -y());
move(screenSize.width() * (1 - sizePct) / 2, screenSize.height() * (1 - sizePct) / 2);
setWindowTitle(config.appName);
setWindowTitle(profile.appName);
auto tabbar = new QTabBar(this);
setCentralWidget(tabbar);
@ -47,6 +60,8 @@ MainWindow::MainWindow(NostalgiaStudioProfile config, QWidget *parent) {
setupMenu();
setupProjectExplorer();
loadPlugins(profile);
readState();
}
@ -57,6 +72,27 @@ MainWindow::~MainWindow() {
}
}
void MainWindow::loadPlugins(NostalgiaStudioProfile profile) {
for (auto p : profile.plugins) {
#if defined(Q_OS_WIN)
auto libName = p.libName + ".dll";
#else
auto libName = "lib" + p.libName + ".so";
#endif
auto pluginPath = QFileInfo(m_profilePath).absolutePath() + "/" + p.dir + "/" + libName;
auto loader = new QPluginLoader(pluginPath);
auto plugin = qobject_cast<Plugin*>(loader->instance());
if (plugin) {
m_cleanupTasks.push_back([loader]() {
loader->unload();
delete loader;
});
m_plugins.push_back(plugin);
}
}
}
void MainWindow::setupMenu() {
auto menu = menuBar();
auto fileMenu = menu->addMenu(tr("&File"));
@ -132,7 +168,7 @@ QAction *MainWindow::addAction(QMenu *menu, QString text, QString toolTip, const
}
QAction *MainWindow::addAction(QMenu *menu, QString text, QString toolTip,
QKeySequence::StandardKey key, const QObject *tgt, const char *cb) {
QKeySequence::StandardKey key, const QObject *tgt, const char *cb) {
auto action = menu->addAction(text);
action->setShortcuts(key);
action->setStatusTip(toolTip);
@ -144,7 +180,7 @@ QAction *MainWindow::addAction(QMenu *menu, QString text, QString toolTip,
}
QAction *MainWindow::addAction(QMenu *menu, QString text, QString toolTip,
QKeySequence::StandardKey key, void (*cb)()) {
QKeySequence::StandardKey key, void (*cb)()) {
auto action = menu->addAction(text);
action->setShortcuts(key);
action->setStatusTip(toolTip);
@ -248,14 +284,19 @@ void MainWindow::showNewWizard() {
return pgs;
}
);
wizard.setAccept([&wizard, ws, PROJECT_NAME, PROJECT_PATH]() {
wizard.setAccept([&wizard, ws, PROJECT_NAME, PROJECT_PATH]() -> int {
auto projectName = wizard.field(PROJECT_NAME).toString();
auto projectPath = wizard.field(PROJECT_PATH).toString();
if (QDir(projectPath).exists()) {
auto path = projectPath + "/" + projectName;
if (!QDir(path).exists()) {
Project(path).create();
return 0;
} else {
return 1;
}
} else {
return 2;
}
}
);
@ -271,36 +312,11 @@ void MainWindow::showImportWizard() {
auto ws = new WizardSelect();
wizard.addPage(ws);
ws->addOption(tr("Tile Sheet"),
[&wizard, TILESHEET_NAME, IMPORT_PATH, BPP]() {
QVector<QWizardPage*> pgs;
auto pg = new WizardFormPage();
pg->addLineEdit(tr("Tile Sheet &Name:"), TILESHEET_NAME + "*", "", [IMPORT_PATH, pg, &wizard](QString projectName) {
auto projectPath = wizard.field(IMPORT_PATH).toString();
auto path = projectPath + "/" + projectName;
if (!QDir(path).exists()) {
return 0;
} else {
pg->showValidationError(tr("This project directory already exists."));
return 1;
}
}
);
pg->addPathBrowse(tr("Tile Sheet &Path:"), IMPORT_PATH + "*", QDir::homePath(), QFileDialog::ExistingFile);
pg->addComboBox(tr("Bits Per Pixe&l:"), BPP, {"4", "8"});
pgs.push_back(pg);
pgs.push_back(new WizardConclusionPage(tr("Importing tile sheet: %1 as %2"), {IMPORT_PATH, TILESHEET_NAME}));
return pgs;
for (auto p : m_plugins) {
for (auto w : p->importWizards()) {
ws->addOption(w.name, w.make);
}
);
wizard.setAccept([&wizard, ws, TILESHEET_NAME, IMPORT_PATH]() {
auto tilesheetName = wizard.field(TILESHEET_NAME).toString();
auto importPath = wizard.field(IMPORT_PATH).toString();
if (QFile(importPath).exists()) {
}
}
);
}
wizard.show();
wizard.exec();

View File

@ -22,6 +22,7 @@
#include <ox/std/types.hpp>
#include "lib/plugin.hpp"
#include "lib/project.hpp"
namespace nostalgia {
@ -32,7 +33,7 @@ struct NostalgiaStudioState {
};
template<typename T>
int ioOp(T *io, NostalgiaStudioState *obj) {
ox::Error ioOp(T *io, NostalgiaStudioState *obj) {
ox::Error err = 0;
io->setFields(1);
err |= io->op("project_path", &obj->projectPath);
@ -40,15 +41,32 @@ int ioOp(T *io, NostalgiaStudioState *obj) {
}
struct NostalgiaStudioProfile {
QString appName;
struct NostalgiaStudioPluginDef {
QString dir;
QString libName;
};
template<typename T>
int ioOp(T *io, NostalgiaStudioProfile *obj) {
ox::Error ioOp(T *io, NostalgiaStudioPluginDef *obj) {
ox::Error err = 0;
io->setFields(1);
io->setFields(2);
err |= io->op("dir", &obj->dir);
err |= io->op("lib_name", &obj->libName);
return err;
}
struct NostalgiaStudioProfile {
QString appName;
QVector<NostalgiaStudioPluginDef> plugins;
};
template<typename T>
ox::Error ioOp(T *io, NostalgiaStudioProfile *obj) {
ox::Error err = 0;
io->setFields(2);
err |= io->op("app_name", &obj->appName);
err |= io->op("plugins", &obj->plugins);
return err;
}
@ -61,6 +79,7 @@ class MainWindow: public QMainWindow {
static const QString StateFilePath;
private:
QString m_profilePath;
NostalgiaStudioState m_state;
QAction *m_importAction = nullptr;
QSharedPointer<Project> m_project;
@ -68,13 +87,16 @@ class MainWindow: public QMainWindow {
QVector<std::function<void()>> m_cleanupTasks;
QVector<QPointer<QDockWidget>> m_dockWidgets;
QTreeView *m_projectExplorer = nullptr;
QVector<Plugin*> m_plugins;
public:
MainWindow(NostalgiaStudioProfile config, QWidget *parent = 0);
MainWindow(QString profilePath);
virtual ~MainWindow();
private:
void loadPlugins(NostalgiaStudioProfile profile);
void setupDockWidgets();
void setupMenu();

View File

@ -1,3 +1,9 @@
{
"app_name": "Nostalgia Studio"
"app_name": "Nostalgia Studio",
"plugins": [
{
"dir": "../lib/nostalgia",
"lib_name": "NostalgiaCore-Studio"
}
]
}

View File

@ -0,0 +1,15 @@
/*
* Copyright 2016-2017 gtalent2@gmail.com
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "lib/json.hpp"
#include "lib/plugin.hpp"
#include "lib/project.hpp"
#include "lib/oxfstreeview.hpp"
#include "lib/wizard.hpp"