[nostalgia/studio] Flesh out editor tab system and cleanup studio::Project to use exceptions instead of returning error codes

This commit is contained in:
Gary Talent 2019-11-26 23:15:02 -06:00
parent aa34239eb5
commit f634c208ac
12 changed files with 218 additions and 122 deletions

View File

@ -1,7 +1,3 @@
cmake_minimum_required(VERSION 2.8.11)
project(nostalgia-studio)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
add_executable(

View File

@ -1,7 +1,3 @@
cmake_minimum_required(VERSION 2.8.11)
project(NostalgiaStudio)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)

View File

@ -114,6 +114,10 @@ QString OxFSFile::name() const {
return m_path.mid(m_path.lastIndexOf("/") + 1);
}
QString OxFSFile::path() const {
return "/" + m_path;
}
// OxFSModel

View File

@ -42,6 +42,8 @@ class OxFSFile {
OxFSFile *parentItem();
QString name() const;
QString path() const;
};
class OxFSModel: public QAbstractItemModel {

View File

@ -22,4 +22,8 @@ QWidget *Plugin::makeEditor(QString, const Context*) {
return nullptr;
}
QVector<EditorMaker> Plugin::editors(const Context*) {
return {};
}
}

View File

@ -10,9 +10,6 @@
#include <functional>
#include <QMainWindow>
#include <QPointer>
#include <QSharedPointer>
#include <QVector>
#include <QWizardPage>
@ -22,14 +19,15 @@
namespace nostalgia::studio {
struct Context {
QString appName;
QString orgName;
QWidget *tabParent = nullptr;
const Project *project = nullptr;
};
struct EditorMaker {
virtual ~EditorMaker() = default;
virtual QWidget *make(QString path, const Context *ctx) = 0;
QStringList fileTypes;
std::function<QWidget*(QString)> make;
};
class Plugin {
@ -43,6 +41,8 @@ class Plugin {
virtual QWidget *makeEditor(QString path, const Context *ctx);
virtual QVector<EditorMaker> editors(const Context *ctx);
};
}

View File

@ -15,8 +15,6 @@ namespace nostalgia::studio {
using namespace ox;
QString Project::ROM_FILE = "/ROM.oxfs";
Project::Project(QString path): m_fs(path.toUtf8()) {
qDebug() << "Project:" << path;
m_path = path;
@ -29,54 +27,35 @@ void Project::create() {
QDir().mkpath(m_path);
}
ox::Error Project::openRomFs() {
QFile file(m_path + ROM_FILE);
auto buffSize = file.size();
auto buff = std::make_unique<uint8_t[]>(buffSize);
if (file.exists()) {
file.open(QIODevice::ReadOnly);
if (file.read(reinterpret_cast<char*>(buff.get()), buffSize) > 0) {
m_fsBuff = std::move(buff);
if (m_fs.valid()) {
return OxError(0);
} else {
return OxError(1);
}
} else {
return OxError(2);
}
} else {
return OxError(3);
}
}
ox::Error Project::saveRomFs() const {
ox::Error err(0);
//QFile file(m_path + ROM_FILE);
//err |= file.open(QIODevice::WriteOnly) == false;
//err |= file.write((const char*) m_fsBuff.get(), m_fs.size()) == -1;
//file.close();
return err;
}
PassThroughFS *Project::romFs() {
return &m_fs;
}
ox::Error Project::mkdir(QString path) const {
auto err = m_fs.mkdir(path.toUtf8().data(), true);
void Project::mkdir(QString path) const {
oxThrowError(m_fs.mkdir(path.toUtf8().data(), true));
emit updated(path);
return err;
}
ox::Error Project::write(QString path, uint8_t *buff, size_t buffLen) const {
auto err = m_fs.write(path.toUtf8().data(), buff, buffLen);
ox::FileStat Project::stat(QString path) const {
auto [s, e] = m_fs.stat(path.toUtf8().data());
oxThrowError(e);
return s;
}
bool Project::exists(QString path) const {
return m_fs.stat(path.toUtf8().data()).error == 0;
}
void Project::writeBuff(QString path, uint8_t *buff, size_t buffLen) const {
oxThrowError(m_fs.write(path.toUtf8().data(), buff, buffLen));
emit updated(path);
return err;
}
ox::ValErr<ox::FileStat> Project::stat(QString path) const {
return m_fs.stat(path.toUtf8().data());
std::vector<uint8_t> Project::loadBuff(QString path) const {
std::vector<uint8_t> buff(stat(path).size);
const auto csPath = path.toUtf8();
oxThrowError(m_fs.read(csPath.data(), buff.data(), buff.size()));
return buff;
}
}

View File

@ -21,10 +21,7 @@ class Project: public QObject {
Q_OBJECT
private:
static QString ROM_FILE;
QString m_path = "";
std::unique_ptr<uint8_t[]> m_fsBuff;
mutable ox::PassThroughFS m_fs;
public:
@ -34,23 +31,27 @@ class Project: public QObject {
void create();
ox::Error openRomFs();
ox::Error saveRomFs() const;
ox::PassThroughFS *romFs();
ox::Error mkdir(QString path) const;
ox::Error write(QString path, uint8_t *buff, size_t buffLen) const;
void mkdir(QString path) const;
/**
* Writes a MetalClaw object to the project at the given path.
*/
template<typename T>
ox::Error writeObj(QString path, T *obj) const;
void writeObj(QString path, T *obj) const;
ox::ValErr<ox::FileStat> stat(QString path) const;
template<typename T>
std::unique_ptr<T> loadObj(QString path) const;
ox::FileStat stat(QString path) const;
bool exists(QString path) const;
private:
void writeBuff(QString path, uint8_t *buff, size_t buffLen) const;
std::vector<uint8_t> loadBuff(QString path) const;
signals:
void updated(QString path) const;
@ -58,19 +59,22 @@ class Project: public QObject {
};
template<typename T>
ox::Error Project::writeObj(QString path, T *obj) const {
auto buffLen = 1024 * 1024 * 10;
QByteArray buff(buffLen, 0);
void Project::writeObj(QString path, T *obj) const {
std::vector<uint8_t> buff(10 * ox::units::MB, 0);
// write MetalClaw
size_t mcSize = 0;
oxReturnError(ox::writeMC(reinterpret_cast<uint8_t*>(buff.data()), buffLen, obj, &mcSize));
oxThrowError(ox::writeMC(buff.data(), buff.size(), obj, &mcSize));
// write to FS
oxReturnError(write(path, reinterpret_cast<uint8_t*>(buff.data()), mcSize));
writeBuff(path, buff.data(), mcSize);
emit updated(path);
}
return OxError(0);
template<typename T>
std::unique_ptr<T> Project::loadObj(QString path) const {
auto obj = std::make_unique<T>();
auto buff = loadBuff(path);
oxThrowError(ox::readMC<T>(buff.data(), buff.size(), obj.get()));
return obj;
}
}

View File

@ -52,9 +52,16 @@ MainWindow::MainWindow(QString profilePath) {
move(screenSize.width() * (1 - sizePct) / 2, screenSize.height() * (1 - sizePct) / 2);
setWindowTitle(m_profile.appName);
m_ctx.appName = m_profile.appName;
m_ctx.orgName = m_profile.orgName;
m_tabbar = new QTabBar(this);
setCentralWidget(m_tabbar);
m_tabs = new QTabWidget(this);
auto tabBar = m_tabs->tabBar();
setCentralWidget(m_tabs);
m_tabs->setTabsClosable(true);
connect(m_tabs, &QTabWidget::tabCloseRequested, this, &MainWindow::closeTab);
connect(tabBar, &QTabBar::tabMoved, this, &MainWindow::moveTab);
tabBar->setMovable(true);
setupMenu();
setupProjectExplorer();
@ -95,6 +102,12 @@ void MainWindow::loadPlugin(QString pluginPath) {
auto plugin = qobject_cast<Plugin*>(loader.instance());
if (plugin) {
m_plugins.push_back(plugin);
auto editorMakers = plugin->editors(&m_ctx);
for (const auto &em : editorMakers) {
for (const auto &ext : em.fileTypes) {
m_editorMakers[ext] = em;
}
}
} else {
qInfo() << loader.errorString();
}
@ -157,6 +170,8 @@ void MainWindow::setupProjectExplorer() {
m_projectExplorer = new QTreeView(dock);
m_projectExplorer->header()->hide();
dock->setWidget(m_projectExplorer);
connect(m_projectExplorer, &QTreeView::activated, this, &MainWindow::openFileSlot);
}
void MainWindow::addDockWidget(Qt::DockWidgetArea area, QDockWidget *dockWidget) {
@ -190,7 +205,7 @@ QAction *MainWindow::addAction(QMenu *menu, QString text, QString toolTip,
return action;
}
int MainWindow::readState(QString) {
int MainWindow::readState() {
int err = 0;
QSettings settings(m_profile.orgName, m_profile.appName);
@ -201,17 +216,15 @@ int MainWindow::readState(QString) {
err |= readJson(json, &m_state);
settings.endGroup();
err |= openProject(m_state.projectPath);
openProject(m_state.projectPath);
return err;
}
int MainWindow::writeState(QString) {
int err = 0;
void MainWindow::writeState() {
// generate JSON for application specific state info
QString json;
err |= writeJson(&json, &m_state);
writeJson(&json, &m_state);
QSettings settings(m_profile.orgName, m_profile.appName);
settings.beginGroup("MainWindow");
@ -219,31 +232,66 @@ int MainWindow::writeState(QString) {
settings.setValue("windowState", saveState());
settings.setValue("json", json);
settings.endGroup();
return err;
}
int MainWindow::openProject(QString projectPath) {
auto err = closeProject();
auto project = new Project(projectPath);
if (err == 0) {
if (m_ctx.project) {
delete m_ctx.project;
m_ctx.project = nullptr;
}
m_ctx.project = project;
m_oxfsView = new OxFSModel(project->romFs());
m_projectExplorer->setModel(m_oxfsView);
connect(m_ctx.project, SIGNAL(updated(QString)), m_oxfsView, SLOT(updateFile(QString)));
m_importAction->setEnabled(true);
m_state.projectPath = projectPath;
qInfo() << "Open project:" << projectPath;
/**
* Read open editor tabs for current project.
*/
QStringList MainWindow::readTabs() {
QStringList tabs;
QSettings settings(m_profile.orgName, m_profile.appName);
settings.beginReadArray("MainWindow/Project:" + m_state.projectPath);
auto size = settings.beginReadArray("openEditors");
for (int i = 0; i < size; i++) {
settings.setArrayIndex(i);
tabs.append(settings.value("filePath").toString());
}
return err;
settings.endArray();
return tabs;
}
int MainWindow::closeProject() {
auto err = 0;
/**
* Write open editor tabs for current project.
*/
void MainWindow::writeTabs(QStringList tabs) {
QSettings settings(m_profile.orgName, m_profile.appName);
settings.beginWriteArray("MainWindow/Project:" + m_state.projectPath + "/openEditors");
for (int i = 0; i < tabs.size(); i++) {
settings.setArrayIndex(i);
settings.setValue("filePath", tabs[i]);
}
settings.endArray();
}
void MainWindow::openProject(QString projectPath) {
closeProject();
auto project = new Project(projectPath);
if (m_ctx.project) {
delete m_ctx.project;
m_ctx.project = nullptr;
}
m_ctx.project = project;
m_oxfsView = new OxFSModel(project->romFs());
m_projectExplorer->setModel(m_oxfsView);
connect(m_ctx.project, &Project::updated, m_oxfsView, &OxFSModel::updateFile);
m_importAction->setEnabled(true);
m_state.projectPath = projectPath;
// reopen tabs
auto openTabs = readTabs();
for (auto t : openTabs) {
openFile(t, true);
}
qInfo() << "Open project:" << projectPath;
}
void MainWindow::closeProject() {
// delete tabs
while (m_tabs->count()) {
auto t = m_tabs->widget(0);
m_tabs->removeTab(0);
delete t;
}
if (m_ctx.project) {
disconnect(m_ctx.project, SIGNAL(updated(QString)), m_oxfsView, SLOT(updateFile(QString)));
@ -261,7 +309,29 @@ int MainWindow::closeProject() {
m_importAction->setEnabled(false);
m_state.projectPath = "";
return err;
}
void MainWindow::openFile(QString path, bool force) {
if (!force && readTabs().contains(path)) {
return;
}
const auto dotIdx = path.lastIndexOf('.') + 1;
if (dotIdx == -1) {
return;
}
const auto ext = path.mid(dotIdx);
const auto lastSlash = path.lastIndexOf('/') + 1;
auto tabName = path.mid(lastSlash);
if (m_editorMakers.contains(ext)) {
auto tab = m_editorMakers[ext].make(path);
m_tabs->addTab(tab, tabName);
// save new tab to application state
auto openTabs = readTabs();
if (!openTabs.contains(path)) {
openTabs.append(path);
}
writeTabs(openTabs);
}
}
void MainWindow::onExit() {
@ -271,16 +341,35 @@ void MainWindow::onExit() {
// private slots
int MainWindow::openProject() {
void MainWindow::openProject() {
auto projectPath = QFileDialog::getExistingDirectory(this, tr("Select Project Directory..."), QDir::homePath());
int err = 0;
if (projectPath != "") {
err |= openProject(projectPath);
if (err == 0) {
err |= writeState();
}
openProject(projectPath);
writeState();
}
return err;
}
void MainWindow::openFileSlot(QModelIndex file) {
auto path = static_cast<OxFSFile*>(file.internalPointer())->path();
return openFile(path);
}
void MainWindow::closeTab(int idx) {
auto tab = m_tabs->widget(idx);
m_tabs->removeTab(idx);
delete tab;
// remove from open tabs list
auto tabs = readTabs();
tabs.removeAt(idx);
writeTabs(tabs);
}
void MainWindow::moveTab(int from, int to) {
// move tab in open tabs list
auto tabs = readTabs();
tabs.move(from, to);
writeTabs(tabs);
}
void MainWindow::showNewWizard() {

View File

@ -17,7 +17,7 @@
#include <QPointer>
#include <QSharedPointer>
#include <QString>
#include <QTabBar>
#include <QTabWidget>
#include <QTreeView>
#include <QVector>
@ -91,8 +91,9 @@ class MainWindow: public QMainWindow {
QVector<QPointer<QDockWidget>> m_dockWidgets;
QTreeView *m_projectExplorer = nullptr;
QVector<Plugin*> m_plugins;
QHash<QString, EditorMaker> m_editorMakers;
QPointer<OxFSModel> m_oxfsView = nullptr;
QTabBar *m_tabbar = nullptr;
QTabWidget *m_tabs = nullptr;
public:
MainWindow(QString profilePath);
@ -122,25 +123,47 @@ class MainWindow: public QMainWindow {
QAction *addAction(QMenu *menu, QString text, QString toolTip,
QKeySequence::StandardKey key, void (*cb)());
int readState(QString path = StateFilePath);
int readState();
int writeState(QString path = StateFilePath);
void writeState();
int openProject(QString);
/**
* Read open editor tabs for current project.
*/
QStringList readTabs();
int closeProject();
/**
* Write open editor tabs for current project.
*/
void writeTabs(QStringList tabs);
void openProject(QString);
void closeProject();
/**
* @param force forces the reopening of the file even if it is already open
*/
void openFile(QString path, bool force = false);
public slots:
void onExit();
private slots:
int openProject();
void openProject();
void openFileSlot(QModelIndex);
void closeTab(int idx);
void moveTab(int from, int to);
void showNewWizard();
void showImportWizard();
void refreshProjectExplorer(QString path);
};
}

View File

@ -20,8 +20,8 @@ const QString NewWorldWizard::FIELD_WORLD_PATH = "World.WorldPath";
NewWorldWizard::NewWorldWizard(const Context *ctx) {
addLineEdit(tr("&Name:"), FIELD_WORLD_PATH, "", [this, ctx](QString worldName) {
worldName = PATH_ZONES + worldName;
auto [stat, err] = ctx->project->stat(worldName);
if (err) {
auto exists = ctx->project->exists(worldName);
if (exists) {
this->showValidationError(tr("World already exists: %1").arg(worldName));
return 1;
}

View File

@ -31,9 +31,8 @@ QVector<WizardMaker> WorldEditorPlugin::newWizards(const Context *ctx) {
qDebug() << "creating Region";
auto path = PATH_ZONES + w->field(NewWorldWizard::FIELD_WORLD_PATH).toString();
Region rgn;
oxReturnError(ctx->project->mkdir(PATH_ZONES));
ctx->project->saveRomFs();
oxReturnError(ctx->project->writeObj(path, &rgn));
ctx->project->mkdir(PATH_ZONES);
ctx->project->writeObj(path, &rgn);
return OxError(0);
}
}