huge amount of pattern editor work (full editing!)
parent
29b71cfaf8
commit
744e9ddaf5
18
notes
18
notes
|
@ -30,6 +30,24 @@ project data {
|
|||
}
|
||||
}
|
||||
|
||||
TODO {
|
||||
- have params end off on a + or >
|
||||
de-hardcode the "» "
|
||||
- figure out how to autohide empty param columns
|
||||
- give Row some convenience methods for working with params
|
||||
implement (top) header span and return channel names
|
||||
|
||||
- subclass QTableView and integrate with model
|
||||
|
||||
- editing, duh
|
||||
multiselect editing (at least delete)
|
||||
|
||||
at some point {
|
||||
undo
|
||||
de-hardcode pattern editor colors
|
||||
}
|
||||
}
|
||||
|
||||
resampler object {
|
||||
one used internally for each note
|
||||
reference to sample
|
||||
|
|
|
@ -3,9 +3,13 @@ using Xybrid::Data::Pattern;
|
|||
using Row = Pattern::Row;
|
||||
using Channel = Pattern::Channel;
|
||||
|
||||
Row Pattern::fallbackRow(-1337, -1337);
|
||||
std::array<unsigned char, 2> Row::fallbackParam {'.', 0};
|
||||
|
||||
|
||||
Channel::Channel(int numRows, std::string name) : Channel() {
|
||||
this->name = name;
|
||||
this->rows.resize(static_cast<unsigned long>(numRows));
|
||||
this->rows.resize(static_cast<size_t>(numRows));
|
||||
}
|
||||
|
||||
Pattern::Pattern() {
|
||||
|
@ -22,6 +26,14 @@ void Pattern::setLength(int r) {
|
|||
if (r < 1) r = 1;
|
||||
rows = r;
|
||||
for (auto & c : channels) {
|
||||
c.rows.resize(static_cast<unsigned long>(rows));
|
||||
c.rows.resize(static_cast<size_t>(rows));
|
||||
}
|
||||
}
|
||||
|
||||
Row& Pattern::rowAt(int c, int r) {
|
||||
auto nc = this->channels.size();
|
||||
if (nc == 0 || rows == 0) return fallbackRow;
|
||||
c = c % static_cast<int>(nc);
|
||||
r = r % rows;
|
||||
return channels[static_cast<size_t>(c)].rows[static_cast<size_t>(r)];
|
||||
}
|
||||
|
|
|
@ -12,9 +12,52 @@ namespace Xybrid::Data {
|
|||
public:
|
||||
class Row { // with std::unique_ptr<std::vector>, each Row is 12 bytes inline on 64-bit (8 bytes on 32)
|
||||
public:
|
||||
static std::array<unsigned char, 2> fallbackParam;
|
||||
|
||||
int16_t port = -1;
|
||||
int16_t note = -1;
|
||||
std::unique_ptr<std::vector<unsigned char[2]>> params = nullptr; // empty by default
|
||||
std::unique_ptr<std::vector<std::array<unsigned char, 2>>> params = nullptr; // empty by default
|
||||
|
||||
Row() = default;
|
||||
Row(int16_t p, int16_t n) : Row() {
|
||||
port = p;
|
||||
note = n;
|
||||
}
|
||||
|
||||
size_t numParams() const {
|
||||
if (!this->params) return 0;
|
||||
return this->params->size();
|
||||
}
|
||||
|
||||
std::array<unsigned char, 2>& param(size_t index) {
|
||||
if (!this->params || this->params->size() <= index) return fallbackParam;
|
||||
return this->params->at(index);
|
||||
}
|
||||
|
||||
void addParam(char c = '.', unsigned char v = 0) {
|
||||
std::array<unsigned char, 2> p {static_cast<unsigned char>(c), v};
|
||||
if (!this->params) this->params.reset(new std::vector<std::array<unsigned char, 2>>({p}));
|
||||
else params->push_back(p);
|
||||
}
|
||||
|
||||
void insertParam(size_t index, char c = '.', unsigned char v = 0) {
|
||||
std::array<unsigned char, 2> p {static_cast<unsigned char>(c), v};
|
||||
if (!this->params) this->params.reset(new std::vector<std::array<unsigned char, 2>>({p}));
|
||||
else {
|
||||
if (index > this->params->size()) index = this->params->size();
|
||||
params->insert(this->params->begin() + static_cast<ptrdiff_t>(index), p);
|
||||
}
|
||||
}
|
||||
|
||||
void removeParam(size_t index) {
|
||||
if (!this->params || this->params->size() <= index) return; // invalid index
|
||||
if (this->params->size() == 1) { // deallocate vector entirely if empty
|
||||
this->params.reset(nullptr);
|
||||
return;
|
||||
}
|
||||
this->params->erase(this->params->begin() + static_cast<ptrdiff_t>(index));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class Channel {
|
||||
|
@ -25,6 +68,10 @@ namespace Xybrid::Data {
|
|||
Channel() = default;
|
||||
Channel(int numRows, std::string name = "");
|
||||
};
|
||||
private:
|
||||
static Row fallbackRow;
|
||||
|
||||
public:
|
||||
|
||||
int rows = 64;
|
||||
|
||||
|
@ -34,6 +81,8 @@ namespace Xybrid::Data {
|
|||
Pattern(int rows, int channels);
|
||||
|
||||
void setLength(int rows);
|
||||
|
||||
Row& rowAt(int channel, int row);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,10 @@
|
|||
using Xybrid::UI::PatternEditorModel;
|
||||
using Xybrid::UI::PatternEditorItemDelegate;
|
||||
|
||||
namespace {
|
||||
|
||||
}
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) :
|
||||
QMainWindow(parent),
|
||||
ui(new Ui::MainWindow) {
|
||||
|
@ -28,26 +32,20 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
pattern.reset(new Xybrid::Data::Pattern(64, 8));
|
||||
pattern->channels[0].rows[0].port = 15;
|
||||
pattern->channels[0].rows[0].note = 64;
|
||||
pattern->channels[0].rows[0].params.reset(new std::vector<unsigned char[2]>(1));
|
||||
pattern->channels[0].rows[0].params->at(0)[0] = 'v';
|
||||
pattern->channels[0].rows[0].params->at(0)[1] = 255;
|
||||
pattern->channels[0].rows[0].addParam('v', 255);
|
||||
pattern->channels[1].rows[0].port = 1;
|
||||
pattern->channels[1].rows[0].note = 0;
|
||||
|
||||
//t->tabBar()->setTabText(0, QString::number(pattern->rows));
|
||||
|
||||
auto pe = t->findChild<QTableView*>("patternEditor");
|
||||
pe->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
pe->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
pe->setItemDelegate(new PatternEditorItemDelegate());
|
||||
auto pe = t->findChild<Xybrid::UI::PatternEditorView*>("patternEditor");
|
||||
|
||||
//pe->installEventFilter(dlg);
|
||||
//pe->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed);
|
||||
//pe->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
|
||||
pe->setGridStyle(Qt::PenStyle::NoPen);
|
||||
pe->setStyleSheet("QTableView::item { border: 0px; margin: 0px; padding: 0px; }");
|
||||
//pe->setStyleSheet("QTableView::item { border: 0px; margin: 0px; padding: 0px; }");
|
||||
//pe->setStyleSheet("QTableView::item { text-overflow: clip; overflow: hidden; white-space: nowrap; }");
|
||||
pmodel.reset(new PatternEditorModel(nullptr));
|
||||
pmodel->setPattern(&*pattern);
|
||||
pe->setModel(&*pmodel);
|
||||
pe->setPattern(&*pattern);
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow() {
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTableView" name="patternEditor">
|
||||
<widget class="Xybrid::UI::PatternEditorView" name="patternEditor">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Iosevka Term Light</family>
|
||||
|
@ -61,7 +61,7 @@
|
|||
</font>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::AnyKeyPressed|QAbstractItemView::EditKeyPressed</set>
|
||||
<set>QAbstractItemView::AnyKeyPressed</set>
|
||||
</property>
|
||||
<property name="dragDropOverwriteMode">
|
||||
<bool>false</bool>
|
||||
|
@ -145,6 +145,13 @@
|
|||
</action>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>Xybrid::UI::PatternEditorView</class>
|
||||
<extends>QTableView</extends>
|
||||
<header>ui/patterneditorview.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
|
|
@ -1,13 +1,32 @@
|
|||
#include "patterneditoritemdelegate.h"
|
||||
using Xybrid::UI::PatternEditorItemDelegate;
|
||||
|
||||
#include "ui/patterneditorview.h"
|
||||
using Xybrid::UI::PatternEditorView;
|
||||
#include "ui/patterneditormodel.h"
|
||||
using Xybrid::UI::PatternEditorModel;
|
||||
|
||||
#include <QDebug>
|
||||
#include <QPainter>
|
||||
#include <QKeyEvent>
|
||||
|
||||
namespace {
|
||||
constexpr int pad = 2;
|
||||
|
||||
constexpr int pianoKeys[] = {
|
||||
Qt::Key_Q, Qt::Key_W, Qt::Key_E, Qt::Key_R, Qt::Key_T, Qt::Key_Y, Qt::Key_U, Qt::Key_I, Qt::Key_O, Qt::Key_P, Qt::Key_BracketLeft, Qt::Key_BracketRight,
|
||||
Qt::Key_A, Qt::Key_S, Qt::Key_D, Qt::Key_F, Qt::Key_G, Qt::Key_H, Qt::Key_J, Qt::Key_K, Qt::Key_L, Qt::Key_Semicolon, Qt::Key_Apostrophe, Qt::Key_Backslash,
|
||||
};
|
||||
constexpr int numberKeys[] = {
|
||||
Qt::Key_0, Qt::Key_1, Qt::Key_2, Qt::Key_3, Qt::Key_4, Qt::Key_5, Qt::Key_6, Qt::Key_7, Qt::Key_8, Qt::Key_9,
|
||||
Qt::Key_A, Qt::Key_B, Qt::Key_C, Qt::Key_D, Qt::Key_E, Qt::Key_F,
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
[[maybe_unused]] void insertDigit(T& val, size_t hex) { // insert hex digit into a particular value
|
||||
if (static_cast<int>(val) == -1) val = 0;
|
||||
val = static_cast<T>((static_cast<size_t>(val) & 15) * 16 + (hex & 15));
|
||||
}
|
||||
}
|
||||
|
||||
void PatternEditorItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
|
||||
|
@ -19,7 +38,146 @@ void PatternEditorItemDelegate::paint(QPainter *painter, const QStyleOptionViewI
|
|||
if (cc % 2 == 0) align = Qt::AlignVCenter | Qt::AlignRight;
|
||||
else align = Qt::AlignVCenter | Qt::AlignLeft;
|
||||
}
|
||||
if (s == QString("» ")) {
|
||||
align = Qt::AlignVCenter | Qt::AlignLeft;
|
||||
painter->setPen(QColor(127,127,127));
|
||||
} else {
|
||||
if (s == QString(" - ")) painter->setPen(QColor(127,127,127));
|
||||
else if (cc == 0) painter->setPen(QColor(191,191,191));
|
||||
else if (cc == 1) painter->setPen(QColor(255,255,255));
|
||||
else if (cc % 2 == 0) painter->setPen(QColor(191,163,255));
|
||||
else painter->setPen(QColor(191,222,255));
|
||||
}
|
||||
painter->drawText(option.rect, align, s);
|
||||
}
|
||||
|
||||
bool PatternEditorItemDelegate::eventFilter(QObject *obj, QEvent *event) {
|
||||
/*if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
|
||||
auto* e = static_cast<QKeyEvent*>(event);
|
||||
if (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace) {
|
||||
e->accept();
|
||||
return true;
|
||||
}
|
||||
}//*/
|
||||
return QObject::eventFilter(obj, event);
|
||||
}
|
||||
|
||||
bool PatternEditorItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option [[maybe_unused]], const QModelIndex &index) {
|
||||
auto type = event->type();
|
||||
if (type == QEvent::KeyPress || type == QEvent::KeyRelease) {
|
||||
auto k = static_cast<QKeyEvent*>(event)->key(); // grab key
|
||||
auto mod = static_cast<QKeyEvent*>(event)->modifiers();
|
||||
// treat delete/backspace keyup as a press because QAbstractItemView explicitly discards the keydown for some stupid reason
|
||||
if (type == QEvent::KeyRelease && (k == Qt::Key_Delete || k == Qt::Key_Backspace)) type = QEvent::KeyPress;
|
||||
//if (type == QEvent::KeyRelease) return false; // TEMP - early exit here
|
||||
|
||||
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());
|
||||
if (cc == 0) { // port column
|
||||
if (k == Qt::Key_Delete) {
|
||||
row.port = -1;
|
||||
return true;
|
||||
}
|
||||
for (size_t i = 0; i < 16; i++) {
|
||||
if (k == numberKeys[i]) {
|
||||
insertDigit(row.port, i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (cc == 1) { // note column
|
||||
if (k == Qt::Key_Delete) {
|
||||
row.note = -1;
|
||||
return true;
|
||||
}
|
||||
if (k == Qt::Key_Z) { // note off
|
||||
row.note = -2;
|
||||
return true;
|
||||
}
|
||||
if (k == Qt::Key_X) { // hard cut
|
||||
row.note = -3;
|
||||
return true;
|
||||
}
|
||||
for (size_t i = 0; i < (sizeof(pianoKeys) / sizeof(int)); i++) {
|
||||
if (k == pianoKeys[i]) { // piano input
|
||||
row.note = static_cast<int16_t>(i + (12*4) + 3); // C-4
|
||||
if (mod & Qt::Modifier::SHIFT) row.note += 24; // shift for +2 octave
|
||||
if (row.port == -1) { // if no port specified, default to last port used (for a note event) in channel, then (TODO) last port value applied
|
||||
for (int i = index.row() - 1; i >= 0; i--) {
|
||||
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;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
} else { // param column
|
||||
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);
|
||||
// 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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
if (k == Qt::Key_Delete || k == Qt::Key_Insert || k == Qt::Key_Backspace) return false;
|
||||
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;
|
||||
} else {
|
||||
for (size_t i = 0; i < 16; i++) {
|
||||
if (k == numberKeys[i]) {
|
||||
insertDigit(row.param(par)[1], i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // new param; set to key pressed and move forward
|
||||
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!)
|
||||
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 false;
|
||||
}//*/
|
||||
|
||||
QSize PatternEditorItemDelegate::sizeHint(const QStyleOptionViewItem &option [[maybe_unused]], const QModelIndex &index) const {
|
||||
|
@ -28,7 +186,12 @@ QSize PatternEditorItemDelegate::sizeHint(const QStyleOptionViewItem &option [[m
|
|||
std::string s = "FF";
|
||||
if (cc == 1) s = "C#2";
|
||||
else if (cc > 1 && cc % 2 == 0) s = "v";
|
||||
if (cc > 1) fm.boundingRect(QString::fromStdString(s)).size() + QSize(pad,0); // only one padding on params
|
||||
/*if (index.data().toString() == QString("» ")) {
|
||||
return fm.boundingRect(QString("»")).size() + QSize(pad*2,0);
|
||||
}//*/
|
||||
if (cc > 1) {
|
||||
return fm.boundingRect(QString::fromStdString(s)).size() + QSize(pad,0); // only one padding on params
|
||||
}
|
||||
return fm.boundingRect(QString::fromStdString(s)).size() + QSize(pad*2,0);
|
||||
//return QSize(24, 16);
|
||||
}
|
||||
|
|
|
@ -9,8 +9,15 @@ namespace Xybrid::UI {
|
|||
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override;
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
//QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||
//void setEditorData(QWidget *editor, const QModelIndex &index) const override;
|
||||
//void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
|
||||
|
||||
signals:
|
||||
void update(const QModelIndex& index);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
#include "patterneditormodel.h"
|
||||
using Xybrid::UI::PatternEditorModel;
|
||||
using Xybrid::Data::Pattern;
|
||||
#include "ui/patterneditorview.h"
|
||||
using Xybrid::UI::PatternEditorView;
|
||||
|
||||
#include <QDebug>
|
||||
#include <QFontMetrics>
|
||||
|
||||
namespace { // helper functions
|
||||
|
@ -51,22 +54,26 @@ QVariant PatternEditorModel::data(const QModelIndex &index, int role) const {
|
|||
if (role == Qt::DisplayRole) {
|
||||
int cc = index.column() % colsPerChannel;
|
||||
int ch = (index.column() - cc) / colsPerChannel;
|
||||
auto& row = pattern->channels[static_cast<unsigned long>(ch)].rows[static_cast<unsigned long>(index.row())];
|
||||
auto& row = pattern->rowAt(ch, index.row());
|
||||
if (cc == 0) { // port
|
||||
if (row.port >= 0 && row.port < 256) return QString::fromStdString(byteStr(row.port));
|
||||
return QString("-");
|
||||
return QString(" - ");
|
||||
} else if (cc == 1) { // note
|
||||
if (row.note >= 0) return QString::fromStdString(noteStr(row.note));
|
||||
return QString("-");
|
||||
if (row.note == -2) return QString(" ^ "); // note off
|
||||
if (row.note == -3) return QString(" x "); // hard cut
|
||||
return QString(" - ");
|
||||
} else {
|
||||
int cp = ((cc - 2) - (cc % 2)) / 2;
|
||||
size_t cp = static_cast<size_t>(((cc - 2) - (cc % 2)) / 2);
|
||||
//return QString::number((cp));
|
||||
if (cc % 2 == 0) {
|
||||
if (row.params && row.params->size() > cp) return QString::fromStdString(std::string(1,static_cast<char>(row.params->at(cp)[0])));
|
||||
return QString("-");
|
||||
if (row.numParams() > cp) return QString::fromStdString(std::string(1,static_cast<char>(row.params->at(cp)[0])));
|
||||
if (row.numParams() == cp) return QString("» ");
|
||||
return QString("");
|
||||
}
|
||||
if (row.params && row.params->size() > cp) return QString::fromStdString(byteStr(row.params->at(cp)[1]));
|
||||
return QString("--");
|
||||
if (row.numParams() > cp) return QString::fromStdString(byteStr(row.params->at(cp)[1]));
|
||||
return QString("");
|
||||
//return QString("--");
|
||||
}
|
||||
}
|
||||
return QVariant();
|
||||
|
@ -78,12 +85,30 @@ QVariant PatternEditorModel::headerData(int section, Qt::Orientation orientation
|
|||
return QString::number(section);
|
||||
} else if (role == Qt::SizeHintRole) {
|
||||
auto fm = QFontMetrics(QFont("Iosevka Term Light", 9));
|
||||
if (orientation == Qt::Orientation::Vertical) return fm.boundingRect("127").size() + QSize(0, 4);
|
||||
if (orientation == Qt::Orientation::Vertical) return fm.boundingRect("127").size() + QSize(4, 4);
|
||||
return QSize(0, fm.height() + 4);
|
||||
}
|
||||
} else if (role == Qt::TextAlignmentRole) return Qt::AlignCenter;
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void PatternEditorModel::setPattern(Pattern* pattern) {
|
||||
this->pattern = pattern;
|
||||
//updateColumnDisplay();
|
||||
}
|
||||
|
||||
void PatternEditorModel::updateColumnDisplay() {
|
||||
/*static int qi = 0;
|
||||
qDebug() << QString("column display request #%1").arg(qi++);//*/
|
||||
if (pattern == nullptr) return;
|
||||
auto view = static_cast<PatternEditorView*>(parent());
|
||||
for (size_t ch = 0; ch < pattern->channels.size(); ch++) {
|
||||
auto& c = pattern->channels.at(ch);
|
||||
size_t maxParams = 0;
|
||||
for (auto& r : c.rows) {
|
||||
if (r.numParams() > maxParams) maxParams = r.numParams();
|
||||
}
|
||||
for (size_t i = 0; i < PatternEditorModel::colsPerChannel; i++) {
|
||||
view->setColumnHidden(static_cast<int>(ch*PatternEditorModel::colsPerChannel+i), i >= 3+2*maxParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace Xybrid::UI {
|
|||
Xybrid::Data::Pattern* pattern;
|
||||
|
||||
public:
|
||||
static constexpr int paramSoftCap = 3;//16; // maximum number of parameter columns that can be displayed per channel; the rest are hidden
|
||||
static constexpr int paramSoftCap = 16; // maximum number of parameter columns that can be displayed per channel; the rest are hidden
|
||||
static constexpr int colsPerChannel = 2 + (2 * paramSoftCap);
|
||||
|
||||
PatternEditorModel(QObject *parent);
|
||||
|
@ -21,5 +21,10 @@ namespace Xybrid::UI {
|
|||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
|
||||
void setPattern(Xybrid::Data::Pattern* pattern);
|
||||
Xybrid::Data::Pattern& getPattern() {
|
||||
return *pattern;
|
||||
}
|
||||
|
||||
void updateColumnDisplay();
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
#include "patterneditorview.h"
|
||||
using Xybrid::UI::PatternEditorView;
|
||||
#include "ui/patterneditormodel.h"
|
||||
using Xybrid::UI::PatternEditorModel;
|
||||
#include "ui/patterneditoritemdelegate.h"
|
||||
using Xybrid::UI::PatternEditorItemDelegate;
|
||||
|
||||
#include "data/pattern.h"
|
||||
using Xybrid::Data::Pattern;
|
||||
|
||||
#include <QKeyEvent>
|
||||
#include <QDebug>
|
||||
|
||||
#include <QHeaderView>
|
||||
|
||||
namespace {
|
||||
Pattern pt(1, 0);
|
||||
}
|
||||
|
||||
PatternEditorView::PatternEditorView(QWidget *parent) : QTableView(parent) {
|
||||
horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
mdl.reset(new PatternEditorModel(this));
|
||||
del.reset(new PatternEditorItemDelegate(this));
|
||||
setItemDelegate(&*del);
|
||||
mdl->setPattern(&pt);
|
||||
setModel(&*mdl);
|
||||
}
|
||||
|
||||
PatternEditorView::~PatternEditorView() {
|
||||
//
|
||||
}
|
||||
|
||||
void PatternEditorView::keyPressEvent(QKeyEvent *event) {
|
||||
if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Insert) {
|
||||
if (!edit(currentIndex(), AnyKeyPressed, event)) {
|
||||
event->ignore();
|
||||
return;
|
||||
}
|
||||
}
|
||||
QAbstractItemView::keyPressEvent(event);
|
||||
}
|
||||
|
||||
void PatternEditorView::setPattern(Xybrid::Data::Pattern *pattern) {
|
||||
mdl->setPattern(pattern);
|
||||
colUpdateNeeded = true;
|
||||
this->viewport()->update();
|
||||
}
|
||||
|
||||
void PatternEditorView::updateGeometries() {
|
||||
if (mdl && colUpdateNeeded && horizontalHeader()->count() > 0) {
|
||||
mdl->updateColumnDisplay();
|
||||
colUpdateNeeded = false;
|
||||
}
|
||||
this->QTableView::updateGeometries();
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QTableView>
|
||||
|
||||
#include "data/pattern.h"
|
||||
/*#include "ui/patterneditormodel.h"
|
||||
#include "ui/patterneditoritemdelegate.h"*/
|
||||
|
||||
namespace Xybrid::UI {
|
||||
class PatternEditorModel;
|
||||
class PatternEditorItemDelegate;
|
||||
class PatternEditorView : public QTableView {
|
||||
Q_OBJECT
|
||||
|
||||
std::unique_ptr<PatternEditorModel> mdl;
|
||||
std::unique_ptr<PatternEditorItemDelegate> del;
|
||||
|
||||
bool colUpdateNeeded = false;
|
||||
|
||||
public:
|
||||
PatternEditorView(QWidget* parent = nullptr);
|
||||
~PatternEditorView() override;
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
|
||||
void setPattern(Xybrid::Data::Pattern* pattern);
|
||||
|
||||
void updateGeometries() override;
|
||||
|
||||
void keyboardSearch(const QString&) override {} // disable accidental search
|
||||
|
||||
};
|
||||
}
|
|
@ -31,13 +31,15 @@ SOURCES += \
|
|||
mainwindow.cpp \
|
||||
data/pattern.cpp \
|
||||
ui/patterneditormodel.cpp \
|
||||
ui/patterneditoritemdelegate.cpp
|
||||
ui/patterneditoritemdelegate.cpp \
|
||||
ui/patterneditorview.cpp
|
||||
|
||||
HEADERS += \
|
||||
mainwindow.h \
|
||||
data/pattern.h \
|
||||
ui/patterneditormodel.h \
|
||||
ui/patterneditoritemdelegate.h
|
||||
ui/patterneditoritemdelegate.h \
|
||||
ui/patterneditorview.h
|
||||
|
||||
FORMS += \
|
||||
mainwindow.ui
|
||||
|
|
Loading…
Reference in New Issue