lots of UI stuff

portability/boost
zetaPRIME 2018-12-01 10:41:14 -05:00
parent 14f1f2d783
commit d8af8f463b
17 changed files with 430 additions and 91 deletions

38
notes
View File

@ -1,8 +1,7 @@
pattern editor {
iosevka works perfectly for tracker patterns
C-4 01 vFF
can freely embed it too (SIL OFL allows bundling)
probably Iosevka Term (Light?)
IMPORTANT LINKS {
https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/
lowpass filter https://www.embeddedrelated.com/showarticle/779.php
}
project data {
@ -47,21 +46,28 @@ TODO {
immediate frontburner {
- allow swapping channels around
double click to rename channel
^ QHeaderView is a QAbstractItemView as of qt5, so can set an item delegate
- double click to rename channel - popup for now since QHeaderView doesn't actually implement editing via delegate...
create new Project and hook that up to the main window instead of just making an empty pattern
( Project::new(), Pattern::new(Project*) )
make and hook up UI for displaying all existing patterns by index, and for displaying the sequence
- right click channel header:
- rename
- add
- delete
toggle box for expand-to-name
- create new Project and hook that up to the main window instead of just making an empty pattern
make and hook up UI for displaying all existing patterns by index...
...and for displaying the sequence
implement drag and drop for pattern swapping/rearrangement (std::rotate)
add miscellaneous editables (artist, song title, project bpm; pattern name, length etc.)
make splitter collapse update pattern editor
- make pattern editor detect ctrl and alt modifiers
make everything relevant check if editing is locked
- figure out what library to use for messagepack (official msgpack-c)
implement saving (probably a FileIO helper class)
hook up new/open/save menu items
- hook up new/open/save menu items
}
? de-hardcode the "» " (probably just make it a static const variable somewhere?)
@ -127,6 +133,7 @@ graph+node+port system {
this way, we don't bother processing anything that doesn't actually contribute to output in any way
before playback starts, playback thread assembles an expanded queue (graph has a function that recursively expands queue given a ref to a pre-reserved vector)
note: also preassemble a map of all unique channel names for note number purposes
can use the same logic to count active nodes (make sure to include the graph i/o ports unless implementing those another way? actually yeah, explicit pull operation)
... how will the worker threads tell when they've outpaced the queue, and how will they wait properly?
something something ready flags (all input nodes and containing graph; main graph and command ports always have the bit set)
@ -142,6 +149,15 @@ graph+node+port system {
(most built-in gadgets can avoid this by nature of aligned power-of-two types (<=8bytes) inherently atomic on modern CPUs)
}
on-the-wire command format {
ushort noteId // for sending commands to the same note
short note // note number >= 0, -1 for none, -2 note off, -3 hard cut
unsigned char numParams * {
unsigned char cmd
unsigned char amount
}
}
keybinds {
pattern editor {
note column {

View File

@ -4,6 +4,7 @@ using Row = Pattern::Row;
using Channel = Pattern::Channel;
Row Pattern::fallbackRow(-1337, -1337);
Channel Pattern::fallbackChannel(0);
std::array<unsigned char, 2> Row::fallbackParam {'.', 0};
@ -30,6 +31,22 @@ void Pattern::setLength(int r) {
}
}
void Pattern::addChannel(int at) {
if (at < 0 || at > static_cast<int>(channels.size())) at = static_cast<int>(channels.size());
channels.insert(channels.begin() + at, rows);
}
void Pattern::deleteChannel(int at) {
if (at < 0 || at >= static_cast<int>(channels.size())) at = static_cast<int>(channels.size()) - 1;
channels.erase(channels.begin() + at);
}
Channel& Pattern::channel(int c) {
auto cc = static_cast<size_t>(c);
if (cc >= channels.size()) return fallbackChannel;
return channels[cc];
}
Row& Pattern::rowAt(int c, int r) {
auto nc = this->channels.size();
if (nc == 0 || rows == 0) return fallbackRow;

View File

@ -2,6 +2,7 @@
#include <stdint.h>
#include <limits>
#include <memory>
#include <list>
#include <vector>
@ -71,12 +72,14 @@ namespace Xybrid::Data {
};
private:
static Row fallbackRow;
static Channel fallbackChannel;
public:
// raw pointer is fine for now, since a project will never be destroyed without explicitly orphaning patterns
// (and probably deleting them since basically the only reason one would be kept alive is if it's open in the pattern editor,
// which would then immediately update with a pattern from opening a new project, or the window would close)
Project* project;
size_t index; // index in project's pattern list
std::string name;
@ -88,7 +91,14 @@ namespace Xybrid::Data {
Pattern(int rows, int channels);
void setLength(int rows);
void addChannel(int at = -1);
void deleteChannel(int at);
size_t numChannels() {
return channels.size();
}
Channel& channel(int channel);
Row& rowAt(int channel, int row);
};
}

View File

@ -1,2 +1,20 @@
#include "project.h"
using Xybrid::Data::Project;
using Xybrid::Data::Pattern;
Project::~Project() {
// orphan patterns so they're not pointing at a non-project
for (auto& pat : patterns) pat->project = nullptr;
}
void Project::updatePatternIndices() {
for (size_t i = 0; i < patterns.size(); i++) patterns[i]->index = i;
}
std::shared_ptr<Pattern> Project::newPattern() {
auto pt = std::make_shared<Pattern>();
pt->project = this;
pt->index = patterns.size();
patterns.push_back(pt);
return pt;
}

View File

@ -7,16 +7,26 @@
#include <vector>
#include <string>
#include <QString>
#include "data/pattern.h"
namespace Xybrid {
class UISocket;
}
namespace Xybrid::Data {
class Project {
public:
bool editingLocked = false;
UISocket* socket;
std::string title;
std::string artist;
QString fileName;
size_t sampleRate = 48000; // global sr for rendering
float tempo = 140.0;
// default time signature
@ -27,5 +37,12 @@ namespace Xybrid::Data {
//std::shared_ptr<Graph> mainGraph;
// list of input nodes is just part of mainGraph
Project() = default;
~Project();
void updatePatternIndices();
std::shared_ptr<Pattern> newPattern();
};
}

View File

@ -5,9 +5,13 @@ using Xybrid::MainWindow;
#include <QDebug>
#include <QKeyEvent>
#include <QTabWidget>
#include <QFileDialog>
#include "ui/patterneditoritemdelegate.h"
using Xybrid::Data::Project;
using Xybrid::Data::Pattern;
using Xybrid::UI::PatternEditorModel;
using Xybrid::UI::PatternEditorItemDelegate;
@ -29,18 +33,12 @@ MainWindow::MainWindow(QWidget *parent) :
//mb->setSizePolicy(QSizePolicy(QSizePolicy::Policy::Maximum, QSizePolicy::Policy::Maximum));
//mb->resize(mb->size().width(), t->tabBar()->size().height());
// prevent right pane of pattern view from being collapsed
ui->patternViewSplitter->setCollapsible(1, false);
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].addParam('v', 255);
pattern->channels[1].rows[0].port = 1;
pattern->channels[1].rows[0].note = 0;
pattern->channels[5].name = "I have a name";
socket.reset(new UISocket());
auto pe = t->findChild<Xybrid::UI::PatternEditorView*>("patternEditor");
pe->setPattern(&*pattern);
menuFileNew();
}
MainWindow::~MainWindow() {
@ -61,3 +59,49 @@ bool MainWindow::eventFilter(QObject *obj [[maybe_unused]], QEvent *event) {
}
return false;
}
void MainWindow::menuFileNew() {
auto hold = project; // keep alive until done
project = std::make_shared<Project>();
project->newPattern();
onNewProjectLoaded();
}
void MainWindow::menuFileOpen() {
auto fileName = QFileDialog::getOpenFileName(this, "Open project...");
if (fileName.isEmpty()) return; // canceled
}
void MainWindow::menuFileSave() {
}
void MainWindow::menuFileSaveAs() {
auto fileName = QFileDialog::getSaveFileName(this, "Save project as...");
if (fileName.isEmpty()) return; // canceled
}
void MainWindow::onNewProjectLoaded() {
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
if (p == nullptr) continue;
pt = p;
break;
}
selectPatternForEditing(pt);
}
bool MainWindow::selectPatternForEditing(Pattern* pattern) {
if (pattern->project != project.get()) return false; // wrong project
if (project->patterns.size() <= pattern->index) return false; // invalid id
auto sp = project->patterns[pattern->index];
if (sp.get() != pattern) return false; // wrong index
auto hold = editingPattern; // keep alive until done
editingPattern = sp;
ui->patternEditor->setPattern(editingPattern.get());
return true;
}

View File

@ -2,7 +2,8 @@
#include <QMainWindow>
#include "data/pattern.h"
#include "uisocket.h"
#include "data/project.h"
#include "ui/patterneditormodel.h"
namespace Ui {
@ -19,10 +20,21 @@ namespace Xybrid {
private:
Ui::MainWindow* ui;
std::unique_ptr<Data::Pattern> pattern; // temporary pattern for testing the editor
std::unique_ptr<UISocket> socket;
std::shared_ptr<Data::Project> project;
std::shared_ptr<Data::Pattern> editingPattern; // temporary pattern for testing the editor
void onNewProjectLoaded();
bool selectPatternForEditing(Data::Pattern*);
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
public slots:
void menuFileNew();
void menuFileOpen();
void menuFileSave();
void menuFileSaveAs();
};
}

View File

@ -59,43 +59,95 @@
<number>0</number>
</property>
<item>
<widget class="Xybrid::UI::PatternEditorView" name="patternEditor">
<property name="font">
<font>
<family>Iosevka Term Light</family>
<pointsize>9</pointsize>
</font>
<widget class="QSplitter" name="patternViewSplitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::AnyKeyPressed</set>
</property>
<property name="dragDropOverwriteMode">
<bool>false</bool>
</property>
<property name="textElideMode">
<enum>Qt::ElideNone</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<property name="gridStyle">
<enum>Qt::NoPen</enum>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>24</number>
</attribute>
<attribute name="horizontalHeaderMinimumSectionSize">
<number>5</number>
</attribute>
<attribute name="verticalHeaderDefaultSectionSize">
<number>24</number>
</attribute>
<attribute name="verticalHeaderMinimumSectionSize">
<number>2</number>
</attribute>
<widget class="QListView" name="listView">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>192</width>
<height>0</height>
</size>
</property>
</widget>
<widget class="QWidget" name="patternViewPane" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>100</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>480</width>
<height>0</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>4</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="Xybrid::UI::PatternEditorView" name="patternEditor">
<property name="font">
<font>
<family>Iosevka Term Light</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="editTriggers">
<set>QAbstractItemView::AnyKeyPressed</set>
</property>
<property name="dragDropOverwriteMode">
<bool>false</bool>
</property>
<property name="textElideMode">
<enum>Qt::ElideNone</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<property name="gridStyle">
<enum>Qt::NoPen</enum>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>24</number>
</attribute>
<attribute name="horizontalHeaderMinimumSectionSize">
<number>5</number>
</attribute>
<attribute name="verticalHeaderDefaultSectionSize">
<number>24</number>
</attribute>
<attribute name="verticalHeaderMinimumSectionSize">
<number>2</number>
</attribute>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
@ -162,7 +214,7 @@
</widget>
<action name="actionOpen">
<property name="text">
<string>Open</string>
<string>&amp;Open</string>
</property>
<property name="shortcut">
<string>Ctrl+O</string>
@ -173,7 +225,7 @@
</action>
<action name="actionNew">
<property name="text">
<string>New</string>
<string>&amp;New</string>
</property>
<property name="shortcut">
<string>Ctrl+N</string>
@ -184,7 +236,7 @@
</action>
<action name="actionSave">
<property name="text">
<string>Save</string>
<string>&amp;Save</string>
</property>
<property name="shortcut">
<string>Ctrl+S</string>
@ -195,7 +247,7 @@
</action>
<action name="actionSave_As">
<property name="text">
<string>Save As...</string>
<string>Sa&amp;ve As...</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+S</string>
@ -215,11 +267,59 @@
</customwidgets>
<resources/>
<connections>
<connection>
<sender>actionNew</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>menuFileNew()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>459</x>
<y>305</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionOpen</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>showMaximized()</slot>
<slot>menuFileOpen()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>459</x>
<y>305</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionSave</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>menuFileSave()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>459</x>
<y>305</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionSave_As</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>menuFileSaveAs()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
@ -232,4 +332,10 @@
</hints>
</connection>
</connections>
<slots>
<slot>menuFileNew()</slot>
<slot>menuFileOpen()</slot>
<slot>menuFileSave()</slot>
<slot>menuFileSaveAs()</slot>
</slots>
</ui>

View File

@ -1,10 +1,18 @@
#include "channelheaderview.h"
using Xybrid::UI::ChannelHeaderView;
using Xybrid::UI::ChannelHeaderItemDelegate;
#include "ui/patterneditormodel.h"
#include <QDebug>
#include <QTextEdit>
ChannelHeaderView::ChannelHeaderView(QWidget *parent) : QHeaderView(Qt::Horizontal, parent) {
//
}
QWidget* ChannelHeaderItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option [[maybe_unused]], const QModelIndex &index) const {
qDebug() << QString("trying edit");
auto e = new QTextEdit(parent);
return e;
}

View File

@ -1,10 +1,24 @@
#pragma once
#include <QHeaderView>
#include <QStyledItemDelegate>
namespace Xybrid::UI {
class ChannelHeaderView : public QHeaderView {
public:
ChannelHeaderView(QWidget* parent = nullptr);
};
class ChannelHeaderItemDelegate : public QStyledItemDelegate {
Q_OBJECT
public:
ChannelHeaderItemDelegate(QWidget *parent = nullptr) : QStyledItemDelegate(parent) {}
//bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override;
protected:
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;
};
}

View File

@ -64,6 +64,7 @@ bool PatternEditorItemDelegate::eventFilter(QObject *obj, QEvent *event) {
}
bool PatternEditorItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option [[maybe_unused]], const QModelIndex &index) {
if (index.data().isNull()) return false; // no channels?
auto type = event->type();
if (type == QEvent::KeyPress) {
auto k = static_cast<QKeyEvent*>(event)->key(); // grab key
@ -186,6 +187,7 @@ bool PatternEditorItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *m
}//*/
QSize PatternEditorItemDelegate::sizeHint(const QStyleOptionViewItem &option [[maybe_unused]], const QModelIndex &index) const {
if (index.data().isNull()) return QSize(0, 0);
auto fm = QFontMetrics(QFont("Iosevka Term Light", 9));
int cc = index.column() % PatternEditorModel::colsPerChannel;
std::string s = "FF";

View File

@ -65,14 +65,17 @@ PatternEditorModel::PatternEditorModel(QObject *parent)
}
int PatternEditorModel::rowCount(const QModelIndex & /*parent*/) const {
//if (pattern->channels.size() == 0) return 1;
return pattern->rows;
}
int PatternEditorModel::columnCount(const QModelIndex & /*parent*/) const {
if (pattern->channels.size() == 0) return 1;
return colsPerChannel * static_cast<int>(pattern->channels.size());
}
QVariant PatternEditorModel::data(const QModelIndex &index, int role) const {
if (pattern->channels.size() == 0) return QVariant();
if (role == Qt::DisplayRole) {
int cc = index.column() % colsPerChannel;
int ch = (index.column() - cc) / colsPerChannel;
@ -102,19 +105,8 @@ QVariant PatternEditorModel::data(const QModelIndex &index, int role) const {
}
QVariant PatternEditorModel::headerData(int section, Qt::Orientation orientation, int role) const {
if (role == Qt::DisplayRole || role == Qt::ToolTipRole) {
if (orientation == Qt::Orientation::Horizontal) {
int cc = section % colsPerChannel;
int ch = (section - cc) / colsPerChannel;
if (cc == 0) {
auto n = QString::fromStdString(pattern->channels.at(static_cast<size_t>(ch)).name);
if (n.length() == 0) return QString("(Channel %1)").arg(ch);
return n;
}
return QString(""); // those aren't even supposed to be there
}
if (role == Qt::ToolTipRole) return QString(""); // no tool tip for simple numbers
if (role == Qt::DisplayRole) {
if (orientation == Qt::Orientation::Horizontal) return QVariant(); // blank actual-header
return QString::number(section);
} else if (role == Qt::SizeHintRole) {
auto fm = QFontMetrics(QFont("Iosevka Term Light", 9));
@ -129,9 +121,11 @@ int PatternEditorModel::hdrColumnCount() const {
}
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<size_t>(section) >= pattern->channels.size()) return QString("");
auto n = QString::fromStdString(pattern->channels.at(static_cast<size_t>(section)).name);
if (n.length() == 0) return QString("(Channel %1)").arg(section);
//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) {
@ -157,7 +151,7 @@ QVariant PatternEditorModel::hdrData(int section, Qt::Orientation, int role) con
void PatternEditorModel::setPattern(Pattern* pattern) {
this->pattern = pattern;
//updateColumnDisplay();
refresh();
}
void PatternEditorModel::updateColumnDisplay() {
@ -165,15 +159,28 @@ void PatternEditorModel::updateColumnDisplay() {
qDebug() << QString("column display request #%1").arg(qi++);//*/
if (pattern == nullptr) return;
auto view = static_cast<PatternEditorView*>(parent());
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++) {
view->setColumnHidden(static_cast<int>(ch*PatternEditorModel::colsPerChannel+i), i >= 3+2*maxParams);
auto col = static_cast<int>(ch*PatternEditorModel::colsPerChannel+i);
if (i < 3+2*maxParams) {
view->setColumnHidden(col, false);
view->resizeColumnToContents(col);
lastShown = col;
chWidth += view->columnWidth(col);
} else view->setColumnHidden(col, true);
//view->setColumnWidth(col, 1);
}
int minWidth = std::min(100, fm.width(QString::fromStdString(c.name)) + 8);
int lsw = view->columnWidth(lastShown);
view->setColumnWidth(lastShown, std::max(lsw + 3, minWidth - (chWidth - lsw)));
}
view->updateHeader(true);
}

View File

@ -32,6 +32,10 @@ namespace Xybrid::UI {
}
void updateColumnDisplay();
void refresh() { // implemented in header to allow inlining
emit layoutChanged();
updateColumnDisplay();
}
};
class PatternEditorHeaderProxyModel : public QAbstractTableModel {

View File

@ -16,6 +16,10 @@ using Xybrid::Data::Pattern;
#include <QHeaderView>
#include <QScrollBar>
#include <QMenu>
#include <QTextEdit>
#include <QInputDialog>
namespace {
Pattern pt(1, 0);
@ -28,16 +32,25 @@ PatternEditorView::PatternEditorView(QWidget *parent) : QTableView(parent) {
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);
horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
// 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);
verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
//horizontalHeader()->setSectionsMovable(true);
//horizontalHeader()->moveSection(1, 3);
verticalHeader()->setStretchLastSection(true);
verticalHeader()->setSectionsClickable(false);
setCornerButtonEnabled(false);
//verticalHeader()->setDefaultAlignment(Qt::AlignTop);
mdl.reset(new PatternEditorModel(this));
del.reset(new PatternEditorItemDelegate(this));
@ -45,9 +58,6 @@ PatternEditorView::PatternEditorView(QWidget *parent) : QTableView(parent) {
mdl->setPattern(&pt);
setModel(&*mdl);
hdr->setModel(&*mdl->hprox);
//horizontalHeader()->setModel(&*mdl);
}
PatternEditorView::~PatternEditorView() {
@ -72,8 +82,6 @@ void PatternEditorView::keyPressEvent(QKeyEvent *event) {
void PatternEditorView::setPattern(Xybrid::Data::Pattern *pattern) {
mdl->setPattern(pattern);
colUpdateNeeded = true;
this->viewport()->update();
}
void PatternEditorView::updateGeometries() {
@ -125,3 +133,41 @@ void PatternEditorView::headerMoved(int logicalIndex, int oldVisualIndex, int ne
// and make sure header follows
emit hdr->model()->headerDataChanged(Qt::Horizontal, 0, static_cast<int>(chn.size()) - 1);
}
void PatternEditorView::headerDoubleClicked(int section) {
startRenameChannel(section);
}
void PatternEditorView::headerContextMenu(QPoint pt) {
int idx = hdr->logicalIndexAt(pt);
QMenu *menu=new QMenu(this);
menu->addAction("Add Channel", [idx, this]() {
mdl->getPattern().addChannel(idx);
mdl->refresh();
});
if (idx < hdr->count() - 1) {
menu->addAction("Delete Channel", [idx, this]() {
mdl->getPattern().deleteChannel(idx);
mdl->refresh();
});
menu->addAction("Rename Channel", [idx, this]() {
startRenameChannel(idx);
});
}
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
c->name = n.toStdString(); // and set name
mdl->updateColumnDisplay(); // update sizes and such
}

View File

@ -24,16 +24,22 @@ namespace Xybrid::UI {
public:
PatternEditorView(QWidget* parent = nullptr);
~PatternEditorView() override;
void updateGeometries() override;
void keyPressEvent(QKeyEvent* event) override;
void keyboardSearch(const QString&) override {} // disable accidental search
void setPattern(Data::Pattern* pattern);
void updateGeometries() override;
void updateHeader(bool full = false);
private:
void updateHeaderOffset(int);
void headerMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex);
void headerDoubleClicked(int section);
void headerContextMenu(QPoint pt);
void keyboardSearch(const QString&) override {} // disable accidental search
void startRenameChannel(int channel);
};
}

11
xybrid/uisocket.h Normal file
View File

@ -0,0 +1,11 @@
#pragma once
#include <QObject>
namespace Xybrid {
class UISocket : public QObject {
Q_OBJECT
signals:
//
};
}

View File

@ -43,7 +43,8 @@ HEADERS += \
ui/patterneditoritemdelegate.h \
ui/patterneditorview.h \
data/project.h \
ui/channelheaderview.h
ui/channelheaderview.h \
uisocket.h
FORMS += \
mainwindow.ui