Files
leocad/qt/lc_qpreferencesdialog.cpp
T
Leonardo Zide 2125868dc6 Validate localized numeric input and unify image limits
Parse dialog numbers using their validator locale so localized decimals and grouped input round trip correctly, while displaying values without digit grouping. Keep invalid input open with a validation message instead of silently saving or clamping it, and serialize Blender settings in their canonical format.

Use a 1–32768 pixel range for image dimensions across Save Image, HTML export, command-line output, POV-Ray, and Blender. Bound export step edits by the model step count, grid spacing to 10000 studs, and area-light grids to 64 samples per axis. Preserve existing show/hide step bounds and avoid a total-pixel cap.

Validation: macOS Qt debug build and numeric dialog checks across ten locales, including localized input, validation messages, image dimension boundaries, and settings round trips.
2026-10-23 12:54:55 -07:00

1508 lines
54 KiB
C++

#include "lc_global.h"
#include "lc_qpreferencesdialog.h"
#include "ui_lc_qpreferencesdialog.h"
#include "lc_qutils.h"
#include "lc_categorydialog.h"
#include "lc_library.h"
#include "lc_application.h"
#include "lc_qutils.h"
#include "lc_glextensions.h"
#include "lc_piecedata.h"
#include "lc_edgecolordialog.h"
#include "lc_blenderpreferences.h"
#include "lc_mainwindow.h"
#include "lc_viewwidget.h"
#include "lc_view.h"
#include "lc_context.h"
static const char* gLanguageLocales[] =
{
"", "cs_CZ", "de_DE", "en_US", "fr_FR", "pt_PT", "es_ES", "zh_CN"
};
lcPreferencesDialog::lcPreferencesDialog(QWidget* Parent, lcPreferencesDialogOptions* Options)
: QDialog(Parent), mOptions(Options), ui(new Ui::lcPreferencesDialog)
{
ui->setupUi(this);
lcSetupDialogHelp(this, ui->buttonBox);
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
ui->povrayLabel->hide();
ui->povrayExecutable->hide();
ui->povrayExecutableBrowse->hide();
delete ui->povrayLabel;
delete ui->povrayLayout;
#endif
ui->Language->addItem("\u010cesky");
ui->Language->addItem("Deutsch");
ui->Language->addItem("English");
ui->Language->addItem("Fran\u00e7ais");
ui->Language->addItem("Portugu\u00eas");
ui->Language->addItem("Espa\u00f1ol");
ui->Language->addItem("\u7b80\u4f53\u4e2d\u6587");
connect(ui->partsLibraryBrowse, &QPushButton::clicked, this, &lcPreferencesDialog::PartsLibraryBrowseClicked);
connect(ui->partsArchiveBrowse, &QPushButton::clicked, this, &lcPreferencesDialog::PartsArchiveBrowseClicked);
connect(ui->ColorConfigBrowseButton, &QPushButton::clicked, this, &lcPreferencesDialog::ColorConfigBrowseButtonClicked);
connect(ui->MinifigSettingsBrowseButton, &QPushButton::clicked, this, &lcPreferencesDialog::MinifigSettingsBrowseButtonClicked);
connect(ui->povrayExecutableBrowse, &QPushButton::clicked, this, &lcPreferencesDialog::PovrayExecutableBrowseClicked);
connect(ui->lgeoPathBrowse, &QPushButton::clicked, this, &lcPreferencesDialog::LgeoPathBrowseClicked);
connect(ui->ColorTheme, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &lcPreferencesDialog::ColorThemeCurrentIndexChanged);
connect(ui->AutomateEdgeColor, &QCheckBox::toggled, this, &lcPreferencesDialog::AutomateEdgeColorToggled);
connect(ui->BlenderAddonSettingsButton, &QPushButton::clicked, this, &lcPreferencesDialog::BlenderAddonSettingsButtonClicked);
connect(ui->studStyleCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &lcPreferencesDialog::StudStyleComboCurrentIndexChanged);
connect(ui->antiAliasing, &QCheckBox::toggled, this, &lcPreferencesDialog::AntiAliasingToggled);
connect(ui->edgeLines, &QCheckBox::toggled, this, &lcPreferencesDialog::EdgeLinesToggled);
connect(ui->ConditionalLinesCheckBox, &QCheckBox::toggled, this, &lcPreferencesDialog::ConditionalLinesCheckBoxToggled);
connect(ui->LineWidthSlider, QOverload<int>::of(&QSlider::valueChanged), this, &lcPreferencesDialog::LineWidthSliderValueChanged);
connect(ui->MeshLODSlider, QOverload<int>::of(&QSlider::valueChanged), this, &lcPreferencesDialog::MeshLODSliderValueChanged);
connect(ui->FadeSteps, &QCheckBox::toggled, this, &lcPreferencesDialog::FadeStepsToggled);
connect(ui->HighlightNewParts, &QCheckBox::toggled, this, &lcPreferencesDialog::HighlightNewPartsToggled);
connect(ui->gridStuds, &QCheckBox::toggled, this, &lcPreferencesDialog::GridStudsToggled);
connect(ui->gridLines, &QCheckBox::toggled, this, &lcPreferencesDialog::GridLinesToggled);
connect(ui->ViewSphereSizeCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &lcPreferencesDialog::ViewSphereSizeComboCurrentIndexChanged);
connect(ui->PreviewViewSphereSizeCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &lcPreferencesDialog::PreviewViewSphereSizeComboCurrentIndexChanged);
connect(ui->newCategory, &QPushButton::clicked, this, &lcPreferencesDialog::NewCategoryClicked);
connect(ui->editCategory, &QPushButton::clicked, this, &lcPreferencesDialog::EditCategoryClicked);
connect(ui->deleteCategory, &QPushButton::clicked, this, &lcPreferencesDialog::DeleteCategoryClicked);
connect(ui->importCategories, &QPushButton::clicked, this, &lcPreferencesDialog::ImportCategoriesClicked);
connect(ui->exportCategories, &QPushButton::clicked, this, &lcPreferencesDialog::ExportCategoriesClicked);
connect(ui->resetCategories, &QPushButton::clicked, this, &lcPreferencesDialog::ResetCategoriesClicked);
connect(ui->shortcutAssign, &QPushButton::clicked, this, &lcPreferencesDialog::ShortcutAssignClicked);
connect(ui->shortcutRemove, &QPushButton::clicked, this, &lcPreferencesDialog::ShortcutRemoveClicked);
connect(ui->shortcutsImport, &QPushButton::clicked, this, &lcPreferencesDialog::ShortcutsImportClicked);
connect(ui->shortcutsExport, &QPushButton::clicked, this, &lcPreferencesDialog::ShortcutsExportClicked);
connect(ui->shortcutsReset, &QPushButton::clicked, this, &lcPreferencesDialog::ShortcutsResetClicked);
connect(ui->KeyboardFilterEdit, &QLineEdit::textEdited, this, &lcPreferencesDialog::KeyboardFilterEditTextEdited);
connect(ui->mouseAssign, &QPushButton::clicked, this, &lcPreferencesDialog::MouseAssignClicked);
connect(ui->mouseRemove, &QPushButton::clicked, this, &lcPreferencesDialog::MouseRemoveClicked);
connect(ui->MouseImportButton, &QPushButton::clicked, this, &lcPreferencesDialog::MouseImportButtonClicked);
connect(ui->MouseExportButton, &QPushButton::clicked, this, &lcPreferencesDialog::MouseExportButtonClicked);
connect(ui->mouseReset, &QPushButton::clicked, this, &lcPreferencesDialog::MouseResetClicked);
connect(ui->BackgroundSolidColorButton, &QToolButton::clicked, this, &lcPreferencesDialog::ColorButtonClicked);
connect(ui->BackgroundGradient1ColorButton, &QToolButton::clicked, this, &lcPreferencesDialog::ColorButtonClicked);
connect(ui->BackgroundGradient2ColorButton, &QToolButton::clicked, this, &lcPreferencesDialog::ColorButtonClicked);
connect(ui->ActiveViewColorButton, &QToolButton::clicked, this, &lcPreferencesDialog::ColorButtonClicked);
connect(ui->InactiveViewColorButton, &QToolButton::clicked, this, &lcPreferencesDialog::ColorButtonClicked);
connect(ui->TextHaloColorButton, &QToolButton::clicked, this, &lcPreferencesDialog::ColorButtonClicked);
connect(ui->TextColorButton, &QToolButton::clicked, this, &lcPreferencesDialog::ColorButtonClicked);
connect(ui->MarqueeBorderColorButton, &QToolButton::clicked, this, &lcPreferencesDialog::ColorButtonClicked);
connect(ui->MarqueeFillColorButton, &QToolButton::clicked, this, &lcPreferencesDialog::ColorButtonClicked);
connect(ui->OverlayColorButton, &QToolButton::clicked, this, &lcPreferencesDialog::ColorButtonClicked);
connect(ui->FadeStepsColor, &QToolButton::clicked, this, &lcPreferencesDialog::ColorButtonClicked);
connect(ui->HighlightNewPartsColor, &QToolButton::clicked, this, &lcPreferencesDialog::ColorButtonClicked);
connect(ui->gridStudColor, &QToolButton::clicked, this, &lcPreferencesDialog::ColorButtonClicked);
connect(ui->gridLineColor, &QToolButton::clicked, this, &lcPreferencesDialog::ColorButtonClicked);
connect(ui->ViewSphereColorButton, &QToolButton::clicked, this, &lcPreferencesDialog::ColorButtonClicked);
connect(ui->ViewSphereTextColorButton, &QToolButton::clicked, this, &lcPreferencesDialog::ColorButtonClicked);
connect(ui->ViewSphereHighlightColorButton, &QToolButton::clicked, this, &lcPreferencesDialog::ColorButtonClicked);
connect(ui->ObjectSelectedColorButton, &QToolButton::clicked, this, &lcPreferencesDialog::ColorButtonClicked);
connect(ui->ObjectFocusedColorButton, &QToolButton::clicked, this, &lcPreferencesDialog::ColorButtonClicked);
connect(ui->CameraColorButton, &QToolButton::clicked, this, &lcPreferencesDialog::ColorButtonClicked);
connect(ui->LightColorButton, &QToolButton::clicked, this, &lcPreferencesDialog::ColorButtonClicked);
connect(ui->ControlPointColorButton, &QToolButton::clicked, this, &lcPreferencesDialog::ColorButtonClicked);
connect(ui->ControlPointFocusedColorButton, &QToolButton::clicked, this, &lcPreferencesDialog::ColorButtonClicked);
connect(ui->categoriesTree, &QTreeWidget::itemSelectionChanged, this, &lcPreferencesDialog::UpdateParts);
connect(ui->categoriesTree->model(), &QAbstractItemModel::rowsInserted, this, &lcPreferencesDialog::CategoriesDropped);
ui->shortcutEdit->installEventFilter(this);
connect(ui->commandList, &QTreeWidget::currentItemChanged, this, &lcPreferencesDialog::CommandChanged);
connect(ui->mouseTree, &QTreeWidget::currentItemChanged, this, &lcPreferencesDialog::MouseTreeItemChanged);
connect(ui->HighContrastButton, &QToolButton::clicked, this, &lcPreferencesDialog::AutomateEdgeColor);
connect(ui->AutomateEdgeColorButton, &QToolButton::clicked, this, &lcPreferencesDialog::AutomateEdgeColor);
ui->partsLibrary->setText(mOptions->LibraryPath);
ui->ColorConfigEdit->setText(mOptions->ColorConfigPath);
ui->MinifigSettingsEdit->setText(mOptions->MinifigSettingsPath);
ui->povrayExecutable->setText(mOptions->POVRayPath);
ui->lgeoPath->setText(mOptions->LGEOPath);
ui->authorName->setText(mOptions->DefaultAuthor);
ui->mouseSensitivity->setValue(mOptions->Preferences.mMouseSensitivity);
const bool ColorThemeBlocked = ui->ColorTheme->blockSignals(true);
ui->ColorTheme->setCurrentIndex(static_cast<int>(mOptions->Preferences.mColorTheme));
ui->ColorTheme->blockSignals(ColorThemeBlocked);
for (unsigned int LanguageIdx = 0; LanguageIdx < LC_ARRAY_COUNT(gLanguageLocales); LanguageIdx++)
{
if (mOptions->Language == gLanguageLocales[LanguageIdx])
{
ui->Language->setCurrentIndex(LanguageIdx);
break;
}
}
ui->checkForUpdates->setCurrentIndex(mOptions->CheckForUpdates);
ui->fixedDirectionKeys->setChecked(mOptions->Preferences.mFixedAxes);
ui->autoLoadMostRecent->setChecked(mOptions->Preferences.mAutoLoadMostRecent);
ui->RestoreTabLayout->setChecked(mOptions->Preferences.mRestoreTabLayout);
ui->AutomateEdgeColor->setChecked(mOptions->Preferences.mAutomateEdgeColor);
ui->antiAliasing->setChecked(mOptions->AASamples != 1);
if (mOptions->AASamples == 8)
ui->antiAliasingSamples->setCurrentIndex(2);
else if (mOptions->AASamples == 4)
ui->antiAliasingSamples->setCurrentIndex(1);
else
ui->antiAliasingSamples->setCurrentIndex(0);
ui->edgeLines->setChecked(mOptions->Preferences.mDrawEdgeLines);
ui->ConditionalLinesCheckBox->setChecked(mOptions->Preferences.mDrawConditionalLines);
lcViewWidget* Widget = gMainWindow->GetActiveView()->GetWidget();
QOpenGLContext* Context = Widget->context();
QOpenGLFunctions* Functions = Context->functions();
#ifndef LC_OPENGLES
if (QSurfaceFormat::defaultFormat().samples() > 1)
{
Functions->glGetFloatv(GL_SMOOTH_LINE_WIDTH_RANGE, mLineWidthRange);
Functions->glGetFloatv(GL_SMOOTH_LINE_WIDTH_GRANULARITY, &mLineWidthGranularity);
}
else
#endif
{
Functions->glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, mLineWidthRange);
mLineWidthGranularity = 1.0f;
}
mLineWidthRange[1] = qMin(mLineWidthRange[1], 10.0f);
mLineWidthGranularity = qMax(mLineWidthGranularity, 0.25f);
ui->LineWidthSlider->setRange(0, (mLineWidthRange[1] - mLineWidthRange[0]) / mLineWidthGranularity);
ui->LineWidthSlider->setValue((mOptions->Preferences.mLineWidth - mLineWidthRange[0]) / mLineWidthGranularity);
ui->MeshLOD->setChecked(mOptions->Preferences.mAllowLOD);
ui->MeshLODSlider->setRange(0, 1500.0f / mMeshLODMultiplier);
ui->MeshLODSlider->setValue(mOptions->Preferences.mMeshLODDistance / mMeshLODMultiplier);
ui->FadeSteps->setChecked(mOptions->Preferences.mFadeSteps);
ui->HighlightNewParts->setChecked(mOptions->Preferences.mHighlightNewParts);
ui->gridStuds->setChecked(mOptions->Preferences.mDrawGridStuds);
ui->gridLines->setChecked(mOptions->Preferences.mDrawGridLines);
ui->gridLineSpacing->setValidator(new QIntValidator(1, LC_MAX_GRID_LINE_SPACING, ui->gridLineSpacing));
ui->gridLineSpacing->setText(lcFormatIntegerLocalized(mOptions->Preferences.mGridLineSpacing, ui->gridLineSpacing->validator()->locale()));
ui->GridOriginCheckBox->setChecked(mOptions->Preferences.mDrawGridOrigin);
ui->AxisIconCheckBox->setChecked(mOptions->Preferences.mDrawAxes);
ui->AxisIconLocationCombo->setCurrentIndex((int)mOptions->Preferences.mAxisIconLocation);
if (!mOptions->Preferences.mBackgroundGradient)
ui->BackgroundSolidRadio->setChecked(true);
else
ui->BackgroundGradientRadio->setChecked(true);
ui->ViewSphereLocationCombo->setCurrentIndex((int)mOptions->Preferences.mViewSphereLocation);
if (mOptions->Preferences.mViewSphereEnabled)
{
switch (mOptions->Preferences.mViewSphereSize)
{
case 200:
ui->ViewSphereSizeCombo->setCurrentIndex(3);
break;
case 100:
ui->ViewSphereSizeCombo->setCurrentIndex(2);
break;
case 50:
ui->ViewSphereSizeCombo->setCurrentIndex(1);
break;
default:
ui->ViewSphereSizeCombo->setCurrentIndex(0);
break;
}
}
else
ui->ViewSphereSizeCombo->setCurrentIndex(0);
ui->PreviewAxisIconCheckBox->setChecked(mOptions->Preferences.mDrawPreviewAxis);
ui->PreviewViewSphereLocationCombo->setCurrentIndex((int)mOptions->Preferences.mPreviewViewSphereLocation);
if (mOptions->Preferences.mPreviewViewSphereEnabled)
{
switch (mOptions->Preferences.mPreviewViewSphereSize)
{
case 100:
ui->PreviewViewSphereSizeCombo->setCurrentIndex(3);
break;
case 75:
ui->PreviewViewSphereSizeCombo->setCurrentIndex(2);
break;
case 50:
ui->PreviewViewSphereSizeCombo->setCurrentIndex(1);
break;
default:
ui->PreviewViewSphereSizeCombo->setCurrentIndex(0);
break;
}
}
else
ui->PreviewViewSphereSizeCombo->setCurrentIndex(0);
ui->studStyleCombo->setCurrentIndex(static_cast<int>(mOptions->StudStyle));
ui->ShadingMode->setCurrentIndex(static_cast<int>(mOptions->Preferences.mShadingMode));
if (!lcContext::SupportsDefaultLights())
{
QStandardItemModel* ShadingModel = qobject_cast<QStandardItemModel*>(ui->ShadingMode->model());
QStandardItem* DefaultLightsItem = ShadingModel->item(static_cast<int>(lcShadingMode::DefaultLights));
DefaultLightsItem->setEnabled(false);
const QString Message = tr("Default Lights is unavailable. Flat shading is used instead.");
DefaultLightsItem->setToolTip(Message);
ui->ShadingMode->setToolTip(Message);
}
if (!lcContext::SupportsMaterial(lcMaterialType::UnlitViewSphere))
{
const std::array<QWidget*, 7> Controls =
{
ui->ViewSphereSizeCombo, ui->ViewSphereLocationCombo,
ui->PreviewViewSphereSizeCombo, ui->PreviewViewSphereLocationCombo,
ui->ViewSphereColorButton, ui->ViewSphereTextColorButton, ui->ViewSphereHighlightColorButton
};
const QString Message = tr("View sphere rendering is unavailable.");
for (QWidget* Control : Controls)
{
Control->setEnabled(false);
Control->setToolTip(Message);
}
}
bool OutlineSupported = false;
{
lcScopedGLContextState GLState;
lcView* View = Widget->GetView();
View->MakeCurrent();
OutlineSupported = View->PrepareOutlineFramebuffers();
}
if (!OutlineSupported)
{
mOptions->Preferences.mSelectionRenderMode = lcSelectionRenderMode::Tint;
lcGetPreferences().mSelectionRenderMode = lcSelectionRenderMode::Tint;
ui->SelectionRenderMode->setToolTip(tr("Outline rendering is unavailable. Face tint and colored edges will be used."));
}
ui->SelectionRenderMode->setCurrentIndex(static_cast<int>(mOptions->Preferences.mSelectionRenderMode));
ui->SelectionRenderMode->setEnabled(OutlineSupported);
ui->SelectionRenderModeLabel->setEnabled(OutlineSupported);
auto SetButtonPixmap = [](quint32 Color, QToolButton* Button)
{
QPixmap Pixmap(12, 12);
Pixmap.fill(QColor(LC_RGBA_RED(Color), LC_RGBA_GREEN(Color), LC_RGBA_BLUE(Color)));
Button->setIcon(Pixmap);
};
SetButtonPixmap(mOptions->Preferences.mBackgroundSolidColor, ui->BackgroundSolidColorButton);
SetButtonPixmap(mOptions->Preferences.mBackgroundGradientColorTop, ui->BackgroundGradient1ColorButton);
SetButtonPixmap(mOptions->Preferences.mBackgroundGradientColorBottom, ui->BackgroundGradient2ColorButton);
SetButtonPixmap(mOptions->Preferences.mTextColor, ui->TextColorButton);
SetButtonPixmap(mOptions->Preferences.mTextHaloColor, ui->TextHaloColorButton);
SetButtonPixmap(mOptions->Preferences.mMarqueeBorderColor, ui->MarqueeBorderColorButton);
SetButtonPixmap(mOptions->Preferences.mMarqueeFillColor, ui->MarqueeFillColorButton);
SetButtonPixmap(mOptions->Preferences.mOverlayColor, ui->OverlayColorButton);
SetButtonPixmap(mOptions->Preferences.mActiveViewColor, ui->ActiveViewColorButton);
SetButtonPixmap(mOptions->Preferences.mInactiveViewColor, ui->InactiveViewColorButton);
SetButtonPixmap(mOptions->Preferences.mFadeStepsColor, ui->FadeStepsColor);
SetButtonPixmap(mOptions->Preferences.mHighlightNewPartsColor, ui->HighlightNewPartsColor);
SetButtonPixmap(mOptions->Preferences.mGridStudColor, ui->gridStudColor);
SetButtonPixmap(mOptions->Preferences.mGridLineColor, ui->gridLineColor);
SetButtonPixmap(mOptions->Preferences.mViewSphereColor, ui->ViewSphereColorButton);
SetButtonPixmap(mOptions->Preferences.mViewSphereTextColor, ui->ViewSphereTextColorButton);
SetButtonPixmap(mOptions->Preferences.mViewSphereHighlightColor, ui->ViewSphereHighlightColorButton);
SetButtonPixmap(mOptions->Preferences.mObjectSelectedColor, ui->ObjectSelectedColorButton);
SetButtonPixmap(mOptions->Preferences.mObjectFocusedColor, ui->ObjectFocusedColorButton);
SetButtonPixmap(mOptions->Preferences.mCameraColor, ui->CameraColorButton);
SetButtonPixmap(mOptions->Preferences.mLightColor, ui->LightColorButton);
SetButtonPixmap(mOptions->Preferences.mControlPointColor, ui->ControlPointColorButton);
SetButtonPixmap(mOptions->Preferences.mControlPointFocusedColor, ui->ControlPointFocusedColorButton);
StudStyleComboCurrentIndexChanged(ui->studStyleCombo->currentIndex());
AntiAliasingToggled();
AutomateEdgeColorToggled();
EdgeLinesToggled();
LineWidthSliderValueChanged();
MeshLODSliderValueChanged();
FadeStepsToggled();
HighlightNewPartsToggled();
GridStudsToggled();
GridLinesToggled();
ViewSphereSizeComboCurrentIndexChanged(ui->ViewSphereSizeCombo->currentIndex());
PreviewViewSphereSizeComboCurrentIndexChanged(ui->PreviewViewSphereSizeCombo->currentIndex());
UpdateCategories();
ui->categoriesTree->setCurrentItem(ui->categoriesTree->topLevelItem(0));
UpdateCommandList();
new lcTreeWidgetColumnStretcher(ui->commandList, 0);
CommandChanged(nullptr);
UpdateMouseTree();
ui->mouseTree->header()->setSectionResizeMode(0, QHeaderView::Stretch);
ui->mouseTree->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
ui->mouseTree->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
MouseTreeItemChanged(nullptr);
for (QLabel* Label : findChildren<QLabel*>())
if (QWidget* Control = Label->buddy())
Label->setToolTip(Control->toolTip());
}
lcPreferencesDialog::~lcPreferencesDialog()
{
delete ui;
}
void lcPreferencesDialog::accept()
{
bool Ok;
int GridLineSpacing = ui->gridLineSpacing->validator()->locale().toInt(ui->gridLineSpacing->text(), &Ok);
if (!Ok || !ui->gridLineSpacing->hasAcceptableInput())
{
QMessageBox::information(this, windowTitle(), tr("Grid spacing must be between 1 and %1 studs.").arg(QString::number(LC_MAX_GRID_LINE_SPACING)));
return;
}
mOptions->LibraryPath = ui->partsLibrary->text();
mOptions->MinifigSettingsPath = ui->MinifigSettingsEdit->text();
mOptions->ColorConfigPath = ui->ColorConfigEdit->text();
mOptions->POVRayPath = ui->povrayExecutable->text();
mOptions->LGEOPath = ui->lgeoPath->text();
mOptions->DefaultAuthor = ui->authorName->text();
mOptions->Preferences.mMouseSensitivity = ui->mouseSensitivity->value();
mOptions->Preferences.mColorTheme = static_cast<lcColorTheme>(ui->ColorTheme->currentIndex());
mOptions->Preferences.mAutomateEdgeColor = ui->AutomateEdgeColor->isChecked();
int Language = ui->Language->currentIndex();
if (Language < 0 || Language > static_cast<int>(LC_ARRAY_COUNT(gLanguageLocales)))
Language = 0;
mOptions->Language = gLanguageLocales[Language];
mOptions->CheckForUpdates = ui->checkForUpdates->currentIndex();
mOptions->Preferences.mFixedAxes = ui->fixedDirectionKeys->isChecked();
mOptions->Preferences.mAutoLoadMostRecent = ui->autoLoadMostRecent->isChecked();
mOptions->Preferences.mRestoreTabLayout = ui->RestoreTabLayout->isChecked();
if (!ui->antiAliasing->isChecked())
mOptions->AASamples = 1;
else if (ui->antiAliasingSamples->currentIndex() == 2)
mOptions->AASamples = 8;
else if (ui->antiAliasingSamples->currentIndex() == 1)
mOptions->AASamples = 4;
else
mOptions->AASamples = 2;
mOptions->Preferences.mDrawEdgeLines = ui->edgeLines->isChecked();
mOptions->Preferences.mDrawConditionalLines = ui->ConditionalLinesCheckBox->isChecked();
mOptions->Preferences.mLineWidth = mLineWidthRange[0] + static_cast<float>(ui->LineWidthSlider->value()) * mLineWidthGranularity;
mOptions->Preferences.mAllowLOD = ui->MeshLOD->isChecked();
mOptions->Preferences.mMeshLODDistance = ui->MeshLODSlider->value() * mMeshLODMultiplier;
mOptions->Preferences.mFadeSteps = ui->FadeSteps->isChecked();
mOptions->Preferences.mHighlightNewParts = ui->HighlightNewParts->isChecked();
mOptions->Preferences.mDrawGridStuds = ui->gridStuds->isChecked();
mOptions->Preferences.mDrawGridLines = ui->gridLines->isChecked();
mOptions->Preferences.mGridLineSpacing = GridLineSpacing;
mOptions->Preferences.mDrawGridOrigin = ui->GridOriginCheckBox->isChecked();
mOptions->Preferences.mBackgroundGradient = ui->BackgroundGradientRadio->isChecked();
mOptions->Preferences.mDrawAxes = ui->AxisIconCheckBox->isChecked();
mOptions->Preferences.mAxisIconLocation = (lcAxisIconLocation)ui->AxisIconLocationCombo->currentIndex();
mOptions->Preferences.mViewSphereEnabled = ui->ViewSphereSizeCombo->currentIndex() > 0;
mOptions->Preferences.mViewSphereLocation = (lcViewSphereLocation)ui->ViewSphereLocationCombo->currentIndex();
switch (ui->ViewSphereSizeCombo->currentIndex())
{
case 3:
mOptions->Preferences.mViewSphereSize = 200;
break;
case 2:
mOptions->Preferences.mViewSphereSize = 100;
break;
case 1:
mOptions->Preferences.mViewSphereSize = 50;
break;
}
mOptions->Preferences.mShadingMode = (lcShadingMode)ui->ShadingMode->currentIndex();
mOptions->Preferences.mSelectionRenderMode = static_cast<lcSelectionRenderMode>(ui->SelectionRenderMode->currentIndex());
mOptions->StudStyle = static_cast<lcStudStyle>(ui->studStyleCombo->currentIndex());
mOptions->Preferences.mDrawPreviewAxis = ui->PreviewAxisIconCheckBox->isChecked();
mOptions->Preferences.mPreviewViewSphereEnabled = ui->PreviewViewSphereSizeCombo->currentIndex() > 0;
mOptions->Preferences.mPreviewViewSphereLocation = (lcViewSphereLocation)ui->PreviewViewSphereLocationCombo->currentIndex();
switch (ui->PreviewViewSphereSizeCombo->currentIndex())
{
case 3:
mOptions->Preferences.mPreviewViewSphereSize = 100;
break;
case 2:
mOptions->Preferences.mPreviewViewSphereSize = 75;
break;
case 1:
mOptions->Preferences.mPreviewViewSphereSize = 50;
break;
}
QDialog::accept();
}
void lcPreferencesDialog::PartsLibraryBrowseClicked()
{
QString Result = QFileDialog::getExistingDirectory(this, tr("Select Pieces Library Folder"), ui->partsLibrary->text());
if (!Result.isEmpty())
ui->partsLibrary->setText(QDir::toNativeSeparators(Result));
}
void lcPreferencesDialog::PartsArchiveBrowseClicked()
{
QString Result = QFileDialog::getOpenFileName(this, tr("Select Pieces Library Archive"), ui->partsLibrary->text(), tr("Supported Archives (*.zip *.bin);;All Files (*.*)"));
if (!Result.isEmpty())
ui->partsLibrary->setText(QDir::toNativeSeparators(Result));
}
void lcPreferencesDialog::BlenderAddonSettingsButtonClicked()
{
#ifndef LC_DISABLE_BLENDER
int Width = 1280;
int Height = 720;
double Scale = 1.0f;
lcBlenderPreferencesDialog::GetBlenderPreferences(
Width,
Height,
Scale,
this);
#endif // !LC_BLENDER_DISABLED
}
void lcPreferencesDialog::ColorConfigBrowseButtonClicked()
{
QString Result = QFileDialog::getOpenFileName(this, tr("Select Color Configuration File"), ui->ColorConfigEdit->text(), tr("Settings Files (*.ldr);;All Files (*.*)"));
if (!Result.isEmpty())
ui->ColorConfigEdit->setText(QDir::toNativeSeparators(Result));
}
void lcPreferencesDialog::MinifigSettingsBrowseButtonClicked()
{
QString Result = QFileDialog::getOpenFileName(this, tr("Select Minifig Settings File"), ui->MinifigSettingsEdit->text(), tr("Settings Files (*.ini);;All Files (*.*)"));
if (!Result.isEmpty())
ui->MinifigSettingsEdit->setText(QDir::toNativeSeparators(Result));
}
void lcPreferencesDialog::PovrayExecutableBrowseClicked()
{
#ifdef Q_OS_WIN
QString filter(tr("Executable Files (*.exe);;All Files (*.*)"));
#else
QString filter(tr("All Files (*.*)"));
#endif
QString Result = QFileDialog::getOpenFileName(this, tr("Select POV-Ray Executable"), ui->povrayExecutable->text(), filter);
if (!Result.isEmpty())
ui->povrayExecutable->setText(QDir::toNativeSeparators(Result));
}
void lcPreferencesDialog::LgeoPathBrowseClicked()
{
QString Result = QFileDialog::getExistingDirectory(this, tr("Open LGEO Folder"), ui->lgeoPath->text());
if (!Result.isEmpty())
ui->lgeoPath->setText(QDir::toNativeSeparators(Result));
}
void lcPreferencesDialog::ColorThemeCurrentIndexChanged(int Index)
{
Q_UNUSED(Index);
if (QMessageBox::question(this, tr("Reset Colors"), tr("Would you like to also reset the interface colors to match the color theme?")) == QMessageBox::Yes)
mOptions->Preferences.SetInterfaceColors(static_cast<lcColorTheme>(ui->ColorTheme->currentIndex()));
}
void lcPreferencesDialog::ColorButtonClicked()
{
QObject* Button = sender();
QString Title;
quint32* Color = nullptr;
QColorDialog::ColorDialogOptions DialogOptions;
if (Button == ui->BackgroundSolidColorButton)
{
Color = &mOptions->Preferences.mBackgroundSolidColor;
Title = tr("Select Background Color");
}
else if (Button == ui->BackgroundGradient1ColorButton)
{
Color = &mOptions->Preferences.mBackgroundGradientColorTop;
Title = tr("Select Gradient Top Color");
}
else if (Button == ui->BackgroundGradient2ColorButton)
{
Color = &mOptions->Preferences.mBackgroundGradientColorBottom;
Title = tr("Select Gradient Bottom Color");
}
else if (Button == ui->TextColorButton)
{
Color = &mOptions->Preferences.mTextColor;
Title = tr("Select Text Color");
}
else if (Button == ui->TextHaloColorButton)
{
Color = &mOptions->Preferences.mTextHaloColor;
Title = tr("Select Text Halo Color");
DialogOptions = QColorDialog::ShowAlphaChannel;
}
else if (Button == ui->MarqueeBorderColorButton)
{
Color = &mOptions->Preferences.mMarqueeBorderColor;
Title = tr("Select Marquee Border Color");
}
else if (Button == ui->MarqueeFillColorButton)
{
Color = &mOptions->Preferences.mMarqueeFillColor;
Title = tr("Select Marquee Fill Color");
DialogOptions = QColorDialog::ShowAlphaChannel;
}
else if (Button == ui->OverlayColorButton)
{
Color = &mOptions->Preferences.mOverlayColor;
Title = tr("Select Overlay Color");
}
else if (Button == ui->ActiveViewColorButton)
{
Color = &mOptions->Preferences.mActiveViewColor;
Title = tr("Select Active View Color");
}
else if (Button == ui->InactiveViewColorButton)
{
Color = &mOptions->Preferences.mInactiveViewColor;
Title = tr("Select Inactive View Color");
}
else if (Button == ui->FadeStepsColor)
{
Color = &mOptions->Preferences.mFadeStepsColor;
Title = tr("Select Fade Color");
DialogOptions = QColorDialog::ShowAlphaChannel;
}
else if (Button == ui->HighlightNewPartsColor)
{
Color = &mOptions->Preferences.mHighlightNewPartsColor;
Title = tr("Select Highlight Color");
DialogOptions = QColorDialog::ShowAlphaChannel;
}
else if (Button == ui->gridStudColor)
{
Color = &mOptions->Preferences.mGridStudColor;
Title = tr("Select Grid Stud Color");
DialogOptions = QColorDialog::ShowAlphaChannel;
}
else if (Button == ui->gridLineColor)
{
Color = &mOptions->Preferences.mGridLineColor;
Title = tr("Select Grid Line Color");
}
else if (Button == ui->ViewSphereColorButton)
{
Color = &mOptions->Preferences.mViewSphereColor;
Title = tr("Select View Sphere Color");
}
else if (Button == ui->ViewSphereTextColorButton)
{
Color = &mOptions->Preferences.mViewSphereTextColor;
Title = tr("Select View Sphere Text Color");
}
else if (Button == ui->ViewSphereHighlightColorButton)
{
Color = &mOptions->Preferences.mViewSphereHighlightColor;
Title = tr("Select View Sphere Highlight Color");
}
else if (Button == ui->ObjectSelectedColorButton)
{
Color = &mOptions->Preferences.mObjectSelectedColor;
Title = tr("Select Object Selected Color");
}
else if (Button == ui->ObjectFocusedColorButton)
{
Color = &mOptions->Preferences.mObjectFocusedColor;
Title = tr("Select Object Focused Color");
}
else if (Button == ui->CameraColorButton)
{
Color = &mOptions->Preferences.mCameraColor;
Title = tr("Select Camera Color");
}
else if (Button == ui->LightColorButton)
{
Color = &mOptions->Preferences.mLightColor;
Title = tr("Select Light Color");
}
else if (Button == ui->ControlPointColorButton)
{
Color = &mOptions->Preferences.mControlPointColor;
Title = tr("Select Control Point Color");
DialogOptions = QColorDialog::ShowAlphaChannel;
}
else if (Button == ui->ControlPointFocusedColorButton)
{
Color = &mOptions->Preferences.mControlPointFocusedColor;
Title = tr("Select Control Point Focused Color");
DialogOptions = QColorDialog::ShowAlphaChannel;
}
else
return;
QColor OldColor = QColor(LC_RGBA_RED(*Color), LC_RGBA_GREEN(*Color), LC_RGBA_BLUE(*Color), LC_RGBA_ALPHA(*Color));
QColor NewColor = QColorDialog::getColor(OldColor, this, Title, DialogOptions);
if (NewColor == OldColor || !NewColor.isValid())
return;
*Color = LC_RGBA(NewColor.red(), NewColor.green(), NewColor.blue(), NewColor.alpha());
QPixmap pix(12, 12);
NewColor.setAlpha(255);
pix.fill(NewColor);
((QToolButton*)Button)->setIcon(pix);
}
void lcPreferencesDialog::AntiAliasingToggled()
{
ui->antiAliasingSamples->setEnabled(ui->antiAliasing->isChecked());
}
void lcPreferencesDialog::EdgeLinesToggled()
{
const bool Enable = ui->edgeLines->isChecked() || ui->ConditionalLinesCheckBox->isChecked();
ui->LineWidthSlider->setEnabled(Enable);
ui->LineWidthLabel->setEnabled(Enable);
}
void lcPreferencesDialog::ConditionalLinesCheckBoxToggled()
{
const bool Enable = ui->edgeLines->isChecked() || ui->ConditionalLinesCheckBox->isChecked();
ui->LineWidthSlider->setEnabled(Enable);
ui->LineWidthLabel->setEnabled(Enable);
}
void lcPreferencesDialog::LineWidthSliderValueChanged()
{
float Value = mLineWidthRange[0] + static_cast<float>(ui->LineWidthSlider->value()) * mLineWidthGranularity;
ui->LineWidthLabel->setText(QString::number(Value));
}
void lcPreferencesDialog::MeshLODSliderValueChanged()
{
float Value = ui->MeshLODSlider->value() * mMeshLODMultiplier;
ui->MeshLODLabel->setText(QString::number(static_cast<int>(Value)));
}
void lcPreferencesDialog::FadeStepsToggled()
{
ui->FadeStepsColor->setEnabled(ui->FadeSteps->isChecked());
}
void lcPreferencesDialog::HighlightNewPartsToggled()
{
ui->HighlightNewPartsColor->setEnabled(ui->HighlightNewParts->isChecked());
}
void lcPreferencesDialog::GridStudsToggled()
{
ui->gridStudColor->setEnabled(ui->gridStuds->isChecked());
}
void lcPreferencesDialog::GridLinesToggled()
{
ui->gridLineColor->setEnabled(ui->gridLines->isChecked());
ui->gridLineSpacing->setEnabled(ui->gridLines->isChecked());
}
void lcPreferencesDialog::PreviewViewSphereSizeComboCurrentIndexChanged(int Index)
{
ui->PreviewViewSphereLocationCombo->setEnabled(Index != 0 && lcContext::SupportsMaterial(lcMaterialType::UnlitViewSphere));
}
void lcPreferencesDialog::ViewSphereSizeComboCurrentIndexChanged(int Index)
{
ui->ViewSphereLocationCombo->setEnabled(Index != 0 && lcContext::SupportsMaterial(lcMaterialType::UnlitViewSphere));
}
void lcPreferencesDialog::AutomateEdgeColorToggled()
{
ui->AutomateEdgeColorButton->setEnabled(ui->AutomateEdgeColor->isChecked());
}
void lcPreferencesDialog::StudStyleComboCurrentIndexChanged(int Index)
{
ui->HighContrastButton->setEnabled(lcIsHighContrast(static_cast<lcStudStyle>(Index)));
}
void lcPreferencesDialog::AutomateEdgeColor()
{
lcAutomateEdgeColorDialog Dialog(this, sender() == ui->HighContrastButton);
if (Dialog.exec() == QDialog::Accepted)
{
mOptions->Preferences.mStudCylinderColorEnabled = Dialog.mStudCylinderColorEnabled;
mOptions->Preferences.mStudCylinderColor = Dialog.mStudCylinderColor;
mOptions->Preferences.mPartEdgeColorEnabled = Dialog.mPartEdgeColorEnabled;
mOptions->Preferences.mPartEdgeColor = Dialog.mPartEdgeColor;
mOptions->Preferences.mBlackEdgeColorEnabled = Dialog.mBlackEdgeColorEnabled;
mOptions->Preferences.mBlackEdgeColor = Dialog.mBlackEdgeColor;
mOptions->Preferences.mDarkEdgeColorEnabled = Dialog.mDarkEdgeColorEnabled;
mOptions->Preferences.mDarkEdgeColor = Dialog.mDarkEdgeColor;
mOptions->Preferences.mPartEdgeContrast = Dialog.mPartEdgeContrast;
mOptions->Preferences.mPartColorValueLDIndex = Dialog.mPartColorValueLDIndex;
}
}
void lcPreferencesDialog::UpdateCategories()
{
QTreeWidgetItem* CategoryItem;
QTreeWidget* CategoriesTree = ui->categoriesTree;
disconnect(ui->categoriesTree->model(), &QAbstractItemModel::rowsInserted, this, &lcPreferencesDialog::CategoriesDropped);
CategoriesTree->clear();
for (int CategoryIndex = 0; CategoryIndex < static_cast<int>(mOptions->Categories.size()); CategoryIndex++)
{
CategoryItem = new QTreeWidgetItem(CategoriesTree, QStringList(mOptions->Categories[CategoryIndex].Name));
CategoryItem->setData(0, CategoryRole, QVariant(CategoryIndex));
CategoryItem->setFlags(CategoryItem->flags() & ~Qt::ItemFlag::ItemIsDropEnabled);
}
CategoryItem = new QTreeWidgetItem(CategoriesTree, QStringList(tr("Unassigned")));
CategoryItem->setData(0, CategoryRole, QVariant(-1));
CategoryItem->setFlags(CategoryItem->flags() & ~Qt::ItemFlag::ItemIsDropEnabled);
connect(ui->categoriesTree->model(), &QAbstractItemModel::rowsInserted, this, &lcPreferencesDialog::CategoriesDropped);
}
void lcPreferencesDialog::UpdateParts()
{
lcPiecesLibrary* Library = lcGetPiecesLibrary();
QTreeWidget* Tree = ui->partsTree;
Tree->clear();
QList<QTreeWidgetItem*> SelectedItems = ui->categoriesTree->selectedItems();
if (SelectedItems.empty())
return;
QTreeWidgetItem* CategoryItem = SelectedItems.first();
int CategoryIndex = CategoryItem->data(0, CategoryRole).toInt();
if (CategoryIndex != -1)
{
std::vector<lcPieceData*> SingleParts, GroupedParts;
Library->GetCategoryEntries(mOptions->Categories[CategoryIndex].Keywords.constData(), false, SingleParts, GroupedParts);
for (lcPieceData* PieceData : SingleParts)
{
QStringList RowList(PieceData->m_strDescription);
RowList.append(PieceData->mFileName);
new QTreeWidgetItem(Tree, RowList);
}
}
else
{
for (lcPieceData* PieceData : Library->GetParts())
{
for (CategoryIndex = 0; CategoryIndex < static_cast<int>(mOptions->Categories.size()); CategoryIndex++)
{
if (Library->PieceDataInCategory(PieceData, mOptions->Categories[CategoryIndex].Keywords.constData()))
break;
}
if (CategoryIndex == static_cast<int>(mOptions->Categories.size()))
{
QStringList RowList(PieceData->m_strDescription);
RowList.append(PieceData->mFileName);
new QTreeWidgetItem(Tree, RowList);
}
}
}
Tree->resizeColumnToContents(0);
Tree->resizeColumnToContents(1);
}
void lcPreferencesDialog::CategoriesDropped(const QModelIndex& Parent, int First, int Last)
{
Q_UNUSED(Parent);
Q_UNUSED(First);
Q_UNUSED(Last);
std::vector<lcLibraryCategory> Categories;
for (int Row = 0; Row < ui->categoriesTree->topLevelItemCount(); Row++)
{
QTreeWidgetItem* CategoryItem = ui->categoriesTree->topLevelItem(Row);
int CategoryIndex = CategoryItem->data(0, CategoryRole).toInt();
if (CategoryIndex >= 0 && CategoryIndex < static_cast<int>(mOptions->Categories.size()))
{
Categories.emplace_back(mOptions->Categories[CategoryIndex]);
CategoryItem->setData(0, CategoryRole, Row);
}
}
mOptions->CategoriesModified = true;
mOptions->CategoriesDefault = false;
mOptions->Categories = std::move(Categories);
}
void lcPreferencesDialog::NewCategoryClicked()
{
lcLibraryCategory Category;
lcCategoryDialog Dialog(this, &Category);
if (Dialog.exec() != QDialog::Accepted)
return;
mOptions->CategoriesModified = true;
mOptions->CategoriesDefault = false;
mOptions->Categories.emplace_back(std::move(Category));
UpdateCategories();
ui->categoriesTree->setCurrentItem(ui->categoriesTree->topLevelItem(static_cast<int>(mOptions->Categories.size()) - 1));
}
void lcPreferencesDialog::EditCategoryClicked()
{
QList<QTreeWidgetItem*> SelectedItems = ui->categoriesTree->selectedItems();
if (SelectedItems.empty())
return;
QTreeWidgetItem* CategoryItem = SelectedItems.first();
int CategoryIndex = CategoryItem->data(0, CategoryRole).toInt();
if (CategoryIndex == -1)
return;
lcCategoryDialog Dialog(this, &mOptions->Categories[CategoryIndex]);
if (Dialog.exec() != QDialog::Accepted)
return;
mOptions->CategoriesModified = true;
mOptions->CategoriesDefault = false;
UpdateCategories();
ui->categoriesTree->setCurrentItem(ui->categoriesTree->topLevelItem(CategoryIndex));
}
void lcPreferencesDialog::DeleteCategoryClicked()
{
QList<QTreeWidgetItem*> selectedItems = ui->categoriesTree->selectedItems();
if (selectedItems.empty())
return;
QTreeWidgetItem* CategoryItem = selectedItems.first();
int CategoryIndex = CategoryItem->data(0, CategoryRole).toInt();
if (CategoryIndex == -1)
return;
QString Question = tr("Are you sure you want to delete the category '%1'?").arg(mOptions->Categories[CategoryIndex].Name);
if (QMessageBox::question(this, tr("Delete Category"), Question, QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
return;
mOptions->CategoriesModified = true;
mOptions->CategoriesDefault = false;
mOptions->Categories.erase(mOptions->Categories.begin() + CategoryIndex);
UpdateCategories();
}
void lcPreferencesDialog::ImportCategoriesClicked()
{
QString FileName = QFileDialog::getOpenFileName(this, tr("Import Categories"), "", tr("Text Files (*.txt);;All Files (*.*)"));
if (FileName.isEmpty())
return;
std::vector<lcLibraryCategory> Categories;
if (!lcLoadCategories(FileName, Categories))
{
QMessageBox::warning(this, tr("Import Categories"), tr("Error loading categories file."));
return;
}
mOptions->Categories = Categories;
mOptions->CategoriesModified = true;
mOptions->CategoriesDefault = false;
}
void lcPreferencesDialog::ExportCategoriesClicked()
{
QString FileName = QFileDialog::getSaveFileName(this, tr("Export Categories"), "", tr("Text Files (*.txt);;All Files (*.*)"));
if (FileName.isEmpty())
return;
if (!lcSaveCategories(FileName, mOptions->Categories))
{
QMessageBox::warning(this, tr("Export Categories"), tr("Error saving categories file."));
return;
}
}
void lcPreferencesDialog::ResetCategoriesClicked()
{
if (QMessageBox::question(this, tr("Reset Categories"), tr("Are you sure you want to load the default categories?"), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
return;
lcResetCategories(mOptions->Categories);
mOptions->CategoriesModified = true;
mOptions->CategoriesDefault = true;
UpdateCategories();
}
bool lcPreferencesDialog::eventFilter(QObject* Object, QEvent* Event)
{
Q_UNUSED(Object);
if (Event->type() == QEvent::KeyPress)
{
QKeyEvent* KeyEvent = static_cast<QKeyEvent*>(Event);
int NextKey = KeyEvent->key();
if (NextKey == Qt::Key_Control || NextKey == Qt::Key_Shift || NextKey == Qt::Key_Meta || NextKey == Qt::Key_Alt)
return true;
Qt::KeyboardModifiers State = KeyEvent->modifiers();
QString Text = QKeySequence(NextKey).toString();
if ((State & Qt::ShiftModifier) && (Text.isEmpty() || !Text.at(0).isPrint() || Text.at(0).isLetter() || Text.at(0).isSpace()))
NextKey |= Qt::SHIFT;
if (State & Qt::ControlModifier)
NextKey |= Qt::CTRL;
if (State & Qt::MetaModifier)
NextKey |= Qt::META;
if (State & Qt::AltModifier)
NextKey |= Qt::ALT;
QKeySequence KeySequence(NextKey);
ui->shortcutEdit->setText(KeySequence.toString(QKeySequence::NativeText));
KeyEvent->accept();
return true;
}
if (Event->type() == QEvent::Shortcut || Event->type() == QEvent::KeyRelease || Event->type() == QEvent::ShortcutOverride)
{
Event->accept();
return true;
}
return QDialog::eventFilter(Object, Event);
}
void lcPreferencesDialog::UpdateCommandList()
{
ui->commandList->clear();
QMap<QString, QTreeWidgetItem*> Sections;
for (unsigned int ActionIndex = 0; ActionIndex < LC_NUM_COMMANDS; ActionIndex++)
{
if (!gCommands[ActionIndex].ID[0])
continue;
const QString Identifier = tr(gCommands[ActionIndex].ID);
qsizetype Pos = Identifier.indexOf(QLatin1Char('.'));
qsizetype SubPos = Identifier.indexOf(QLatin1Char('.'), Pos + 1);
if (SubPos == -1)
SubPos = Pos;
const QString ParentSection = Identifier.left(Pos);
if (SubPos != Pos)
{
if (!Sections.contains(ParentSection))
{
QTreeWidgetItem* CategoryItem = new QTreeWidgetItem(ui->commandList, QStringList(ParentSection));
QFont Font = CategoryItem->font(0);
Font.setBold(true);
CategoryItem->setFont(0, Font);
Sections.insert(ParentSection, CategoryItem);
ui->commandList->expandItem(CategoryItem);
}
}
const QString Section = Identifier.left(SubPos);
const QString SubId = Identifier.mid(SubPos + 1);
if (!Sections.contains(Section))
{
QTreeWidgetItem* Parent = Sections[ParentSection];
QTreeWidgetItem* CategoryItem;
QString SubSection;
if (Pos != SubPos)
SubSection = Identifier.mid(Pos + 1, SubPos - Pos - 1);
else
SubSection = Section;
if (Parent)
CategoryItem = new QTreeWidgetItem(Parent, QStringList(SubSection));
else
CategoryItem = new QTreeWidgetItem(ui->commandList, QStringList(SubSection));
QFont Font = CategoryItem->font(0);
Font.setBold(true);
CategoryItem->setFont(0, Font);
Sections.insert(Section, CategoryItem);
ui->commandList->expandItem(CategoryItem);
}
QTreeWidgetItem* Item = new QTreeWidgetItem;
QKeySequence Sequence(mOptions->KeyboardShortcuts.mShortcuts[ActionIndex]);
Item->setText(0, qApp->translate("Menu", gCommands[ActionIndex].MenuName).remove('&').remove(QLatin1String("...")));
Item->setText(1, Sequence.toString(QKeySequence::NativeText));
Item->setData(0, Qt::UserRole, QVariant::fromValue(ActionIndex));
if (mOptions->KeyboardShortcuts.mShortcuts[ActionIndex] != gCommands[ActionIndex].DefaultShortcut)
SetShortcutModified(Item, true);
Sections[Section]->addChild(Item);
}
}
void lcPreferencesDialog::SetShortcutModified(QTreeWidgetItem* TreeItem, bool Modified)
{
QFont Font = TreeItem->font(0);
Font.setItalic(Modified);
TreeItem->setFont(0, Font);
Font.setBold(Modified);
TreeItem->setFont(1, Font);
}
void lcPreferencesDialog::CommandChanged(QTreeWidgetItem* Current)
{
if (!Current || !Current->data(0, Qt::UserRole).isValid())
{
ui->shortcutEdit->setText(QString());
ui->shortcutGroup->setEnabled(false);
return;
}
ui->shortcutGroup->setEnabled(true);
int ShortcutIndex = qvariant_cast<int>(Current->data(0, Qt::UserRole));
QKeySequence Key(mOptions->KeyboardShortcuts.mShortcuts[ShortcutIndex]);
ui->shortcutEdit->setText(Key.toString(QKeySequence::NativeText));
}
void lcPreferencesDialog::KeyboardFilterEditTextEdited(const QString& Text)
{
if (Text.isEmpty())
{
std::function<void(QTreeWidgetItem*)> ShowItems = [&ShowItems](QTreeWidgetItem* ParentItem)
{
for (int ChildIdx = 0; ChildIdx < ParentItem->childCount(); ChildIdx++)
ShowItems(ParentItem->child(ChildIdx));
ParentItem->setHidden(false);
};
ShowItems(ui->commandList->invisibleRootItem());
}
else
{
std::function<bool(QTreeWidgetItem*,bool)> ShowItems = [&ShowItems, &Text](QTreeWidgetItem* ParentItem, bool ForceVisible)
{
ForceVisible |= ParentItem->text(0).contains(Text, Qt::CaseInsensitive) || ParentItem->text(1).contains(Text, Qt::CaseInsensitive);
bool Visible = ForceVisible;
for (int ChildIdx = 0; ChildIdx < ParentItem->childCount(); ChildIdx++)
Visible |= ShowItems(ParentItem->child(ChildIdx), ForceVisible);
ParentItem->setHidden(!Visible);
return Visible;
};
ShowItems(ui->commandList->invisibleRootItem(), false);
}
}
void lcPreferencesDialog::ShortcutAssignClicked()
{
QTreeWidgetItem* CurrentItem = ui->commandList->currentItem();
if (!CurrentItem || !CurrentItem->data(0, Qt::UserRole).isValid())
return;
uint ShortcutIndex = CurrentItem->data(0, Qt::UserRole).toUInt();
QString (&Shortcuts)[LC_NUM_COMMANDS] = mOptions->KeyboardShortcuts.mShortcuts;
if (ShortcutIndex >= LC_ARRAY_COUNT(Shortcuts))
return;
QString NewShortcut = ui->shortcutEdit->text();
if (!NewShortcut.isEmpty())
{
for (uint ExistingIndex = 0; ExistingIndex < LC_ARRAY_COUNT(Shortcuts); ExistingIndex++)
{
QKeySequence Sequence(Shortcuts[ExistingIndex]);
if (NewShortcut == Sequence.toString(QKeySequence::NativeText) && ExistingIndex != ShortcutIndex)
{
QString ActionText = qApp->translate("Menu", gCommands[ExistingIndex].MenuName).remove('&').remove(QLatin1String("..."));
QString QuestionText = tr("The shortcut '%1' is already assigned to '%2'. Do you want to replace it?").arg(NewShortcut, ActionText);
if (QMessageBox::question(this, tr("Override Shortcut"), QuestionText, QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
return;
mOptions->KeyboardShortcuts.mShortcuts[ExistingIndex].clear();
std::function<QTreeWidgetItem* (QTreeWidgetItem*)> FindItem = [&FindItem, ExistingIndex](QTreeWidgetItem* ParentItem) -> QTreeWidgetItem*
{
for (int ChildIdx = 0; ChildIdx < ParentItem->childCount(); ChildIdx++)
{
QTreeWidgetItem* ChildItem = ParentItem->child(ChildIdx);
uint ChildIndex = ChildItem->data(0, Qt::UserRole).toUInt();
if (ChildIndex == ExistingIndex)
return ChildItem;
QTreeWidgetItem* ExistingItem = FindItem(ChildItem);
if (ExistingItem)
return ExistingItem;
}
return nullptr;
};
QTreeWidgetItem* ExistingItem = FindItem(ui->commandList->invisibleRootItem());
if (ExistingItem)
{
ExistingItem->setText(1, QString());
SetShortcutModified(ExistingItem, gCommands[ShortcutIndex].DefaultShortcut[0] != 0);
}
}
}
}
mOptions->KeyboardShortcuts.mShortcuts[ShortcutIndex] = NewShortcut;
CurrentItem->setText(1, NewShortcut);
SetShortcutModified(CurrentItem, mOptions->KeyboardShortcuts.mShortcuts[ShortcutIndex] != gCommands[ShortcutIndex].DefaultShortcut);
mOptions->KeyboardShortcutsModified = true;
mOptions->KeyboardShortcutsDefault = false;
}
void lcPreferencesDialog::ShortcutRemoveClicked()
{
ui->shortcutEdit->setText(QString());
ShortcutAssignClicked();
}
void lcPreferencesDialog::ShortcutsImportClicked()
{
QString FileName = QFileDialog::getOpenFileName(this, tr("Import Shortcuts"), "", tr("Text Files (*.txt);;All Files (*.*)"));
if (FileName.isEmpty())
return;
lcKeyboardShortcuts Shortcuts;
if (!Shortcuts.Load(FileName))
{
QMessageBox::warning(this, tr("Import Keyboard Shortcuts"), tr("Error loading keyboard shortcuts file."));
return;
}
mOptions->KeyboardShortcuts = Shortcuts;
mOptions->KeyboardShortcutsModified = true;
mOptions->KeyboardShortcutsDefault = false;
}
void lcPreferencesDialog::ShortcutsExportClicked()
{
QString FileName = QFileDialog::getSaveFileName(this, tr("Export Shortcuts"), "", tr("Text Files (*.txt);;All Files (*.*)"));
if (FileName.isEmpty())
return;
if (!mOptions->KeyboardShortcuts.Save(FileName))
{
QMessageBox::warning(this, tr("Export Keyboard Shortcuts"), tr("Error saving keyboard shortcuts file."));
return;
}
}
void lcPreferencesDialog::ShortcutsResetClicked()
{
if (QMessageBox::question(this, tr("Reset Keyboard Shortcuts"), tr("Are you sure you want to load the default keyboard shortcuts?"), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
return;
mOptions->KeyboardShortcuts.Reset();
UpdateCommandList();
mOptions->KeyboardShortcutsModified = true;
mOptions->KeyboardShortcutsDefault = true;
}
void lcPreferencesDialog::UpdateMouseTree()
{
ui->mouseTree->clear();
for (int ToolIdx = 0; ToolIdx < static_cast<int>(lcTool::Count); ToolIdx++)
UpdateMouseTreeItem(ToolIdx);
}
void lcPreferencesDialog::UpdateMouseTreeItem(int ItemIndex)
{
auto GetShortcutText = [](Qt::MouseButton Button, Qt::KeyboardModifiers Modifiers)
{
QString Shortcut = QKeySequence(Modifiers).toString(QKeySequence::NativeText);
switch (Button)
{
case Qt::LeftButton:
Shortcut += tr("Left Button");
break;
case Qt::MiddleButton:
Shortcut += tr("Middle Button");
break;
case Qt::RightButton:
Shortcut += tr("Right Button");
break;
default:
Shortcut.clear();
}
return Shortcut;
};
QString Shortcut1 = GetShortcutText(mOptions->MouseShortcuts.mShortcuts[ItemIndex].Button1, mOptions->MouseShortcuts.mShortcuts[ItemIndex].Modifiers1);
QString Shortcut2 = GetShortcutText(mOptions->MouseShortcuts.mShortcuts[ItemIndex].Button2, mOptions->MouseShortcuts.mShortcuts[ItemIndex].Modifiers2);
QTreeWidgetItem* Item = ui->mouseTree->topLevelItem(ItemIndex);
if (Item)
{
Item->setText(1, Shortcut1);
Item->setText(2, Shortcut2);
}
else
new QTreeWidgetItem(ui->mouseTree, QStringList() << tr(gToolNames[ItemIndex]) << Shortcut1 << Shortcut2);
}
void lcPreferencesDialog::MouseAssignClicked()
{
QTreeWidgetItem* Current = ui->mouseTree->currentItem();
if (!Current)
return;
int ButtonIndex = ui->mouseButton->currentIndex();
Qt::MouseButton Button = Qt::NoButton;
Qt::KeyboardModifiers Modifiers = Qt::NoModifier;
if (ButtonIndex)
{
switch (ButtonIndex)
{
case 1:
Button = Qt::LeftButton;
break;
case 2:
Button = Qt::MiddleButton;
break;
case 3:
Button = Qt::RightButton;
break;
}
if (ui->mouseControl->isChecked())
Modifiers |= Qt::ControlModifier;
if (ui->mouseShift->isChecked())
Modifiers |= Qt::ShiftModifier;
if (ui->mouseAlt->isChecked())
Modifiers |= Qt::AltModifier;
for (int ToolIdx = 0; ToolIdx < static_cast<int>(lcTool::Count); ToolIdx++)
{
if (ToolIdx == ButtonIndex)
continue;
if (mOptions->MouseShortcuts.mShortcuts[ToolIdx].Button2 == Button && mOptions->MouseShortcuts.mShortcuts[ToolIdx].Modifiers2 == Modifiers)
{
if (QMessageBox::question(this, tr("Override Shortcut"), tr("This shortcut is already assigned to '%1'. Do you want to replace it?").arg(tr(gToolNames[ToolIdx])), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)
return;
mOptions->MouseShortcuts.mShortcuts[ToolIdx].Button2 = Qt::NoButton;
mOptions->MouseShortcuts.mShortcuts[ToolIdx].Modifiers2 = Qt::NoModifier;
}
if (mOptions->MouseShortcuts.mShortcuts[ToolIdx].Button1 == Button && mOptions->MouseShortcuts.mShortcuts[ToolIdx].Modifiers1 == Modifiers)
{
if (QMessageBox::question(this, tr("Override Shortcut"), tr("This shortcut is already assigned to '%1'. Do you want to replace it?").arg(tr(gToolNames[ToolIdx])), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)
return;
mOptions->MouseShortcuts.mShortcuts[ToolIdx].Button1 = mOptions->MouseShortcuts.mShortcuts[ToolIdx].Button2;
mOptions->MouseShortcuts.mShortcuts[ToolIdx].Modifiers1 = mOptions->MouseShortcuts.mShortcuts[ToolIdx].Modifiers2;
mOptions->MouseShortcuts.mShortcuts[ToolIdx].Button2 = Qt::NoButton;
mOptions->MouseShortcuts.mShortcuts[ToolIdx].Modifiers2 = Qt::NoModifier;
UpdateMouseTreeItem(ToolIdx);
}
}
}
int ItemIndex = ui->mouseTree->indexOfTopLevelItem(Current);
mOptions->MouseShortcuts.mShortcuts[ItemIndex].Button2 = mOptions->MouseShortcuts.mShortcuts[ItemIndex].Button1;
mOptions->MouseShortcuts.mShortcuts[ItemIndex].Modifiers2 = mOptions->MouseShortcuts.mShortcuts[ItemIndex].Modifiers1;
mOptions->MouseShortcuts.mShortcuts[ItemIndex].Button1 = Button;
mOptions->MouseShortcuts.mShortcuts[ItemIndex].Modifiers1 = Modifiers;
mOptions->MouseShortcutsModified = true;
mOptions->MouseShortcutsDefault = false;
UpdateMouseTreeItem(ItemIndex);
}
void lcPreferencesDialog::MouseRemoveClicked()
{
QTreeWidgetItem* Current = ui->mouseTree->currentItem();
if (!Current)
return;
int ItemIndex = ui->mouseTree->indexOfTopLevelItem(Current);
mOptions->MouseShortcuts.mShortcuts[ItemIndex].Button1 = mOptions->MouseShortcuts.mShortcuts[ItemIndex].Button2;
mOptions->MouseShortcuts.mShortcuts[ItemIndex].Modifiers1 = mOptions->MouseShortcuts.mShortcuts[ItemIndex].Modifiers2;
mOptions->MouseShortcuts.mShortcuts[ItemIndex].Button2 = Qt::NoButton;
mOptions->MouseShortcuts.mShortcuts[ItemIndex].Modifiers2 = Qt::NoModifier;
mOptions->MouseShortcutsModified = true;
mOptions->MouseShortcutsDefault = false;
UpdateMouseTreeItem(ItemIndex);
MouseTreeItemChanged(Current);
}
void lcPreferencesDialog::MouseImportButtonClicked()
{
QString FileName = QFileDialog::getOpenFileName(this, tr("Import Shortcuts"), "", tr("Text Files (*.txt);;All Files (*.*)"));
if (FileName.isEmpty())
return;
lcMouseShortcuts Shortcuts;
if (!Shortcuts.Load(FileName))
{
QMessageBox::warning(this, tr("Import Mouse Shortcuts"), tr("Error loading mouse shortcuts file."));
return;
}
mOptions->MouseShortcuts = Shortcuts;
UpdateMouseTree();
mOptions->MouseShortcutsModified = true;
mOptions->MouseShortcutsDefault = false;
}
void lcPreferencesDialog::MouseExportButtonClicked()
{
QString FileName = QFileDialog::getSaveFileName(this, tr("Export Shortcuts"), "", tr("Text Files (*.txt);;All Files (*.*)"));
if (FileName.isEmpty())
return;
if (!mOptions->MouseShortcuts.Save(FileName))
QMessageBox::warning(this, tr("Export Mouse Shortcuts"), tr("Error saving mouse shortcuts file."));
}
void lcPreferencesDialog::MouseResetClicked()
{
if (QMessageBox::question(this, tr("Reset Mouse Shortcuts"), tr("Are you sure you want to load the default mouse shortcuts?"), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
return;
mOptions->MouseShortcuts.Reset();
UpdateMouseTree();
mOptions->MouseShortcutsModified = true;
mOptions->MouseShortcutsDefault = true;
}
void lcPreferencesDialog::MouseTreeItemChanged(QTreeWidgetItem* Current)
{
if (!Current)
{
ui->MouseShortcutGroup->setEnabled(false);
return;
}
ui->MouseShortcutGroup->setEnabled(true);
int ToolIndex = ui->mouseTree->indexOfTopLevelItem(Current);
Qt::MouseButton Button = mOptions->MouseShortcuts.mShortcuts[ToolIndex].Button1;
switch (Button)
{
case Qt::LeftButton:
ui->mouseButton->setCurrentIndex(1);
break;
case Qt::MiddleButton:
ui->mouseButton->setCurrentIndex(2);
break;
case Qt::RightButton:
ui->mouseButton->setCurrentIndex(3);
break;
default:
ui->mouseButton->setCurrentIndex(0);
break;
}
Qt::KeyboardModifiers Modifiers = mOptions->MouseShortcuts.mShortcuts[ToolIndex].Modifiers1;
ui->mouseControl->setChecked((Modifiers & Qt::ControlModifier) != 0);
ui->mouseShift->setChecked((Modifiers & Qt::ShiftModifier) != 0);
ui->mouseAlt->setChecked((Modifiers & Qt::AltModifier) != 0);
}