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 {
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
separate files for "settings" and "state"
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
distortion effect

View File

@ -3,7 +3,8 @@ using namespace Xybrid::Config;
#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::presets = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation).append("/xybrid/nodes");

View File

@ -5,6 +5,7 @@
namespace Xybrid::Config {
namespace Directories {
const extern QString configFile;
const extern QString stateFile;
extern QString projects;
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
}
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 saveConfig();
void loadUIState();
void saveUIState();
}

View File

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

View File

@ -41,6 +41,7 @@ using Xybrid::MainWindow;
#include "editing/projectcommands.h"
#include "editing/patterncommands.h"
#include "config/uistate.h"
#include "config/pluginregistry.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; }");
}
{ /* 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 */ } {
// model
ui->patternList->setModel(new PatternListModel(ui->patternList, this));
@ -501,17 +539,30 @@ void MainWindow::menuFileNew() {
void MainWindow::menuFileOpen() {
if (auto fileName = FileOps::showOpenDialog(this, "Open project...", Config::Directories::projects, FileOps::Filter::project); !fileName.isEmpty()) {
auto np = FileOps::loadProject(fileName);
if (!np) {
QMessageBox::critical(this, "Error", "Error loading project");
return;
}
if (audioEngine->playingProject() == project) audioEngine->stop();
project = np;
onNewProjectLoaded();
openProject(fileName);
}
}
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() {
if (project->fileName.isEmpty()) menuFileSaveAs();
else {
@ -528,6 +579,7 @@ void MainWindow::menuFileSaveAs() {
}
if (auto fileName = FileOps::showSaveAsDialog(this, "Save project as...", saveDir, FileOps::Filter::project, "xyp"); !fileName.isEmpty()) {
FileOps::saveProject(project, fileName);
UIState::addRecentFile(fileName);
undoStack->setClean();
updateTitle();
}

View File

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