From f634c208acd6fb188c359d4807713597105bb210 Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Tue, 26 Nov 2019 23:15:02 -0600 Subject: [PATCH] [nostalgia/studio] Flesh out editor tab system and cleanup studio::Project to use exceptions instead of returning error codes --- src/nostalgia/studio/CMakeLists.txt | 4 - src/nostalgia/studio/lib/CMakeLists.txt | 4 - src/nostalgia/studio/lib/oxfstreeview.cpp | 4 + src/nostalgia/studio/lib/oxfstreeview.hpp | 2 + src/nostalgia/studio/lib/plugin.cpp | 4 + src/nostalgia/studio/lib/plugin.hpp | 12 +- src/nostalgia/studio/lib/project.cpp | 59 +++---- src/nostalgia/studio/lib/project.hpp | 44 ++--- src/nostalgia/studio/mainwindow.cpp | 161 ++++++++++++++---- src/nostalgia/studio/mainwindow.hpp | 37 +++- src/nostalgia/world/studio/newworldwizard.cpp | 4 +- .../world/studio/worldstudioplugin.cpp | 5 +- 12 files changed, 218 insertions(+), 122 deletions(-) diff --git a/src/nostalgia/studio/CMakeLists.txt b/src/nostalgia/studio/CMakeLists.txt index 27234154..75ae47d0 100644 --- a/src/nostalgia/studio/CMakeLists.txt +++ b/src/nostalgia/studio/CMakeLists.txt @@ -1,7 +1,3 @@ -cmake_minimum_required(VERSION 2.8.11) - -project(nostalgia-studio) - set(CMAKE_INCLUDE_CURRENT_DIR ON) add_executable( diff --git a/src/nostalgia/studio/lib/CMakeLists.txt b/src/nostalgia/studio/lib/CMakeLists.txt index 44f0e705..4614c961 100644 --- a/src/nostalgia/studio/lib/CMakeLists.txt +++ b/src/nostalgia/studio/lib/CMakeLists.txt @@ -1,7 +1,3 @@ -cmake_minimum_required(VERSION 2.8.11) - -project(NostalgiaStudio) - set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) diff --git a/src/nostalgia/studio/lib/oxfstreeview.cpp b/src/nostalgia/studio/lib/oxfstreeview.cpp index 9abf0869..d2b86635 100644 --- a/src/nostalgia/studio/lib/oxfstreeview.cpp +++ b/src/nostalgia/studio/lib/oxfstreeview.cpp @@ -114,6 +114,10 @@ QString OxFSFile::name() const { return m_path.mid(m_path.lastIndexOf("/") + 1); } +QString OxFSFile::path() const { + return "/" + m_path; +} + // OxFSModel diff --git a/src/nostalgia/studio/lib/oxfstreeview.hpp b/src/nostalgia/studio/lib/oxfstreeview.hpp index eba8a46c..ab3679c6 100644 --- a/src/nostalgia/studio/lib/oxfstreeview.hpp +++ b/src/nostalgia/studio/lib/oxfstreeview.hpp @@ -42,6 +42,8 @@ class OxFSFile { OxFSFile *parentItem(); QString name() const; + + QString path() const; }; class OxFSModel: public QAbstractItemModel { diff --git a/src/nostalgia/studio/lib/plugin.cpp b/src/nostalgia/studio/lib/plugin.cpp index beae48cb..d22a056d 100644 --- a/src/nostalgia/studio/lib/plugin.cpp +++ b/src/nostalgia/studio/lib/plugin.cpp @@ -22,4 +22,8 @@ QWidget *Plugin::makeEditor(QString, const Context*) { return nullptr; } +QVector Plugin::editors(const Context*) { + return {}; +} + } diff --git a/src/nostalgia/studio/lib/plugin.hpp b/src/nostalgia/studio/lib/plugin.hpp index 00dc9a8b..c512cba6 100644 --- a/src/nostalgia/studio/lib/plugin.hpp +++ b/src/nostalgia/studio/lib/plugin.hpp @@ -10,9 +10,6 @@ #include -#include -#include -#include #include #include @@ -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 make; }; class Plugin { @@ -43,6 +41,8 @@ class Plugin { virtual QWidget *makeEditor(QString path, const Context *ctx); + virtual QVector editors(const Context *ctx); + }; } diff --git a/src/nostalgia/studio/lib/project.cpp b/src/nostalgia/studio/lib/project.cpp index 8b1b371b..f3595deb 100644 --- a/src/nostalgia/studio/lib/project.cpp +++ b/src/nostalgia/studio/lib/project.cpp @@ -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(buffSize); - if (file.exists()) { - file.open(QIODevice::ReadOnly); - if (file.read(reinterpret_cast(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 Project::stat(QString path) const { - return m_fs.stat(path.toUtf8().data()); +std::vector Project::loadBuff(QString path) const { + std::vector buff(stat(path).size); + const auto csPath = path.toUtf8(); + oxThrowError(m_fs.read(csPath.data(), buff.data(), buff.size())); + return buff; } } diff --git a/src/nostalgia/studio/lib/project.hpp b/src/nostalgia/studio/lib/project.hpp index 68ccdbca..e9da57c9 100644 --- a/src/nostalgia/studio/lib/project.hpp +++ b/src/nostalgia/studio/lib/project.hpp @@ -21,10 +21,7 @@ class Project: public QObject { Q_OBJECT private: - static QString ROM_FILE; - QString m_path = ""; - std::unique_ptr 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 - ox::Error writeObj(QString path, T *obj) const; + void writeObj(QString path, T *obj) const; - ox::ValErr stat(QString path) const; + template + std::unique_ptr 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 loadBuff(QString path) const; signals: void updated(QString path) const; @@ -58,19 +59,22 @@ class Project: public QObject { }; template -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 buff(10 * ox::units::MB, 0); // write MetalClaw size_t mcSize = 0; - oxReturnError(ox::writeMC(reinterpret_cast(buff.data()), buffLen, obj, &mcSize)); + oxThrowError(ox::writeMC(buff.data(), buff.size(), obj, &mcSize)); // write to FS - oxReturnError(write(path, reinterpret_cast(buff.data()), mcSize)); - + writeBuff(path, buff.data(), mcSize); emit updated(path); +} - return OxError(0); +template +std::unique_ptr Project::loadObj(QString path) const { + auto obj = std::make_unique(); + auto buff = loadBuff(path); + oxThrowError(ox::readMC(buff.data(), buff.size(), obj.get())); + return obj; } } diff --git a/src/nostalgia/studio/mainwindow.cpp b/src/nostalgia/studio/mainwindow.cpp index 71acddac..79ce712a 100644 --- a/src/nostalgia/studio/mainwindow.cpp +++ b/src/nostalgia/studio/mainwindow.cpp @@ -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(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(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() { diff --git a/src/nostalgia/studio/mainwindow.hpp b/src/nostalgia/studio/mainwindow.hpp index d68e4ada..10eb818b 100644 --- a/src/nostalgia/studio/mainwindow.hpp +++ b/src/nostalgia/studio/mainwindow.hpp @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include #include @@ -91,8 +91,9 @@ class MainWindow: public QMainWindow { QVector> m_dockWidgets; QTreeView *m_projectExplorer = nullptr; QVector m_plugins; + QHash m_editorMakers; QPointer 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); + }; } diff --git a/src/nostalgia/world/studio/newworldwizard.cpp b/src/nostalgia/world/studio/newworldwizard.cpp index cf2fdbbb..9e14f3c8 100644 --- a/src/nostalgia/world/studio/newworldwizard.cpp +++ b/src/nostalgia/world/studio/newworldwizard.cpp @@ -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; } diff --git a/src/nostalgia/world/studio/worldstudioplugin.cpp b/src/nostalgia/world/studio/worldstudioplugin.cpp index b1f40e17..ce265e2b 100644 --- a/src/nostalgia/world/studio/worldstudioplugin.cpp +++ b/src/nostalgia/world/studio/worldstudioplugin.cpp @@ -31,9 +31,8 @@ QVector 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); } }