huge amount of pattern editor work (full editing!)

portability/boost
zetaPRIME 2018-11-23 09:03:35 -05:00
parent 29b71cfaf8
commit 744e9ddaf5
12 changed files with 407 additions and 31 deletions

18
notes
View File

@ -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

View File

@ -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)];
}

View File

@ -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);
};
}

View File

@ -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() {

View File

@ -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>

View File

@ -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);
}

View File

@ -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);
};
}

View File

@ -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);
}
}
}

View File

@ -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();
};
}

View File

@ -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();
}

View File

@ -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
};
}

View File

@ -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