mirror of
https://github.com/leozide/leocad.git
synced 2026-10-26 22:10:32 +00:00
Replace manual piece-data retain/release bookkeeping with shared copyable asset-reference handles. Pieces, models, selection, minifigs, thumbnails, and stored insertion previews retain their dependencies explicitly, while transient queries continue to borrow pointers. Separate registered lookup and acquisition from asynchronous load requests and explicit readiness waits. Keep loader-held references through work and notification delivery, resetting shared request handles on the UI thread and guarding callback-triggered library teardown. Validated with the Qt 6.10 debug build and 26 offscreen lifecycle checks, including cancellation, stale requests, copy/move operations, model/preview teardown, undo, project merge/export, and loader shutdown.
443 lines
13 KiB
C++
443 lines
13 KiB
C++
#include "lc_global.h"
|
|
#include "lc_minifigdialog.h"
|
|
#include "ui_lc_minifigdialog.h"
|
|
#include "lc_viewwidget.h"
|
|
#include "lc_colorpicker.h"
|
|
#include "minifig.h"
|
|
#include "lc_piecedata.h"
|
|
#include "lc_library.h"
|
|
#include "lc_view.h"
|
|
#include "lc_model.h"
|
|
#include "camera.h"
|
|
#include "lc_doublespinbox.h"
|
|
#include "lc_qutils.h"
|
|
#include "lc_partselectionpopup.h"
|
|
|
|
lcMinifigDialog::lcMinifigDialog(QWidget* Parent)
|
|
: QDialog(Parent), ui(new Ui::lcMinifigDialog)
|
|
{
|
|
ui->setupUi(this);
|
|
|
|
QGridLayout* MinifigLayout = ui->MinifigLayout;
|
|
|
|
bool HasSpinBox[LC_MFW_NUMITEMS] = { true, true, true, false, false, false, false, true, true, true, true, true, true, true, true, true, true };
|
|
bool IsLeft[LC_MFW_NUMITEMS] = { true, true, true, true, false, false, false, false, true, false, true, false, true, false, true, false, true };
|
|
int LeftRow = 0, RightRow = 2;
|
|
|
|
QString Labels[LC_MFW_NUMITEMS] =
|
|
{
|
|
tr("Hat"), tr("Hat Accessory"), tr("Head"), tr("Neck"), tr("Torso"), tr("Hip"), tr("Hip Accessory"), tr("Left Arm"), tr("Right Arm"), tr("Left Hand"), tr("Right Hand"),
|
|
tr("Left Hand Accessory"), tr("Right Hand Accessory"), tr("Left Leg"), tr("Right Leg"), tr("Left Leg Accessory"), tr("Right Leg Accessory")
|
|
};
|
|
|
|
QSizePolicy PieceButtonSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
|
PieceButtonSizePolicy.setHorizontalStretch(2);
|
|
PieceButtonSizePolicy.setVerticalStretch(0);
|
|
|
|
QPixmap Pixmap(1, 1);
|
|
Pixmap.fill(QColor::fromRgba64(0, 0, 0, 0));
|
|
|
|
for (int ItemIndex = 0; ItemIndex < LC_MFW_NUMITEMS; ItemIndex++)
|
|
{
|
|
bool Left = IsLeft[ItemIndex];
|
|
|
|
lcElidableToolButton* PieceButton = new lcElidableToolButton(this);
|
|
mPieceButtons[ItemIndex] = PieceButton;
|
|
PieceButton->setSizePolicy(PieceButtonSizePolicy);
|
|
PieceButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
|
PieceButton->setIcon(Pixmap);
|
|
|
|
connect(PieceButton, &QToolButton::clicked, this, &lcMinifigDialog::PieceButtonClicked);
|
|
|
|
lcColorPicker* ColorPicker = new lcColorPicker(this);
|
|
mColorPickers[ItemIndex] = ColorPicker;
|
|
|
|
connect(ColorPicker, &lcColorPicker::ColorChanged, this, &lcMinifigDialog::ColorChanged);
|
|
|
|
lcDoubleSpinBox* SpinBox = HasSpinBox[ItemIndex] ? new lcDoubleSpinBox(this) : nullptr;
|
|
mSpinBoxes[ItemIndex] = SpinBox;
|
|
|
|
if (SpinBox)
|
|
{
|
|
SpinBox->setRange(-360.0, 360.0);
|
|
SpinBox->SetSnap(lcFloatPropertySnap::Rotation);
|
|
SpinBox->setSingleStep(5.0);
|
|
|
|
connect(SpinBox, QOverload<double>::of(&lcDoubleSpinBox::valueChanged), this, &lcMinifigDialog::AngleChanged);
|
|
}
|
|
|
|
QWidget* Label = new QLabel(Labels[ItemIndex], this);
|
|
|
|
auto AddRow=[MinifigLayout, Label, PieceButton, ColorPicker, SpinBox](int& Row, int Column)
|
|
{
|
|
if (Row)
|
|
{
|
|
MinifigLayout->setRowMinimumHeight(Row++, 2);
|
|
}
|
|
|
|
MinifigLayout->addWidget(Label, Row++, Column + 0, 1, 3);
|
|
MinifigLayout->addWidget(PieceButton, Row, Column + 0);
|
|
MinifigLayout->addWidget(ColorPicker, Row, Column + 1);
|
|
|
|
if (SpinBox)
|
|
MinifigLayout->addWidget(SpinBox, Row, Column + 2);
|
|
|
|
Row++;
|
|
};
|
|
|
|
if (Left)
|
|
AddRow(LeftRow, 0);
|
|
else
|
|
AddRow(RightRow, 4);
|
|
}
|
|
|
|
QFrame* PreviewFrame = new QFrame(ui->widget);
|
|
QSizePolicy PreviewSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
|
|
PreviewSizePolicy.setHorizontalStretch(3);
|
|
PreviewSizePolicy.setVerticalStretch(0);
|
|
PreviewFrame->setSizePolicy(PreviewSizePolicy);
|
|
PreviewFrame->setFrameShape(QFrame::NoFrame);
|
|
PreviewFrame->setFrameShadow(QFrame::Plain);
|
|
|
|
MinifigLayout->addWidget(PreviewFrame, 0, 3, -1, 1);
|
|
|
|
QGridLayout* PreviewLayout = new QGridLayout(PreviewFrame);
|
|
PreviewLayout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
mMinifigWizard = new MinifigWizard();
|
|
mView = new lcView(lcViewType::Minifig, mMinifigWizard->GetModel());
|
|
|
|
lcViewWidget* ViewWidget = new lcViewWidget(nullptr, mView);
|
|
ViewWidget->setMinimumWidth(100);
|
|
PreviewLayout->addWidget(ViewWidget);
|
|
|
|
mView->MakeCurrent();
|
|
|
|
mCurrentTemplateName = MinifigWizard::GetDefaultTemplateName();
|
|
mMinifigWizard->LoadTemplate(mCurrentTemplateName);
|
|
|
|
for (int ItemIndex = 0; ItemIndex < LC_MFW_NUMITEMS; ItemIndex++)
|
|
{
|
|
const std::vector<lcMinifigPieceInfo>& PieceList = mMinifigWizard->mSettings[ItemIndex];
|
|
int PieceIndex = mMinifigWizard->GetSelectionIndex(ItemIndex);
|
|
QToolButton* PieceButton = mPieceButtons[ItemIndex];
|
|
|
|
PieceButton->setText(PieceList[PieceIndex].Description);
|
|
|
|
lcColorPicker* ColorPicker = mColorPickers[ItemIndex];
|
|
QSignalBlocker ColorPickerBlocker(ColorPicker);
|
|
|
|
ColorPicker->SetCurrentColor(mMinifigWizard->mMinifig.ColorIndices[ItemIndex]);
|
|
}
|
|
|
|
UpdateTemplateCombo();
|
|
|
|
mMinifigWizard->Calculate();
|
|
mView->GetCamera()->SetViewpoint(lcVector3(0.0f, -270.0f, 90.0f));
|
|
mView->ZoomExtents(lcGeometryBoundsMode::IncludeFallback, false);
|
|
|
|
ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)->setFocus();
|
|
|
|
connect(ui->TemplateComboBox, &QComboBox::currentTextChanged, this, &lcMinifigDialog::TemplateComboBoxCurrentTextChanged);
|
|
connect(ui->TemplateSaveButton, &QPushButton::clicked, this, &lcMinifigDialog::TemplateSaveButtonClicked);
|
|
connect(ui->TemplateDeleteButton, &QPushButton::clicked, this, &lcMinifigDialog::TemplateDeleteButtonClicked);
|
|
connect(ui->TemplateImportButton, &QPushButton::clicked, this, &lcMinifigDialog::TemplateImportButtonClicked);
|
|
connect(ui->TemplateExportButton, &QPushButton::clicked, this, &lcMinifigDialog::TemplateExportButtonClicked);
|
|
}
|
|
|
|
lcMinifigDialog::~lcMinifigDialog()
|
|
{
|
|
delete mMinifigWizard;
|
|
delete ui;
|
|
}
|
|
|
|
void lcMinifigDialog::UpdateTemplateCombo()
|
|
{
|
|
QSignalBlocker Blocker(ui->TemplateComboBox);
|
|
|
|
ui->TemplateComboBox->clear();
|
|
|
|
if (mCurrentTemplateName.isEmpty())
|
|
ui->TemplateComboBox->addItem(QString());
|
|
|
|
QString CurrentName = mCurrentTemplateName;
|
|
|
|
for (const auto& [Name, Template] : mMinifigWizard->GetTemplates())
|
|
{
|
|
if (Name == MinifigWizard::GetDefaultTemplateName())
|
|
continue;
|
|
|
|
if (mCurrentTemplateModified && Name == mCurrentTemplateName)
|
|
{
|
|
CurrentName += " *";
|
|
ui->TemplateComboBox->addItem(CurrentName);
|
|
}
|
|
else
|
|
ui->TemplateComboBox->addItem(Name);
|
|
}
|
|
|
|
if (ui->TemplateComboBox->count())
|
|
ui->TemplateComboBox->insertSeparator(ui->TemplateComboBox->count());
|
|
ui->TemplateComboBox->addItem(MinifigWizard::GetDefaultTemplateName());
|
|
|
|
ui->TemplateComboBox->setCurrentText(CurrentName);
|
|
}
|
|
|
|
void lcMinifigDialog::TemplateComboBoxCurrentTextChanged(const QString& TemplateName)
|
|
{
|
|
const auto& Templates = mMinifigWizard->GetTemplates();
|
|
const auto& Position = Templates.find(TemplateName);
|
|
|
|
if (Position == Templates.end())
|
|
return;
|
|
|
|
mCurrentTemplateName = TemplateName;
|
|
mCurrentTemplateModified = false;
|
|
|
|
UpdateTemplateCombo();
|
|
|
|
const lcMinifigTemplate& Template = Position->second;
|
|
|
|
mView->MakeCurrent();
|
|
|
|
mMinifigWizard->LoadTemplate(TemplateName);
|
|
|
|
for (int PartIdx = 0; PartIdx < LC_MFW_NUMITEMS; PartIdx++)
|
|
{
|
|
if (!Template.Parts[PartIdx].isEmpty())
|
|
{
|
|
lcPieceData* PieceData = mMinifigWizard->mMinifig.PieceData[PartIdx].Get();
|
|
|
|
if (PieceData)
|
|
{
|
|
for (const lcMinifigPieceInfo& MinifigPieceInfo : mMinifigWizard->mSettings[PartIdx])
|
|
{
|
|
if (PieceData == MinifigPieceInfo.PieceData)
|
|
{
|
|
mPieceButtons[PartIdx]->setText(MinifigPieceInfo.Description);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mPieceButtons[PartIdx]->setText(tr("None"));
|
|
}
|
|
|
|
QSignalBlocker ColorBlocker(mColorPickers[PartIdx]);
|
|
|
|
mColorPickers[PartIdx]->SetCurrentColorCode(Template.ColorCodes[PartIdx]);
|
|
|
|
lcDoubleSpinBox* AngleSpinBox = mSpinBoxes[PartIdx];
|
|
|
|
if (AngleSpinBox)
|
|
{
|
|
QSignalBlocker AngleBlocker(AngleSpinBox);
|
|
|
|
AngleSpinBox->SetValue(Template.Angles[PartIdx]);
|
|
}
|
|
}
|
|
|
|
mView->Redraw();
|
|
}
|
|
|
|
void lcMinifigDialog::TemplateSaveButtonClicked()
|
|
{
|
|
QString CurrentName = mCurrentTemplateName;
|
|
bool Ok;
|
|
QString TemplateName = QInputDialog::getText(this, tr("Save Template"), tr("Template Name:"), QLineEdit::Normal, CurrentName, &Ok);
|
|
|
|
if (!Ok)
|
|
return;
|
|
|
|
if (TemplateName.isEmpty())
|
|
{
|
|
QMessageBox::information(this, tr("Save Template"), tr("Template name cannot be empty."));
|
|
return;
|
|
}
|
|
|
|
if (TemplateName == MinifigWizard::GetDefaultTemplateName())
|
|
{
|
|
QMessageBox::information(this, tr("Save Template"), tr("Cannot overwrite the default template."));
|
|
return;
|
|
}
|
|
|
|
if (mMinifigWizard->GetTemplates().count(TemplateName))
|
|
{
|
|
QString Question = tr("Are you sure you want to overwrite the template '%1'?").arg(TemplateName);
|
|
if (QMessageBox::question(this, tr("Overwrite Template"), Question, QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
|
|
return;
|
|
}
|
|
|
|
lcMinifigTemplate Template;
|
|
|
|
for (int ItemIndex = 0; ItemIndex < LC_MFW_NUMITEMS; ItemIndex++)
|
|
{
|
|
Template.Parts[ItemIndex] = mMinifigWizard->mSettings[ItemIndex][mMinifigWizard->GetSelectionIndex(ItemIndex)].PieceData->mFileName;
|
|
Template.ColorCodes[ItemIndex] = mColorPickers[ItemIndex]->GetCurrentColorCode();
|
|
QDoubleSpinBox* AngleSpinBox = mSpinBoxes[ItemIndex];
|
|
Template.Angles[ItemIndex] = AngleSpinBox ? AngleSpinBox->value() : 0.0f;
|
|
}
|
|
|
|
mMinifigWizard->SaveTemplate(TemplateName, Template);
|
|
|
|
mCurrentTemplateName = TemplateName;
|
|
mCurrentTemplateModified = false;
|
|
|
|
UpdateTemplateCombo();
|
|
}
|
|
|
|
void lcMinifigDialog::TemplateDeleteButtonClicked()
|
|
{
|
|
if (mCurrentTemplateName.isEmpty() || mCurrentTemplateName == MinifigWizard::GetDefaultTemplateName())
|
|
return;
|
|
|
|
QString Template = mCurrentTemplateName;
|
|
QString Question = tr("Are you sure you want to delete the template '%1'?").arg(Template);
|
|
|
|
if (QMessageBox::question(this, tr("Delete Template"), Question, QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
|
|
return;
|
|
|
|
mMinifigWizard->DeleteTemplate(Template);
|
|
|
|
mCurrentTemplateName.clear();
|
|
mCurrentTemplateModified = false;
|
|
|
|
UpdateTemplateCombo();
|
|
}
|
|
|
|
void lcMinifigDialog::TemplateImportButtonClicked()
|
|
{
|
|
QString FileName = QFileDialog::getOpenFileName(this, tr("Import Templates"), "", tr("Minifig Template Files (*.minifig);;All Files (*.*)"));
|
|
|
|
if (FileName.isEmpty())
|
|
return;
|
|
|
|
QFile File(FileName);
|
|
|
|
if (!File.open(QIODevice::ReadOnly))
|
|
{
|
|
QMessageBox::warning(this, tr("Error"), tr("Error reading file '%1':\n%2").arg(FileName, File.errorString()));
|
|
return;
|
|
}
|
|
|
|
QByteArray FileData = File.readAll();
|
|
mMinifigWizard->AddTemplatesJson(FileData);
|
|
|
|
UpdateTemplateCombo();
|
|
}
|
|
|
|
void lcMinifigDialog::TemplateExportButtonClicked()
|
|
{
|
|
QString FileName = QFileDialog::getSaveFileName(this, tr("Export Templates"), "", tr("Minifig Template Files (*.minifig);;All Files (*.*)"));
|
|
|
|
if (FileName.isEmpty())
|
|
return;
|
|
|
|
QFile File(FileName);
|
|
|
|
if (!File.open(QIODevice::WriteOnly))
|
|
{
|
|
QMessageBox::warning(this, tr("Error"), tr("Error writing to file '%1':\n%2").arg(FileName, File.errorString()));
|
|
return;
|
|
}
|
|
|
|
QByteArray Templates = mMinifigWizard->GetTemplatesJson();
|
|
File.write(Templates);
|
|
}
|
|
|
|
void lcMinifigDialog::SetCurrentTemplateModified()
|
|
{
|
|
if (mCurrentTemplateName == MinifigWizard::GetDefaultTemplateName())
|
|
mCurrentTemplateName.clear();
|
|
else
|
|
mCurrentTemplateModified = true;
|
|
}
|
|
|
|
void lcMinifigDialog::PieceButtonClicked()
|
|
{
|
|
QToolButton* PieceButton = qobject_cast<QToolButton*>(sender());
|
|
|
|
if (!PieceButton)
|
|
return;
|
|
|
|
std::array<QToolButton*, LC_MFW_NUMITEMS>::iterator Search = std::find(mPieceButtons.begin(), mPieceButtons.end(), PieceButton);
|
|
|
|
if (Search == mPieceButtons.end())
|
|
return;
|
|
|
|
int ItemIndex = static_cast<int>(std::distance(mPieceButtons.begin(), Search));
|
|
lcPieceData* CurrentPieceData = mMinifigWizard->mMinifig.PieceData[ItemIndex].Get();
|
|
|
|
QPoint Position = PieceButton->mapToGlobal(PieceButton->rect().bottomLeft());
|
|
std::vector<std::pair<lcPieceData*, std::string>> Parts;
|
|
|
|
Parts.reserve(mMinifigWizard->mSettings[ItemIndex].size());
|
|
Parts.emplace_back(nullptr, tr("None").toStdString());
|
|
|
|
for (const lcMinifigPieceInfo& Setting : mMinifigWizard->mSettings[ItemIndex])
|
|
if (Setting.PieceData)
|
|
Parts.emplace_back(Setting.PieceData, Setting.Description);
|
|
|
|
int ColorIndex = mMinifigWizard->mMinifig.ColorIndices[ItemIndex];
|
|
|
|
std::optional<lcPieceData*> Result = lcShowPartSelectionPopup(CurrentPieceData, Parts, ColorIndex, PieceButton, Position);
|
|
|
|
if (!Result.has_value())
|
|
return;
|
|
|
|
lcPieceData* NewPiece = Result.value();
|
|
|
|
SetCurrentTemplateModified();
|
|
UpdateTemplateCombo();
|
|
|
|
mView->MakeCurrent();
|
|
mMinifigWizard->SetPieceData(ItemIndex, NewPiece);
|
|
mView->Redraw();
|
|
|
|
if (NewPiece)
|
|
{
|
|
const std::vector<lcMinifigPieceInfo>& PieceList = mMinifigWizard->mSettings[ItemIndex];
|
|
int PieceIndex = mMinifigWizard->GetSelectionIndex(ItemIndex);
|
|
|
|
PieceButton->setText(PieceList[PieceIndex].Description);
|
|
}
|
|
else
|
|
{
|
|
PieceButton->setText("None");
|
|
}
|
|
}
|
|
|
|
void lcMinifigDialog::ColorChanged(int Index)
|
|
{
|
|
std::array<lcColorPicker*, LC_MFW_NUMITEMS>::iterator Search = std::find(mColorPickers.begin(), mColorPickers.end(), sender());
|
|
|
|
if (Search == mColorPickers.end())
|
|
return;
|
|
|
|
SetCurrentTemplateModified();
|
|
UpdateTemplateCombo();
|
|
|
|
int ItemIndex = static_cast<int>(std::distance(mColorPickers.begin(), Search));
|
|
|
|
mMinifigWizard->SetColorIndex(ItemIndex, Index);
|
|
mView->Redraw();
|
|
}
|
|
|
|
void lcMinifigDialog::AngleChanged(double Value)
|
|
{
|
|
std::array<lcDoubleSpinBox*, LC_MFW_NUMITEMS>::iterator Search = std::find(mSpinBoxes.begin(), mSpinBoxes.end(), sender());
|
|
|
|
if (Search == mSpinBoxes.end())
|
|
return;
|
|
|
|
SetCurrentTemplateModified();
|
|
UpdateTemplateCombo();
|
|
|
|
int ItemIndex = static_cast<int>(std::distance(mSpinBoxes.begin(), Search));
|
|
|
|
mMinifigWizard->SetAngle(ItemIndex, Value);
|
|
mView->Redraw();
|
|
}
|