pattern strut, multiselect performance fix (only one UI update, thanks!)

portability/boost
zetaPRIME 2018-12-08 07:01:45 -05:00
parent d107547e1b
commit 12a563afc4
10 changed files with 83 additions and 46 deletions

31
notes
View File

@ -45,33 +45,18 @@ project data {
TODO {
immediate frontburner {
- implement saving (yay, QCbor*)
- save empty space as a number of skipped rows (make sure to have something keeping a row count!)
undo {
- single-row pattern edits
- multi-select edits
- channel move
- channel rename
- channel add
- channel delete
- pattern move
- pattern rename
- pattern add (w/ duplication and sequence insert, as single index)
- pattern delete (w/ resulting sequence edit)
- generic sequencer edit
make selection follow pattern move where applicable
group 1 {
- make selection follow pattern move where applicable
- strut command in pattern editor (mostly selection agnostic)
}
group 2 {
skeleton audio engine
skeleton plugin registry
skeleton graph and node
}
# fix how qt5.12 broke header text (removed elide for now)
- multiselect editing (at least delete) [ delete done ]
pattern editing - spacebar for strut (param placeholder)
add metadata and pattern properties (artist, song title, project bpm; pattern name, length etc.)
}

View File

@ -7,6 +7,8 @@
#include "editing/projectcommands.h"
#include "editing/patterncommands.h"
#include <QDebug>
using Xybrid::Data::Project;
using Xybrid::Data::Pattern;
using namespace Xybrid::Editing;
@ -15,25 +17,35 @@ CompositeCommand::CompositeCommand(std::shared_ptr<Project> p) {
project = p;
}
CompositeCommand* CompositeCommand::reserve(int count) {
if (count <= 0) return this;
children.reserve(static_cast<size_t>(count));
return this;
}
CompositeCommand* CompositeCommand::compose(ProjectCommand* c) {
if (!project) {
project = c->project;
} else if (c->project != project) return this; // fail
if (!children.empty() && children.back()->mergeWith(c)) delete c;
} else if (c->project != project && !c->cancel()) return this; // fail
c->composed = true;
if (!children.empty() && children.back()->mergeWith(c)) c->cancel();
else children.emplace_back(c);
return this;
}
CompositeCommand* CompositeCommand::compose(PatternCommand* c) {
if (!project) {
project = c->pattern->project->socket->window->getProject();
} else if (c->pattern->project != project.get()) return this; // fail
if (!children.empty() && children.back()->mergeWith(c)) delete c;
} else if (c->pattern->project != project.get() && !c->cancel()) return this; // fail
if (!pattern) pattern = c->pattern;
c->composed = true;
if (!children.empty() && children.back()->mergeWith(c)) c->cancel();
else children.emplace_back(c);
return this;
}
bool CompositeCommand::commit(QString name) {
if (!project->socket || !project->socket->undoStack) return cancel();
if (!project || !project->socket || !project->socket->undoStack) return cancel();
if (children.empty()) return cancel(); // no children? no action
if (!name.isEmpty()) setText(name);
project->socket->undoStack->push(this);
return true;
@ -45,10 +57,12 @@ bool CompositeCommand::cancel() {
void CompositeCommand::redo() {
for (auto i = children.begin(); i != children.end(); i++) (*i)->redo();
if (pattern) emit project->socket->patternUpdated(pattern.get());
}
void CompositeCommand::undo() {
for (auto i = children.rbegin(); i != children.rend(); i++) (*i)->undo();
if (pattern) emit project->socket->patternUpdated(pattern.get());
}

View File

@ -15,6 +15,7 @@ namespace Xybrid::Editing {
class PatternCommand;
class CompositeCommand : public QUndoCommand {
std::shared_ptr<Data::Project> project;
std::shared_ptr<Data::Pattern> pattern;
std::vector<std::unique_ptr<QUndoCommand>> children;
public:
@ -22,6 +23,8 @@ namespace Xybrid::Editing {
CompositeCommand(std::shared_ptr<Data::Project>);
~CompositeCommand() override = default;
CompositeCommand* reserve(int);
CompositeCommand* compose(ProjectCommand*);
CompositeCommand* compose(PatternCommand*);

View File

@ -41,12 +41,12 @@ bool PatternDeltaCommand::mergeWith(const QUndoCommand* o_) {
void PatternDeltaCommand::redo() {
pattern->rowAt(ch, rw) = row;
emit pattern->project->socket->rowUpdated(pattern.get(), ch, rw);
if (!composed) emit pattern->project->socket->rowUpdated(pattern.get(), ch, rw);
}
void PatternDeltaCommand::undo() {
pattern->rowAt(ch, rw) = oldRow;
emit pattern->project->socket->rowUpdated(pattern.get(), ch, rw);
if (!composed) emit pattern->project->socket->rowUpdated(pattern.get(), ch, rw);
}
PatternRenameCommand::PatternRenameCommand(const std::shared_ptr<Pattern>& pattern, const std::string &to) {

View File

@ -11,6 +11,7 @@ namespace Xybrid::Editing {
friend class CompositeCommand;
protected:
std::shared_ptr<Data::Pattern> pattern;
bool composed = false;
public:
bool commit();

View File

@ -66,19 +66,23 @@ bool ProjectPatternMoveCommand::mergeWith(const QUndoCommand* o_) {
}
void ProjectPatternMoveCommand::redo() {
bool move = project->socket->window->patternSelection() == from && project->socket->window->sequenceSelection() == -1;
auto p = project->patterns[static_cast<size_t>(from)];
project->patterns.erase(project->patterns.begin() + from);
project->patterns.insert(project->patterns.begin() + to, p);
project->updatePatternIndices();
emit project->socket->updatePatternLists();
if (move) project->socket->window->patternSelection(to);
}
void ProjectPatternMoveCommand::undo() {
bool move = project->socket->window->patternSelection() == to && project->socket->window->sequenceSelection() == -1;
auto p = project->patterns[static_cast<size_t>(to)];
project->patterns.erase(project->patterns.begin() + to);
project->patterns.insert(project->patterns.begin() + from, p);
project->updatePatternIndices();
emit project->socket->updatePatternLists();
if (move) project->socket->window->patternSelection(from);
}
ProjectPatternAddCommand::ProjectPatternAddCommand(const std::shared_ptr<Project>& project, int at, int atSeq, const std::shared_ptr<Pattern>& copyOf) {

View File

@ -11,6 +11,7 @@ namespace Xybrid::Editing {
friend class CompositeCommand;
protected:
std::shared_ptr<Data::Project> project;
bool composed = false;
public:
bool commit();

View File

@ -270,14 +270,14 @@ void MainWindow::onNewProjectLoaded() {
undoStack->clear();
project->socket = socket.get();
Pattern* pt = project->patterns.front().get();
for (auto* p : project->sequence) { // find first non-spacer in sequence, else fall back to first pattern numerically
if (p == nullptr) continue;
pt = p;
updatePatternLists();
patternSelection(0);
sequenceSelection(-1);
for (size_t i = 0; i < project->sequence.size(); i++) { // find first non-spacer in sequence, else fall back to first pattern numerically
if (project->sequence[i] == nullptr) continue;
sequenceSelection(static_cast<int>(i));
break;
}
updatePatternLists();
selectPatternForEditing(pt);
updateTitle();
}

View File

@ -40,6 +40,7 @@ namespace {
struct SelectionBounds {
int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
int ch1, ch2;
SelectionBounds(const QModelIndexList& sel) {
x1 = std::numeric_limits<int>::max();
y1 = std::numeric_limits<int>::max();
@ -49,6 +50,8 @@ namespace {
x2 = std::max(x2, s.column());
y2 = std::max(y2, s.row());
}
ch1 = (x1 - (x1 % PatternEditorModel::colsPerChannel)) / PatternEditorModel::colsPerChannel;
ch2 = (x2 - (x2 % PatternEditorModel::colsPerChannel)) / PatternEditorModel::colsPerChannel;
}
[[maybe_unused]] bool portSelected(int c) {
@ -63,6 +66,9 @@ namespace {
int cx = (c * PatternEditorModel::colsPerChannel) + 2 + (p*2);
return (cx+1 >= x1 && cx <= x2);
}
[[maybe_unused]] int maxParamSelected(int c) {
return std::min((x2 - (c * PatternEditorModel::colsPerChannel) - 2) / 2, PatternEditorModel::paramSoftCap);
}
};
}
@ -97,7 +103,7 @@ void PatternEditorItemDelegate::paint(QPainter *painter, const QStyleOptionViewI
align = Qt::AlignVCenter | Qt::AlignLeft;
painter->setPen(ColorScheme::current.patternFgBlank);
} else {
if (s == QString(" - ")) painter->setPen(ColorScheme::current.patternFgBlank);
if (s == QString(" - ") || s == QString("- ")) painter->setPen(ColorScheme::current.patternFgBlank);
else if (cc == 0) painter->setPen(ColorScheme::current.patternFgPort);
else if (cc == 1) painter->setPen(ColorScheme::current.patternFgNote);
else if (cc % 2 == 0) painter->setPen(ColorScheme::current.patternFgParamCmd);
@ -134,22 +140,43 @@ bool PatternEditorItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *m
auto* sm = static_cast<PatternEditorView*>(parent())->selectionModel();
auto sel = sm->selectedIndexes();
bool multi = sel.size() > 1;
if (mod & Qt::Modifier::CTRL) {
} else if (mod & Qt::Modifier::ALT) {
} else {
if (sel.size() > 1) {
if (k == Qt::Key_Space) {
if (mod & Qt::Modifier::SHIFT) return dc->cancel(); // TODO: once playback is a thing, shift+space to preview row?
dc->cancel();
SelectionBounds s(sel);
auto* cc = (new CompositeCommand())->reserve((1 + s.y2 - s.y1) * (1 + s.ch2 - s.ch1));
for (int c = s.ch1; c <= s.ch2; c++) {
size_t mpc = 0; // max params in channel
for (int r = 0; r < p->rows; r++) mpc = std::max(mpc, p->rowAt(c, r).numParams());
auto mp = s.maxParamSelected(c);
if (mp < 0) continue;
auto mps = std::min(mpc, static_cast<size_t>(mp));
for (int r = s.y1; r <= s.y2; r++) {
if (multi && mps <= p->rowAt(c, r).numParams()) continue;
auto* dc = new PatternDeltaCommand(p, c, r);
for (size_t i = dc->row.numParams(); i < mps; i++) dc->row.addParam(' ');
if (!multi) dc->row.param(mps) = {' ', 0};
cc->compose(dc);
}
}
return cc->commit("pattern strut");
}
if (multi) {
if (k == Qt::Key_Delete) {
dc->cancel();
auto* cc = new CompositeCommand();
SelectionBounds s(sel);
int chMin = (s.x1 - (s.x1 % PatternEditorModel::colsPerChannel)) / PatternEditorModel::colsPerChannel;
int chMax = (s.x2 - (s.x2 % PatternEditorModel::colsPerChannel)) / PatternEditorModel::colsPerChannel;
auto* cc = (new CompositeCommand())->reserve((1 + s.y2 - s.y1) * (1 + s.ch2 - s.ch1));
for (int c = chMin; c <= chMax; c++) {
for (int c = s.ch1; c <= s.ch2; c++) {
for (int r = s.y1; r <= s.y2; r++) {
auto* dc = new PatternDeltaCommand(p, c, r);
if (s.portSelected(c)) dc->row.port = -1;
@ -216,7 +243,7 @@ bool PatternEditorItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *m
size_t par = static_cast<size_t>((cc - (cc % 2)) / 2 - 1);
if (k == Qt::Key_Insert) { // insert from within any place in the param columns
if (row.numParams() >= PatternEditorModel::paramSoftCap) return false; // no overruns
row.insertParam(par);
row.insertParam(par, ' ');
auto view = static_cast<PatternEditorView*>(parent());
size_t cpar = row.numParams() - 1;
if (cpar > par) cpar = par;
@ -251,7 +278,6 @@ bool PatternEditorItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *m
if (k == Qt::Key_Delete || k == Qt::Key_Insert || k == Qt::Key_Backspace) return false;
char chr = static_cast<QKeyEvent*>(event)->text().toUtf8()[0];
row.addParam(chr);
// update whole row (phew!)
auto view = static_cast<PatternEditorView*>(parent());
view->setCurrentIndex(index.siblingAtColumn(ch * PatternEditorModel::colsPerChannel + static_cast<int>(row.numParams()) * 2 + 1));
return dc->commit();

View File

@ -96,7 +96,10 @@ QVariant PatternEditorModel::data(const QModelIndex &index, int role) const {
if (row.numParams() == cp) return QString("» ");
return QString("");
}
if (row.numParams() > cp) return QString::fromStdString(byteStr(row.params->at(cp)[1]));
if (row.numParams() > cp) {
if (row.params->at(cp)[0] == ' ') return QString("- ");
return QString::fromStdString(byteStr(row.params->at(cp)[1]));
}
return QString("");
//return QString("--");
}