Files
leocad/qt/lc_qutils.cpp
T
Leonardo Zide 41e5432395 Extract piece registration storage into lcPieceManager
Library and project name registries were accessed directly by source discovery,
cache processing and UI consumers. Centralize their storage and index operations
in a library-owned lcPieceManager, with ordered borrowed snapshots and copied
file metadata for worker include lookup.

Keep source resolution, reference counts, unload/deletion policy, cache I/O and
existing synchronization in lcPiecesLibrary. Preserve project-index collisions,
name normalization and reentrant teardown ordering while routing registration
changes through the manager.

Return the GetParts snapshot by value, name model rename/reservation operations
RenameModelPieceData, and centralize manager forward declarations in the PCH.
Document the registry boundary and register the new files in qmake.

Validation: macOS Qt 6.10 Debug build, whitespace checks and 35 offscreen lifecycle
checks, including archive/directory cold and warm caches, source precedence,
worker includes, borrowed snapshot lifetimes, rename collisions, project transfer
and nested external-project destruction. Successful GPU rendering is not covered
by the offscreen checks.
2026-10-19 11:07:43 -07:00

303 lines
8.2 KiB
C++

#include "lc_global.h"
#include "lc_qutils.h"
#include "lc_application.h"
#include "lc_library.h"
#include "lc_model.h"
#include "lc_piecedata.h"
#include "lc_string.h"
#ifdef Q_OS_WIN
#include <windows.h>
#include <TlHelp32.h>
#endif
QString lcFormatValue(float Value, int Precision)
{
QString String = QString::number(Value, 'f', Precision);
const qsizetype Dot = String.indexOf('.');
if (Dot != -1)
{
while (String.endsWith('0'))
String.chop(1);
if (String.endsWith('.'))
String.chop(1);
}
return String;
}
QString lcFormatValueLocalized(float Value)
{
QLocale Locale = QLocale::system();
Locale.setNumberOptions(QLocale::OmitGroupSeparator);
QString DecimalPoint = Locale.decimalPoint();
QString String = Locale.toString(Value, 'f', 4);
if (String.indexOf(DecimalPoint) != -1)
{
while (String.endsWith('0'))
String.chop(1);
if (String.endsWith(DecimalPoint))
String.chop(1);
}
return String;
}
float lcParseValueLocalized(const QString& Value)
{
return QLocale::system().toFloat(Value);
}
#ifdef Q_OS_WIN
int lcTerminateChildProcess(QWidget* Parent, const qint64 Pid, const qint64 Ppid)
{
DWORD PID = DWORD(Pid);
DWORD PPID = DWORD(Ppid);
HANDLE hSnapshot = INVALID_HANDLE_VALUE, hProcess = INVALID_HANDLE_VALUE;
PROCESSENTRY32 PE32;
if ((hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, PPID)) == INVALID_HANDLE_VALUE)
{
QMessageBox::warning(Parent, QObject::tr("Error"), QString("%1 failed: %1").arg("CreateToolhelp32Snapshot").arg(GetLastError()));
return -1;
}
PE32.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hSnapshot, &PE32) == FALSE)
{
QMessageBox::warning(Parent, QObject::tr("Error"), QString("%1 failed: %2").arg("Process32First").arg(GetLastError()));
CloseHandle(hSnapshot);
return -2;
}
do
{
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
QRegularExpression ExeExpression("^(?:cmd\\.exe|conhost\\.exe|blender\\.exe)$", QRegularExpression::CaseInsensitiveOption);
#else
QRegExp ExeExpression("^(?:cmd\\.exe|conhost\\.exe|blender\\.exe)$", Qt::CaseInsensitive);
#endif
if (QString::fromWCharArray(PE32.szExeFile).contains(ExeExpression))
{
if ((PE32.th32ProcessID == PID && PE32.th32ParentProcessID == PPID) || PE32.th32ParentProcessID == PID)
{
if ((hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, PE32.th32ProcessID)) == INVALID_HANDLE_VALUE)
{
QMessageBox::warning(Parent, QObject::tr("Error"), QObject::tr("%1 failed: %2").arg("OpenProcess").arg(GetLastError()));
return -3;
}
else
{
TerminateProcess(hProcess, 9);
CloseHandle(hProcess);
}
}
}
}
while (Process32Next(hSnapshot, &PE32));
CloseHandle(hSnapshot);
return 0;
}
bool lcRunElevatedProcess(const LPCWSTR ExeName, const LPCWSTR Arguments, const LPCWSTR WorkingFolder)
{
SHELLEXECUTEINFO ShellExecuteInfo = {};
ShellExecuteInfo.cbSize = sizeof(SHELLEXECUTEINFO);
ShellExecuteInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
ShellExecuteInfo.hwnd = NULL;
ShellExecuteInfo.lpVerb = L"runas";
ShellExecuteInfo.lpFile = ExeName;
ShellExecuteInfo.lpParameters = Arguments;
ShellExecuteInfo.lpDirectory = WorkingFolder;
ShellExecuteInfo.nShow = SW_SHOW;
ShellExecuteInfo.hInstApp = NULL;
if (!ShellExecuteEx(&ShellExecuteInfo) || !ShellExecuteInfo.hProcess)
return false;
if (WaitForSingleObject(ShellExecuteInfo.hProcess, INFINITE))
return false;
DWORD ExitCode;
if (!GetExitCodeProcess(ShellExecuteInfo.hProcess, &ExitCode))
return false;
CloseHandle(ShellExecuteInfo.hProcess);
return ExitCode == 0;
}
#endif
// Resize all columns to content except for one stretching column. (taken from QT creator)
lcTreeWidgetColumnStretcher::lcTreeWidgetColumnStretcher(QTreeWidget* TreeWidget, int ColumnToStretch)
: QObject(TreeWidget->header()), mColumnToStretch(ColumnToStretch), mInteractiveResize(false), mStretchWidth(0)
{
parent()->installEventFilter(this);
connect(TreeWidget->header(), &QHeaderView::sectionResized, this, &lcTreeWidgetColumnStretcher::SectionResized);
QHideEvent Fake;
lcTreeWidgetColumnStretcher::eventFilter(parent(), &Fake);
}
void lcTreeWidgetColumnStretcher::SectionResized(int LogicalIndex, int OldSize, int NewSize)
{
Q_UNUSED(OldSize)
if (LogicalIndex == mColumnToStretch)
{
QHeaderView* HeaderView = qobject_cast<QHeaderView*>(parent());
if (HeaderView->isVisible())
mInteractiveResize = true;
mStretchWidth = NewSize;
}
}
bool lcTreeWidgetColumnStretcher::eventFilter(QObject* Object, QEvent* Event)
{
if (Object == parent())
{
QHeaderView* HeaderView = qobject_cast<QHeaderView*>(Object);
if (Event->type() == QEvent::Show)
{
for (int i = 0; i < HeaderView->count(); ++i)
HeaderView->setSectionResizeMode(i, QHeaderView::Interactive);
mStretchWidth = HeaderView->sectionSize(mColumnToStretch);
}
else if (Event->type() == QEvent::Hide)
{
if (!mInteractiveResize)
for (int i = 0; i < HeaderView->count(); ++i)
HeaderView->setSectionResizeMode(i, i == mColumnToStretch ? QHeaderView::Stretch : QHeaderView::ResizeToContents);
}
else if (Event->type() == QEvent::Resize)
{
if (HeaderView->sectionResizeMode(mColumnToStretch) == QHeaderView::Interactive) {
const int StretchWidth = HeaderView->isVisible() ? mStretchWidth : 32;
HeaderView->resizeSection(mColumnToStretch, StretchWidth);
}
}
}
return false;
}
lcPieceIdStringModel::lcPieceIdStringModel(lcModel* Model, QObject* Parent)
: QAbstractListModel(Parent)
{
lcPiecesLibrary* Library = lcGetPiecesLibrary();
mSortedPieces = Library->GetParts();
mSortedPieces.erase(std::remove_if(mSortedPieces.begin(), mSortedPieces.end(), [Model](lcPieceData* PieceData)
{
return PieceData->IsModel() && PieceData->GetModel()->IncludesModel(Model);
}), mSortedPieces.end());
auto PieceCompare = [](lcPieceData* PieceData1, lcPieceData* PieceData2)
{
return strcmp(PieceData1->m_strDescription, PieceData2->m_strDescription) < 0;
};
std::sort(mSortedPieces.begin(), mSortedPieces.end(), PieceCompare);
}
QModelIndex lcPieceIdStringModel::Index(lcPieceData* PieceData) const
{
for (size_t PieceDataIndex = 0; PieceDataIndex < mSortedPieces.size(); PieceDataIndex++)
if (mSortedPieces[PieceDataIndex] == PieceData)
return index(static_cast<int>(PieceDataIndex), 0);
return QModelIndex();
}
std::vector<bool> lcPieceIdStringModel::GetFilteredRows(const QString& FilterText) const
{
const std::string Text = FilterText.toStdString();
std::vector<bool> FilteredRows(mSortedPieces.size());
for (size_t PieceDataIndex = 0; PieceDataIndex < mSortedPieces.size(); PieceDataIndex++)
{
const lcPieceData* PieceData = mSortedPieces[PieceDataIndex];
FilteredRows[PieceDataIndex] = (lcstrcasestr(PieceData->m_strDescription, Text.c_str()) || lcstrcasestr(PieceData->mFileName, Text.c_str()));
}
return FilteredRows;
}
int lcPieceIdStringModel::rowCount(const QModelIndex& Parent) const
{
Q_UNUSED(Parent);
return static_cast<int>(mSortedPieces.size());
}
QVariant lcPieceIdStringModel::data(const QModelIndex& Index, int Role) const
{
if (Index.row() < static_cast<int>(mSortedPieces.size()))
{
if (Role == Qt::DisplayRole)
return QString::fromLatin1(mSortedPieces[Index.row()]->m_strDescription);
else if (Role == Qt::UserRole)
return QVariant::fromValue(reinterpret_cast<void*>(mSortedPieces[Index.row()]));
}
return QVariant();
}
lcColorDialogPopup::lcColorDialogPopup(const QColor& InitialColor, QWidget* Parent)
: QWidget(Parent)
{
QVBoxLayout* Layout = new QVBoxLayout(this);
Layout->setContentsMargins(0, 0, 0, 0);
mColorDialog = new QColorDialog(InitialColor, this);
mColorDialog->setWindowFlags(Qt::Widget);
mColorDialog->setOptions(QColorDialog::DontUseNativeDialog | QColorDialog::NoButtons);
Layout->addWidget(mColorDialog);
QDialogButtonBox* ButtonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel, this);
Layout->addWidget(ButtonBox);
QObject::connect(ButtonBox, &QDialogButtonBox::accepted, this, &lcColorDialogPopup::Accept);
QObject::connect(ButtonBox, &QDialogButtonBox::rejected, this, &lcColorDialogPopup::Reject);
}
void lcColorDialogPopup::Accept()
{
QColor Color = mColorDialog->currentColor();
emit ColorSelected(Color);
Close();
}
void lcColorDialogPopup::Reject()
{
Close();
}
void lcColorDialogPopup::Close()
{
QMenu* Menu = qobject_cast<QMenu*>(parent());
if (Menu)
Menu->close();
}