2023-08-06 08:48:11 -04:00
|
|
|
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
2023-04-09 15:31:19 -04:00
|
|
|
#include "fullmodelviewer.h"
|
|
|
|
|
2023-07-06 17:38:01 -04:00
|
|
|
#include "boneeditor.h"
|
2023-07-07 16:02:45 -04:00
|
|
|
#include "magic_enum.hpp"
|
2023-07-08 15:29:42 -04:00
|
|
|
#include <QFileDialog>
|
2023-07-07 16:02:45 -04:00
|
|
|
#include <QFormLayout>
|
|
|
|
#include <QGroupBox>
|
2023-07-08 15:29:42 -04:00
|
|
|
#include <QMenuBar>
|
|
|
|
#include <QRadioButton>
|
2023-04-09 15:31:19 -04:00
|
|
|
#include <QVBoxLayout>
|
|
|
|
|
2023-10-12 23:44:48 -04:00
|
|
|
FullModelViewer::FullModelViewer(GameData *data, FileCache &cache, QWidget *parent)
|
|
|
|
: QMainWindow(parent)
|
|
|
|
, data(data)
|
|
|
|
{
|
2023-09-26 00:37:55 -04:00
|
|
|
setWindowTitle(QStringLiteral("Full Model Viewer"));
|
2023-07-09 11:04:30 -04:00
|
|
|
setMinimumWidth(1280);
|
|
|
|
setMinimumHeight(720);
|
2023-07-07 16:16:21 -04:00
|
|
|
|
2023-07-09 11:31:18 -04:00
|
|
|
auto dummyWidget = new QWidget();
|
|
|
|
setCentralWidget(dummyWidget);
|
2023-07-07 16:16:21 -04:00
|
|
|
|
2023-07-09 11:31:18 -04:00
|
|
|
auto layout = new QVBoxLayout();
|
|
|
|
dummyWidget->setLayout(layout);
|
2023-07-08 15:29:42 -04:00
|
|
|
|
2023-09-26 00:37:55 -04:00
|
|
|
auto fileMenu = menuBar()->addMenu(QStringLiteral("File"));
|
2023-07-08 15:29:42 -04:00
|
|
|
|
2023-09-26 00:37:55 -04:00
|
|
|
auto datOpenAction = fileMenu->addAction(QStringLiteral("Load character DAT..."));
|
|
|
|
datOpenAction->setIcon(QIcon::fromTheme(QStringLiteral("document-open")));
|
2023-12-09 22:35:59 -05:00
|
|
|
connect(datOpenAction, &QAction::triggered, [this] {
|
2023-09-26 00:37:55 -04:00
|
|
|
auto fileName =
|
|
|
|
QFileDialog::getOpenFileName(nullptr, QStringLiteral("Open DAT File"), QStringLiteral("~"), QStringLiteral("FFXIV Character DAT File (*.dat)"));
|
2023-07-08 15:29:42 -04:00
|
|
|
|
|
|
|
auto buffer = physis_read_file(fileName.toStdString().c_str());
|
|
|
|
|
|
|
|
auto charDat = physis_chardat_parse(buffer);
|
|
|
|
|
|
|
|
gearView->setRace(charDat.race);
|
|
|
|
gearView->setGender(charDat.gender);
|
2023-10-12 23:44:48 -04:00
|
|
|
// gearView->setSubrace(charDat.subrace);
|
2023-07-08 15:29:42 -04:00
|
|
|
gearView->setFace(charDat.head);
|
|
|
|
gearView->setHair(charDat.hair);
|
|
|
|
updateBustScaling((float)charDat.bust / 100.0f);
|
|
|
|
updateHeightScaling((float)charDat.height / 100.0f);
|
|
|
|
});
|
|
|
|
|
2023-07-07 16:16:21 -04:00
|
|
|
cmp = physis_cmp_parse(physis_gamedata_extract_file(data, "chara/xls/charamake/human.cmp"));
|
|
|
|
|
2023-07-09 10:54:27 -04:00
|
|
|
gearView = new GearView(data, cache);
|
2023-07-07 16:16:21 -04:00
|
|
|
updateCharacterParameters();
|
|
|
|
|
|
|
|
connect(gearView, &GearView::modelReloaded, this, &FullModelViewer::updateCharacterParameters);
|
|
|
|
|
|
|
|
auto viewportLayout = new QHBoxLayout();
|
2023-07-09 11:04:30 -04:00
|
|
|
viewportLayout->setContentsMargins(0, 0, 0, 0);
|
2023-07-07 16:16:21 -04:00
|
|
|
viewportLayout->addWidget(gearView, 1);
|
|
|
|
layout->addLayout(viewportLayout);
|
|
|
|
|
|
|
|
auto characterEditorWidget = new QWidget();
|
|
|
|
auto characterEditorLayout = new QFormLayout();
|
|
|
|
characterEditorWidget->setLayout(characterEditorLayout);
|
|
|
|
|
|
|
|
auto characterHeight = new QSlider();
|
|
|
|
characterHeight->setOrientation(Qt::Horizontal);
|
|
|
|
characterHeight->setSliderPosition(50);
|
|
|
|
connect(characterHeight, &QSlider::sliderMoved, this, [this](int position) {
|
|
|
|
const float scale = (float)position / 100.0f;
|
|
|
|
updateHeightScaling(scale);
|
|
|
|
});
|
2023-09-26 00:37:55 -04:00
|
|
|
characterEditorLayout->addRow(QStringLiteral("Height"), characterHeight);
|
2023-07-07 16:16:21 -04:00
|
|
|
|
|
|
|
auto bustSize = new QSlider();
|
|
|
|
bustSize->setOrientation(Qt::Horizontal);
|
|
|
|
bustSize->setSliderPosition(50);
|
|
|
|
connect(bustSize, &QSlider::sliderMoved, this, [this](int position) {
|
|
|
|
const float scale = (float)position / 100.0f;
|
|
|
|
updateBustScaling(scale);
|
|
|
|
});
|
2023-09-26 00:37:55 -04:00
|
|
|
characterEditorLayout->addRow(QStringLiteral("Bust Size"), bustSize);
|
2023-07-07 16:16:21 -04:00
|
|
|
|
2023-07-08 15:29:42 -04:00
|
|
|
characterEditorLayout->addWidget(addFaceGroup());
|
|
|
|
characterEditorLayout->addWidget(addHairGroup());
|
|
|
|
characterEditorLayout->addWidget(addEarGroup());
|
|
|
|
characterEditorLayout->addWidget(addTailGroup());
|
|
|
|
|
2023-07-07 16:16:21 -04:00
|
|
|
auto tabWidget = new QTabWidget();
|
2023-09-26 00:37:55 -04:00
|
|
|
tabWidget->addTab(new BoneEditor(gearView), QStringLiteral("Bone Editor"));
|
|
|
|
tabWidget->addTab(characterEditorWidget, QStringLiteral("Character Editor"));
|
2023-07-07 16:16:21 -04:00
|
|
|
viewportLayout->addWidget(tabWidget);
|
|
|
|
|
|
|
|
auto controlLayout = new QHBoxLayout();
|
|
|
|
layout->addLayout(controlLayout);
|
|
|
|
|
|
|
|
raceCombo = new QComboBox();
|
|
|
|
controlLayout->addWidget(raceCombo);
|
|
|
|
|
|
|
|
for (auto [race, race_name] : magic_enum::enum_entries<Race>()) {
|
2023-09-26 00:37:55 -04:00
|
|
|
raceCombo->addItem(QLatin1String(race_name.data()), (int)race);
|
2023-04-09 15:31:19 -04:00
|
|
|
}
|
|
|
|
|
2023-07-07 16:29:43 -04:00
|
|
|
subraceCombo = new QComboBox();
|
|
|
|
connect(subraceCombo, qOverload<int>(&QComboBox::currentIndexChanged), [this](int index) {
|
|
|
|
gearView->setSubrace((Subrace)subraceCombo->itemData(index).toInt());
|
|
|
|
});
|
|
|
|
controlLayout->addWidget(subraceCombo);
|
|
|
|
|
|
|
|
connect(raceCombo, qOverload<int>(&QComboBox::currentIndexChanged), [this](int index) {
|
|
|
|
gearView->setRace((Race)raceCombo->itemData(index).toInt());
|
|
|
|
|
|
|
|
updateSupportedSubraces();
|
|
|
|
});
|
|
|
|
updateSupportedSubraces();
|
|
|
|
|
2023-04-09 15:31:19 -04:00
|
|
|
genderCombo = new QComboBox();
|
|
|
|
connect(genderCombo, qOverload<int>(&QComboBox::currentIndexChanged), [this](int index) {
|
2023-07-07 16:29:43 -04:00
|
|
|
gearView->setGender((Gender)genderCombo->itemData(index).toInt());
|
2023-04-09 15:31:19 -04:00
|
|
|
});
|
|
|
|
controlLayout->addWidget(genderCombo);
|
|
|
|
|
|
|
|
for (auto [gender, gender_name] : magic_enum::enum_entries<Gender>()) {
|
2023-09-26 00:37:55 -04:00
|
|
|
genderCombo->addItem(QLatin1String(gender_name.data()), (int)gender);
|
2023-04-09 15:31:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
connect(this, &FullModelViewer::gearChanged, this, &FullModelViewer::reloadGear);
|
2023-09-25 23:48:03 -04:00
|
|
|
connect(gearView, &GearView::loadingChanged, this, &FullModelViewer::loadingChanged);
|
|
|
|
connect(this, &FullModelViewer::loadingChanged, this, [this, tabWidget](const bool loading) {
|
|
|
|
raceCombo->setEnabled(!loading);
|
|
|
|
subraceCombo->setEnabled(!loading);
|
|
|
|
genderCombo->setEnabled(!loading);
|
|
|
|
tabWidget->setEnabled(!loading);
|
|
|
|
});
|
2023-04-09 15:31:19 -04:00
|
|
|
|
|
|
|
reloadGear();
|
|
|
|
}
|
|
|
|
|
2023-10-12 23:44:48 -04:00
|
|
|
void FullModelViewer::clear()
|
|
|
|
{
|
2023-04-09 15:31:19 -04:00
|
|
|
topSlot.reset();
|
|
|
|
bottomSlot.reset();
|
|
|
|
|
|
|
|
Q_EMIT gearChanged();
|
|
|
|
}
|
|
|
|
|
2023-10-12 23:44:48 -04:00
|
|
|
void FullModelViewer::addGear(GearInfo &info)
|
|
|
|
{
|
2023-07-07 16:16:21 -04:00
|
|
|
switch (info.slot) {
|
2023-10-12 23:44:48 -04:00
|
|
|
case Slot::Body:
|
2023-12-09 15:24:54 -05:00
|
|
|
if (!topSlot || *topSlot != info) {
|
2023-04-09 15:31:19 -04:00
|
|
|
topSlot = info;
|
2023-09-25 23:48:03 -04:00
|
|
|
}
|
2023-10-12 23:44:48 -04:00
|
|
|
break;
|
|
|
|
case Slot::Legs:
|
2023-12-09 15:24:54 -05:00
|
|
|
if (!bottomSlot || *bottomSlot != info) {
|
2023-04-09 15:31:19 -04:00
|
|
|
bottomSlot = info;
|
2023-10-12 23:44:48 -04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
2023-04-09 15:31:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
Q_EMIT gearChanged();
|
|
|
|
}
|
|
|
|
|
2023-09-25 23:48:03 -04:00
|
|
|
void FullModelViewer::reloadGear()
|
|
|
|
{
|
2023-04-09 15:31:19 -04:00
|
|
|
if (topSlot.has_value()) {
|
|
|
|
gearView->addGear(*topSlot);
|
|
|
|
} else {
|
|
|
|
// smallclothes body
|
|
|
|
GearInfo info = {};
|
|
|
|
info.name = "Smallclothes Body";
|
|
|
|
info.slot = Slot::Body;
|
|
|
|
|
|
|
|
gearView->addGear(info);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bottomSlot.has_value()) {
|
|
|
|
gearView->addGear(*bottomSlot);
|
|
|
|
} else {
|
|
|
|
// smallclothes legs
|
|
|
|
GearInfo info = {};
|
|
|
|
info.name = "Smallclothes Legs";
|
|
|
|
info.slot = Slot::Legs;
|
|
|
|
|
|
|
|
gearView->addGear(info);
|
|
|
|
}
|
2023-07-08 11:58:23 -04:00
|
|
|
|
|
|
|
// smallclothes hands
|
|
|
|
{
|
|
|
|
GearInfo info = {};
|
|
|
|
info.name = "Smallclothes Hands";
|
|
|
|
info.slot = Slot::Hands;
|
|
|
|
|
|
|
|
gearView->addGear(info);
|
|
|
|
}
|
|
|
|
|
|
|
|
// smallclothes hands
|
|
|
|
{
|
|
|
|
GearInfo info = {};
|
|
|
|
info.name = "Smallclothes Feet";
|
|
|
|
info.slot = Slot::Feet;
|
|
|
|
|
|
|
|
gearView->addGear(info);
|
|
|
|
}
|
2023-04-09 15:31:19 -04:00
|
|
|
}
|
|
|
|
|
2023-10-12 23:44:48 -04:00
|
|
|
void FullModelViewer::updateHeightScaling(float scale)
|
|
|
|
{
|
|
|
|
auto &boneData = *gearView->part().skeleton;
|
2023-12-09 22:35:59 -05:00
|
|
|
for (uint32_t i = 0; i < boneData.num_bones; i++) {
|
2023-07-07 16:02:45 -04:00
|
|
|
const std::string_view name{boneData.bones[i].name};
|
|
|
|
if (name == "n_root") {
|
2023-10-12 23:44:48 -04:00
|
|
|
auto racialScaling = physis_cmp_get_racial_scaling_parameters(cmp, gearView->currentRace, gearView->currentSubrace);
|
2023-07-07 16:02:45 -04:00
|
|
|
|
2023-10-12 23:44:48 -04:00
|
|
|
const float minSize = gearView->currentGender == Gender::Male ? racialScaling.male_min_size : racialScaling.female_min_size;
|
|
|
|
const float maxSize = gearView->currentGender == Gender::Male ? racialScaling.male_max_size : racialScaling.female_max_size;
|
2023-07-07 16:02:45 -04:00
|
|
|
|
2023-07-07 16:16:21 -04:00
|
|
|
const float size = glm::mix(minSize, maxSize, scale);
|
2023-07-07 16:02:45 -04:00
|
|
|
|
2023-07-07 16:16:21 -04:00
|
|
|
boneData.bones[i].scale[0] = size;
|
|
|
|
boneData.bones[i].scale[1] = size;
|
|
|
|
boneData.bones[i].scale[2] = size;
|
2023-07-07 16:02:45 -04:00
|
|
|
|
2023-07-07 16:16:21 -04:00
|
|
|
gearView->part().reloadRenderer();
|
2023-07-07 16:02:45 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
heightScale = scale;
|
|
|
|
}
|
|
|
|
|
2023-10-12 23:44:48 -04:00
|
|
|
void FullModelViewer::updateBustScaling(float scale)
|
|
|
|
{
|
|
|
|
auto &boneData = *gearView->part().skeleton;
|
2023-12-09 22:35:59 -05:00
|
|
|
for (uint32_t i = 0; i < boneData.num_bones; i++) {
|
2023-07-07 16:02:45 -04:00
|
|
|
const std::string_view name{boneData.bones[i].name};
|
|
|
|
if (name == "j_mune_l" || name == "j_mune_r") {
|
2023-10-12 23:44:48 -04:00
|
|
|
auto racialScaling = physis_cmp_get_racial_scaling_parameters(cmp, gearView->currentRace, gearView->currentSubrace);
|
2023-07-07 16:02:45 -04:00
|
|
|
|
2023-07-07 16:16:21 -04:00
|
|
|
const float rangeX = glm::mix(racialScaling.bust_min_x, racialScaling.bust_max_x, scale);
|
|
|
|
const float rangeY = glm::mix(racialScaling.bust_min_y, racialScaling.bust_max_y, scale);
|
|
|
|
const float rangeZ = glm::mix(racialScaling.bust_min_z, racialScaling.bust_max_z, scale);
|
2023-07-07 16:02:45 -04:00
|
|
|
|
2023-07-07 16:16:21 -04:00
|
|
|
boneData.bones[i].scale[0] = rangeX;
|
|
|
|
boneData.bones[i].scale[1] = rangeY;
|
|
|
|
boneData.bones[i].scale[2] = rangeZ;
|
2023-07-07 16:02:45 -04:00
|
|
|
|
2023-07-07 16:16:21 -04:00
|
|
|
gearView->part().reloadRenderer();
|
2023-07-07 16:02:45 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bustScale = scale;
|
|
|
|
}
|
|
|
|
|
2023-10-12 23:44:48 -04:00
|
|
|
void FullModelViewer::updateCharacterParameters()
|
|
|
|
{
|
2023-07-07 16:02:45 -04:00
|
|
|
updateHeightScaling(heightScale);
|
|
|
|
updateBustScaling(bustScale);
|
|
|
|
}
|
|
|
|
|
2023-10-12 23:44:48 -04:00
|
|
|
void FullModelViewer::updateSupportedSubraces()
|
|
|
|
{
|
2023-07-07 16:29:43 -04:00
|
|
|
subraceCombo->clear();
|
|
|
|
for (auto subrace : physis_get_supported_subraces(gearView->currentRace).subraces) {
|
2023-09-26 00:37:55 -04:00
|
|
|
subraceCombo->addItem(QLatin1String(magic_enum::enum_name(subrace).data()), (int)subrace);
|
2023-07-07 16:29:43 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-12 23:44:48 -04:00
|
|
|
QGroupBox *FullModelViewer::addFaceGroup()
|
|
|
|
{
|
2023-09-26 00:37:55 -04:00
|
|
|
auto faceGroup = new QGroupBox(QStringLiteral("Face"));
|
2023-07-08 15:29:42 -04:00
|
|
|
auto faceGroupLayout = new QVBoxLayout();
|
|
|
|
faceGroup->setLayout(faceGroupLayout);
|
|
|
|
|
2023-09-26 00:37:55 -04:00
|
|
|
auto faceRadio1 = new QRadioButton(QStringLiteral("Face 1"));
|
2023-12-09 22:35:59 -05:00
|
|
|
connect(faceRadio1, &QRadioButton::clicked, this, [this] {
|
2023-07-08 15:29:42 -04:00
|
|
|
gearView->setFace(1);
|
|
|
|
});
|
|
|
|
faceGroupLayout->addWidget(faceRadio1);
|
|
|
|
|
2023-09-26 00:37:55 -04:00
|
|
|
auto faceRadio2 = new QRadioButton(QStringLiteral("Face 2"));
|
2023-12-09 22:35:59 -05:00
|
|
|
connect(faceRadio2, &QRadioButton::clicked, this, [this] {
|
2023-07-08 15:29:42 -04:00
|
|
|
gearView->setFace(2);
|
|
|
|
});
|
|
|
|
faceGroupLayout->addWidget(faceRadio2);
|
|
|
|
|
2023-09-26 00:37:55 -04:00
|
|
|
auto faceRadio3 = new QRadioButton(QStringLiteral("Face 3"));
|
2023-12-09 22:35:59 -05:00
|
|
|
connect(faceRadio3, &QRadioButton::clicked, this, [this] {
|
2023-07-08 15:29:42 -04:00
|
|
|
gearView->setFace(3);
|
|
|
|
});
|
|
|
|
faceGroupLayout->addWidget(faceRadio3);
|
|
|
|
|
|
|
|
return faceGroup;
|
|
|
|
}
|
|
|
|
|
2023-10-12 23:44:48 -04:00
|
|
|
QGroupBox *FullModelViewer::addHairGroup()
|
|
|
|
{
|
2023-09-26 00:37:55 -04:00
|
|
|
auto hairGroup = new QGroupBox(QStringLiteral("Hair"));
|
2023-07-08 15:29:42 -04:00
|
|
|
auto hairGroupLayout = new QVBoxLayout();
|
|
|
|
hairGroup->setLayout(hairGroupLayout);
|
|
|
|
|
2023-09-26 00:37:55 -04:00
|
|
|
auto hairRadio1 = new QRadioButton(QStringLiteral("Hair 1"));
|
2023-12-09 22:35:59 -05:00
|
|
|
connect(hairRadio1, &QRadioButton::clicked, this, [this] {
|
2023-07-08 15:29:42 -04:00
|
|
|
gearView->setHair(1);
|
|
|
|
});
|
|
|
|
hairGroupLayout->addWidget(hairRadio1);
|
|
|
|
|
2023-09-26 00:37:55 -04:00
|
|
|
auto hairRadio2 = new QRadioButton(QStringLiteral("Hair 2"));
|
2023-12-09 22:35:59 -05:00
|
|
|
connect(hairRadio2, &QRadioButton::clicked, this, [this] {
|
2023-07-08 15:29:42 -04:00
|
|
|
gearView->setHair(2);
|
|
|
|
});
|
|
|
|
hairGroupLayout->addWidget(hairRadio2);
|
|
|
|
|
2023-09-26 00:37:55 -04:00
|
|
|
auto hairRadio3 = new QRadioButton(QStringLiteral("Hair 3"));
|
2023-12-09 22:35:59 -05:00
|
|
|
connect(hairRadio3, &QRadioButton::clicked, this, [this] {
|
2023-07-08 15:29:42 -04:00
|
|
|
gearView->setHair(3);
|
|
|
|
});
|
|
|
|
hairGroupLayout->addWidget(hairRadio3);
|
|
|
|
|
|
|
|
return hairGroup;
|
|
|
|
}
|
|
|
|
|
2023-10-12 23:44:48 -04:00
|
|
|
QGroupBox *FullModelViewer::addEarGroup()
|
|
|
|
{
|
2023-09-26 00:37:55 -04:00
|
|
|
auto earGroup = new QGroupBox(QStringLiteral("Ears"));
|
2023-07-08 15:29:42 -04:00
|
|
|
auto earGroupLayout = new QVBoxLayout();
|
|
|
|
earGroup->setLayout(earGroupLayout);
|
|
|
|
|
2023-09-26 00:37:55 -04:00
|
|
|
auto earRadio1 = new QRadioButton(QStringLiteral("Ears 1"));
|
2023-12-09 22:35:59 -05:00
|
|
|
connect(earRadio1, &QRadioButton::clicked, this, [this] {
|
2023-07-08 15:29:42 -04:00
|
|
|
gearView->setEar(1);
|
|
|
|
});
|
|
|
|
earGroupLayout->addWidget(earRadio1);
|
|
|
|
|
2023-09-26 00:37:55 -04:00
|
|
|
auto earRadio2 = new QRadioButton(QStringLiteral("Ears 2"));
|
2023-12-09 22:35:59 -05:00
|
|
|
connect(earRadio2, &QRadioButton::clicked, this, [this] {
|
2023-07-08 15:29:42 -04:00
|
|
|
gearView->setEar(2);
|
|
|
|
});
|
|
|
|
earGroupLayout->addWidget(earRadio2);
|
|
|
|
|
2023-09-26 00:37:55 -04:00
|
|
|
auto earRadio3 = new QRadioButton(QStringLiteral("Ears 3"));
|
2023-12-09 22:35:59 -05:00
|
|
|
connect(earRadio3, &QRadioButton::clicked, this, [this] {
|
2023-07-08 15:29:42 -04:00
|
|
|
gearView->setEar(3);
|
|
|
|
});
|
|
|
|
earGroupLayout->addWidget(earRadio3);
|
|
|
|
|
|
|
|
return earGroup;
|
|
|
|
}
|
|
|
|
|
2023-10-12 23:44:48 -04:00
|
|
|
QGroupBox *FullModelViewer::addTailGroup()
|
|
|
|
{
|
2023-09-26 00:37:55 -04:00
|
|
|
auto tailGroup = new QGroupBox(QStringLiteral("Tail"));
|
2023-07-08 15:29:42 -04:00
|
|
|
auto tailGroupLayout = new QVBoxLayout();
|
|
|
|
tailGroup->setLayout(tailGroupLayout);
|
|
|
|
|
2023-09-26 00:37:55 -04:00
|
|
|
auto tailRadio1 = new QRadioButton(QStringLiteral("Tail 1"));
|
2023-12-09 22:35:59 -05:00
|
|
|
connect(tailRadio1, &QRadioButton::clicked, this, [this] {
|
2023-07-08 15:29:42 -04:00
|
|
|
gearView->setTail(1);
|
|
|
|
});
|
|
|
|
tailGroupLayout->addWidget(tailRadio1);
|
|
|
|
|
2023-09-26 00:37:55 -04:00
|
|
|
auto tailRadio2 = new QRadioButton(QStringLiteral("Tail 2"));
|
2023-12-09 22:35:59 -05:00
|
|
|
connect(tailRadio2, &QRadioButton::clicked, this, [this] {
|
2023-07-08 15:29:42 -04:00
|
|
|
gearView->setTail(2);
|
|
|
|
});
|
|
|
|
tailGroupLayout->addWidget(tailRadio2);
|
|
|
|
|
2023-09-26 00:37:55 -04:00
|
|
|
auto tailRadio3 = new QRadioButton(QStringLiteral("Tail 3"));
|
2023-12-09 22:35:59 -05:00
|
|
|
connect(tailRadio3, &QRadioButton::clicked, this, [this] {
|
2023-07-08 15:29:42 -04:00
|
|
|
gearView->setTail(3);
|
|
|
|
});
|
|
|
|
tailGroupLayout->addWidget(tailRadio3);
|
|
|
|
|
|
|
|
return tailGroup;
|
|
|
|
}
|
|
|
|
|
2023-04-09 15:31:19 -04:00
|
|
|
#include "moc_fullmodelviewer.cpp"
|