UI stuff, undo/redo for pattern and channel editing

portability/boost
zetaPRIME 2018-12-06 14:27:22 -05:00
parent 4105e38404
commit c297ac40ba
14 changed files with 381 additions and 67 deletions

24
notes
View File

@ -49,7 +49,21 @@ TODO {
- 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 rename
- channel move
- channel add
- channel delete
pattern rename
pattern move
pattern add
pattern delete
(just have sequencer state stuff up there)
generic sequencer edit
}
# fix how qt5.12 broke header text (removed elide for now)
@ -67,15 +81,11 @@ TODO {
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
make the save routine displace the old file and write a new one
}
}
saving and loading {
msgpack object
probably have a field of raw data for (some) node saves, and keep it resident in case of plugin crashes
}
resampler object {
one used internally for each note
reference to sample

View File

@ -1,4 +1,11 @@
msgpack
Xybrid's various save formats are CBOR objects; CBOR was chosen over msgpack due to Qt5.12 having built-in support
probably have a field of raw data for (some) node saves, and keep it resident in case of plugin crashes
extensions {
.xyp - project
.xyg - graph
}
@ -7,21 +14,23 @@ msgpack
project: [
"xybrid:project"
(uint32 version)
(int version)
{
"meta": { "artist": ... "title": ... "comment": ... etc. }
"patterns": [ array of pattern structs ]
"sequence": [ array of pattern numbers, uint32, separator is MAX_VALUE ]
"sequence": [ array of pattern numbers, int, separator is anything negative ]
}
]
pattern: {
"name": "asdf"
"rows": 64 // actually necessary?
"channels": [ {
"name": "asdf"
"rows": [ // probably better name...
[ int16 port, int16 note, n*(uint8 c, uint8 amt) ] // for each row
[ int port, int note, n*(int c, int amt) ] // for each row
// any number of empty rows is expressed as an integer number of rows skipped
// this number is only written if there are any non-empty rows after
]
} ]
} ] // if no channels have their last row filled, an integer representing the
// total number of rows is written as the last entry of the channel array
}

View File

@ -18,6 +18,7 @@ Row& Row::operator=(const Row& o) noexcept {
note = o.note;
// copy-constructor the underlying vector
if (o.params) params.reset(new std::vector<std::array<unsigned char, 2>>(*o.params));
else params.reset();
return *this;
}

View File

@ -0,0 +1,140 @@
#include "patterncommands.h"
#include "uisocket.h"
#include "data/project.h"
#include <QDebug>
#include <QUndoStack>
using Xybrid::Data::Pattern;
using namespace Xybrid::Editing;
bool PatternCommand::commit() {
if (!pattern->valid()) return false;
if (!pattern->project->socket || !pattern->project->socket->undoStack) return false;
pattern->project->socket->undoStack->push(this);
return true;
}
bool PatternCommand::cancel() {
delete this;
return false;
}
PatternDeltaCommand::PatternDeltaCommand(const std::shared_ptr<Pattern>& pattern, int c, int r) {
this->pattern = pattern;
ch = c;
rw = r;
oldRow = pattern->rowAt(c, r);
row = oldRow;
setText("edit row");
}
bool PatternDeltaCommand::mergeWith(const QUndoCommand* o_) {
if (o_->id() != id()) return false;
auto* o = static_cast<const PatternDeltaCommand*>(o_);
if (o->pattern != pattern) return false;
if (o->ch != ch || o->rw != rw) return false;
row = static_cast<const PatternDeltaCommand*>(o)->row;
return true;
}
void PatternDeltaCommand::redo() {
pattern->rowAt(ch, rw) = row;
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);
}
PatternChannelMoveCommand::PatternChannelMoveCommand(const std::shared_ptr<Xybrid::Data::Pattern> &pattern, int from, int to) {
this->pattern = pattern;
this->from = from;
this->to = to;
setText("move channel");
}
bool PatternChannelMoveCommand::mergeWith(const QUndoCommand* o_) {
if (o_->id() != id()) return false;
auto* o = static_cast<const PatternChannelMoveCommand*>(o_);
if (o->pattern != pattern) return false;
if (o->from != to) return false;
to = o->to;
return true;
}
void PatternChannelMoveCommand::redo() {
Pattern::Channel ch = std::move(*(pattern->channels.begin()+from));
pattern->channels.erase(pattern->channels.begin()+from);
pattern->channels.insert(pattern->channels.begin()+to, std::move(ch));
emit pattern->project->socket->patternUpdated(pattern.get());
}
void PatternChannelMoveCommand::undo() {
Pattern::Channel ch = std::move(*(pattern->channels.begin()+to));
pattern->channels.erase(pattern->channels.begin()+to);
pattern->channels.insert(pattern->channels.begin()+from, std::move(ch));
emit pattern->project->socket->patternUpdated(pattern.get());
}
PatternChannelRenameCommand::PatternChannelRenameCommand(const std::shared_ptr<Xybrid::Data::Pattern>& pattern, int channel, const std::string &to) {
this->pattern = pattern;
idx = channel;
from = pattern->channel(idx).name;
this->to = to;
setText("rename channel");
}
bool PatternChannelRenameCommand::mergeWith(const QUndoCommand* o_) {
if (o_->id() != id()) return false;
auto* o = static_cast<const PatternChannelRenameCommand*>(o_);
if (o->pattern != pattern) return false;
if (o->idx != idx) return false;
to = o->to;
return true;
}
void PatternChannelRenameCommand::redo() {
pattern->channel(idx).name = to;
emit pattern->project->socket->patternUpdated(pattern.get());
}
void PatternChannelRenameCommand::undo() {
pattern->channel(idx).name = from;
emit pattern->project->socket->patternUpdated(pattern.get());
}
PatternChannelAddCommand::PatternChannelAddCommand(const std::shared_ptr<Xybrid::Data::Pattern>& pattern, int channel) {
this->pattern = pattern;
idx = channel;
setText("add channel");
}
void PatternChannelAddCommand::redo() {
pattern->addChannel(idx);
emit pattern->project->socket->patternUpdated(pattern.get());
}
void PatternChannelAddCommand::undo() {
pattern->deleteChannel(idx);
emit pattern->project->socket->patternUpdated(pattern.get());
}
PatternChannelDeleteCommand::PatternChannelDeleteCommand(const std::shared_ptr<Xybrid::Data::Pattern>& pattern, int channel) {
this->pattern = pattern;
idx = channel;
setText("delete channel");
}
void PatternChannelDeleteCommand::redo() {
ch = std::move(*(pattern->channels.begin()+idx));
pattern->channels.erase(pattern->channels.begin()+idx);
emit pattern->project->socket->patternUpdated(pattern.get());
}
void PatternChannelDeleteCommand::undo() {
pattern->channels.insert(pattern->channels.begin()+idx, std::move(ch));
emit pattern->project->socket->patternUpdated(pattern.get());
}

View File

@ -0,0 +1,91 @@
#pragma once
#include "data/pattern.h"
#include <QUndoCommand>
namespace Xybrid::Editing {
class PatternCommand : public QUndoCommand {
//
protected:
std::shared_ptr<Data::Pattern> pattern;
public:
bool commit();
bool cancel();
};
class PatternDeltaCommand : public PatternCommand {
int ch, rw;
Data::Pattern::Row oldRow;
public:
Data::Pattern::Row row;
PatternDeltaCommand(const std::shared_ptr<Data::Pattern>& pattern, int channel, int row);
~PatternDeltaCommand() override = default;
int id() const override {
return 2000;
}
bool mergeWith(const QUndoCommand*) override;
void redo() override;
void undo() override;
};
class PatternChannelMoveCommand : public PatternCommand {
int from, to;
public:
PatternChannelMoveCommand(const std::shared_ptr<Data::Pattern>& pattern, int from, int to);
~PatternChannelMoveCommand() override = default;
int id() const override {
return 2100;
}
bool mergeWith(const QUndoCommand*) override;
void redo() override;
void undo() override;
};
class PatternChannelRenameCommand : public PatternCommand {
int idx;
std::string from, to;
public:
PatternChannelRenameCommand(const std::shared_ptr<Data::Pattern>& pattern, int channel, const std::string& to);
~PatternChannelRenameCommand() override = default;
int id() const override {
return 2101;
}
bool mergeWith(const QUndoCommand*) override;
void redo() override;
void undo() override;
};
class PatternChannelAddCommand : public PatternCommand {
int idx;
public:
PatternChannelAddCommand(const std::shared_ptr<Data::Pattern>& pattern, int channel);
~PatternChannelAddCommand() override = default;
void redo() override;
void undo() override;
};
class PatternChannelDeleteCommand : public PatternCommand {
int idx;
Data::Pattern::Channel ch;
public:
PatternChannelDeleteCommand(const std::shared_ptr<Data::Pattern>& pattern, int channel);
~PatternChannelDeleteCommand() override = default;
void redo() override;
void undo() override;
};
}

View File

@ -0,0 +1,8 @@
#pragma once
namespace Xybrid::Editing {
class PatternLens {
public:
//
};
}

View File

@ -1,5 +1,7 @@
#include "fileops.h"
#include "uisocket.h"
#include <QDebug>
#include <QFile>
@ -8,6 +10,8 @@
#include <QCborStreamReader>
#include <QCborStreamWriter>
#include <QUndoStack>
using Xybrid::Data::Project;
using Xybrid::Data::Pattern;
@ -99,6 +103,8 @@ bool Xybrid::FileOps::saveProject(std::shared_ptr<Project> project, QString file
root.toCborValue().toCbor(w);
file.close();
if (project->socket && project->socket->undoStack) project->socket->undoStack->setClean();
return true;
}

View File

@ -9,6 +9,7 @@ using Xybrid::MainWindow;
#include <QFileDialog>
#include <QInputDialog>
#include <QMessageBox>
#include <QUndoStack>
#include "util/strings.h"
#include "fileops.h"
@ -26,7 +27,7 @@ using Xybrid::UI::PatternEditorModel;
using Xybrid::UI::PatternEditorItemDelegate;
namespace {
constexpr const auto projectFilter = u8"Xybrid project (*.xyp)\nAll files (*)";
}
MainWindow::MainWindow(QWidget *parent) :
@ -34,8 +35,21 @@ MainWindow::MainWindow(QWidget *parent) :
ui(new Ui::MainWindow) {
ui->setupUi(this);
auto t = ui->tabWidget;
undoStack = new QUndoStack(this);
//undoStack->setUndoLimit(256);
connect(undoStack, &QUndoStack::cleanChanged, [this](bool) {
updateTitle();
});
auto* undoAction = undoStack->createUndoAction(this, tr("&Undo"));
undoAction->setShortcuts(QKeySequence::Undo);
ui->menuEdit->addAction(undoAction);
auto* redoAction = undoStack->createRedoAction(this, tr("&Redo"));
redoAction->setShortcuts(QKeySequence::Redo);
ui->menuEdit->addAction(redoAction);
auto t = ui->tabWidget;
t->setCornerWidget(ui->menuBar);
t->setCornerWidget(ui->label, Qt::TopLeftCorner);
auto mb = ui->menuBar;
@ -190,7 +204,19 @@ MainWindow::MainWindow(QWidget *parent) :
// Set up signaling from project to UI
socket.reset(new UISocket());
socket->undoStack = undoStack;
connect(socket.get(), &UISocket::updatePatternLists, this, &MainWindow::updatePatternLists);
connect(socket.get(), &UISocket::patternUpdated, [this](Pattern* p) {
if (editingPattern.get() != p) return;
ui->patternEditor->refresh();
});
connect(socket.get(), &UISocket::rowUpdated, [this](Pattern* p, int ch, int r) {
if (editingPattern.get() != p) return;
const auto cpc = PatternEditorModel::colsPerChannel;
auto ind = ui->patternEditor->model()->index(r, ch * cpc);
emit ui->patternEditor->model()->dataChanged(ind, ind.siblingAtColumn((ch+1)*cpc-1));
static_cast<PatternEditorModel*>(ui->patternEditor->model())->updateColumnDisplay();
});
// and start with a new project
menuFileNew();
@ -229,7 +255,7 @@ void MainWindow::menuFileNew() {
}
void MainWindow::menuFileOpen() {
auto fileName = QFileDialog::getOpenFileName(this, "Open project...");
auto fileName = QFileDialog::getOpenFileName(this, "Open project...", QString(), projectFilter);
if (fileName.isEmpty()) return; // canceled
auto np = FileOps::loadProject(fileName);
if (!np) {
@ -248,12 +274,14 @@ void MainWindow::menuFileSave() {
}
void MainWindow::menuFileSaveAs() {
auto fileName = QFileDialog::getSaveFileName(this, "Save project as...");
auto fileName = QFileDialog::getSaveFileName(this, "Save project as...", QString(), projectFilter);
if (fileName.isEmpty()) return; // canceled
FileOps::saveProject(project, fileName);
}
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
@ -263,6 +291,8 @@ void MainWindow::onNewProjectLoaded() {
}
updatePatternLists();
selectPatternForEditing(pt);
updateTitle();
}
void MainWindow::updatePatternLists() {
@ -272,6 +302,15 @@ void MainWindow::updatePatternLists() {
selectPatternForEditing(project->patterns[std::min(editingPattern->index, project->patterns.size() - 1)].get());
}
void MainWindow::updateTitle() {
QString title = u8"Xybrid - %1%2";
if (project->fileName.isEmpty()) title = title.arg("(new project)");
else title = title.arg(QFileInfo(project->fileName).baseName());
if (undoStack->isClean()) title = title.arg("");
else title = title.arg("*");
this->setWindowTitle(title);
}
bool MainWindow::selectPatternForEditing(Pattern* pattern) {
if (!pattern || pattern == editingPattern.get()) return false; // no u
if (pattern->project != project.get()) return false; // wrong project

View File

@ -6,6 +6,8 @@
#include "data/project.h"
#include "ui/patterneditormodel.h"
class QUndoStack;
namespace Ui {
class MainWindow;
}
@ -24,10 +26,14 @@ namespace Xybrid {
std::shared_ptr<Data::Project> project;
std::shared_ptr<Data::Pattern> editingPattern; // temporary pattern for testing the editor
QUndoStack* undoStack;
void onNewProjectLoaded();
void updatePatternLists();
bool selectPatternForEditing(Data::Pattern*);
void updateTitle();
public:
Data::Project* getProject() const {
return project.get();

View File

@ -9,6 +9,9 @@ using Xybrid::UI::PatternEditorView;
#include "ui/patterneditormodel.h"
using Xybrid::UI::PatternEditorModel;
#include "editing/patterncommands.h"
using namespace Xybrid::Editing;
#include <limits>
#include <QDebug>
@ -90,18 +93,20 @@ bool PatternEditorItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *m
if (index.data().isNull()) return false; // no channels?
auto type = event->type();
if (type == QEvent::KeyPress) {
if (static_cast<QKeyEvent*>(event)->isAutoRepeat()) return false; // reject autorepeat
auto k = static_cast<QKeyEvent*>(event)->key(); // grab key
auto mod = static_cast<QKeyEvent*>(event)->modifiers();
auto m = static_cast<PatternEditorModel*>(model); // we know this will always be pattern editor
auto p = m->getPattern();
int cc = index.column() % PatternEditorModel::colsPerChannel;
int ch = (index.column() - cc) / PatternEditorModel::colsPerChannel;
auto& row = p->rowAt(ch, index.row());
auto* dc = new PatternDeltaCommand(p, ch, index.row());
auto& row = dc->row;//p->rowAt(ch, index.row());
auto* sm = static_cast<PatternEditorView*>(parent())->selectionModel();
auto sel = sm->selectedIndexes();
if (static_cast<QKeyEvent*>(event)->isAutoRepeat()) return false; // reject autorepeat
if (mod & Qt::Modifier::CTRL) {
} else if (mod & Qt::Modifier::ALT) {
@ -138,6 +143,7 @@ bool PatternEditorItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *m
//editorEvent(event, model, option, *i);
}
for (auto s : sel) sm->select(s, QItemSelectionModel::Select);*/
delete dc;
return true;
}
@ -148,26 +154,26 @@ bool PatternEditorItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *m
if (cc == 0) { // port column
if (k == Qt::Key_Delete) {
row.port = -1;
return true;
return dc->commit();
}
for (size_t i = 0; i < 16; i++) {
if (k == numberKeys[i]) {
insertDigit(row.port, i);
return true;
return dc->commit();
}
}
} else if (cc == 1) { // note column
if (k == Qt::Key_Delete) {
row.note = -1;
return true;
return dc->commit();
}
if (k == Qt::Key_Equal) { // note off
row.note = -2;
return true;
return dc->commit();
}
if (k == Qt::Key_Plus) { // shift for hard cut; for some reason this is a separate keycode
row.note = -3;
return true;
return dc->commit();
}
for (size_t i = 0; i < (sizeof(pianoKeys) / sizeof(pianoKeys[0])); i++) {
if (k == pianoKeys[i]) { // piano input
@ -178,17 +184,15 @@ bool PatternEditorItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *m
auto& r = p->rowAt(ch, i);
if (r.port >= 0 && r.note != -1) {
row.port = r.port;
auto ind = index.siblingAtColumn(index.column() - 1);
emit model->dataChanged(ind, ind, {Qt::DisplayRole});
break;
}
}
}
return true;
return dc->commit();
} else if (i < 10 && k == numberKeys[i]) { // set octave
if (row.note >= 0) row.note = static_cast<int16_t>((row.note % 12) + 12*i);
static_cast<PatternEditorModel*>(model)->updateColumnDisplay();
return true;
return dc->commit();
}
}
} else { // param column
@ -196,39 +200,33 @@ bool PatternEditorItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *m
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);
// update whole row (phew!)
emit model->dataChanged(index.siblingAtColumn(ch * PatternEditorModel::colsPerChannel), index.siblingAtColumn((ch+1) * PatternEditorModel::colsPerChannel-1), {Qt::DisplayRole});
m->updateColumnDisplay(); // update column autohide
auto view = static_cast<PatternEditorView*>(parent());
size_t cpar = row.numParams() - 1;
if (cpar > par) cpar = par;
view->setCurrentIndex(index.siblingAtColumn(ch * PatternEditorModel::colsPerChannel + static_cast<int>(cpar) * 2 + 2));
return true;
return dc->commit();
}
if (par < row.numParams()) {
if (k == Qt::Key_Delete) { // remove selected parameter
row.removeParam(par);
// update whole row (phew!)
emit model->dataChanged(index.siblingAtColumn(ch * PatternEditorModel::colsPerChannel), index.siblingAtColumn((ch+1) * PatternEditorModel::colsPerChannel-1), {Qt::DisplayRole});
m->updateColumnDisplay(); // update column autohide
if (par >= row.numParams()) { // snap to arrow if beyond
auto view = static_cast<PatternEditorView*>(parent());
view->setCurrentIndex(index.siblingAtColumn(ch * PatternEditorModel::colsPerChannel + static_cast<int>(row.numParams()) * 2 + 2));
}
return true;
return dc->commit();
}
if (k == Qt::Key_Delete || k == Qt::Key_Insert || k == Qt::Key_Backspace) return false;
if (k == Qt::Key_Delete || k == Qt::Key_Insert || k == Qt::Key_Backspace) return dc->cancel();
if (cc % 2 == 0) { // char column; set to key pressed and move forward
char chr = static_cast<QKeyEvent*>(event)->text().toUtf8()[0];
row.param(par)[0] = static_cast<unsigned char>(chr);
auto view = static_cast<PatternEditorView*>(parent());
view->setCurrentIndex(index.siblingAtColumn(index.column()+1));
return true;
return dc->commit();
} else {
for (size_t i = 0; i < 16; i++) {
if (k == numberKeys[i]) {
insertDigit(row.param(par)[1], i);
return true;
return dc->commit();
}
}
}
@ -237,15 +235,14 @@ bool PatternEditorItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *m
char chr = static_cast<QKeyEvent*>(event)->text().toUtf8()[0];
row.addParam(chr);
// update whole row (phew!)
emit model->dataChanged(index.siblingAtColumn(ch * PatternEditorModel::colsPerChannel), index.siblingAtColumn((ch+1) * PatternEditorModel::colsPerChannel-1), {Qt::DisplayRole});
m->updateColumnDisplay(); // update column autohide
auto view = static_cast<PatternEditorView*>(parent());
view->setCurrentIndex(index.siblingAtColumn(ch * PatternEditorModel::colsPerChannel + static_cast<int>(row.numParams()) * 2 + 1));
return true;
return dc->commit();
}
}
}
//
// kill command if unused
delete dc;
}
return false;
}//*/

View File

@ -14,6 +14,9 @@ using Xybrid::UI::ChannelHeaderView;
using Xybrid::Data::Project;
using Xybrid::Data::Pattern;
#include "editing/patterncommands.h"
using namespace Xybrid::Editing;
#include <QKeyEvent>
#include <QDebug>
@ -131,6 +134,10 @@ void PatternEditorView::updateHeader(bool full) {
hdr->setGeometry(bh->x(), bh->y(), bh->width(), bh->height());
hdr->setOffset(bh->offset());
}
void PatternEditorView::refresh() {
mdl->refresh();
}
void PatternEditorView::updateHeaderOffset(int) {
updateHeader(false);
}
@ -140,21 +147,7 @@ void PatternEditorView::headerMoved(int logicalIndex, int oldVisualIndex, int ne
hdr->moveSection(newVisualIndex, logicalIndex); // maintain straight-through order
if (logicalIndex >= hdr->count() - 1) return; // no dragging the endcap :|
int min = std::min(oldVisualIndex, newVisualIndex);
int max = std::max(oldVisualIndex, newVisualIndex);
int nf = 0;
if (newVisualIndex > oldVisualIndex) nf = min + 1;
else nf = max;
auto& chn = mdl->getPattern()->channels;
std::rotate(chn.begin() + min, chn.begin() + nf, chn.begin() + max + 1);
this->dataChanged( // update everything
mdl->index(0, 0, QModelIndex()),
mdl->index(mdl->rowCount() - 1, mdl->columnCount() - 1, QModelIndex())
);
mdl->updateColumnDisplay(); // update geometries and hide flags
// and make sure header follows
emit hdr->model()->headerDataChanged(Qt::Horizontal, 0, static_cast<int>(chn.size()) - 1);
(new PatternChannelMoveCommand(mdl->getPattern(), oldVisualIndex, newVisualIndex))->commit();
}
void PatternEditorView::headerDoubleClicked(int section) {
@ -166,15 +159,13 @@ void PatternEditorView::headerContextMenu(QPoint pt) {
std::shared_ptr<Pattern> p = mdl->getPattern();
QMenu* menu = new QMenu(this);
menu->addAction("Add Channel", [this, idx, p]() {
p->addChannel(idx);
mdl->refresh();
menu->addAction("Add Channel", [/*this,*/ idx, p]() {
(new PatternChannelAddCommand(p, idx))->commit();
});
if (idx < hdr->count() - 1) {
menu->addAction("Delete Channel", [this, idx, p]() {
if (QMessageBox::warning(this, "Are you sure?", QString("Remove channel %1 from pattern %2?").arg(Util::numAndName(idx, p->channel(idx).name)).arg(Util::numAndName(p->index, p->name)), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) return;
p->deleteChannel(idx);
mdl->refresh();
(new PatternChannelDeleteCommand(p, idx))->commit();
});
menu->addAction("Rename Channel", [this, idx, p]() {
if (p != mdl->getPattern()) return; // swapped already
@ -194,6 +185,5 @@ void PatternEditorView::startRenameChannel(int channel) {
auto n = QInputDialog::getText(this, "Rename...", capt, QLineEdit::Normal, QString::fromStdString(c->name), &ok);
if (!ok) return; // canceled
if (p != mdl->getPattern() || c != &p->channel(channel)) return; // abort if this somehow isn't the channel it was before
c->name = n.toStdString(); // and set name
mdl->updateColumnDisplay(); // update sizes and such
(new PatternChannelRenameCommand(p, channel, n.toStdString()))->commit();
}

View File

@ -36,6 +36,8 @@ namespace Xybrid::UI {
void updateHeader(bool full = false);
void refresh();
private:
void updateHeaderOffset(int);
void headerMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex);

View File

@ -2,10 +2,22 @@
#include <QObject>
class QUndoStack;
namespace Xybrid::Data {
class Project;
class Pattern;
}
namespace Xybrid {
class UISocket : public QObject {
Q_OBJECT
public:
QUndoStack* undoStack;
signals:
void updatePatternLists();
void patternUpdated(Data::Pattern* pattern);
void rowUpdated(Data::Pattern* pattern, int channel, int row);
};
}

View File

@ -38,7 +38,8 @@ SOURCES += \
ui/patternsequencermodel.cpp \
ui/patternlistmodel.cpp \
config/colorscheme.cpp \
fileops.cpp
fileops.cpp \
editing/patterncommands.cpp
HEADERS += \
mainwindow.h \
@ -53,7 +54,9 @@ HEADERS += \
ui/patternlistmodel.h \
util/strings.h \
config/colorscheme.h \
fileops.h
fileops.h \
editing/patternlens.h \
editing/patterncommands.h
FORMS += \
mainwindow.ui