mirror of
https://github.com/leozide/leocad.git
synced 2026-10-26 22:10:32 +00:00
Explain array counts, offsets, rotations, image dimensions, file naming, and step ranges with translated tooltips and contextual help. Give both dialogs a standard Help button and dialog-scoped F1 support that falls back from the focused control through its parent groups to the overview. Link image field labels to their buddies and share tooltip and field help text. Add accessible names for unlabeled controls. Document the writing and maintenance conventions, with concise guidance in the existing AGENTS.md. Refresh all eight language catalogs while preserving existing translations and review status. The 62 new help and accessibility strings remain unfinished pending translation. Validation: Qt 6.10 debug build; runtime checks for seven F1 routes, Help, Escape, dialog state, spin-box focus, and five label buddies with matching help; visual layout inspection; catalog coverage for all 2,118 active source messages and existing translation preservation; git diff --check.
328 lines
9.1 KiB
C++
328 lines
9.1 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
|
|
|
|
void lcSetupDialogHelp(QDialog* Dialog, QDialogButtonBox* ButtonBox)
|
|
{
|
|
const auto ShowHelp = [Dialog](QWidget* Widget)
|
|
{
|
|
while (Widget && Widget->whatsThis().isEmpty() && Widget != Dialog)
|
|
Widget = Widget->parentWidget();
|
|
|
|
if (Widget && !Widget->whatsThis().isEmpty())
|
|
QWhatsThis::showText(Widget->mapToGlobal(QPoint(0, Widget->height())), Widget->whatsThis(), Widget);
|
|
};
|
|
|
|
QObject::connect(ButtonBox, &QDialogButtonBox::helpRequested, Dialog, [Dialog, ShowHelp]()
|
|
{
|
|
ShowHelp(Dialog);
|
|
});
|
|
|
|
QShortcut* HelpShortcut = new QShortcut(QKeySequence(Qt::Key_F1), Dialog);
|
|
HelpShortcut->setContext(Qt::WidgetWithChildrenShortcut);
|
|
QObject::connect(HelpShortcut, &QShortcut::activated, Dialog, [Dialog, ShowHelp]()
|
|
{
|
|
QWidget* Focus = QApplication::focusWidget();
|
|
ShowHelp(Focus && Dialog->isAncestorOf(Focus) ? Focus : Dialog);
|
|
});
|
|
}
|
|
|
|
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("Stop Blender Process"), QString("%1 failed: %1").arg("CreateToolhelp32Snapshot").arg(GetLastError()));
|
|
return -1;
|
|
}
|
|
PE32.dwSize = sizeof(PROCESSENTRY32);
|
|
if (Process32First(hSnapshot, &PE32) == FALSE)
|
|
{
|
|
QMessageBox::warning(Parent, QObject::tr("Stop Blender Process"), 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("Stop Blender Process"), 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();
|
|
}
|