recent files tracking and menu items!

master
zetaPRIME 2022-03-13 06:47:17 -04:00
parent d3521416f2
commit 10bcacf8c1
11 changed files with 221 additions and 51 deletions

8
notes
View File

@ -34,12 +34,18 @@ TODO {
immediate frontburner { immediate frontburner {
look into performance/timing/sync of audio engine; it's having buffer issues way more than it feels like it should look into performance/timing/sync of audio engine; it's having buffer issues way more than it feels like it should
recent files list and backend { - recent files list and backend {
settings manager to handle loading+saving settings manager to handle loading+saving
separate files for "settings" and "state" separate files for "settings" and "state"
probably an "update all relevant windows" thing for recents probably an "update all relevant windows" thing for recents
} }
prompt save on... {
new project
open project (any)
close
}
add i/o nodes to default project add i/o nodes to default project
distortion effect distortion effect

View File

@ -3,7 +3,8 @@ using namespace Xybrid::Config;
#include <QStandardPaths> #include <QStandardPaths>
const QString Directories::configFile = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation).append("/xybrid/config.json"); const QString Directories::configFile = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation).append("/xybrid/config.dat");
const QString Directories::stateFile = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation).append("/xybrid/state.dat");
QString Directories::projects = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation).append("/xybrid/projects"); QString Directories::projects = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation).append("/xybrid/projects");
QString Directories::presets = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation).append("/xybrid/nodes"); QString Directories::presets = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation).append("/xybrid/nodes");

View File

@ -5,6 +5,7 @@
namespace Xybrid::Config { namespace Xybrid::Config {
namespace Directories { namespace Directories {
const extern QString configFile; const extern QString configFile;
const extern QString stateFile;
extern QString projects; extern QString projects;
extern QString presets; extern QString presets;

20
xybrid/config/uistate.cpp Normal file
View File

@ -0,0 +1,20 @@
#include "uistate.h"
#include <algorithm>
#include "fileops.h"
using namespace Xybrid::Config;
std::list<QString> UIState::recentFiles;
void UIState::save() {
FileOps::saveUIState();
}
void UIState::addRecentFile(const QString &f) {
if (!recentFiles.empty() && recentFiles.front() == f) return; // if it's already the most recent file, skip
recentFiles.remove(f); // remove any existing instance from later in the list
recentFiles.push_front(f);
while (recentFiles.size() > MAX_RECENTS) recentFiles.pop_back(); // trim to max size
save(); // and save changes
}

15
xybrid/config/uistate.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#include <list>
#include <QString>
namespace Xybrid::Config {
namespace UIState {
const constexpr size_t MAX_RECENTS = 10;
extern std::list<QString> recentFiles;
void save();
void addRecentFile(const QString& f);
}
}

109
xybrid/fileops-config.cpp Normal file
View File

@ -0,0 +1,109 @@
#include "fileops.h"
#include "uisocket.h"
#include "config/uistate.h"
#include <QDebug>
#include <QFile>
#include <QCborMap>
#include <QCborArray>
#include <QCborStreamReader>
#include <QCborStreamWriter>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QUndoStack>
#include <QFileDialog>
#define qs QStringLiteral
using Xybrid::Data::Project;
using Xybrid::Data::Pattern;
using Xybrid::Data::Graph;
using Xybrid::Data::Node;
namespace FileOps = Xybrid::FileOps;
using namespace Xybrid::Config;
void FileOps::loadConfig() {
QFile file(Config::Directories::configFile);
if (file.open(QFile::ReadOnly)) { // file exists! read in
QCborStreamReader read(&file);
auto root = QCborValue::fromCbor(read).toMap();
file.close();
if (auto dirs = root[qs("directories")].toMap(); !dirs.isEmpty()) {
if (auto s = dirs[qs("projects")].toString(); !s.isNull()) Config::Directories::projects = s;
if (auto s = dirs[qs("presets")].toString(); !s.isNull()) Config::Directories::presets = s;
}
}
// make sure directories exist
if (auto d = QDir(Config::Directories::projects); !d.exists()) d.mkpath(".");
if (auto d = QDir(Config::Directories::presets); !d.exists()) d.mkpath(".");
}
void FileOps::saveConfig() {
QFileInfo fi(Config::Directories::configFile);
fi.dir().mkpath("."); // make sure directory exists
QFile file(fi.filePath());
if (!file.open(QFile::WriteOnly)) return;
QCborMap root;
{
QCborMap dirs;
dirs[qs("projects")] = Config::Directories::projects;
dirs[qs("presets")] = Config::Directories::presets;
root[qs("directories")] = dirs;
}
// write out
QCborStreamWriter w(&file);
root.toCborValue().toCbor(w);
file.close();
}
void FileOps::loadUIState() {
QFile file(Config::Directories::stateFile);
if (file.open(QFile::ReadOnly)) { // file exists! read in
QCborStreamReader read(&file);
auto root = QCborValue::fromCbor(read).toMap();
file.close();
if (auto recent = root[qs("recent")].toArray(); !recent.isEmpty()) {
UIState::recentFiles.clear();
for (auto r : recent) UIState::recentFiles.push_back(r.toString());
}
}
}
void FileOps::saveUIState() {
QFileInfo fi(Config::Directories::stateFile);
fi.dir().mkpath("."); // make sure directory exists
QFile file(fi.filePath());
if (!file.open(QFile::WriteOnly)) return;
QCborMap root;
{
QCborArray recent;
for (auto& r : UIState::recentFiles) recent.append(r);
root[qs("recent")] = recent;
}
// write out
QCborStreamWriter w(&file);
root.toCborValue().toCbor(w);
file.close();
}

View File

@ -339,45 +339,4 @@ std::shared_ptr<Node> FileOps::loadNode(QString fileName, std::shared_ptr<Graph>
return Node::fromCbor(root.at(2), parent); // let Node handle the rest return Node::fromCbor(root.at(2), parent); // let Node handle the rest
} }
void FileOps::loadConfig() {
QFile file(Config::Directories::configFile);
if (file.open(QFile::ReadOnly)) { // file exists! read in
QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
auto root = doc.object();
file.close();
if (auto dirs = root["directories"].toObject(); !dirs.isEmpty()) {
if (auto s = dirs["projects"].toString(); !s.isNull()) Config::Directories::projects = s;
if (auto s = dirs["presets"].toString(); !s.isNull()) Config::Directories::presets = s;
}
}
// make sure directories exist
if (auto d = QDir(Config::Directories::projects); !d.exists()) d.mkpath(".");
if (auto d = QDir(Config::Directories::presets); !d.exists()) d.mkpath(".");
}
void FileOps::saveConfig() {
QFileInfo fi(Config::Directories::configFile);
fi.dir().mkpath("."); // make sure directory exists
QFile file(fi.filePath());
if (!file.open(QFile::WriteOnly)) return;
QJsonDocument doc;
QJsonObject root;
{
QJsonObject dirs;
dirs["projects"] = Config::Directories::projects;
dirs["presets"] = Config::Directories::presets;
root["directories"] = dirs;
}
doc.setObject(root);
file.write(doc.toJson(QJsonDocument::Indented));
file.close();
}

View File

@ -33,4 +33,7 @@ namespace Xybrid::FileOps {
void loadConfig(); void loadConfig();
void saveConfig(); void saveConfig();
void loadUIState();
void saveUIState();
} }

View File

@ -28,6 +28,7 @@ int main(int argc, char *argv[]) {
QFontDatabase::addApplicationFont(":/fonts/Arcon-Rounded-Regular.otf"); QFontDatabase::addApplicationFont(":/fonts/Arcon-Rounded-Regular.otf");
Xybrid::FileOps::loadConfig(); Xybrid::FileOps::loadConfig();
Xybrid::FileOps::loadUIState();
Xybrid::Config::PluginRegistry::init(); Xybrid::Config::PluginRegistry::init();
Xybrid::Audio::AudioEngine::init(); Xybrid::Audio::AudioEngine::init();

View File

@ -41,6 +41,7 @@ using Xybrid::MainWindow;
#include "editing/projectcommands.h" #include "editing/projectcommands.h"
#include "editing/patterncommands.h" #include "editing/patterncommands.h"
#include "config/uistate.h"
#include "config/pluginregistry.h" #include "config/pluginregistry.h"
#include "audio/audioengine.h" #include "audio/audioengine.h"
@ -124,6 +125,43 @@ MainWindow::MainWindow(QWidget *parent) :
//ui->menuBar->setStyleSheet("QMenuBar { background: transparent; vertical-align: center; } QMenuBar::item { } QMenuBar::item:!pressed { background: transparent; }"); //ui->menuBar->setStyleSheet("QMenuBar { background: transparent; vertical-align: center; } QMenuBar::item { } QMenuBar::item:!pressed { background: transparent; }");
} }
{ /* Set up recent file entries */ } {
auto fm = ui->menuFile;
auto bfr = ui->actionNew_Window;
for (size_t i = 0; i < UIState::MAX_RECENTS; i++) {
auto ac = new QAction(fm);
ac->setVisible(false);
fm->insertAction(bfr, ac);
recentFileActions.push_back(ac);
QObject::connect(ac, &QAction::triggered, ac, [this, i]() { openRecentProject(i); });
}
fm->insertSeparator(bfr);
// update list every time we show this menu
QObject::connect(fm, &QMenu::aboutToShow, fm, [this]() {
auto ri = UIState::recentFiles.begin();
auto sz = UIState::recentFiles.size();
for (size_t i = 0; i < UIState::MAX_RECENTS; i++) {
auto ac = recentFileActions[i];
if (i<sz) {
QFileInfo fi(*ri);
QString ix = i == 9 ? qs("1&0") : qs("&%1").arg(i+1);
ac->setText(qs("%1 %2").arg(ix, fi.fileName()));
ac->setVisible(true);
ri++;
} else {
ac->setVisible(false);
ac->setText(QString());
}
}
});
}
{ /* Set up pattern list */ } { { /* Set up pattern list */ } {
// model // model
ui->patternList->setModel(new PatternListModel(ui->patternList, this)); ui->patternList->setModel(new PatternListModel(ui->patternList, this));
@ -501,17 +539,30 @@ void MainWindow::menuFileNew() {
void MainWindow::menuFileOpen() { void MainWindow::menuFileOpen() {
if (auto fileName = FileOps::showOpenDialog(this, "Open project...", Config::Directories::projects, FileOps::Filter::project); !fileName.isEmpty()) { if (auto fileName = FileOps::showOpenDialog(this, "Open project...", Config::Directories::projects, FileOps::Filter::project); !fileName.isEmpty()) {
auto np = FileOps::loadProject(fileName); openProject(fileName);
if (!np) {
QMessageBox::critical(this, "Error", "Error loading project");
return;
}
if (audioEngine->playingProject() == project) audioEngine->stop();
project = np;
onNewProjectLoaded();
} }
} }
void MainWindow::openProject(const QString& fileName) {
auto np = FileOps::loadProject(fileName);
if (!np) {
QMessageBox::critical(this, "Error", "Error loading project");
return;
}
if (audioEngine->playingProject() == project) audioEngine->stop();
project = np;
UIState::addRecentFile(fileName);
onNewProjectLoaded();
}
void MainWindow::openRecentProject(size_t idx) {
if (idx > UIState::recentFiles.size()) return;
auto it = UIState::recentFiles.begin();
for (size_t i = 0; i < idx; i++) it++;
openProject(QString(*it)); // need to copy string before opening
}
void MainWindow::menuFileSave() { void MainWindow::menuFileSave() {
if (project->fileName.isEmpty()) menuFileSaveAs(); if (project->fileName.isEmpty()) menuFileSaveAs();
else { else {
@ -528,6 +579,7 @@ void MainWindow::menuFileSaveAs() {
} }
if (auto fileName = FileOps::showSaveAsDialog(this, "Save project as...", saveDir, FileOps::Filter::project, "xyp"); !fileName.isEmpty()) { if (auto fileName = FileOps::showSaveAsDialog(this, "Save project as...", saveDir, FileOps::Filter::project, "xyp"); !fileName.isEmpty()) {
FileOps::saveProject(project, fileName); FileOps::saveProject(project, fileName);
UIState::addRecentFile(fileName);
undoStack->setClean(); undoStack->setClean();
updateTitle(); updateTitle();
} }

View File

@ -30,7 +30,10 @@ namespace Xybrid {
std::shared_ptr<Data::Sample> editingSample; std::shared_ptr<Data::Sample> editingSample;
QUndoStack* undoStack; QUndoStack* undoStack;
std::vector<QAction*> recentFileActions;
void openProject(const QString& fileName);
void openRecentProject(size_t idx);
void onNewProjectLoaded(); void onNewProjectLoaded();
void updatePatternLists(); void updatePatternLists();
bool selectPatternForEditing(Data::Pattern*); bool selectPatternForEditing(Data::Pattern*);