Files
leocad/qt/lc_qpropertiesdialog.cpp
T
Leonardo Zide 7bc6c018b1 Rename PieceInfo to lcPieceData
Use PieceData consistently for the shared part/model record, its related
APIs and variables, and its source files. Keep the existing reference
counting and loading behavior so ownership and readiness changes can be
reviewed separately.
2026-10-18 23:22:28 -07:00

354 lines
12 KiB
C++

#include "lc_global.h"
#include "lc_qpropertiesdialog.h"
#include "ui_lc_qpropertiesdialog.h"
#include "lc_qutils.h"
#include "lc_colors.h"
#include "lc_application.h"
#include "lc_piecedata.h"
class lcFrozenPartsView : public QTableView
{
public:
explicit lcFrozenPartsView(lcPartsTableWidget* PartsTable)
: QTableView(PartsTable), mPartsTable(PartsTable)
{
}
protected:
QItemSelectionModel::SelectionFlags selectionCommand(const QModelIndex& Index, const QEvent* Event = nullptr) const override
{
return mPartsTable->TrackSelectionCommand(Index, Event, QTableView::selectionCommand(Index, Event));
}
void setSelection(const QRect& Rect, QItemSelectionModel::SelectionFlags Command) override
{
if (!mPartsTable->SelectSharedRange(Command))
QTableView::setSelection(Rect, Command);
}
private:
lcPartsTableWidget* mPartsTable;
};
lcPartsTableWidget::lcPartsTableWidget(QWidget* Parent)
: QTableWidget(Parent)
{
}
void lcPartsTableWidget::FreezePartColumn()
{
mPartColumnWidth = qMax(sizeHintForColumn(0), horizontalHeader()->sectionSizeHint(0));
horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed);
setTextElideMode(Qt::ElideRight);
for (int Row = 0; Row < rowCount(); Row++)
if (QTableWidgetItem* Item = item(Row, 0))
Item->setToolTip(Item->text());
// Both views share the data and selection, so sorting and row highlights stay in sync.
mFrozenColumn = new lcFrozenPartsView(this);
mFrozenColumn->setModel(model());
mFrozenColumn->setSelectionModel(selectionModel());
mFrozenColumn->setSelectionBehavior(QAbstractItemView::SelectRows);
mFrozenColumn->setSelectionMode(selectionMode());
mFrozenColumn->setEditTriggers(QAbstractItemView::NoEditTriggers);
mFrozenColumn->setTextElideMode(Qt::ElideRight);
mFrozenColumn->setFocusPolicy(Qt::NoFocus);
mFrozenColumn->setFocusProxy(this);
mFrozenColumn->setFrameShape(QFrame::NoFrame);
mFrozenColumn->verticalHeader()->hide();
mFrozenColumn->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed);
mFrozenColumn->horizontalHeader()->setSectionsClickable(true);
mFrozenColumn->horizontalHeader()->setSortIndicatorShown(true);
mFrozenColumn->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
mFrozenColumn->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
mFrozenColumn->viewport()->installEventFilter(this);
mFrozenColumn->horizontalHeader()->viewport()->installEventFilter(this);
setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
mFrozenColumn->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
for (int Column = 1; Column < columnCount(); Column++)
mFrozenColumn->hideColumn(Column);
for (int Row = 0; Row < rowCount(); Row++)
mFrozenColumn->setRowHeight(Row, rowHeight(Row));
connect(verticalScrollBar(), &QScrollBar::valueChanged, mFrozenColumn->verticalScrollBar(), &QScrollBar::setValue);
connect(mFrozenColumn->verticalScrollBar(), &QScrollBar::valueChanged, verticalScrollBar(), &QScrollBar::setValue);
connect(verticalHeader(), &QHeaderView::sectionResized, this, [this](int Row, int, int Height)
{
mFrozenColumn->setRowHeight(Row, Height);
});
connect(horizontalHeader(), &QHeaderView::sectionResized, this, [this](int Column, int, int)
{
if (Column == 0)
UpdateFrozenGeometry();
});
connect(horizontalHeader(), &QHeaderView::sortIndicatorChanged, mFrozenColumn->horizontalHeader(), &QHeaderView::setSortIndicator);
connect(mFrozenColumn->horizontalHeader(), &QHeaderView::sectionClicked, this, [this](int)
{
const Qt::SortOrder Order = horizontalHeader()->sortIndicatorSection() == 0 && horizontalHeader()->sortIndicatorOrder() == Qt::AscendingOrder
? Qt::DescendingOrder : Qt::AscendingOrder;
horizontalHeader()->setSortIndicator(0, Order);
});
mFrozenColumn->horizontalHeader()->setSortIndicator(horizontalHeader()->sortIndicatorSection(), horizontalHeader()->sortIndicatorOrder());
viewport()->stackUnder(mFrozenColumn);
UpdateFrozenGeometry();
mFrozenColumn->show();
}
bool lcPartsTableWidget::eventFilter(QObject* Object, QEvent* Event)
{
if (mFrozenColumn && Event->type() == QEvent::Wheel &&
(Object == mFrozenColumn->viewport() || Object == mFrozenColumn->horizontalHeader()->viewport()))
{
QCoreApplication::sendEvent(viewport(), Event);
return true;
}
return QTableWidget::eventFilter(Object, Event);
}
QItemSelectionModel::SelectionFlags lcPartsTableWidget::TrackSelectionCommand(const QModelIndex& Index, const QEvent* Event, QItemSelectionModel::SelectionFlags Command) const
{
// Qt keeps a separate range anchor in each view, even with a shared selection model.
mSelectionEnd = Index;
if (Command.testFlag(QItemSelectionModel::Current))
{
if (!mSelectionAnchor.isValid())
mSelectionAnchor = currentIndex().isValid() ? currentIndex() : Index;
}
else if (Command != QItemSelectionModel::NoUpdate || (Event && Event->type() == QEvent::MouseButtonPress))
mSelectionAnchor = Index;
return Command;
}
QItemSelectionModel::SelectionFlags lcPartsTableWidget::selectionCommand(const QModelIndex& Index, const QEvent* Event) const
{
return TrackSelectionCommand(Index, Event, QTableWidget::selectionCommand(Index, Event));
}
bool lcPartsTableWidget::SelectSharedRange(QItemSelectionModel::SelectionFlags Command)
{
if (!Command.testFlag(QItemSelectionModel::Current) || !mSelectionAnchor.isValid() || !mSelectionEnd.isValid())
return false;
const int FirstRow = qMin(mSelectionAnchor.row(), mSelectionEnd.row());
const int LastRow = qMax(mSelectionAnchor.row(), mSelectionEnd.row());
selectionModel()->select(QItemSelection(model()->index(FirstRow, 0), model()->index(LastRow, columnCount() - 1)), Command);
return true;
}
void lcPartsTableWidget::setSelection(const QRect& Rect, QItemSelectionModel::SelectionFlags Command)
{
if (!SelectSharedRange(Command))
QTableWidget::setSelection(Rect, Command);
}
void lcPartsTableWidget::UpdateFrozenGeometry()
{
if (!mFrozenColumn)
return;
// Reserve at least half the viewport for counts, while letting short names use less space.
const int PartWidth = qMin(mPartColumnWidth, qMax(1, viewport()->width() / 2));
if (columnWidth(0) != PartWidth)
setColumnWidth(0, PartWidth);
mFrozenColumn->setColumnWidth(0, columnWidth(0));
mFrozenColumn->horizontalHeader()->setFixedHeight(horizontalHeader()->height());
mFrozenColumn->setGeometry(viewport()->geometry().x(), frameWidth(), columnWidth(0), viewport()->height() + horizontalHeader()->height());
}
void lcPartsTableWidget::focusInEvent(QFocusEvent* Event)
{
const bool HasCurrent = currentIndex().isValid();
QTableWidget::focusInEvent(Event);
// Receiving focus should not mark the first cell before the user chooses a row.
if (!HasCurrent)
selectionModel()->clearCurrentIndex();
}
void lcPartsTableWidget::resizeEvent(QResizeEvent* Event)
{
QTableWidget::resizeEvent(Event);
UpdateFrozenGeometry();
}
void lcPartsTableWidget::EnsureCountVisible(const QModelIndex& Index)
{
if (mFrozenColumn && Index.column() > 0 && visualRect(Index).left() < columnWidth(0))
horizontalScrollBar()->setValue(horizontalScrollBar()->value() + visualRect(Index).left() - columnWidth(0));
}
QModelIndex lcPartsTableWidget::moveCursor(CursorAction Action, Qt::KeyboardModifiers Modifiers)
{
const QModelIndex Index = QTableWidget::moveCursor(Action, Modifiers);
EnsureCountVisible(Index);
return Index;
}
void lcPartsTableWidget::scrollTo(const QModelIndex& Index, ScrollHint Hint)
{
if (!mFrozenColumn || Index.column() > 0)
{
QTableWidget::scrollTo(Index, Hint);
EnsureCountVisible(Index);
}
else
mFrozenColumn->scrollTo(Index, Hint);
}
class lcPartsTableWidgetItem : public QTableWidgetItem
{
public:
explicit lcPartsTableWidgetItem(const QString& Text, int Type = QTableWidgetItem::Type)
: QTableWidgetItem(Text, Type)
{
mLast = false;
}
bool operator<(const QTableWidgetItem& Other) const override
{
if (mLast)
return false;
if (((const lcPartsTableWidgetItem&)Other).mLast)
return true;
if (column() > 0)
{
int Count = text().toInt();
int OtherCount = Other.text().toInt();
return Count < OtherCount;
}
return QTableWidgetItem::operator<(Other);
}
bool mLast;
};
lcPropertiesDialog::lcPropertiesDialog(QWidget* Parent, lcPropertiesDialogOptions* Options)
: QDialog(Parent), mOptions(Options), ui(new Ui::lcPropertiesDialog)
{
ui->setupUi(this);
setWindowTitle(tr("%1 Properties").arg(mOptions->Properties.mFileName));
ui->DescriptionEdit->setText(mOptions->Properties.mDescription);
ui->AuthorEdit->setText(mOptions->Properties.mAuthor);
ui->CommentsEdit->setText(mOptions->Properties.mComments);
const lcVector3 Dimensions = Options->BoundingBox.Max - Options->BoundingBox.Min;
QString Format = tr("%1 x %2 x %3 cm\n%4 x %5 x %6 inches\n%7 x %8 x %9 LDU");
QString Measurements = Format.arg(QString::number(Dimensions.x * 0.04, 'f', 2), QString::number(Dimensions.y * 0.04, 'f', 2), QString::number(Dimensions.z * 0.04, 'f', 2),
QString::number(Dimensions.x / 64.0, 'f', 2), QString::number(Dimensions.y / 64.0, 'f', 2), QString::number(Dimensions.z / 64.0, 'f', 2),
QString::number(Dimensions.x, 'f', 2), QString::number(Dimensions.y, 'f', 2), QString::number(Dimensions.z, 'f', 2));
ui->MeasurementsLabel->setText(Measurements);
const lcPartsList& PartsList = mOptions->PartsList;
QStringList HorizontalLabels;
std::vector<bool> ColorsUsed(gColorList.size());
for (const auto& PartIt : PartsList)
for (const auto& ColorIt : PartIt.second)
ColorsUsed[ColorIt.first] = true;
std::vector<int> ColorColumns(gColorList.size());
int ColorCount = 0;
HorizontalLabels.append(tr("Part"));
for (size_t ColorIndex = 0; ColorIndex < gColorList.size(); ColorIndex++)
{
if (ColorsUsed[ColorIndex])
{
ColorColumns[ColorIndex] = ColorCount++;
HorizontalLabels.append(gColorList[ColorIndex].Name);
}
}
HorizontalLabels.append(tr("Total"));
QTableWidget* PartsTable = ui->PartsTable;
PartsTable->setColumnCount(ColorCount + 2);
PartsTable->setRowCount((int)PartsList.size() + 1);
PartsTable->setHorizontalHeaderLabels(HorizontalLabels);
PartsTable->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
std::vector<int> InfoTotals(PartsList.size());
std::vector<int> ColorTotals(ColorCount);
int Row = 0, Total = 0;
for (const auto& PartIt : PartsList)
{
PartsTable->setItem(Row, 0, new lcPartsTableWidgetItem(PartIt.first->m_strDescription));
for (const auto& ColorIt : PartIt.second)
{
int ColorIndex = ColorIt.first;
int Count = ColorIt.second;
lcPartsTableWidgetItem* Item = new lcPartsTableWidgetItem(QString::number(Count));
Item->setTextAlignment(Qt::AlignCenter);
PartsTable->setItem(Row, ColorColumns[ColorIndex] + 1, Item);
InfoTotals[Row] += Count;
ColorTotals[ColorColumns[ColorIndex]] += Count;
Total += Count;
}
for (int Column = 0; Column <= ColorCount; Column++)
if (!PartsTable->item(Row, Column))
PartsTable->setItem(Row, Column, new lcPartsTableWidgetItem(QString()));
Row++;
}
lcPartsTableWidgetItem* Item = new lcPartsTableWidgetItem(tr("Total"));
Item->mLast = true;
PartsTable->setItem((int)InfoTotals.size(), 0, Item);
for (Row = 0; Row < (int)InfoTotals.size(); Row++)
{
Item = new lcPartsTableWidgetItem(QString::number(InfoTotals[Row]));
Item->setTextAlignment(Qt::AlignCenter);
PartsTable->setItem(Row, ColorCount + 1, Item);
}
for (int ColorIndex = 0; ColorIndex < ColorCount; ColorIndex++)
{
Item = new lcPartsTableWidgetItem(QString::number(ColorTotals[ColorIndex]));
Item->mLast = true;
Item->setTextAlignment(Qt::AlignCenter);
PartsTable->setItem((int)InfoTotals.size(), ColorIndex + 1, Item);
}
Item = new lcPartsTableWidgetItem(QString::number(Total));
Item->mLast = true;
Item->setTextAlignment(Qt::AlignCenter);
PartsTable->setItem((int)InfoTotals.size(), ColorCount + 1, Item);
ui->PartsTable->FreezePartColumn();
}
lcPropertiesDialog::~lcPropertiesDialog()
{
delete ui;
}
void lcPropertiesDialog::accept()
{
mOptions->Properties.mDescription = ui->DescriptionEdit->text();
mOptions->Properties.mAuthor = ui->AuthorEdit->text();
mOptions->Properties.mComments = ui->CommentsEdit->toPlainText();
QDialog::accept();
}