1 #include "lc_global.h"
2 #include "lc_qpreferencesdialog.h"
3 #include "ui_lc_qpreferencesdialog.h"
4 #include "lc_qutils.h"
5 #include "lc_qcategorydialog.h"
6 #include "lc_library.h"
7 #include "lc_application.h"
8 #include "lc_qutils.h"
9 #include "lc_glextensions.h"
10 #include "pieceinf.h"
11 #include "lc_edgecolordialog.h"
12 
13 static const char* gLanguageLocales[] =
14 {
15 	"", "cs_CZ", "de_DE", "en_US", "fr_FR", "pt_PT", "es_ES"
16 };
17 
lcQPreferencesDialog(QWidget * Parent,lcPreferencesDialogOptions * Options)18 lcQPreferencesDialog::lcQPreferencesDialog(QWidget* Parent, lcPreferencesDialogOptions* Options)
19 	: QDialog(Parent), mOptions(Options), ui(new Ui::lcQPreferencesDialog)
20 {
21 	ui->setupUi(this);
22 
23 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
24 	ui->povrayLabel->hide();
25 	ui->povrayExecutable->hide();
26 	ui->povrayExecutableBrowse->hide();
27 	delete ui->povrayLabel;
28 	delete ui->povrayLayout;
29 #endif
30 
31 	connect(ui->BackgroundSolidColorButton, &QToolButton::clicked, this, &lcQPreferencesDialog::ColorButtonClicked);
32 	connect(ui->BackgroundGradient1ColorButton, &QToolButton::clicked, this, &lcQPreferencesDialog::ColorButtonClicked);
33 	connect(ui->BackgroundGradient2ColorButton, &QToolButton::clicked, this, &lcQPreferencesDialog::ColorButtonClicked);
34 	connect(ui->ActiveViewColorButton, &QToolButton::clicked, this, &lcQPreferencesDialog::ColorButtonClicked);
35 	connect(ui->InactiveViewColorButton, &QToolButton::clicked, this, &lcQPreferencesDialog::ColorButtonClicked);
36 	connect(ui->AxesColorButton, &QToolButton::clicked, this, &lcQPreferencesDialog::ColorButtonClicked);
37 	connect(ui->TextColorButton, &QToolButton::clicked, this, &lcQPreferencesDialog::ColorButtonClicked);
38 	connect(ui->MarqueeBorderColorButton, &QToolButton::clicked, this, &lcQPreferencesDialog::ColorButtonClicked);
39 	connect(ui->MarqueeFillColorButton, &QToolButton::clicked, this, &lcQPreferencesDialog::ColorButtonClicked);
40 	connect(ui->OverlayColorButton, &QToolButton::clicked, this, &lcQPreferencesDialog::ColorButtonClicked);
41 	connect(ui->FadeStepsColor, &QToolButton::clicked, this, &lcQPreferencesDialog::ColorButtonClicked);
42 	connect(ui->HighlightNewPartsColor, &QToolButton::clicked, this, &lcQPreferencesDialog::ColorButtonClicked);
43 	connect(ui->gridStudColor, &QToolButton::clicked, this, &lcQPreferencesDialog::ColorButtonClicked);
44 	connect(ui->gridLineColor, &QToolButton::clicked, this, &lcQPreferencesDialog::ColorButtonClicked);
45 	connect(ui->ViewSphereColorButton, &QToolButton::clicked, this, &lcQPreferencesDialog::ColorButtonClicked);
46 	connect(ui->ViewSphereTextColorButton, &QToolButton::clicked, this, &lcQPreferencesDialog::ColorButtonClicked);
47 	connect(ui->ViewSphereHighlightColorButton, &QToolButton::clicked, this, &lcQPreferencesDialog::ColorButtonClicked);
48 	connect(ui->categoriesTree, SIGNAL(itemSelectionChanged()), this, SLOT(updateParts()));
49 	ui->shortcutEdit->installEventFilter(this);
50 	connect(ui->commandList, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), this, SLOT(commandChanged(QTreeWidgetItem*)));
51 	connect(ui->mouseTree, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), this, SLOT(MouseTreeItemChanged(QTreeWidgetItem*)));
52 	connect(ui->HighContrastButton, SIGNAL(clicked()), this, SLOT(AutomateEdgeColor()));
53 	connect(ui->AutomateEdgeColorButton, SIGNAL(clicked()), this, SLOT(AutomateEdgeColor()));
54 
55 	ui->partsLibrary->setText(mOptions->LibraryPath);
56 	ui->ColorConfigEdit->setText(mOptions->ColorConfigPath);
57 	ui->MinifigSettingsEdit->setText(mOptions->MinifigSettingsPath);
58 	ui->povrayExecutable->setText(mOptions->POVRayPath);
59 	ui->lgeoPath->setText(mOptions->LGEOPath);
60 	ui->authorName->setText(mOptions->DefaultAuthor);
61 	ui->mouseSensitivity->setValue(mOptions->Preferences.mMouseSensitivity);
62 	const bool ColorThemeBlocked = ui->ColorTheme->blockSignals(true);
63 	ui->ColorTheme->setCurrentIndex(static_cast<int>(mOptions->Preferences.mColorTheme));
64 	ui->ColorTheme->blockSignals(ColorThemeBlocked);
65 	for (unsigned int LanguageIdx = 0; LanguageIdx < LC_ARRAY_COUNT(gLanguageLocales); LanguageIdx++)
66 	{
67 		if (mOptions->Language == gLanguageLocales[LanguageIdx])
68 		{
69 			ui->Language->setCurrentIndex(LanguageIdx);
70 			break;
71 		}
72 	}
73 	ui->checkForUpdates->setCurrentIndex(mOptions->CheckForUpdates);
74 	ui->fixedDirectionKeys->setChecked(mOptions->Preferences.mFixedAxes);
75 	ui->autoLoadMostRecent->setChecked(mOptions->Preferences.mAutoLoadMostRecent);
76 	ui->RestoreTabLayout->setChecked(mOptions->Preferences.mRestoreTabLayout);
77 	ui->AutomateEdgeColor->setChecked(mOptions->Preferences.mAutomateEdgeColor);
78 
79 	ui->antiAliasing->setChecked(mOptions->AASamples != 1);
80 	if (mOptions->AASamples == 8)
81 		ui->antiAliasingSamples->setCurrentIndex(2);
82 	else if (mOptions->AASamples == 4)
83 		ui->antiAliasingSamples->setCurrentIndex(1);
84 	else
85 		ui->antiAliasingSamples->setCurrentIndex(0);
86 	ui->edgeLines->setChecked(mOptions->Preferences.mDrawEdgeLines);
87 	ui->ConditionalLinesCheckBox->setChecked(mOptions->Preferences.mDrawConditionalLines);
88 
89 	if (!gSupportsShaderObjects)
90 	{
91 		ui->ConditionalLinesCheckBox->setChecked(false);
92 		ui->ConditionalLinesCheckBox->setEnabled(false);
93 	}
94 
95 #ifndef LC_OPENGLES
96 	if (QSurfaceFormat::defaultFormat().samples() > 1)
97 	{
98 		glGetFloatv(GL_SMOOTH_LINE_WIDTH_RANGE, mLineWidthRange);
99 		glGetFloatv(GL_SMOOTH_LINE_WIDTH_GRANULARITY, &mLineWidthGranularity);
100 	}
101 	else
102 #endif
103 	{
104 		glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, mLineWidthRange);
105 		mLineWidthGranularity = 1.0f;
106 	}
107 
108 	ui->LineWidthSlider->setRange(0, (mLineWidthRange[1] - mLineWidthRange[0]) / mLineWidthGranularity);
109 	ui->LineWidthSlider->setValue((mOptions->Preferences.mLineWidth - mLineWidthRange[0]) / mLineWidthGranularity);
110 
111 	ui->MeshLOD->setChecked(mOptions->Preferences.mAllowLOD);
112 
113 	ui->MeshLODSlider->setRange(0, 1500.0f / mMeshLODMultiplier);
114 	ui->MeshLODSlider->setValue(mOptions->Preferences.mMeshLODDistance / mMeshLODMultiplier);
115 
116 	ui->FadeSteps->setChecked(mOptions->Preferences.mFadeSteps);
117 	ui->HighlightNewParts->setChecked(mOptions->Preferences.mHighlightNewParts);
118 	ui->gridStuds->setChecked(mOptions->Preferences.mDrawGridStuds);
119 	ui->gridLines->setChecked(mOptions->Preferences.mDrawGridLines);
120 	ui->gridLineSpacing->setText(QString::number(mOptions->Preferences.mGridLineSpacing));
121 	ui->GridOriginCheckBox->setChecked(mOptions->Preferences.mDrawGridOrigin);
122 	ui->AxisIconCheckBox->setChecked(mOptions->Preferences.mDrawAxes);
123 
124 	ui->AxisIconLocationCombo->setCurrentIndex((int)mOptions->Preferences.mAxisIconLocation);
125 
126 	if (!mOptions->Preferences.mBackgroundGradient)
127 		ui->BackgroundSolidRadio->setChecked(true);
128 	else
129 		ui->BackgroundGradientRadio->setChecked(true);
130 
131 	ui->ViewSphereLocationCombo->setCurrentIndex((int)mOptions->Preferences.mViewSphereLocation);
132 
133 	if (mOptions->Preferences.mViewSphereEnabled)
134 	{
135 		switch (mOptions->Preferences.mViewSphereSize)
136 		{
137 		case 200:
138 			ui->ViewSphereSizeCombo->setCurrentIndex(3);
139 			break;
140 		case 100:
141 			ui->ViewSphereSizeCombo->setCurrentIndex(2);
142 			break;
143 		case 50:
144 			ui->ViewSphereSizeCombo->setCurrentIndex(1);
145 			break;
146 		default:
147 			ui->ViewSphereSizeCombo->setCurrentIndex(0);
148 			break;
149 		}
150 	}
151 	else
152 		ui->ViewSphereSizeCombo->setCurrentIndex(0);
153 
154 	ui->PreviewAxisIconCheckBox->setChecked(mOptions->Preferences.mDrawPreviewAxis);
155 
156 	ui->PreviewViewSphereLocationCombo->setCurrentIndex((int)mOptions->Preferences.mPreviewViewSphereLocation);
157 
158 	if (mOptions->Preferences.mPreviewViewSphereEnabled)
159 	{
160 		switch (mOptions->Preferences.mPreviewViewSphereSize)
161 		{
162 		case 100:
163 			ui->PreviewViewSphereSizeCombo->setCurrentIndex(3);
164 			break;
165 		case 75:
166 			ui->PreviewViewSphereSizeCombo->setCurrentIndex(2);
167 			break;
168 		case 50:
169 			ui->PreviewViewSphereSizeCombo->setCurrentIndex(1);
170 			break;
171 		default:
172 			ui->PreviewViewSphereSizeCombo->setCurrentIndex(0);
173 			break;
174 		}
175 	}
176 	else
177 		ui->PreviewViewSphereSizeCombo->setCurrentIndex(0);
178 
179 	if (!lcGetPiecesLibrary()->SupportsStudStyle())
180 		ui->studStyleCombo->setEnabled(false);
181 
182 	ui->studStyleCombo->setCurrentIndex(static_cast<int>(mOptions->StudStyle));
183 
184 	if (!gSupportsShaderObjects)
185 		ui->ShadingMode->removeItem(static_cast<int>(lcShadingMode::DefaultLights));
186 	ui->ShadingMode->setCurrentIndex(static_cast<int>(mOptions->Preferences.mShadingMode));
187 
188 	auto SetButtonPixmap = [](quint32 Color, QToolButton* Button)
189 	{
190 		QPixmap Pixmap(12, 12);
191 
192 		Pixmap.fill(QColor(LC_RGBA_RED(Color), LC_RGBA_GREEN(Color), LC_RGBA_BLUE(Color)));
193 		Button->setIcon(Pixmap);
194 	};
195 
196 	SetButtonPixmap(mOptions->Preferences.mBackgroundSolidColor, ui->BackgroundSolidColorButton);
197 	SetButtonPixmap(mOptions->Preferences.mBackgroundGradientColorTop, ui->BackgroundGradient1ColorButton);
198 	SetButtonPixmap(mOptions->Preferences.mBackgroundGradientColorBottom, ui->BackgroundGradient2ColorButton);
199 	SetButtonPixmap(mOptions->Preferences.mAxesColor, ui->AxesColorButton);
200 	SetButtonPixmap(mOptions->Preferences.mTextColor, ui->TextColorButton);
201 	SetButtonPixmap(mOptions->Preferences.mMarqueeBorderColor, ui->MarqueeBorderColorButton);
202 	SetButtonPixmap(mOptions->Preferences.mMarqueeFillColor, ui->MarqueeFillColorButton);
203 	SetButtonPixmap(mOptions->Preferences.mOverlayColor, ui->OverlayColorButton);
204 	SetButtonPixmap(mOptions->Preferences.mActiveViewColor, ui->ActiveViewColorButton);
205 	SetButtonPixmap(mOptions->Preferences.mInactiveViewColor, ui->InactiveViewColorButton);
206 	SetButtonPixmap(mOptions->Preferences.mFadeStepsColor, ui->FadeStepsColor);
207 	SetButtonPixmap(mOptions->Preferences.mHighlightNewPartsColor, ui->HighlightNewPartsColor);
208 	SetButtonPixmap(mOptions->Preferences.mGridStudColor, ui->gridStudColor);
209 	SetButtonPixmap(mOptions->Preferences.mGridLineColor, ui->gridLineColor);
210 	SetButtonPixmap(mOptions->Preferences.mViewSphereColor, ui->ViewSphereColorButton);
211 	SetButtonPixmap(mOptions->Preferences.mViewSphereTextColor, ui->ViewSphereTextColorButton);
212 	SetButtonPixmap(mOptions->Preferences.mViewSphereHighlightColor, ui->ViewSphereHighlightColorButton);
213 
214 	on_studStyleCombo_currentIndexChanged(ui->studStyleCombo->currentIndex());
215 	on_antiAliasing_toggled();
216 	on_AutomateEdgeColor_toggled();
217 	on_edgeLines_toggled();
218 	on_LineWidthSlider_valueChanged();
219 	on_MeshLODSlider_valueChanged();
220 	on_FadeSteps_toggled();
221 	on_HighlightNewParts_toggled();
222 	on_gridStuds_toggled();
223 	on_gridLines_toggled();
224 	on_ViewSphereSizeCombo_currentIndexChanged(ui->ViewSphereSizeCombo->currentIndex());
225 	on_PreviewViewSphereSizeCombo_currentIndexChanged(ui->PreviewViewSphereSizeCombo->currentIndex());
226 
227 	updateCategories();
228 	ui->categoriesTree->setCurrentItem(ui->categoriesTree->topLevelItem(0));
229 
230 	updateCommandList();
231 	new lcQTreeWidgetColumnStretcher(ui->commandList, 0);
232 	commandChanged(nullptr);
233 
234 	UpdateMouseTree();
235 	ui->mouseTree->header()->setSectionResizeMode(0, QHeaderView::Stretch);
236 	ui->mouseTree->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
237 	ui->mouseTree->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
238 	MouseTreeItemChanged(nullptr);
239 }
240 
~lcQPreferencesDialog()241 lcQPreferencesDialog::~lcQPreferencesDialog()
242 {
243 	delete ui;
244 }
245 
accept()246 void lcQPreferencesDialog::accept()
247 {
248 	int gridLineSpacing = ui->gridLineSpacing->text().toInt();
249 	if (gridLineSpacing < 1)
250 	{
251 		QMessageBox::information(this, "LeoCAD", tr("Grid spacing must be greater than 0."));
252 		return;
253 	}
254 
255 	mOptions->LibraryPath = ui->partsLibrary->text();
256 	mOptions->MinifigSettingsPath = ui->MinifigSettingsEdit->text();
257 	mOptions->ColorConfigPath = ui->ColorConfigEdit->text();
258 	mOptions->POVRayPath = ui->povrayExecutable->text();
259 	mOptions->LGEOPath = ui->lgeoPath->text();
260 	mOptions->DefaultAuthor = ui->authorName->text();
261 	mOptions->Preferences.mMouseSensitivity = ui->mouseSensitivity->value();
262 	mOptions->Preferences.mColorTheme = static_cast<lcColorTheme>(ui->ColorTheme->currentIndex());
263 	mOptions->Preferences.mAutomateEdgeColor = ui->AutomateEdgeColor->isChecked();
264 
265 	int Language = ui->Language->currentIndex();
266 	if (Language < 0 || Language > static_cast<int>(LC_ARRAY_COUNT(gLanguageLocales)))
267 		Language = 0;
268 	mOptions->Language = gLanguageLocales[Language];
269 
270 	mOptions->CheckForUpdates = ui->checkForUpdates->currentIndex();
271 	mOptions->Preferences.mFixedAxes = ui->fixedDirectionKeys->isChecked();
272 	mOptions->Preferences.mAutoLoadMostRecent = ui->autoLoadMostRecent->isChecked();
273 	mOptions->Preferences.mRestoreTabLayout = ui->RestoreTabLayout->isChecked();
274 
275 	if (!ui->antiAliasing->isChecked())
276 		mOptions->AASamples = 1;
277 	else if (ui->antiAliasingSamples->currentIndex() == 2)
278 		mOptions->AASamples = 8;
279 	else if (ui->antiAliasingSamples->currentIndex() == 1)
280 		mOptions->AASamples = 4;
281 	else
282 		mOptions->AASamples = 2;
283 
284 	mOptions->Preferences.mDrawEdgeLines = ui->edgeLines->isChecked();
285 	mOptions->Preferences.mDrawConditionalLines = ui->ConditionalLinesCheckBox->isChecked();
286 	mOptions->Preferences.mLineWidth = mLineWidthRange[0] + static_cast<float>(ui->LineWidthSlider->value()) * mLineWidthGranularity;
287 	mOptions->Preferences.mAllowLOD = ui->MeshLOD->isChecked();
288 	mOptions->Preferences.mMeshLODDistance = ui->MeshLODSlider->value() * mMeshLODMultiplier;
289 	mOptions->Preferences.mFadeSteps = ui->FadeSteps->isChecked();
290 	mOptions->Preferences.mHighlightNewParts = ui->HighlightNewParts->isChecked();
291 
292 	mOptions->Preferences.mDrawGridStuds = ui->gridStuds->isChecked();
293 	mOptions->Preferences.mDrawGridLines = ui->gridLines->isChecked();
294 	mOptions->Preferences.mGridLineSpacing = gridLineSpacing;
295 	mOptions->Preferences.mDrawGridOrigin = ui->GridOriginCheckBox->isChecked();
296 
297 	mOptions->Preferences.mBackgroundGradient = ui->BackgroundGradientRadio->isChecked();
298 	mOptions->Preferences.mDrawAxes = ui->AxisIconCheckBox->isChecked();
299 	mOptions->Preferences.mAxisIconLocation = (lcAxisIconLocation)ui->AxisIconLocationCombo->currentIndex();
300 	mOptions->Preferences.mViewSphereEnabled = ui->ViewSphereSizeCombo->currentIndex() > 0;
301 	mOptions->Preferences.mViewSphereLocation = (lcViewSphereLocation)ui->ViewSphereLocationCombo->currentIndex();
302 
303 	switch (ui->ViewSphereSizeCombo->currentIndex())
304 	{
305 	case 3:
306 		mOptions->Preferences.mViewSphereSize = 200;
307 		break;
308 	case 2:
309 		mOptions->Preferences.mViewSphereSize = 100;
310 		break;
311 	case 1:
312 		mOptions->Preferences.mViewSphereSize = 50;
313 		break;
314 	}
315 
316 	mOptions->Preferences.mShadingMode = (lcShadingMode)ui->ShadingMode->currentIndex();
317 
318 	mOptions->StudStyle = static_cast<lcStudStyle>(ui->studStyleCombo->currentIndex());
319 
320 	mOptions->Preferences.mDrawPreviewAxis = ui->PreviewAxisIconCheckBox->isChecked();
321 	mOptions->Preferences.mPreviewViewSphereEnabled = ui->PreviewViewSphereSizeCombo->currentIndex() > 0;
322 	mOptions->Preferences.mPreviewViewSphereLocation = (lcViewSphereLocation)ui->PreviewViewSphereLocationCombo->currentIndex();
323 
324 	switch (ui->PreviewViewSphereSizeCombo->currentIndex())
325 	{
326 	case 3:
327 		mOptions->Preferences.mPreviewViewSphereSize = 100;
328 		break;
329 	case 2:
330 		mOptions->Preferences.mPreviewViewSphereSize = 75;
331 		break;
332 	case 1:
333 		mOptions->Preferences.mPreviewViewSphereSize = 50;
334 		break;
335 	}
336 
337 	QDialog::accept();
338 }
339 
on_partsLibraryBrowse_clicked()340 void lcQPreferencesDialog::on_partsLibraryBrowse_clicked()
341 {
342 	QString result = QFileDialog::getExistingDirectory(this, tr("Select Parts Library Folder"), ui->partsLibrary->text());
343 
344 	if (!result.isEmpty())
345 		ui->partsLibrary->setText(QDir::toNativeSeparators(result));
346 }
347 
on_partsArchiveBrowse_clicked()348 void lcQPreferencesDialog::on_partsArchiveBrowse_clicked()
349 {
350 	QString result = QFileDialog::getOpenFileName(this, tr("Select Parts Library Archive"), ui->partsLibrary->text(), tr("Supported Archives (*.zip *.bin);;All Files (*.*)"));
351 
352 	if (!result.isEmpty())
353 		ui->partsLibrary->setText(QDir::toNativeSeparators(result));
354 }
355 
on_ColorConfigBrowseButton_clicked()356 void lcQPreferencesDialog::on_ColorConfigBrowseButton_clicked()
357 {
358 	QString Result = QFileDialog::getOpenFileName(this, tr("Select Color Configuration File"), ui->ColorConfigEdit->text(), tr("Settings Files (*.ldr);;All Files (*.*)"));
359 
360 	if (!Result.isEmpty())
361 		ui->ColorConfigEdit->setText(QDir::toNativeSeparators(Result));
362 }
363 
on_MinifigSettingsBrowseButton_clicked()364 void lcQPreferencesDialog::on_MinifigSettingsBrowseButton_clicked()
365 {
366 	QString Result = QFileDialog::getOpenFileName(this, tr("Select Minifig Settings File"), ui->MinifigSettingsEdit->text(), tr("Settings Files (*.ini);;All Files (*.*)"));
367 
368 	if (!Result.isEmpty())
369 		ui->MinifigSettingsEdit->setText(QDir::toNativeSeparators(Result));
370 }
371 
on_povrayExecutableBrowse_clicked()372 void lcQPreferencesDialog::on_povrayExecutableBrowse_clicked()
373 {
374 #ifdef Q_OS_WIN
375 	QString filter(tr("Executable Files (*.exe);;All Files (*.*)"));
376 #else
377 	QString filter(tr("All Files (*.*)"));
378 #endif
379 
380 	QString result = QFileDialog::getOpenFileName(this, tr("Select POV-Ray Executable"), ui->povrayExecutable->text(), filter);
381 
382 	if (!result.isEmpty())
383 		ui->povrayExecutable->setText(QDir::toNativeSeparators(result));
384 }
385 
on_lgeoPathBrowse_clicked()386 void lcQPreferencesDialog::on_lgeoPathBrowse_clicked()
387 {
388 	QString result = QFileDialog::getExistingDirectory(this, tr("Open LGEO Folder"), ui->lgeoPath->text());
389 
390 	if (!result.isEmpty())
391 		ui->lgeoPath->setText(QDir::toNativeSeparators(result));
392 }
393 
on_ColorTheme_currentIndexChanged(int Index)394 void lcQPreferencesDialog::on_ColorTheme_currentIndexChanged(int Index)
395 {
396 	Q_UNUSED(Index);
397 
398 	if (QMessageBox::question(this, tr("Reset Colors"), tr("Would you like to also reset the interface colors to match the color theme?")) == QMessageBox::Yes)
399 		mOptions->Preferences.SetInterfaceColors(static_cast<lcColorTheme>(ui->ColorTheme->currentIndex()));
400 }
401 
ColorButtonClicked()402 void lcQPreferencesDialog::ColorButtonClicked()
403 {
404 	QObject* Button = sender();
405 	QString Title;
406 	quint32* Color = nullptr;
407 	QColorDialog::ColorDialogOptions DialogOptions;
408 
409 	if (Button == ui->BackgroundSolidColorButton)
410 	{
411 		Color = &mOptions->Preferences.mBackgroundSolidColor;
412 		Title = tr("Select Background Color");
413 	}
414 	else if (Button == ui->BackgroundGradient1ColorButton)
415 	{
416 		Color = &mOptions->Preferences.mBackgroundGradientColorTop;
417 		Title = tr("Select Gradient Top Color");
418 	}
419 	else if (Button == ui->BackgroundGradient2ColorButton)
420 	{
421 		Color = &mOptions->Preferences.mBackgroundGradientColorBottom;
422 		Title = tr("Select Gradient Bottom Color");
423 	}
424 	else if (Button == ui->AxesColorButton)
425 	{
426 		Color = &mOptions->Preferences.mAxesColor;
427 		Title = tr("Select Axes Color");
428 	}
429 	else if (Button == ui->TextColorButton)
430 	{
431 		Color = &mOptions->Preferences.mTextColor;
432 		Title = tr("Select Text Color");
433 	}
434 	else if (Button == ui->MarqueeBorderColorButton)
435 	{
436 		Color = &mOptions->Preferences.mMarqueeBorderColor;
437 		Title = tr("Select Marquee Border Color");
438 	}
439 	else if (Button == ui->MarqueeFillColorButton)
440 	{
441 		Color = &mOptions->Preferences.mMarqueeFillColor;
442 		Title = tr("Select Marquee Fill Color");
443 		DialogOptions = QColorDialog::ShowAlphaChannel;
444 	}
445 	else if (Button == ui->OverlayColorButton)
446 	{
447 		Color = &mOptions->Preferences.mOverlayColor;
448 		Title = tr("Select Overlay Color");
449 	}
450 	else if (Button == ui->ActiveViewColorButton)
451 	{
452 		Color = &mOptions->Preferences.mActiveViewColor;
453 		Title = tr("Select Active View Color");
454 	}
455 	else if (Button == ui->InactiveViewColorButton)
456 	{
457 		Color = &mOptions->Preferences.mInactiveViewColor;
458 		Title = tr("Select Inactive View Color");
459 	}
460 	else if (Button == ui->FadeStepsColor)
461 	{
462 		Color = &mOptions->Preferences.mFadeStepsColor;
463 		Title = tr("Select Fade Color");
464 		DialogOptions = QColorDialog::ShowAlphaChannel;
465 	}
466 	else if (Button == ui->HighlightNewPartsColor)
467 	{
468 		Color = &mOptions->Preferences.mHighlightNewPartsColor;
469 		Title = tr("Select Highlight Color");
470 		DialogOptions = QColorDialog::ShowAlphaChannel;
471 	}
472 	else if (Button == ui->gridStudColor)
473 	{
474 		Color = &mOptions->Preferences.mGridStudColor;
475 		Title = tr("Select Grid Stud Color");
476 		DialogOptions = QColorDialog::ShowAlphaChannel;
477 	}
478 	else if (Button == ui->gridLineColor)
479 	{
480 		Color = &mOptions->Preferences.mGridLineColor;
481 		Title = tr("Select Grid Line Color");
482 	}
483 	else if (Button == ui->ViewSphereColorButton)
484 	{
485 		Color = &mOptions->Preferences.mViewSphereColor;
486 		Title = tr("Select View Sphere Color");
487 	}
488 	else if (Button == ui->ViewSphereTextColorButton)
489 	{
490 		Color = &mOptions->Preferences.mViewSphereTextColor;
491 		Title = tr("Select View Sphere Text Color");
492 	}
493 	else if (Button == ui->ViewSphereHighlightColorButton)
494 	{
495 		Color = &mOptions->Preferences.mViewSphereHighlightColor;
496 		Title = tr("Select View Sphere Highlight Color");
497 	}
498 	else
499 		return;
500 
501 	QColor oldColor = QColor(LC_RGBA_RED(*Color), LC_RGBA_GREEN(*Color), LC_RGBA_BLUE(*Color), LC_RGBA_ALPHA(*Color));
502 	QColor newColor = QColorDialog::getColor(oldColor, this, Title, DialogOptions);
503 
504 	if (newColor == oldColor || !newColor.isValid())
505 		return;
506 
507 	*Color = LC_RGBA(newColor.red(), newColor.green(), newColor.blue(), newColor.alpha());
508 
509 	QPixmap pix(12, 12);
510 
511 	newColor.setAlpha(255);
512 	pix.fill(newColor);
513 	((QToolButton*)Button)->setIcon(pix);
514 }
515 
on_antiAliasing_toggled()516 void lcQPreferencesDialog::on_antiAliasing_toggled()
517 {
518 	ui->antiAliasingSamples->setEnabled(ui->antiAliasing->isChecked());
519 }
520 
on_edgeLines_toggled()521 void lcQPreferencesDialog::on_edgeLines_toggled()
522 {
523 	const bool Enable = ui->edgeLines->isChecked() || ui->ConditionalLinesCheckBox->isChecked();
524 
525 	ui->LineWidthSlider->setEnabled(Enable);
526 	ui->LineWidthLabel->setEnabled(Enable);
527 }
528 
on_ConditionalLinesCheckBox_toggled()529 void lcQPreferencesDialog::on_ConditionalLinesCheckBox_toggled()
530 {
531 	const bool Enable = ui->edgeLines->isChecked() || ui->ConditionalLinesCheckBox->isChecked();
532 
533 	ui->LineWidthSlider->setEnabled(Enable);
534 	ui->LineWidthLabel->setEnabled(Enable);
535 }
536 
on_LineWidthSlider_valueChanged()537 void lcQPreferencesDialog::on_LineWidthSlider_valueChanged()
538 {
539 	float Value = mLineWidthRange[0] + static_cast<float>(ui->LineWidthSlider->value()) * mLineWidthGranularity;
540 	ui->LineWidthLabel->setText(QString::number(Value));
541 }
542 
on_MeshLODSlider_valueChanged()543 void lcQPreferencesDialog::on_MeshLODSlider_valueChanged()
544 {
545 	float Value = ui->MeshLODSlider->value() * mMeshLODMultiplier;
546 	ui->MeshLODLabel->setText(QString::number(static_cast<int>(Value)));
547 }
548 
on_FadeSteps_toggled()549 void lcQPreferencesDialog::on_FadeSteps_toggled()
550 {
551 	ui->FadeStepsColor->setEnabled(ui->FadeSteps->isChecked());
552 }
553 
on_HighlightNewParts_toggled()554 void lcQPreferencesDialog::on_HighlightNewParts_toggled()
555 {
556 	ui->HighlightNewPartsColor->setEnabled(ui->HighlightNewParts->isChecked());
557 }
558 
on_gridStuds_toggled()559 void lcQPreferencesDialog::on_gridStuds_toggled()
560 {
561 	ui->gridStudColor->setEnabled(ui->gridStuds->isChecked());
562 }
563 
on_gridLines_toggled()564 void lcQPreferencesDialog::on_gridLines_toggled()
565 {
566 	ui->gridLineColor->setEnabled(ui->gridLines->isChecked());
567 	ui->gridLineSpacing->setEnabled(ui->gridLines->isChecked());
568 }
569 
on_PreviewViewSphereSizeCombo_currentIndexChanged(int Index)570 void lcQPreferencesDialog::on_PreviewViewSphereSizeCombo_currentIndexChanged(int Index)
571 {
572 	ui->PreviewViewSphereLocationCombo->setEnabled(Index != 0);
573 }
574 
on_ViewSphereSizeCombo_currentIndexChanged(int Index)575 void lcQPreferencesDialog::on_ViewSphereSizeCombo_currentIndexChanged(int Index)
576 {
577 	ui->ViewSphereLocationCombo->setEnabled(Index != 0);
578 }
579 
on_AutomateEdgeColor_toggled()580 void lcQPreferencesDialog::on_AutomateEdgeColor_toggled()
581 {
582 	ui->AutomateEdgeColorButton->setEnabled(ui->AutomateEdgeColor->isChecked());
583 }
584 
on_studStyleCombo_currentIndexChanged(int index)585 void lcQPreferencesDialog::on_studStyleCombo_currentIndexChanged(int index)
586 {
587 	ui->HighContrastButton->setEnabled(lcIsHighContrast(static_cast<lcStudStyle>(index)));
588 }
589 
AutomateEdgeColor()590 void lcQPreferencesDialog::AutomateEdgeColor()
591 {
592 	lcAutomateEdgeColorDialog Dialog(this, sender() == ui->HighContrastButton);
593 	if (Dialog.exec() == QDialog::Accepted)
594 	{
595 		mOptions->Preferences.mStudCylinderColor = Dialog.mStudCylinderColor;
596 		mOptions->Preferences.mPartEdgeColor = Dialog.mPartEdgeColor;
597 		mOptions->Preferences.mBlackEdgeColor = Dialog.mBlackEdgeColor;
598 		mOptions->Preferences.mDarkEdgeColor = Dialog.mDarkEdgeColor;
599 		mOptions->Preferences.mPartEdgeContrast = Dialog.mPartEdgeContrast;
600 		mOptions->Preferences.mPartColorValueLDIndex = Dialog.mPartColorValueLDIndex;
601 	}
602 }
603 
updateCategories()604 void lcQPreferencesDialog::updateCategories()
605 {
606 	QTreeWidgetItem* CategoryItem;
607 	QTreeWidget* CategoriesTree = ui->categoriesTree;
608 
609 	CategoriesTree->clear();
610 
611 	for (int CategoryIndex = 0; CategoryIndex < static_cast<int>(mOptions->Categories.size()); CategoryIndex++)
612 	{
613 		CategoryItem = new QTreeWidgetItem(CategoriesTree, QStringList(mOptions->Categories[CategoryIndex].Name));
614 		CategoryItem->setData(0, CategoryRole, QVariant(CategoryIndex));
615 	}
616 
617 	CategoryItem = new QTreeWidgetItem(CategoriesTree, QStringList(tr("Unassigned")));
618 	CategoryItem->setData(0, CategoryRole, QVariant(-1));
619 }
620 
updateParts()621 void lcQPreferencesDialog::updateParts()
622 {
623 	lcPiecesLibrary *Library = lcGetPiecesLibrary();
624 	QTreeWidget *tree = ui->partsTree;
625 
626 	tree->clear();
627 
628 	QList<QTreeWidgetItem*> selectedItems = ui->categoriesTree->selectedItems();
629 
630 	if (selectedItems.empty())
631 		return;
632 
633 	QTreeWidgetItem *categoryItem = selectedItems.first();
634 	int categoryIndex = categoryItem->data(0, CategoryRole).toInt();
635 
636 	if (categoryIndex != -1)
637 	{
638 		lcArray<PieceInfo*> singleParts, groupedParts;
639 
640 		Library->GetCategoryEntries(mOptions->Categories[categoryIndex].Keywords.constData(), false, singleParts, groupedParts);
641 
642 		for (int partIndex = 0; partIndex < singleParts.GetSize(); partIndex++)
643 		{
644 			PieceInfo *info = singleParts[partIndex];
645 
646 			QStringList rowList(info->m_strDescription);
647 			rowList.append(info->mFileName);
648 
649 			new QTreeWidgetItem(tree, rowList);
650 		}
651 	}
652 	else
653 	{
654 		for (const auto& PartIt : Library->mPieces)
655 		{
656 			PieceInfo* Info = PartIt.second;
657 
658 			for (categoryIndex = 0; categoryIndex < static_cast<int>(mOptions->Categories.size()); categoryIndex++)
659 			{
660 				if (Library->PieceInCategory(Info, mOptions->Categories[categoryIndex].Keywords.constData()))
661 					break;
662 			}
663 
664 			if (categoryIndex == static_cast<int>(mOptions->Categories.size()))
665 			{
666 				QStringList rowList(Info->m_strDescription);
667 				rowList.append(Info->mFileName);
668 
669 				new QTreeWidgetItem(tree, rowList);
670 			}
671 		}
672 	}
673 
674 	tree->resizeColumnToContents(0);
675 	tree->resizeColumnToContents(1);
676 }
677 
on_newCategory_clicked()678 void lcQPreferencesDialog::on_newCategory_clicked()
679 {
680 	lcLibraryCategory category;
681 
682 	lcQCategoryDialog dialog(this, &category);
683 	if (dialog.exec() != QDialog::Accepted)
684 		return;
685 
686 	mOptions->CategoriesModified = true;
687 	mOptions->CategoriesDefault = false;
688 	mOptions->Categories.emplace_back(std::move(category));
689 
690 	updateCategories();
691 	ui->categoriesTree->setCurrentItem(ui->categoriesTree->topLevelItem(static_cast<int>(mOptions->Categories.size()) - 1));
692 }
693 
on_editCategory_clicked()694 void lcQPreferencesDialog::on_editCategory_clicked()
695 {
696 	QList<QTreeWidgetItem*> selectedItems = ui->categoriesTree->selectedItems();
697 
698 	if (selectedItems.empty())
699 		return;
700 
701 	QTreeWidgetItem *categoryItem = selectedItems.first();
702 	int categoryIndex = categoryItem->data(0, CategoryRole).toInt();
703 
704 	if (categoryIndex == -1)
705 		return;
706 
707 	lcQCategoryDialog dialog(this, &mOptions->Categories[categoryIndex]);
708 	if (dialog.exec() != QDialog::Accepted)
709 		return;
710 
711 	mOptions->CategoriesModified = true;
712 	mOptions->CategoriesDefault = false;
713 
714 	updateCategories();
715 	ui->categoriesTree->setCurrentItem(ui->categoriesTree->topLevelItem(categoryIndex));
716 }
717 
on_deleteCategory_clicked()718 void lcQPreferencesDialog::on_deleteCategory_clicked()
719 {
720 	QList<QTreeWidgetItem*> selectedItems = ui->categoriesTree->selectedItems();
721 
722 	if (selectedItems.empty())
723 		return;
724 
725 	QTreeWidgetItem *categoryItem = selectedItems.first();
726 	int categoryIndex = categoryItem->data(0, CategoryRole).toInt();
727 
728 	if (categoryIndex == -1)
729 		return;
730 
731 	QString question = tr("Are you sure you want to delete the category '%1'?").arg(mOptions->Categories[categoryIndex].Name);
732 	if (QMessageBox::question(this, "LeoCAD", question, QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
733 		return;
734 
735 	mOptions->CategoriesModified = true;
736 	mOptions->CategoriesDefault = false;
737 	mOptions->Categories.erase(mOptions->Categories.begin() + categoryIndex);
738 
739 	updateCategories();
740 }
741 
on_importCategories_clicked()742 void lcQPreferencesDialog::on_importCategories_clicked()
743 {
744 	QString FileName = QFileDialog::getOpenFileName(this, tr("Import Categories"), "", tr("Text Files (*.txt);;All Files (*.*)"));
745 
746 	if (FileName.isEmpty())
747 		return;
748 
749 	std::vector<lcLibraryCategory> Categories;
750 	if (!lcLoadCategories(FileName, Categories))
751 	{
752 		QMessageBox::warning(this, "LeoCAD", tr("Error loading categories file."));
753 		return;
754 	}
755 
756 	mOptions->Categories = Categories;
757 	mOptions->CategoriesModified = true;
758 	mOptions->CategoriesDefault = false;
759 }
760 
on_exportCategories_clicked()761 void lcQPreferencesDialog::on_exportCategories_clicked()
762 {
763 	QString FileName = QFileDialog::getSaveFileName(this, tr("Export Categories"), "", tr("Text Files (*.txt);;All Files (*.*)"));
764 
765 	if (FileName.isEmpty())
766 		return;
767 
768 	if (!lcSaveCategories(FileName, mOptions->Categories))
769 	{
770 		QMessageBox::warning(this, "LeoCAD", tr("Error saving categories file."));
771 		return;
772 	}
773 }
774 
on_resetCategories_clicked()775 void lcQPreferencesDialog::on_resetCategories_clicked()
776 {
777 	if (QMessageBox::question(this, "LeoCAD", tr("Are you sure you want to load the default categories?"), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
778 		return;
779 
780 	lcResetCategories(mOptions->Categories);
781 
782 	mOptions->CategoriesModified = true;
783 	mOptions->CategoriesDefault = true;
784 
785 	updateCategories();
786 }
787 
eventFilter(QObject * object,QEvent * event)788 bool lcQPreferencesDialog::eventFilter(QObject *object, QEvent *event)
789 {
790 	Q_UNUSED(object);
791 
792 	if (event->type() == QEvent::KeyPress)
793 	{
794 		QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
795 
796 		int nextKey = keyEvent->key();
797 		if (nextKey == Qt::Key_Control || nextKey == Qt::Key_Shift || nextKey == Qt::Key_Meta || nextKey == Qt::Key_Alt)
798 			return true;
799 
800 		Qt::KeyboardModifiers state = keyEvent->modifiers();
801 		QString text = QKeySequence(nextKey).toString();
802 		if ((state & Qt::ShiftModifier) && (text.isEmpty() || !text.at(0).isPrint() || text.at(0).isLetter() || text.at(0).isSpace()))
803 			nextKey |= Qt::SHIFT;
804 		if (state & Qt::ControlModifier)
805 			nextKey |= Qt::CTRL;
806 		if (state & Qt::MetaModifier)
807 			nextKey |= Qt::META;
808 		if (state & Qt::AltModifier)
809 			nextKey |= Qt::ALT;
810 
811 		QKeySequence ks(nextKey);
812 		ui->shortcutEdit->setText(ks.toString(QKeySequence::NativeText));
813 		keyEvent->accept();
814 
815 		return true;
816 	}
817 
818 	if (event->type() == QEvent::Shortcut || event->type() == QEvent::KeyRelease || event->type() == QEvent::ShortcutOverride)
819 	{
820 		event->accept();
821 		return true;
822 	}
823 
824 	return QDialog::eventFilter(object, event);
825 }
826 
updateCommandList()827 void lcQPreferencesDialog::updateCommandList()
828 {
829 	ui->commandList->clear();
830 	QMap<QString, QTreeWidgetItem*> sections;
831 
832 	for (unsigned int actionIdx = 0; actionIdx < LC_NUM_COMMANDS; actionIdx++)
833 	{
834 		if (!gCommands[actionIdx].ID[0])
835 			continue;
836 
837 		const QString identifier = tr(gCommands[actionIdx].ID);
838 
839 		int pos = identifier.indexOf(QLatin1Char('.'));
840 		int subPos = identifier.indexOf(QLatin1Char('.'), pos + 1);
841 		if (subPos == -1)
842 			subPos = pos;
843 
844 		const QString parentSection = identifier.left(pos);
845 
846 		if (subPos != pos)
847 		{
848 			if (!sections.contains(parentSection))
849 			{
850 				QTreeWidgetItem *categoryItem = new QTreeWidgetItem(ui->commandList, QStringList(parentSection));
851 				QFont f = categoryItem->font(0);
852 				f.setBold(true);
853 				categoryItem->setFont(0, f);
854 				sections.insert(parentSection, categoryItem);
855 				ui->commandList->expandItem(categoryItem);
856 			}
857 		}
858 
859 		const QString section = identifier.left(subPos);
860 		const QString subId = identifier.mid(subPos + 1);
861 
862 		if (!sections.contains(section))
863 		{
864 			QTreeWidgetItem *parent = sections[parentSection];
865 			QTreeWidgetItem *categoryItem;
866 			QString subSection;
867 
868 			if (pos != subPos)
869 				subSection = identifier.mid(pos + 1, subPos - pos - 1);
870 			else
871 				subSection = section;
872 
873 			if (parent)
874 				categoryItem = new QTreeWidgetItem(parent, QStringList(subSection));
875 			else
876 				categoryItem = new QTreeWidgetItem(ui->commandList, QStringList(subSection));
877 
878 			QFont f = categoryItem->font(0);
879 			f.setBold(true);
880 			categoryItem->setFont(0, f);
881 			sections.insert(section, categoryItem);
882 			ui->commandList->expandItem(categoryItem);
883 		}
884 
885 		QTreeWidgetItem *item = new QTreeWidgetItem;
886 		QKeySequence sequence(mOptions->KeyboardShortcuts.mShortcuts[actionIdx]);
887 		item->setText(0, qApp->translate("Menu", gCommands[actionIdx].MenuName).remove('&').remove(QLatin1String("...")));
888 		item->setText(1, sequence.toString(QKeySequence::NativeText));
889 		item->setData(0, Qt::UserRole, QVariant::fromValue(actionIdx));
890 
891 		if (mOptions->KeyboardShortcuts.mShortcuts[actionIdx] != gCommands[actionIdx].DefaultShortcut)
892 			setShortcutModified(item, true);
893 
894 		sections[section]->addChild(item);
895 	}
896 }
897 
setShortcutModified(QTreeWidgetItem * treeItem,bool modified)898 void lcQPreferencesDialog::setShortcutModified(QTreeWidgetItem *treeItem, bool modified)
899 {
900 	QFont font = treeItem->font(0);
901 	font.setItalic(modified);
902 	treeItem->setFont(0, font);
903 	font.setBold(modified);
904 	treeItem->setFont(1, font);
905 }
906 
commandChanged(QTreeWidgetItem * current)907 void lcQPreferencesDialog::commandChanged(QTreeWidgetItem *current)
908 {
909 	if (!current || !current->data(0, Qt::UserRole).isValid())
910 	{
911 		ui->shortcutEdit->setText(QString());
912 		ui->shortcutGroup->setEnabled(false);
913 		return;
914 	}
915 
916 	ui->shortcutGroup->setEnabled(true);
917 
918 	int shortcutIndex = qvariant_cast<int>(current->data(0, Qt::UserRole));
919 	QKeySequence key(mOptions->KeyboardShortcuts.mShortcuts[shortcutIndex]);
920 	ui->shortcutEdit->setText(key.toString(QKeySequence::NativeText));
921 }
922 
on_KeyboardFilterEdit_textEdited(const QString & Text)923 void lcQPreferencesDialog::on_KeyboardFilterEdit_textEdited(const QString& Text)
924 {
925 	if (Text.isEmpty())
926 	{
927 		std::function<void(QTreeWidgetItem*)> ShowItems = [&ShowItems](QTreeWidgetItem* ParentItem)
928 		{
929 			for (int ChildIdx = 0; ChildIdx < ParentItem->childCount(); ChildIdx++)
930 				ShowItems(ParentItem->child(ChildIdx));
931 
932 			ParentItem->setHidden(false);
933 		};
934 
935 		ShowItems(ui->commandList->invisibleRootItem());
936 	}
937 	else
938 	{
939 		std::function<bool(QTreeWidgetItem*,bool)> ShowItems = [&ShowItems, &Text](QTreeWidgetItem* ParentItem, bool ForceVisible)
940 		{
941 			ForceVisible |= (bool)ParentItem->text(0).contains(Text, Qt::CaseInsensitive) | (bool)ParentItem->text(1).contains(Text, Qt::CaseInsensitive);
942 			bool Visible = ForceVisible;
943 
944 			for (int ChildIdx = 0; ChildIdx < ParentItem->childCount(); ChildIdx++)
945 				Visible |= ShowItems(ParentItem->child(ChildIdx), ForceVisible);
946 
947 			ParentItem->setHidden(!Visible);
948 
949 			return Visible;
950 		};
951 
952 		ShowItems(ui->commandList->invisibleRootItem(), false);
953 	}
954 }
955 
on_shortcutAssign_clicked()956 void lcQPreferencesDialog::on_shortcutAssign_clicked()
957 {
958 	QTreeWidgetItem* CurrentItem = ui->commandList->currentItem();
959 
960 	if (!CurrentItem || !CurrentItem->data(0, Qt::UserRole).isValid())
961 		return;
962 
963 	uint ShortcutIndex = CurrentItem->data(0, Qt::UserRole).toUInt();
964 	QString (&Shortcuts)[LC_NUM_COMMANDS] = mOptions->KeyboardShortcuts.mShortcuts;
965 
966 	if (ShortcutIndex >= LC_ARRAY_COUNT(Shortcuts))
967 		return;
968 
969 	QString NewShortcut = ui->shortcutEdit->text();
970 
971 	if (!NewShortcut.isEmpty())
972 	{
973 		for (uint ExistingIndex = 0; ExistingIndex < LC_ARRAY_COUNT(Shortcuts); ExistingIndex++)
974 		{
975 			if (NewShortcut == Shortcuts[ExistingIndex] && ExistingIndex != ShortcutIndex)
976 			{
977 				QString ActionText = qApp->translate("Menu", gCommands[ExistingIndex].MenuName).remove('&').remove(QLatin1String("..."));
978 				QString QuestionText = tr("The shortcut '%1' is already assigned to '%2'. Do you want to replace it?").arg(NewShortcut, ActionText);
979 
980 				if (QMessageBox::question(this, tr("Override Shortcut"), QuestionText, QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
981 					return;
982 
983 				mOptions->KeyboardShortcuts.mShortcuts[ExistingIndex].clear();
984 
985 				std::function<QTreeWidgetItem* (QTreeWidgetItem*)> FindItem = [&FindItem, ExistingIndex](QTreeWidgetItem* ParentItem) -> QTreeWidgetItem*
986 				{
987 					for (int ChildIdx = 0; ChildIdx < ParentItem->childCount(); ChildIdx++)
988 					{
989 						QTreeWidgetItem* ChildItem = ParentItem->child(ChildIdx);
990 						uint ChildIndex = ChildItem->data(0, Qt::UserRole).toUInt();
991 
992 						if (ChildIndex == ExistingIndex)
993 							return ChildItem;
994 
995 						QTreeWidgetItem* ExistingItem = FindItem(ChildItem);
996 
997 						if (ExistingItem)
998 							return ExistingItem;
999 					}
1000 
1001 					return nullptr;
1002 				};
1003 
1004 				QTreeWidgetItem* ExistingItem = FindItem(ui->commandList->invisibleRootItem());
1005 
1006 				if (ExistingItem)
1007 				{
1008 					ExistingItem->setText(1, QString());
1009 					setShortcutModified(ExistingItem, gCommands[ShortcutIndex].DefaultShortcut[0] != 0);
1010 				}
1011 			}
1012 		}
1013 	}
1014 
1015 	mOptions->KeyboardShortcuts.mShortcuts[ShortcutIndex] = NewShortcut;
1016 	CurrentItem->setText(1, NewShortcut);
1017 
1018 	setShortcutModified(CurrentItem, mOptions->KeyboardShortcuts.mShortcuts[ShortcutIndex] != gCommands[ShortcutIndex].DefaultShortcut);
1019 
1020 	mOptions->KeyboardShortcutsModified = true;
1021 	mOptions->KeyboardShortcutsDefault = false;
1022 }
1023 
on_shortcutRemove_clicked()1024 void lcQPreferencesDialog::on_shortcutRemove_clicked()
1025 {
1026 	ui->shortcutEdit->setText(QString());
1027 
1028 	on_shortcutAssign_clicked();
1029 }
1030 
on_shortcutsImport_clicked()1031 void lcQPreferencesDialog::on_shortcutsImport_clicked()
1032 {
1033 	QString FileName = QFileDialog::getOpenFileName(this, tr("Import shortcuts"), "", tr("Text Files (*.txt);;All Files (*.*)"));
1034 
1035 	if (FileName.isEmpty())
1036 		return;
1037 
1038 	lcKeyboardShortcuts Shortcuts;
1039 	if (!Shortcuts.Load(FileName))
1040 	{
1041 		QMessageBox::warning(this, "LeoCAD", tr("Error loading keyboard shortcuts file."));
1042 		return;
1043 	}
1044 
1045 	mOptions->KeyboardShortcuts = Shortcuts;
1046 
1047 	mOptions->KeyboardShortcutsModified = true;
1048 	mOptions->KeyboardShortcutsDefault = false;
1049 }
1050 
on_shortcutsExport_clicked()1051 void lcQPreferencesDialog::on_shortcutsExport_clicked()
1052 {
1053 	QString FileName = QFileDialog::getSaveFileName(this, tr("Export shortcuts"), "", tr("Text Files (*.txt);;All Files (*.*)"));
1054 
1055 	if (FileName.isEmpty())
1056 		return;
1057 
1058 	if (!mOptions->KeyboardShortcuts.Save(FileName))
1059 	{
1060 		QMessageBox::warning(this, "LeoCAD", tr("Error saving keyboard shortcuts file."));
1061 		return;
1062 	}
1063 }
1064 
on_shortcutsReset_clicked()1065 void lcQPreferencesDialog::on_shortcutsReset_clicked()
1066 {
1067 	if (QMessageBox::question(this, "LeoCAD", tr("Are you sure you want to load the default keyboard shortcuts?"), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
1068 		return;
1069 
1070 	mOptions->KeyboardShortcuts.Reset();
1071 	updateCommandList();
1072 
1073 	mOptions->KeyboardShortcutsModified = true;
1074 	mOptions->KeyboardShortcutsDefault = true;
1075 }
1076 
UpdateMouseTree()1077 void lcQPreferencesDialog::UpdateMouseTree()
1078 {
1079 	ui->mouseTree->clear();
1080 
1081 	for (int ToolIdx = 0; ToolIdx < static_cast<int>(lcTool::Count); ToolIdx++)
1082 		UpdateMouseTreeItem(ToolIdx);
1083 }
1084 
UpdateMouseTreeItem(int ItemIndex)1085 void lcQPreferencesDialog::UpdateMouseTreeItem(int ItemIndex)
1086 {
1087 	auto GetShortcutText = [](Qt::MouseButton Button, Qt::KeyboardModifiers Modifiers)
1088 	{
1089 		QString Shortcut = QKeySequence(Modifiers).toString(QKeySequence::NativeText);
1090 
1091 		switch (Button)
1092 		{
1093 		case Qt::LeftButton:
1094 			Shortcut += tr("Left Button");
1095 			break;
1096 
1097 		case Qt::MiddleButton:
1098 			Shortcut += tr("Middle Button");
1099 			break;
1100 
1101 		case Qt::RightButton:
1102 			Shortcut += tr("Right Button");
1103 			break;
1104 
1105 		default:
1106 			Shortcut.clear();
1107 		}
1108 		return Shortcut;
1109 	};
1110 
1111 	QString Shortcut1 = GetShortcutText(mOptions->MouseShortcuts.mShortcuts[ItemIndex].Button1, mOptions->MouseShortcuts.mShortcuts[ItemIndex].Modifiers1);
1112 	QString Shortcut2 = GetShortcutText(mOptions->MouseShortcuts.mShortcuts[ItemIndex].Button2, mOptions->MouseShortcuts.mShortcuts[ItemIndex].Modifiers2);
1113 
1114 	QTreeWidgetItem* Item = ui->mouseTree->topLevelItem(ItemIndex);
1115 
1116 	if (Item)
1117 	{
1118 		Item->setText(1, Shortcut1);
1119 		Item->setText(2, Shortcut2);
1120 	}
1121 	else
1122 		new QTreeWidgetItem(ui->mouseTree, QStringList() << tr(gToolNames[ItemIndex]) << Shortcut1 << Shortcut2);
1123 }
1124 
on_mouseAssign_clicked()1125 void lcQPreferencesDialog::on_mouseAssign_clicked()
1126 {
1127 	QTreeWidgetItem* Current = ui->mouseTree->currentItem();
1128 
1129 	if (!Current)
1130 		return;
1131 
1132 	int ButtonIndex = ui->mouseButton->currentIndex();
1133 	Qt::MouseButton Button = Qt::NoButton;
1134 	Qt::KeyboardModifiers Modifiers = Qt::NoModifier;
1135 
1136 	if (ButtonIndex)
1137 	{
1138 		switch (ButtonIndex)
1139 		{
1140 		case 1:
1141 			Button = Qt::LeftButton;
1142 			break;
1143 
1144 		case 2:
1145 			Button = Qt::MiddleButton;
1146 			break;
1147 
1148 		case 3:
1149 			Button = Qt::RightButton;
1150 			break;
1151 		}
1152 
1153 		if (ui->mouseControl->isChecked())
1154 			Modifiers |= Qt::ControlModifier;
1155 
1156 		if (ui->mouseShift->isChecked())
1157 			Modifiers |= Qt::ShiftModifier;
1158 
1159 		if (ui->mouseAlt->isChecked())
1160 			Modifiers |= Qt::AltModifier;
1161 
1162 		for (int ToolIdx = 0; ToolIdx < static_cast<int>(lcTool::Count); ToolIdx++)
1163 		{
1164 			if (ToolIdx == ButtonIndex)
1165 				continue;
1166 
1167 			if (mOptions->MouseShortcuts.mShortcuts[ToolIdx].Button2 == Button && mOptions->MouseShortcuts.mShortcuts[ToolIdx].Modifiers2 == Modifiers)
1168 			{
1169 				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)
1170 					return;
1171 
1172 				mOptions->MouseShortcuts.mShortcuts[ToolIdx].Button2 = Qt::NoButton;
1173 				mOptions->MouseShortcuts.mShortcuts[ToolIdx].Modifiers2 = Qt::NoModifier;
1174 			}
1175 
1176 			if (mOptions->MouseShortcuts.mShortcuts[ToolIdx].Button1 == Button && mOptions->MouseShortcuts.mShortcuts[ToolIdx].Modifiers1 == Modifiers)
1177 			{
1178 				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)
1179 					return;
1180 
1181 				mOptions->MouseShortcuts.mShortcuts[ToolIdx].Button1 = mOptions->MouseShortcuts.mShortcuts[ToolIdx].Button2;
1182 				mOptions->MouseShortcuts.mShortcuts[ToolIdx].Modifiers1 = mOptions->MouseShortcuts.mShortcuts[ToolIdx].Modifiers2;
1183 				mOptions->MouseShortcuts.mShortcuts[ToolIdx].Button2 = Qt::NoButton;
1184 				mOptions->MouseShortcuts.mShortcuts[ToolIdx].Modifiers2 = Qt::NoModifier;
1185 
1186 				UpdateMouseTreeItem(ToolIdx);
1187 			}
1188 		}
1189 	}
1190 
1191 	int ItemIndex = ui->mouseTree->indexOfTopLevelItem(Current);
1192 	mOptions->MouseShortcuts.mShortcuts[ItemIndex].Button2 = mOptions->MouseShortcuts.mShortcuts[ItemIndex].Button1;
1193 	mOptions->MouseShortcuts.mShortcuts[ItemIndex].Modifiers2 = mOptions->MouseShortcuts.mShortcuts[ItemIndex].Modifiers1;
1194 	mOptions->MouseShortcuts.mShortcuts[ItemIndex].Button1 = Button;
1195 	mOptions->MouseShortcuts.mShortcuts[ItemIndex].Modifiers1 = Modifiers;
1196 
1197 	mOptions->MouseShortcutsModified = true;
1198 	mOptions->MouseShortcutsDefault = false;
1199 
1200 	UpdateMouseTreeItem(ItemIndex);
1201 }
1202 
on_mouseRemove_clicked()1203 void lcQPreferencesDialog::on_mouseRemove_clicked()
1204 {
1205 	QTreeWidgetItem* Current = ui->mouseTree->currentItem();
1206 
1207 	if (!Current)
1208 		return;
1209 
1210 	int ItemIndex = ui->mouseTree->indexOfTopLevelItem(Current);
1211 	mOptions->MouseShortcuts.mShortcuts[ItemIndex].Button1 = mOptions->MouseShortcuts.mShortcuts[ItemIndex].Button2;
1212 	mOptions->MouseShortcuts.mShortcuts[ItemIndex].Modifiers1 = mOptions->MouseShortcuts.mShortcuts[ItemIndex].Modifiers2;
1213 	mOptions->MouseShortcuts.mShortcuts[ItemIndex].Button2 = Qt::NoButton;
1214 	mOptions->MouseShortcuts.mShortcuts[ItemIndex].Modifiers2 = Qt::NoModifier;
1215 
1216 	mOptions->MouseShortcutsModified = true;
1217 	mOptions->MouseShortcutsDefault = false;
1218 
1219 	UpdateMouseTreeItem(ItemIndex);
1220 	MouseTreeItemChanged(Current);
1221 }
1222 
on_MouseImportButton_clicked()1223 void lcQPreferencesDialog::on_MouseImportButton_clicked()
1224 {
1225 	QString FileName = QFileDialog::getOpenFileName(this, tr("Import Shortcuts"), "", tr("Text Files (*.txt);;All Files (*.*)"));
1226 
1227 	if (FileName.isEmpty())
1228 		return;
1229 
1230 	lcMouseShortcuts Shortcuts;
1231 	if (!Shortcuts.Load(FileName))
1232 	{
1233 		QMessageBox::warning(this, "LeoCAD", tr("Error loading mouse shortcuts file."));
1234 		return;
1235 	}
1236 
1237 	mOptions->MouseShortcuts = Shortcuts;
1238 	UpdateMouseTree();
1239 
1240 	mOptions->MouseShortcutsModified = true;
1241 	mOptions->MouseShortcutsDefault = false;
1242 }
1243 
on_MouseExportButton_clicked()1244 void lcQPreferencesDialog::on_MouseExportButton_clicked()
1245 {
1246 	QString FileName = QFileDialog::getSaveFileName(this, tr("Export Shortcuts"), "", tr("Text Files (*.txt);;All Files (*.*)"));
1247 
1248 	if (FileName.isEmpty())
1249 		return;
1250 
1251 	if (!mOptions->MouseShortcuts.Save(FileName))
1252 		QMessageBox::warning(this, "LeoCAD", tr("Error saving mouse shortcuts file."));
1253 }
1254 
on_mouseReset_clicked()1255 void lcQPreferencesDialog::on_mouseReset_clicked()
1256 {
1257 	if (QMessageBox::question(this, "LeoCAD", tr("Are you sure you want to load the default mouse shortcuts?"), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
1258 		return;
1259 
1260 	mOptions->MouseShortcuts.Reset();
1261 	UpdateMouseTree();
1262 
1263 	mOptions->MouseShortcutsModified = true;
1264 	mOptions->MouseShortcutsDefault = true;
1265 }
1266 
MouseTreeItemChanged(QTreeWidgetItem * Current)1267 void lcQPreferencesDialog::MouseTreeItemChanged(QTreeWidgetItem* Current)
1268 {
1269 	if (!Current)
1270 	{
1271 		ui->MouseShortcutGroup->setEnabled(false);
1272 		return;
1273 	}
1274 
1275 	ui->MouseShortcutGroup->setEnabled(true);
1276 
1277 	int ToolIndex = ui->mouseTree->indexOfTopLevelItem(Current);
1278 
1279 	Qt::MouseButton Button = mOptions->MouseShortcuts.mShortcuts[ToolIndex].Button1;
1280 
1281 	switch (Button)
1282 	{
1283 	case Qt::LeftButton:
1284 		ui->mouseButton->setCurrentIndex(1);
1285 		break;
1286 
1287 	case Qt::MiddleButton:
1288 		ui->mouseButton->setCurrentIndex(2);
1289 		break;
1290 
1291 	case Qt::RightButton:
1292 		ui->mouseButton->setCurrentIndex(3);
1293 		break;
1294 
1295 	default:
1296 		ui->mouseButton->setCurrentIndex(0);
1297 		break;
1298 	}
1299 
1300 	Qt::KeyboardModifiers Modifiers = mOptions->MouseShortcuts.mShortcuts[ToolIndex].Modifiers1;
1301 	ui->mouseControl->setChecked((Modifiers & Qt::ControlModifier) != 0);
1302 	ui->mouseShift->setChecked((Modifiers & Qt::ShiftModifier) != 0);
1303 	ui->mouseAlt->setChecked((Modifiers & Qt::AltModifier) != 0);
1304 }
1305