196 lines
7.2 KiB
C++
196 lines
7.2 KiB
C++
#include "patterneditorview.h"
|
|
using Xybrid::UI::PatternEditorView;
|
|
#include "ui/patterneditormodel.h"
|
|
using Xybrid::UI::PatternEditorModel;
|
|
#include "ui/patterneditoritemdelegate.h"
|
|
using Xybrid::UI::PatternEditorItemDelegate;
|
|
|
|
#include "util/strings.h"
|
|
|
|
#include "ui/channelheaderview.h"
|
|
using Xybrid::UI::ChannelHeaderView;
|
|
|
|
#include "data/project.h"
|
|
using Xybrid::Data::Project;
|
|
using Xybrid::Data::Pattern;
|
|
|
|
#include "editing/patterncommands.h"
|
|
using namespace Xybrid::Editing;
|
|
|
|
#include <QKeyEvent>
|
|
#include <QDebug>
|
|
|
|
#include <QHeaderView>
|
|
#include <QScrollBar>
|
|
#include <QHBoxLayout>
|
|
#include <QMenu>
|
|
#include <QTextEdit>
|
|
#include <QInputDialog>
|
|
#include <QMessageBox>
|
|
|
|
|
|
namespace {
|
|
std::shared_ptr<Pattern> pt = std::make_shared<Pattern>(1, 0); // fallback pattern
|
|
}
|
|
|
|
PatternEditorView::PatternEditorView(QWidget *parent) : QTableView(parent) {
|
|
hdr.reset(new ChannelHeaderView(this));
|
|
hdr->setSectionResizeMode(QHeaderView::Fixed);
|
|
hdr->setTextElideMode(Qt::ElideNone);//Middle);
|
|
hdr->setStretchLastSection(true);
|
|
hdr->setSectionsMovable(true);
|
|
hdr->setFirstSectionMovable(true);
|
|
hdr->setEditTriggers(EditTrigger::DoubleClicked);
|
|
|
|
// hook up scrolling to update header position
|
|
connect(horizontalScrollBar(), &QScrollBar::valueChanged, this, &PatternEditorView::updateHeaderOffset);
|
|
// and header swap
|
|
connect(hdr.get(), &QHeaderView::sectionMoved, this, &PatternEditorView::headerMoved);
|
|
|
|
// hook up context menu
|
|
hdr->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
connect(hdr.get(), &QHeaderView::customContextMenuRequested, this, &PatternEditorView::headerContextMenu);
|
|
|
|
connect(hdr.get(), &QHeaderView::sectionDoubleClicked, this, &PatternEditorView::headerDoubleClicked);
|
|
|
|
horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed);//ResizeToContents);
|
|
horizontalHeader()->setStretchLastSection(true);
|
|
verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
|
verticalHeader()->setStretchLastSection(true);
|
|
verticalHeader()->setSectionsClickable(false);
|
|
setCornerButtonEnabled(false);
|
|
//verticalHeader()->setDefaultAlignment(Qt::AlignTop);
|
|
|
|
cornerBoxBox.reset(new QWidget(this));
|
|
QHBoxLayout *layoutCheckBox = new QHBoxLayout(cornerBoxBox.get());
|
|
cornerBox.reset(new QCheckBox());
|
|
layoutCheckBox->addWidget(cornerBox.get());
|
|
layoutCheckBox->setAlignment(Qt::AlignCenter);
|
|
layoutCheckBox->setContentsMargins(0,0,0,0);
|
|
cornerBox->setToolTip("Expand to fit names");
|
|
cornerBox->setFocusPolicy(Qt::NoFocus);
|
|
connect(cornerBox.get(), &QCheckBox::toggled, this, [this](bool state) {
|
|
mdl->fitHeaderToName = state;
|
|
mdl->updateColumnDisplay();
|
|
});
|
|
|
|
mdl.reset(new PatternEditorModel(this));
|
|
del.reset(new PatternEditorItemDelegate(this));
|
|
setItemDelegate(&*del);
|
|
mdl->setPattern(pt);
|
|
setModel(&*mdl);
|
|
hdr->setModel(&*mdl->hprox);
|
|
}
|
|
|
|
PatternEditorView::~PatternEditorView() {
|
|
//
|
|
/*mdl.release();
|
|
del.release();
|
|
hdr.release();
|
|
cornerBoxBox.release();
|
|
cornerBox.release();*/
|
|
//horizontalHeader()->deleteLater();
|
|
}
|
|
|
|
void PatternEditorView::keyPressEvent(QKeyEvent *event) {
|
|
if (/*event->modifiers() & Qt::Modifier::CTRL &&*/ (event->key() == Qt::Key_Tab || event->key() == Qt::Key_Backtab)) { // don't block ctrl+tab
|
|
event->ignore();
|
|
return;
|
|
//QKeyEvent()
|
|
}
|
|
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(const std::shared_ptr<Pattern>& pattern) {
|
|
mdl->setPattern(pattern);
|
|
}
|
|
|
|
void PatternEditorView::updateGeometries() {
|
|
if (mdl && colUpdateNeeded && horizontalHeader()->count() > 0) {
|
|
mdl->updateColumnDisplay();
|
|
colUpdateNeeded = false;
|
|
} else updateHeader(true); // do this once on every geom update
|
|
if (cornerBox) {
|
|
cornerBoxBox->setGeometry(verticalHeader()->x(), horizontalHeader()->y(), verticalHeader()->width(), horizontalHeader()->height());
|
|
//cornerBox->move(verticalHeader()->x() + verticalHeader()->width() / 2 - cornerBox->minimumWidth() / 2, horizontalHeader()->y() + cornerBox->height() / 2);
|
|
}
|
|
this->QTableView::updateGeometries();
|
|
}
|
|
|
|
void PatternEditorView::updateHeader(bool full) {
|
|
auto* bh = horizontalHeader(); // base header
|
|
if (full) {
|
|
hdr->reset(); // force section update
|
|
for (int i = 0; i < hdr->count(); i++) { // set sizes
|
|
constexpr int cpc = PatternEditorModel::colsPerChannel;
|
|
int w = 0;
|
|
for (int j = 0; j < cpc; j++) {
|
|
w += bh->sectionSize(i*cpc+j);
|
|
}
|
|
hdr->resizeSection(i, w);
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
|
|
void PatternEditorView::headerMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex) {
|
|
if (logicalIndex == newVisualIndex) return; // assume moving back
|
|
hdr->moveSection(newVisualIndex, logicalIndex); // maintain straight-through order
|
|
if (logicalIndex >= hdr->count() - 1) return; // no dragging the endcap :|
|
|
|
|
(new PatternChannelMoveCommand(mdl->getPattern(), oldVisualIndex, newVisualIndex))->commit();
|
|
}
|
|
|
|
void PatternEditorView::headerDoubleClicked(int section) {
|
|
startRenameChannel(section);
|
|
}
|
|
|
|
void PatternEditorView::headerContextMenu(QPoint pt) {
|
|
int idx = hdr->logicalIndexAt(pt);
|
|
std::shared_ptr<Pattern> p = mdl->getPattern();
|
|
|
|
QMenu* menu = new QMenu(this);
|
|
menu->addAction("Add Channel", this, [/*this,*/ idx, p]() {
|
|
(new PatternChannelAddCommand(p, idx))->commit();
|
|
});
|
|
if (idx < hdr->count() - 1) {
|
|
menu->addAction("Delete Channel", this, [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;
|
|
(new PatternChannelDeleteCommand(p, idx))->commit();
|
|
});
|
|
menu->addAction("Rename Channel...", this, [this, idx, p]() {
|
|
if (p != mdl->getPattern()) return; // swapped already
|
|
startRenameChannel(idx);
|
|
});
|
|
}
|
|
menu->setAttribute(Qt::WA_DeleteOnClose);
|
|
menu->popup(hdr->mapToGlobal(pt));
|
|
|
|
}
|
|
|
|
void PatternEditorView::startRenameChannel(int channel) {
|
|
auto p = mdl->getPattern();
|
|
if (static_cast<size_t>(channel) >= p->numChannels()) return;
|
|
auto c = &p->channel(channel);
|
|
bool ok = false;
|
|
auto capt = QString("Rename channel %1:").arg(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
|
|
(new PatternChannelRenameCommand(p, channel, n.toStdString()))->commit();
|
|
}
|