Files
leocad/qt/lc_qpreferencesdialog.cpp
T
Leonardo Zide c9f473b1bf Add fading outlines for new-piece highlights
Highlight newly added pieces with the configured highlight color in outline
mode, and widen outlines with an outward opacity falloff. Keep highlight
coverage independent of selection in the existing mask so overlap preserves
selection and focus priority, and limit the obscured fill to selection.
Skip selection visibility and occluder draws in highlight-only scenes while
retaining colored-edge fallback for line meshes and unsupported views.

Rename the shared preference to Highlight Style throughout the UI, renderer,
and persisted settings without migrating the previous key. Update contextual
help and translation catalogs, preserving existing translations and leaving
new text unfinished. Name the selection-only mask draw DrawSelectionMask.

Validation: macOS debug build; desktop OpenGL checks for fading, focus colors,
selection/highlight overlap, occlusion, transparency, MSAA, and edge fallback;
preference round trips; compilation of all nine translation catalogs.

Fixes #461
2026-10-23 23:49:46 -07:00

1509 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.mHighlightRenderMode = lcHighlightRenderMode::Tint;
lcGetPreferences().mHighlightRenderMode = lcHighlightRenderMode::Tint;
ui->HighlightRenderMode->setToolTip(tr("Outline rendering is unavailable; the Tint and Edges style will be used."));
ui->HighlightRenderModeLabel->setToolTip(ui->HighlightRenderMode->toolTip());
}
ui->HighlightRenderMode->setCurrentIndex(static_cast<int>(mOptions->Preferences.mHighlightRenderMode));
ui->HighlightRenderMode->setEnabled(OutlineSupported);
ui->HighlightRenderModeLabel->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.mHighlightRenderMode = static_cast<lcHighlightRenderMode>(ui->HighlightRenderMode->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(lcFormatDoubleLocalized(Value, locale(), 'g', 6));
}
void lcPreferencesDialog::MeshLODSliderValueChanged()
{
float Value = ui->MeshLODSlider->value() * mMeshLODMultiplier;
ui->MeshLODLabel->setText(lcFormatIntegerLocalized(static_cast<int>(Value), locale()));
}
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);
}