recent files tracking and menu items!
parent
d3521416f2
commit
10bcacf8c1
8
notes
8
notes
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
namespace Xybrid::Config {
|
||||
namespace Directories {
|
||||
const extern QString configFile;
|
||||
const extern QString stateFile;
|
||||
|
||||
extern QString projects;
|
||||
extern QString presets;
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -33,4 +33,7 @@ namespace Xybrid::FileOps {
|
|||
void loadConfig();
|
||||
void saveConfig();
|
||||
|
||||
void loadUIState();
|
||||
void saveUIState();
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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*);
|
||||
|
|
Loading…
Reference in New Issue