add runeeditor

This commit is contained in:
mrbesen 2023-05-01 22:36:11 +02:00
parent ee7a11b0ef
commit 14ec8a711b
Signed by: MrBesen
GPG Key ID: 596B2350DCD67504
14 changed files with 703 additions and 32 deletions

View File

@ -1,8 +1,10 @@
#pragma once
#include "clientaccess.h"
#include "restclient.h"
#include "datadragonimagecache.h"
#include "memoryimagecache.h"
#include "position.h"
#include "restclient.h"
#include "runeaspekt.h"
#include "runepage.h"
#include "runestyle.h"
@ -214,8 +216,13 @@ public:
const std::string& getRuneStyleByID(uint32_t id);
QPixmap getImageResource(QString path);
private:
ClientAccess access;
MemoryImageCache memImageCache;
DataDragonImageCache imageCache;
};
std::ostream& operator<<(std::ostream&, const ClientAPI::ReadyCheckState&);

View File

@ -0,0 +1,43 @@
#pragma once
#include <QPushButton>
#include <vector>
namespace Ui {
class RuneAspektButton;
}
class RuneAspektButton : public QPushButton {
Q_OBJECT
public:
explicit RuneAspektButton(QWidget* parent = nullptr);
~RuneAspektButton();
void setAspektsVector(std::vector<uint32_t>& aspekts);
void setAspketSlot(int slot);
void setAspektId(uint32_t id);
bool isSelected() const;
signals:
void aspektToggled(bool newState);
public slots:
void buttonPressed();
void dataChanged();
private slots:
void setShowSelection(bool selected); // show/hide the red border
public:
uint32_t aspektId = 0;
int aspektSlot = -1;
private:
Ui::RuneAspektButton* ui;
std::vector<uint32_t>* aspekts = nullptr;
};

59
include/runeeditor.h Normal file
View File

@ -0,0 +1,59 @@
#pragma once
#include <vector>
#include <QDialog>
#include "runeaspekt.h"
#include "runepage.h"
#include "runestyle.h"
namespace Ui {
class RuneEditor;
}
class ClientAPI;
class RuneAspektButton;
class QGridLayout;
class RuneEditor : public QDialog {
Q_OBJECT
public:
explicit RuneEditor(QWidget* parent = nullptr);
~RuneEditor();
void setClient(ClientAPI& client);
void setRunepage(const ::RunePage& rp);
void selectStyle(uint32_t id);
void selectSubStyle(uint32_t id);
void clearLayout(QLayout* layout);
void setName(QString text);
QString getName() const;
constexpr const RunePage& getRunepage() const { return runepage; }
signals:
void selectionChanged();
private slots:
void aspectCliecked();
private:
const RuneStyle* getRuneStyle(uint32_t id) const;
RuneAspektButton* createStyleButton(const RuneStyle& rs, bool selected);
RuneAspektButton* createAspektButton(const RuneAspekt& ra);
RuneAspektButton* createButtonFromResource(QString resource);
void fillRuneStyle(QGridLayout* target, const RuneStyle& rs, bool subRunes = false);
QString fixString(QString text);
Ui::RuneEditor* ui;
ClientAPI* client = nullptr;
::RunePage runepage;
std::vector<RuneAspekt> aspekts;
std::vector<RuneStyle> styles;
};

View File

@ -15,6 +15,7 @@ namespace Ui {
class DataDragon;
class DropEvent;
class ClientAPI;
class RunePageList : public QListWidget {
Q_OBJECT
@ -26,7 +27,8 @@ public:
explicit RunePageList(QWidget* parent = nullptr);
~RunePageList();
constexpr void setClient(bool b) { isClient = b; }
constexpr void setIsClient(bool b) { isClient = b; }
constexpr void setClient(ClientAPI& client) { this->client = &client; }
constexpr void setOther(QListWidget* other) { this->other = other; }
constexpr void setDataDragon(DataDragon& dd) { this->dd = &dd; }
@ -46,6 +48,8 @@ private slots:
void itemChangedCallback(QListWidgetItem* item);
void openContextMenu(const QPoint&);
void deleteCurrentItem();
void editCurrentItem();
void duplicateCurrentItem();
private:
void clearItems();
@ -61,5 +65,6 @@ private:
Ui::RunePageList* ui;
QListWidget* other = nullptr;
DataDragon* dd = nullptr;
ClientAPI* client = nullptr;
bool isClient;
};

View File

@ -2,16 +2,31 @@
#include <cstdint>
#include <string>
#include <vector>
// fwd.
class QJsonObject;
struct RuneStyleSlot {
std::vector<int> perks;
QString type;
RuneStyleSlot();
RuneStyleSlot(const QJsonObject& json);
};
struct RuneStyle {
uint32_t id;
std::string name;
std::string iconPath;
std::string tooltip;
std::vector<int> allowedSubStyles;
QString idName;
std::vector<RuneStyleSlot> runeSlots;
RuneStyle();
explicit RuneStyle(const QJsonObject& json);
};

View File

@ -53,7 +53,9 @@ SOURCES += \
src/mainwindow.cpp \
src/memoryimagecache.cpp \
src/restclient.cpp \
src/runeaspektbutton.cpp \
src/runedisplay.cpp \
src/runeeditor.cpp \
src/runemanager.cpp \
src/runepage.cpp \
src/runepagelist.cpp \
@ -83,7 +85,9 @@ HEADERS += \
include/mainwindow.h \
include/memoryimagecache.h \
include/restclient.h \
include/runeaspektbutton.h \
include/runedisplay.h \
include/runeeditor.h \
include/runemanager.h \
include/runepage.h \
include/runepagelist.h \
@ -94,7 +98,9 @@ HEADERS += \
FORMS += \
ui/championsearch.ui \
ui/mainwindow.ui \
ui/runeaspektbutton.ui \
ui/runedisplay.ui \
ui/runeeditor.ui \
ui/runemanager.ui \
ui/runepagelist.ui \
ui/settingstab.ui \

View File

@ -9,7 +9,7 @@
#include "json.h"
ClientAPI::ClientAPI(const ClientAccess& ca) : RestClient(ca.getURL()), access(ca) {
ClientAPI::ClientAPI(const ClientAccess& ca) : RestClient(ca.getURL()), access(ca), memImageCache(40), imageCache("runes", "") {
basicauth = ca.getBasicAuth();
disableCertCheck = true;
// enableDebugging();
@ -332,3 +332,38 @@ std::vector<RuneStyle> ClientAPI::getAllRuneStyles() {
}
return out;
}
QPixmap ClientAPI::getImageResource(QString path) {
if(path.isEmpty()) return {};
std::string simplePath;
{
QString simplePathQ = path;
simplePath = simplePathQ.replace('/', '_').toStdString();
}
// query mem cache
QPixmap img = memImageCache.getImage(path.toStdString(), 0);
if(!img.isNull()) return img;
// query HDD cache
img = imageCache.getImage(simplePath);
if(!img.isNull()) {
// update mem cache
memImageCache.addImage(img, path.toStdString(), 0);
return img;
}
qInfo() << "requesting: " << path;
QByteArray arr = requestRaw(path.toStdString());
QPixmap out;
out.loadFromData(arr);
// store HDD cache
imageCache.addImageRaw(arr, simplePath);
// store memchache
memImageCache.addImage(out, path.toStdString(), 0);
return out;
}

View File

@ -209,12 +209,33 @@ ClientAPI::RunePage::RunePage(const QJsonObject& json) {
}
}
RuneStyleSlot::RuneStyleSlot() {}
RuneStyleSlot::RuneStyleSlot(const QJsonObject& json) {
type = getValue<QString>(json, "type");
auto perksj = json["perks"];
if(perksj.isArray()) {
perks = readVector<int>(perksj.toArray());
}
}
RuneStyle::RuneStyle() {}
RuneStyle::RuneStyle(const QJsonObject& json) {
id = getValue<int32_t>(json, "id", 0);
name = getValue<std::string>(json, "name");
iconPath = getValue<std::string>(json, "iconPath");
tooltip = getValue<std::string>(json, "tooltip");
idName = getValue<QString>(json, "idName");
auto subStylesRef = json["allowedSubStyles"];
if(subStylesRef.isArray()) {
allowedSubStyles = readVector<int>(subStylesRef.toArray());
}
auto runeSlotsRef = json["slots"];
if(runeSlotsRef.isArray()) {
runeSlots = readVector<RuneStyleSlot>(runeSlotsRef.toArray());
}
}

68
src/runeaspektbutton.cpp Normal file
View File

@ -0,0 +1,68 @@
#include "runeaspektbutton.h"
#include "ui_runeaspektbutton.h"
#include <QDebug>
RuneAspektButton::RuneAspektButton(QWidget* parent) : QPushButton(parent), ui(new Ui::RuneAspektButton) {
ui->setupUi(this);
QObject::connect(this, &QPushButton::pressed, this, &RuneAspektButton::buttonPressed);
}
RuneAspektButton::~RuneAspektButton() {
delete this->ui;
}
void RuneAspektButton::setAspektsVector(std::vector<uint32_t>& aspekts) {
this->aspekts = &aspekts;
dataChanged();
}
void RuneAspektButton::setAspketSlot(int slot) {
aspektSlot = slot;
dataChanged();
}
void RuneAspektButton::setAspektId(uint32_t id) {
aspektId = id;
dataChanged();
}
bool RuneAspektButton::isSelected() const {
return (aspektSlot >= 0 // aspektSlot is valid
&& (int) aspekts->size() > aspektSlot // aspektSlot is valid
&& aspektId != 0 // aspektId is valid
&& aspekts->at(aspektSlot) == aspektId); // aspekt is selected
}
void RuneAspektButton::buttonPressed() {
if(Q_UNLIKELY(!(aspekts && aspektSlot >= 0))) return;
// toggle
bool oldState = isSelected();
int newValue = (oldState ? 0 : aspektId);
aspekts->at(aspektSlot) = newValue;
emit aspektToggled(!oldState);
}
void RuneAspektButton::dataChanged() {
if(!aspekts) return;
bool selection = isSelected();
qDebug() << text() << " datachanged - isSelected: " << selection;
setShowSelection(selection);
}
void RuneAspektButton::setShowSelection(bool selected) {
if(selected) {
setStyleSheet("border: 1px solid red;");
} else {
setStyleSheet("");
}
}

238
src/runeeditor.cpp Normal file
View File

@ -0,0 +1,238 @@
#include "runeeditor.h"
#include "ui_runeeditor.h"
#include <QByteArray>
#include <QDebug>
#include <QGridLayout>
#include <QPixmap>
#include <QPushButton>
#include "clientapi.h"
#include "runeaspektbutton.h"
RuneEditor::RuneEditor(QWidget* parent) : QDialog(parent), ui(new Ui::RuneEditor) {
ui->setupUi(this);
QObject::connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &RuneEditor::accept);
QObject::connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &RuneEditor::reject);
}
RuneEditor::~RuneEditor() {
delete this->ui;
}
void RuneEditor::setClient(ClientAPI& client) {
this->client = &client;
try {
// build ui
aspekts = client.getAllRuneAspekts();
styles = client.getAllRuneStyles();
for(const RuneStyle& rs : styles) {
QPushButton* runeStyleBtn = createStyleButton(rs, rs.id == runepage.primaryStyle);
if(!runeStyleBtn) continue;
QObject::connect(runeStyleBtn, &QPushButton::pressed, [this, id = rs.id](){
selectStyle(id);
});
ui->style->addWidget(runeStyleBtn);
}
} catch(RestClient::WebException& e) {
qCritical() << "webexception: " << e.curlresponse;
if(e.curlresponse == CURLE_COULDNT_CONNECT) {
this->client = nullptr;
}
}
}
void RuneEditor::setRunepage(const ::RunePage& rp) {
qInfo() << "runepage: " << rp.selectedAspects.size();
selectStyle(rp.primaryStyle);
selectSubStyle(rp.secondaryStyle);
runepage = rp;
emit selectionChanged();
}
void RuneEditor::selectStyle(uint32_t id) {
if(runepage.primaryStyle == id) return;
const RuneStyle* style = getRuneStyle(id);
if(style) {
runepage.primaryStyle = id;
runepage.secondaryStyle = 0;
runepage.selectedAspects.clear();
runepage.selectedAspects.resize(9, 0);
clearLayout(ui->substyle);
clearLayout(ui->stylePerks);
clearLayout(ui->substylePerks);
// populate substyles
for(int subStyleId : style->allowedSubStyles) {
const RuneStyle* substyle = getRuneStyle(subStyleId);
QPushButton* subStyleBtn = createStyleButton(*substyle, false);
if(!subStyleBtn) continue;
QObject::connect(subStyleBtn, &QPushButton::pressed, [this, subStyleId](){
selectSubStyle(subStyleId);
});
ui->substyle->addWidget(subStyleBtn);
}
// populate perks
fillRuneStyle(ui->stylePerks, *style);
}
}
void RuneEditor::selectSubStyle(uint32_t id) {
if(runepage.secondaryStyle == id) return;
const RuneStyle* substyle = getRuneStyle(id);
if(substyle) {
runepage.secondaryStyle = id;
clearLayout(ui->substylePerks);
// populate perks
fillRuneStyle(ui->substylePerks, *substyle, true);
}
}
void RuneEditor::clearLayout(QLayout* layout) {
while(layout->count()) {
QLayoutItem* item = layout->takeAt(0);
delete item->widget();
delete item;
}
}
void RuneEditor::setName(QString text) {
ui->runepageName->setText(text);
}
QString RuneEditor::getName() const {
return ui->runepageName->text();
}
void RuneEditor::aspectCliecked() {
// do something?
}
const RuneStyle* RuneEditor::getRuneStyle(uint32_t id) const {
auto it = std::find_if(styles.cbegin(), styles.cend(), [id](const RuneStyle& rs) {
return rs.id == id;
});
return it == styles.cend() ? nullptr : &*it;
}
RuneAspektButton* RuneEditor::createStyleButton(const RuneStyle& rs, bool selected) {
RuneAspektButton* styleBtn = createButtonFromResource(QString::fromStdString(rs.iconPath));
if(!styleBtn) return nullptr;
styleBtn->setText(QString::fromStdString(rs.name));
styleBtn->setToolTip(QString::fromStdString(rs.tooltip));
if(selected) {
styleBtn->setStyleSheet("border: 1px solid red;");
}
return styleBtn;
}
RuneAspektButton* RuneEditor::createAspektButton(const RuneAspekt& ra) {
RuneAspektButton* aspektBtn = createButtonFromResource(QString::fromStdString(ra.iconPath));
if(!aspektBtn) return nullptr;
aspektBtn->setText(QString::fromStdString(ra.name));
aspektBtn->setToolTip(QString::fromStdString(ra.tooltip));
aspektBtn->setAspektsVector(runepage.selectedAspects);
aspektBtn->setAspektId(ra.id);
return aspektBtn;
}
RuneAspektButton* RuneEditor::createButtonFromResource(QString resource) {
if(!client) {
return nullptr;
}
QPixmap icon;
try {
icon = client->getImageResource(resource.remove(0, 1));
} catch(RestClient::WebException& e) {
qCritical() << "webexception: " << e.curlresponse;
if(e.curlresponse == CURLE_COULDNT_CONNECT) {
client = nullptr;
}
}
RuneAspektButton* rab = new RuneAspektButton(this);
rab->setIcon(icon);
QObject::connect(this, &RuneEditor::selectionChanged, rab, &RuneAspektButton::dataChanged);
QObject::connect(rab, &RuneAspektButton::aspektToggled, this, &RuneEditor::aspectCliecked);
QObject::connect(rab, &RuneAspektButton::aspektToggled, this, &RuneEditor::selectionChanged);
return rab;
}
void RuneEditor::fillRuneStyle(QGridLayout* target, const RuneStyle& rs, bool subRunes) {
int row = 0;
for(size_t idx = 0; idx < rs.runeSlots.size(); ++idx) {
const RuneStyleSlot& rss = rs.runeSlots.at(idx);
if(subRunes && rss.type != "kMixedRegularSplashable") continue;
int rowOffset = 0;
if(subRunes) {
rowOffset = 4;
if(row > 0) {
rowOffset = 3;
}
} else {
if(row >= 4) {
rowOffset = 2;
}
}
for(int perkNr = 0; perkNr < (int) rss.perks.size(); ++perkNr) {
uint32_t perk = rss.perks.at(perkNr);
auto itFound = std::find_if(aspekts.cbegin(), aspekts.cend(), [perk](const RuneAspekt& ra) {
return ra.id == perk;
});
if(itFound == aspekts.cend()) {
continue;
}
RuneAspektButton* aspektBtn = createAspektButton(*itFound);
if(!aspektBtn) continue;
aspektBtn->setAspketSlot(row + rowOffset);
target->addWidget(aspektBtn, row, perkNr);
}
++ row;
}
}
QString RuneEditor::fixString(QString text) {
return text.replace("&nbsp;", "").replace("</?.*?>", "");
}

View File

@ -10,8 +10,8 @@
RuneManager::RuneManager(QWidget* parent) : QWidget(parent), ui(new Ui::RuneManager) {
ui->setupUi(this);
ui->listClientRunes->setClient(true);
ui->listaaRunes->setClient(false);
ui->listClientRunes->setIsClient(true);
ui->listaaRunes->setIsClient(false);
ui->listClientRunes->setOther(ui->listaaRunes);
ui->listaaRunes->setOther(ui->listClientRunes);
@ -69,25 +69,36 @@ void RuneManager::loadRunes() {
}
if(client) {
// load meta data
runeInfo = client->getAllRuneAspekts();
QCoreApplication::processEvents();
runeStyles = client->getAllRuneStyles();
QCoreApplication::processEvents();
try {
// load meta data
runeInfo = client->getAllRuneAspekts();
QCoreApplication::processEvents();
runeStyles = client->getAllRuneStyles();
QCoreApplication::processEvents();
this->ui->listClientRunes->setRuneInfos(runeInfo, runeStyles);
this->ui->listaaRunes->setRuneInfos(runeInfo, runeStyles);
ui->listClientRunes->setClient(*client);
ui->listaaRunes->setClient(*client);
// load runepages
const std::vector<ClientAPI::RunePage> runePages = client->getAllRunePages();
ui->listClientRunes->loadRunePages(runePages);
this->ui->listClientRunes->setRuneInfos(runeInfo, runeStyles);
this->ui->listaaRunes->setRuneInfos(runeInfo, runeStyles);
// reload runepages - so they ids can get their names
reloadAARunes();
// load runepages
const std::vector<ClientAPI::RunePage> runePages = client->getAllRunePages();
ui->listClientRunes->loadRunePages(runePages);
// check if autosync is enabled
if(config && config->getConfig().runepagesConfig.autoSync) {
syncRunes();
// reload runepages - so they ids can get their names
reloadAARunes();
// check if autosync is enabled
if(config && config->getConfig().runepagesConfig.autoSync) {
syncRunes();
}
} catch(RestClient::WebException& e) {
qCritical() << "webexception: " << e.curlresponse;
if(e.curlresponse == CURLE_COULDNT_CONNECT) {
client.reset();
}
}
}
@ -110,15 +121,25 @@ void RuneManager::saveRunePageClient(int id, QString name, const RunePage& rp) {
newPage.runepage = rp;
newPage.id = id;
if(id == -1) {
// create new page
if(!client->createRunePage(newPage)) {
// TODO: some error occured
try {
if(id == -1) {
// create new page
if(!client->createRunePage(newPage)) {
// TODO: some error occured
}
} else {
// edit existing page
if(!client->editRunePage(newPage)) {
// TODO: some error occured
}
}
} else {
// edit existing page
if(!client->editRunePage(newPage)) {
// TODO: some error occured
} catch(RestClient::WebException& e) {
qCritical() << "webexception: " << e.curlresponse;
if(e.curlresponse == CURLE_COULDNT_CONNECT) {
client.reset();
setRunesEnabled(false);
}
}
}
@ -150,8 +171,18 @@ void RuneManager::saveRunePageAA(int id, QString name, const RunePage& rp) {
void RuneManager::deleteRunepageClient(int id) {
if(client) {
if(!client->deleteRunePage(id)) {
// TODO: some error occured
try {
if(!client->deleteRunePage(id)) {
// TODO: some error occured
}
} catch(RestClient::WebException& e) {
qCritical() << "webexception: " << e.curlresponse;
if(e.curlresponse == CURLE_COULDNT_CONNECT) {
client.reset();
setRunesEnabled(false);
}
}
}
}

View File

@ -8,6 +8,7 @@
#include <QTextStream>
#include "datadragon.h"
#include "runeeditor.h"
RunePageList::RunePageList(QWidget* parent) : QListWidget(parent), ui(new Ui::RunePageList) {
ui->setupUi(this);
@ -41,7 +42,6 @@ void RunePageList::setRuneInfos(const std::vector<RuneAspekt>& runeInfo, const s
}
void RunePageList::dropEvent(QDropEvent* event) {
qDebug() << "drop event";
if(event->source() == nullptr || event->source() != other) {
event->ignore();
return;
@ -53,7 +53,6 @@ void RunePageList::dropEvent(QDropEvent* event) {
return;
}
QListWidgetItem* item = selected.at(0);
qDebug() << "data: " << item->data(RoleId);
// compare rune pages for duplicates?
@ -78,6 +77,8 @@ void RunePageList::openContextMenu(const QPoint& pos) {
QMenu menu;
menu.addAction(QIcon::fromTheme("edit-delete"), RunePageList::tr("Delete"), this, &RunePageList::deleteCurrentItem);
menu.addAction(QIcon::fromTheme("document-open"), RunePageList::tr("Edit (Beta)"), this, &RunePageList::editCurrentItem);
menu.addAction(QIcon::fromTheme("edit-copy"), RunePageList::tr("Duplicate"), this, &RunePageList::duplicateCurrentItem);
menu.exec(globalPos);
}
@ -96,6 +97,51 @@ void RunePageList::deleteCurrentItem() {
}
}
void RunePageList::editCurrentItem() {
QListWidgetItem* item = currentItem();
if(!item) return;
RunePage* rp = (RunePage*) item->data(RolePointer).toULongLong();
const uint32_t id = item->data(RoleId).toUInt();
RuneEditor re;
re.setName(item->text());
re.setClient(*client);
re.setRunepage(*rp);
int result = re.exec();
// check result - save
if(result == QDialog::Accepted) {
// update config
emit runepageChanged(id, re.getName(), re.getRunepage());
}
}
void RunePageList::duplicateCurrentItem() {
QListWidgetItem* item = currentItem();
if(!item) return;
const RunePage* rp = (RunePage*) item->data(RolePointer).toULongLong();
QString name = item->text();
static const QRegularExpression regex(".*(\\d)+$");
QRegularExpressionMatchIterator regexIt = regex.globalMatch(name);
int num = 0;
if(regexIt.hasNext()) {
QRegularExpressionMatch match = regexIt.next();
QStringRef ref = match.capturedRef(1);
name.chop(ref.size());
num = ref.toInt();
}
name += QString::number(num+1);
emit runepageChanged(-1, name, *rp);
}
void RunePageList::clearItems() {
while(count()) {
QListWidgetItem* item = takeItem(0);

18
ui/runeaspektbutton.ui Normal file
View File

@ -0,0 +1,18 @@
<ui version="4.0">
<author/>
<comment/>
<exportmacro/>
<class>RuneAspektButton</class>
<widget class="QPushButton" name="RuneAspektButton">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
</widget>
<pixmapfunction/>
<connections/>
</ui>

79
ui/runeeditor.ui Normal file
View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RuneEditor</class>
<widget class="QWidget" name="RuneEditor">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>576</width>
<height>455</height>
</rect>
</property>
<property name="windowTitle">
<string>Runepage Editor</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="2">
<widget class="QLineEdit" name="runepageName"/>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="substyle"/>
</item>
<item row="1" column="0">
<layout class="QHBoxLayout" name="style"/>
</item>
<item row="3" column="1" rowspan="3">
<layout class="QGridLayout" name="substylePerks"/>
</item>
<item row="2" column="1">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="8" column="1">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
</property>
</widget>
</item>
<item row="2" column="0" rowspan="7">
<layout class="QGridLayout" name="stylePerks">
<property name="rightMargin">
<number>10</number>
</property>
</layout>
</item>
<item row="6" column="1" rowspan="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>120</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>