#include "patterneditormodel.h" using Xybrid::UI::PatternEditorModel; using Xybrid::UI::PatternEditorHeaderProxyModel; using Xybrid::Data::Pattern; #include "ui/patterneditorview.h" using Xybrid::UI::PatternEditorView; #include "util/strings.h" #include #include #include #include #include namespace { // helper functions int cellWidthBase = -1; int cellWidthParam; int cellWidthParamTab; int headerHeight; QSize hsz, hhsz; constexpr char hexmap[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; constexpr char notemap[] = "C-C#D-D#E-F-F#G-G#A-A#B-"; QString hexStr(unsigned char *data, int len) { QString s(len * 2, ' '); for (int i = 0; i < len; ++i) { s[2 * i] = hexmap[(data[i] & 0xF0) >> 4]; s[2 * i + 1] = hexmap[data[i] & 0x0F]; } return s; } QString byteStr(int t) { unsigned char c = static_cast(t & 255); return hexStr(&c, 1); } QString noteStr(int n) { QString s(3, ' '); int nn = n % 12; int oc = (n - nn) / 12; s[2] = '0' + static_cast(oc); s[0] = notemap[nn*2]; s[1] = notemap[nn*2+1]; return s; } } PatternEditorHeaderProxyModel::PatternEditorHeaderProxyModel(QObject *parent, PatternEditorModel* p) : QAbstractTableModel(parent), pm(p) { } int PatternEditorHeaderProxyModel::rowCount(const QModelIndex&) const { return 1; } int PatternEditorHeaderProxyModel::columnCount(const QModelIndex&) const { return pm->hdrColumnCount(); } QVariant PatternEditorHeaderProxyModel::data(const QModelIndex&, int) const { return QVariant(); } QVariant PatternEditorHeaderProxyModel::headerData(int section, Qt::Orientation orientation, int role) const { return pm->hdrData(section, orientation, role); } PatternEditorModel::PatternEditorModel(QObject *parent) :QAbstractTableModel(parent) { hprox = new PatternEditorHeaderProxyModel(parent, this); auto view = static_cast(parent); int rowHeight = [] { QFontMetrics fm = QFontMetrics(QFont()); return (fm.boundingRect("255").size() + QSize(8, 4)).height(); }(); endHeight = (view->viewport()->height() - rowHeight) / 2; connect(view->verticalScrollBar(), &QScrollBar::rangeChanged, this, [this, view, rowHeight] { endHeight = (view->viewport()->height() - rowHeight) / 2; emit this->layoutChanged(); }); } int PatternEditorModel::rowCount(const QModelIndex & /*parent*/) const { //if (pattern->channels.size() == 0) return 1; return pattern->rows + 2; } int PatternEditorModel::columnCount(const QModelIndex & /*parent*/) const { if (pattern->channels.size() == 0) return 1; return colsPerChannel * static_cast(pattern->channels.size()) + 1; } QVariant PatternEditorModel::data(const QModelIndex &index, int role) const { if (role == Qt::DisplayRole) { if (index.row() == 0) return QVariant(); if (index.row() > pattern->rows || index.column() >= colsPerChannel * static_cast(pattern->channels.size())) return QVariant(); int cc = index.column() % colsPerChannel; int ch = (index.column() - cc) / colsPerChannel; auto& row = pattern->rowAt(ch, index.row()-1); if (cc == 0) { // port if (row.port >= 0 && row.port < 256) return byteStr(row.port); if (row.port == -2) return qs("(G)"); if (row.port == -3) return qs("L"); return qs(" - "); } else if (cc == 1) { // note if (row.note >= 0) return noteStr(row.note); if (row.note == -2) return qs(" ^ "); // note off if (row.note == -3) return qs(" x "); // hard cut return qs(" - "); } else { size_t cp = static_cast(((cc - 2) - (cc % 2)) / 2); if (cc % 2 == 0) { if (row.numParams() > cp) return QString(1,static_cast(row.params->at(cp)[0])); if (row.numParams() == cp) return qs("ยป "); return qs(""); } if (row.numParams() > cp) { if (row.params->at(cp)[0] == ' ') return qs("- "); return byteStr(row.params->at(cp)[1]); } return qs(""); } } else if (role == Qt::SizeHintRole) { if (index.row() >= pattern->rows) return QSize(-1, -1); } return QVariant(); } QVariant PatternEditorModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role == Qt::DisplayRole) { if (orientation == Qt::Orientation::Horizontal) return QVariant(); // blank actual-header if (section == 0 || section > pattern->rows) return QVariant(); // blank end section return QString::number(section-1); } else if (role == Qt::SizeHintRole) { if (orientation == Qt::Orientation::Vertical) if (section == 0 || section > pattern->rows) return QSize(-1, endHeight); // fill ends to center if (hsz.isEmpty()) { auto fm = QFontMetrics(QFont(/*"Iosevka Term Light", 9*/)); hsz = fm.boundingRect("255").size() + QSize(8, 4); // this should fit 0-999 hhsz = QSize(0, hsz.height()); } return orientation == Qt::Vertical ? hsz : hhsz; //return QSize(0, fm.height() + 4); } else if (role == Qt::TextAlignmentRole) return Qt::AlignCenter; return QVariant(); } Qt::ItemFlags PatternEditorModel::flags(const QModelIndex &index) const { if (index.row() == 0 || index.row() >= pattern->rows + 1 || (pattern->channels.size() > 0 && index.column() >= colsPerChannel * static_cast(pattern->channels.size()))) { return QAbstractTableModel::flags(index) & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled); } return QAbstractTableModel::flags(index); } int PatternEditorModel::hdrColumnCount() const { return static_cast(pattern->channels.size()) + 1; } QVariant PatternEditorModel::hdrData(int section, Qt::Orientation, int role) const { if (role == Qt::DisplayRole || role == Qt::ToolTipRole) { if (pattern->channels.size() == 0) return QString("(no channels)"); if (static_cast(section) >= pattern->channels.size()) return QString(""); auto n = pattern->channels.at(static_cast(section)).name; //if (n.length() == 0) return QString("(Channel %1)").arg(section); if (n.length() == 0) return QString("(ch%1)").arg(section); return n; } else if (role == Qt::SizeHintRole) { if (cellWidthBase <= 0) { auto fm = QFontMetrics(QFont("Iosevka Term Light", 9)); headerHeight = fm.height()+4; cellWidthBase = fm.horizontalAdvance(QString("FF")) + fm.horizontalAdvance(QString("C#2")) + cellPadding*4; cellWidthParamTab = fm.horizontalAdvance(QString("v")) + cellPadding; cellWidthParam = cellWidthParamTab + fm.horizontalAdvance(QString("FF")) + cellPadding; } return QSize(0, headerHeight); } return QVariant(); } void PatternEditorModel::setPattern(const std::shared_ptr& pattern) { if (this->pattern == pattern) return; this->pattern = pattern; refresh(); } void PatternEditorModel::updateColumnDisplay() { if (pattern == nullptr) return; auto view = static_cast(parent()); //view->setUpdatesEnabled(false); //auto fm = QFontMetrics(QFont("Iosevka Term Light", 9)); 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(); } int lastShown = 0; int chWidth = 0; for (size_t i = 0; i < PatternEditorModel::colsPerChannel; i++) { auto col = static_cast(ch*PatternEditorModel::colsPerChannel+i); view->setColumnHidden(col, false); // always unhide to ensure hiding actually prevents columns from being selected if (i < 3+2*maxParams) { view->resizeColumnToContents(col); lastShown = col; chWidth += view->columnWidth(col); } else view->setColumnHidden(col, true); //view->setColumnWidth(col, 1); } int minWidth = 0; if (fitHeaderToName) { // ensure exact fit QStyleOptionHeader opt; opt.initFrom(view->horizontalHeader()); opt.state = QStyle::State_None | QStyle::State_Raised | QStyle::State_Horizontal; opt.orientation = Qt::Horizontal; opt.section = 0; opt.fontMetrics = view->horizontalHeader()->fontMetrics(); opt.text = c.name; minWidth = view->horizontalHeader()->style()->sizeFromContents(QStyle::CT_HeaderSection, &opt, QSize(), view->horizontalHeader()).width(); } int lsw = view->columnWidth(lastShown); view->setColumnWidth(lastShown, std::max(lsw + 3, minWidth - (chWidth - lsw))); } //view->setUpdatesEnabled(true); view->updateHeader(true); } void PatternEditorModel::updateFold() { auto view = static_cast(parent()); //view->setUpdatesEnabled(false); int ifold = 1; if (folded && pattern->fold > 1) ifold = pattern->fold; int rows = this->rowCount()-2; view->setRowHidden(0, false); view->setRowHidden(rows+1, false); for (int i = 0; i < rows; i++) { view->setRowHidden(i+1, false); // dispel any "phantoms" we might end up having if (i < pattern->rows && i % ifold != 0) view->setRowHidden(i+1, true); } //view->setUpdatesEnabled(true); } void PatternEditorModel::toggleFold() { folded = !folded; refresh(); }