proper multiselect delete
parent
8238a15771
commit
d107547e1b
8
notes
8
notes
|
@ -50,7 +50,7 @@ TODO {
|
|||
|
||||
undo {
|
||||
- single-row pattern edits
|
||||
? multi-select edits
|
||||
- multi-select edits
|
||||
|
||||
- channel move
|
||||
- channel rename
|
||||
|
@ -63,11 +63,13 @@ TODO {
|
|||
- pattern delete (w/ resulting sequence edit)
|
||||
|
||||
- generic sequencer edit
|
||||
|
||||
make selection follow pattern move where applicable
|
||||
}
|
||||
|
||||
# fix how qt5.12 broke header text (removed elide for now)
|
||||
|
||||
/ multiselect editing (at least delete) [ delete partially implemented, pending undo functionality ]
|
||||
- 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.)
|
||||
|
@ -77,6 +79,8 @@ TODO {
|
|||
|
||||
|
||||
at some point {
|
||||
pattern cut+copy+paste
|
||||
|
||||
pattern editor cells can have (dynamic) tool tips; set this up with port names, etc.
|
||||
? de-hardcode the "» " (probably just make it a static const variable somewhere?)
|
||||
make everything relevant check if editing is locked
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
#include "compositecommand.h"
|
||||
|
||||
#include "data/project.h"
|
||||
#include "uisocket.h"
|
||||
#include "mainwindow.h"
|
||||
|
||||
#include "editing/projectcommands.h"
|
||||
#include "editing/patterncommands.h"
|
||||
|
||||
using Xybrid::Data::Project;
|
||||
using Xybrid::Data::Pattern;
|
||||
using namespace Xybrid::Editing;
|
||||
|
||||
CompositeCommand::CompositeCommand(std::shared_ptr<Project> p) {
|
||||
project = p;
|
||||
}
|
||||
|
||||
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 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 children.emplace_back(c);
|
||||
return this;
|
||||
}
|
||||
|
||||
bool CompositeCommand::commit(QString name) {
|
||||
if (!project->socket || !project->socket->undoStack) return cancel();
|
||||
if (!name.isEmpty()) setText(name);
|
||||
project->socket->undoStack->push(this);
|
||||
return true;
|
||||
}
|
||||
bool CompositeCommand::cancel() {
|
||||
delete this;
|
||||
return false;
|
||||
}
|
||||
|
||||
void CompositeCommand::redo() {
|
||||
for (auto i = children.begin(); i != children.end(); i++) (*i)->redo();
|
||||
}
|
||||
|
||||
void CompositeCommand::undo() {
|
||||
for (auto i = children.rbegin(); i != children.rend(); i++) (*i)->undo();
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <QUndoCommand>
|
||||
|
||||
namespace Xybrid::Data {
|
||||
class Project;
|
||||
class Pattern;
|
||||
}
|
||||
|
||||
namespace Xybrid::Editing {
|
||||
class ProjectCommand;
|
||||
class PatternCommand;
|
||||
class CompositeCommand : public QUndoCommand {
|
||||
std::shared_ptr<Data::Project> project;
|
||||
std::vector<std::unique_ptr<QUndoCommand>> children;
|
||||
|
||||
public:
|
||||
CompositeCommand() = default;
|
||||
CompositeCommand(std::shared_ptr<Data::Project>);
|
||||
~CompositeCommand() override = default;
|
||||
|
||||
CompositeCommand* compose(ProjectCommand*);
|
||||
CompositeCommand* compose(PatternCommand*);
|
||||
|
||||
bool commit(QString name = QString());
|
||||
bool cancel();
|
||||
|
||||
void redo() override;
|
||||
void undo() override;
|
||||
};
|
||||
}
|
|
@ -11,7 +11,7 @@ using namespace Xybrid::Editing;
|
|||
|
||||
bool PatternCommand::commit() {
|
||||
if (!pattern->valid()) return false;
|
||||
if (!pattern->project->socket || !pattern->project->socket->undoStack) return false;
|
||||
if (!pattern->project->socket || !pattern->project->socket->undoStack) return cancel();
|
||||
pattern->project->socket->undoStack->push(this);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -5,8 +5,10 @@
|
|||
#include <QUndoCommand>
|
||||
|
||||
namespace Xybrid::Editing {
|
||||
class CompositeCommand;
|
||||
|
||||
class PatternCommand : public QUndoCommand {
|
||||
//
|
||||
friend class CompositeCommand;
|
||||
protected:
|
||||
std::shared_ptr<Data::Pattern> pattern;
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ using Xybrid::Data::Pattern;
|
|||
using namespace Xybrid::Editing;
|
||||
|
||||
bool ProjectCommand::commit() {
|
||||
if (!project->socket || !project->socket->undoStack) return false;
|
||||
if (!project->socket || !project->socket->undoStack) return cancel();
|
||||
project->socket->undoStack->push(this);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -5,8 +5,10 @@
|
|||
#include <QUndoCommand>
|
||||
|
||||
namespace Xybrid::Editing {
|
||||
class CompositeCommand;
|
||||
|
||||
class ProjectCommand : public QUndoCommand {
|
||||
//
|
||||
friend class CompositeCommand;
|
||||
protected:
|
||||
std::shared_ptr<Data::Project> project;
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ using Xybrid::UI::PatternEditorView;
|
|||
#include "ui/patterneditormodel.h"
|
||||
using Xybrid::UI::PatternEditorModel;
|
||||
|
||||
#include "editing/compositecommand.h"
|
||||
#include "editing/patterncommands.h"
|
||||
using namespace Xybrid::Editing;
|
||||
|
||||
|
@ -36,6 +37,33 @@ namespace {
|
|||
if (static_cast<int>(val) == -1) val = 0;
|
||||
val = static_cast<T>((static_cast<size_t>(val) & 15) * 16 + (hex & 15));
|
||||
}
|
||||
|
||||
struct SelectionBounds {
|
||||
int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
|
||||
SelectionBounds(const QModelIndexList& sel) {
|
||||
x1 = std::numeric_limits<int>::max();
|
||||
y1 = std::numeric_limits<int>::max();
|
||||
for (auto s : sel) {
|
||||
x1 = std::min(x1, s.column());
|
||||
y1 = std::min(y1, s.row());
|
||||
x2 = std::max(x2, s.column());
|
||||
y2 = std::max(y2, s.row());
|
||||
}
|
||||
}
|
||||
|
||||
[[maybe_unused]] bool portSelected(int c) {
|
||||
int cx = c * PatternEditorModel::colsPerChannel;
|
||||
return (cx >= x1 && cx <= x2);
|
||||
}
|
||||
[[maybe_unused]] bool noteSelected(int c) {
|
||||
int cx = (c * PatternEditorModel::colsPerChannel) + 1;
|
||||
return (cx >= x1 && cx <= x2);
|
||||
}
|
||||
[[maybe_unused]] bool paramSelected(int c, int p) {
|
||||
int cx = (c * PatternEditorModel::colsPerChannel) + 2 + (p*2);
|
||||
return (cx+1 >= x1 && cx <= x2);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void PatternEditorItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
|
||||
|
@ -114,37 +142,26 @@ bool PatternEditorItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *m
|
|||
} else {
|
||||
if (sel.size() > 1) {
|
||||
if (k == Qt::Key_Delete) {
|
||||
int x1 = std::numeric_limits<int>::max(), y1 = std::numeric_limits<int>::max(), x2 = 0, y2 = 0;
|
||||
for (auto s : sel) {
|
||||
x1 = std::min(x1, s.column());
|
||||
y1 = std::min(y1, s.row());
|
||||
x2 = std::max(x2, s.column());
|
||||
y2 = std::max(y2, s.row());
|
||||
}
|
||||
int chMin = (x1 - (x1 % PatternEditorModel::colsPerChannel)) / PatternEditorModel::colsPerChannel;
|
||||
int chMax = (x2 - (x2 % PatternEditorModel::colsPerChannel)) / PatternEditorModel::colsPerChannel;
|
||||
//qDebug() << "channels " << chMin << " to " << chMax << ", rows " << y1 << " to " << y2;
|
||||
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;
|
||||
|
||||
// TODO: make this only delete the relevant columns
|
||||
for (int c = chMin; c <= chMax; c++) {
|
||||
for (int r = y1; r <= y2; r++) {
|
||||
p->rowAt(c, r).port = -1;
|
||||
p->rowAt(c, r).note = -1;
|
||||
p->rowAt(c, r).params.reset();
|
||||
for (int r = s.y1; r <= s.y2; r++) {
|
||||
auto* dc = new PatternDeltaCommand(p, c, r);
|
||||
if (s.portSelected(c)) dc->row.port = -1;
|
||||
if (s.noteSelected(c)) dc->row.note = -1;
|
||||
for (int i = static_cast<int>(dc->row.numParams()) - 1; i >= 0; i--) {
|
||||
if (s.paramSelected(c, i)) dc->row.removeParam(static_cast<size_t>(i));
|
||||
}
|
||||
cc->compose(dc);
|
||||
}
|
||||
}
|
||||
m->refresh();
|
||||
|
||||
|
||||
/*for (auto i = sel.rbegin(); i != sel.rend(); ++i) {
|
||||
int cc = index.column() % PatternEditorModel::colsPerChannel;
|
||||
int ch = (index.column() - cc) / PatternEditorModel::colsPerChannel;
|
||||
auto& row = p->rowAt(ch, index.row());
|
||||
//editorEvent(event, model, option, *i);
|
||||
}
|
||||
for (auto s : sel) sm->select(s, QItemSelectionModel::Select);*/
|
||||
delete dc;
|
||||
return true;
|
||||
return cc->commit("delete selection");
|
||||
}
|
||||
|
||||
// for all other commands, reset selection to cursor and defer
|
||||
|
@ -242,7 +259,7 @@ bool PatternEditorItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *m
|
|||
}
|
||||
}
|
||||
// kill command if unused
|
||||
delete dc;
|
||||
dc->cancel();
|
||||
}
|
||||
return false;
|
||||
}//*/
|
||||
|
|
|
@ -40,7 +40,8 @@ SOURCES += \
|
|||
config/colorscheme.cpp \
|
||||
fileops.cpp \
|
||||
editing/patterncommands.cpp \
|
||||
editing/projectcommands.cpp
|
||||
editing/projectcommands.cpp \
|
||||
editing/compositecommand.cpp
|
||||
|
||||
HEADERS += \
|
||||
mainwindow.h \
|
||||
|
@ -57,7 +58,8 @@ HEADERS += \
|
|||
config/colorscheme.h \
|
||||
fileops.h \
|
||||
editing/patterncommands.h \
|
||||
editing/projectcommands.h
|
||||
editing/projectcommands.h \
|
||||
editing/compositecommand.h
|
||||
|
||||
FORMS += \
|
||||
mainwindow.ui
|
||||
|
|
Loading…
Reference in New Issue