src/main.cpp

changeset 201
5d201ee4a9c3
parent 200
ca23936b455b
child 202
b05af0bab735
equal deleted inserted replaced
200:ca23936b455b 201:5d201ee4a9c3
1 /*
2 * LDForge: LDraw parts authoring CAD
3 * Copyright (C) 2013 - 2020 Teemu Piippo
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <QApplication> 1 #include <QApplication>
2 #include <QFileDialog>
3 #include <QMessageBox>
20 #include "mainwindow.h" 4 #include "mainwindow.h"
5 #include "ui_mainwindow.h"
21 #include "version.h" 6 #include "version.h"
7 #include "document.h"
8 #include "settingseditor/settingseditor.h"
9 #include "widgets/colorselectdialog.h"
10
11 static const QDir LOCALE_DIR {":/locale"};
22 12
23 static void doQtRegistrations() 13 static void doQtRegistrations()
24 { 14 {
25 QCoreApplication::setApplicationName(::appName); 15 QCoreApplication::setApplicationName(::appName);
26 QCoreApplication::setOrganizationName("hecknology.net"); 16 QCoreApplication::setOrganizationName("hecknology.net");
27 QCoreApplication::setOrganizationDomain("hecknology.net"); 17 QCoreApplication::setOrganizationDomain("hecknology.net");
28 qRegisterMetaTypeStreamOperators<Library>("Library"); 18 qRegisterMetaTypeStreamOperators<Library>("Library");
29 qRegisterMetaTypeStreamOperators<Libraries>("Libraries"); 19 qRegisterMetaTypeStreamOperators<Libraries>("Libraries");
30 } 20 }
31 21
22 template<typename BaseType, typename MemberType, typename DataType>
23 struct MemberData
24 {
25 std::size_t member;
26 DataType payload;
27 constexpr MemberType memberInstance(BaseType* instance) const
28 {
29 return *reinterpret_cast<MemberType*>(reinterpret_cast<char*>(instance) + this->member);
30 }
31 };
32
33 static constexpr MemberData<Ui_MainWindow, QAction*, gl::RenderStyle> renderStyleButtons[] = {
34 { offsetof(Ui_MainWindow, actionRenderStyleNormal), gl::RenderStyle::Normal },
35 { offsetof(Ui_MainWindow, actionRenderStyleBfc), gl::RenderStyle::BfcRedGreen },
36 { offsetof(Ui_MainWindow, actionRenderStyleRandom), gl::RenderStyle::RandomColors },
37 { offsetof(Ui_MainWindow, actionRenderStylePickScene), gl::RenderStyle::PickScene },
38 };
39
40 static std::optional<ModelId> openModelFromPath(
41 const QString& path,
42 const LibraryManager* libraries,
43 DocumentManager* documents,
44 QWidget* parent)
45 {
46 QString errorString;
47 QTextStream errorStream{&errorString};
48 const std::optional<ModelId> modelIdOpt = documents->openModel(
49 path,
50 errorStream,
51 DocumentManager::OpenType::ManuallyOpened);
52 if (modelIdOpt.has_value()) {
53 documents->loadDependenciesForModel(modelIdOpt.value(), path, *libraries, errorStream);
54 if (not errorString.isEmpty()) {
55 QMessageBox::warning(
56 parent,
57 QObject::tr("Problem loading references"),
58 errorString);
59 }
60 }
61 else {
62 QMessageBox::critical(
63 parent,
64 QObject::tr("Problem opening file"),
65 format(QObject::tr("Could not open %1: %2"), path, errorString));
66 }
67 return modelIdOpt;
68 }
69
70 static QString getOpenModelPath(QWidget* parent)
71 {
72 return QFileDialog::getOpenFileName(
73 parent,
74 QObject::tr("Open model"),
75 "",
76 QObject::tr("LDraw models (*.ldr *.dat)"));
77 }
78
79 static const QString localeCode(const QString& locale)
80 {
81 if (locale == "system") {
82 return QLocale::system().name();
83 }
84 else {
85 return locale;
86 }
87 }
88
89 /**
90 * @brief Changes the application language to the specified language
91 */
92 static void changeLanguage(const QString& locale, QTranslator* translator)
93 {
94 if (not locale.isEmpty()) {
95 const QString localeCode = ::localeCode(locale);
96 QLocale::setDefault({localeCode});
97 qApp->removeTranslator(translator);
98 const QString path = LOCALE_DIR.filePath(localeCode + ".qm");
99 const bool loadSuccessful = translator->load(path);
100 if (loadSuccessful)
101 {
102 qApp->installTranslator(translator);
103 }
104 }
105 }
106
107 /*
108 void MainWindow::handleDocumentSplitterChange()
109 {
110 EditorTabWidget* currentDocument = this->currentDocument();
111 if (currentDocument != nullptr)
112 {
113 this->documentSplitterState = currentDocument->saveSplitterState();
114 for (int i = 0; i < this->ui->tabs->count(); i += 1)
115 {
116 EditorTabWidget* document = qobject_cast<EditorTabWidget*>(this->ui->tabs->widget(i));
117 if (document != nullptr and document != currentDocument)
118 {
119 document->restoreSplitterState(this->documentSplitterState);
120 }
121 }
122 this->settings.setMainSplitterState(this->documentSplitterState);
123 }
124 }
125 */
126
127 static EditorTabWidget* currentTabWidget(Ui_MainWindow* ui)
128 {
129 return qobject_cast<EditorTabWidget*>(ui->tabs->currentWidget());
130 };
131
132
133 static void closeDocument(DocumentManager* documents, EditorTabWidget *document)
134 {
135 std::optional<ModelId> modelId = documents->findIdForModel(document->model);
136 if (modelId.has_value()) {
137 documents->closeDocument(modelId.value());
138 delete document;
139 }
140 }
141
142 static void handleTabCloseButton(Ui_MainWindow* ui, DocumentManager* documents, int tabIndex)
143 {
144 if (tabIndex >= 0 and tabIndex < ui->tabs->count()) {
145 EditorTabWidget* tab = qobject_cast<EditorTabWidget*>(ui->tabs->widget(tabIndex));
146 if (tab != nullptr) {
147 closeDocument(documents, tab);
148 }
149 }
150 }
151
152 static std::optional<ModelId> findCurrentModelId(Ui_MainWindow* ui, DocumentManager* documents)
153 {
154 const EditorTabWidget* tab = currentTabWidget(ui);
155 if (tab != nullptr) {
156 return documents->findIdForModel(tab->model);
157 }
158 else {
159 return {};
160 }
161 }
162
163 /**
164 * @brief Updates the title of the main window so to contain the app's name
165 * and version as well as the open document name.
166 */
167 static QString title()
168 {
169 QString title = ::appName;
170 title += " ";
171 title += fullVersionString();
172 return title;
173 }
174
175 static ldraw::ColorTable loadColors(const LibraryManager* libraries)
176 {
177 QTextStream errors;
178 return libraries->loadColorTable(errors);
179 }
180
181 static QString tabName(const QFileInfo& fileInfo)
182 {
183 QString result = fileInfo.baseName();
184 if (result.isEmpty()) {
185 result = QObject::tr("<unnamed>");
186 }
187 return result;
188 }
189
190 void rebuildRecentFilesMenu(QMenu* menu, const QStringList& strings, QWidget* parent)
191 {
192 menu->clear();
193 for (const QString& path : strings) {
194 QAction* action = new QAction{path, parent};
195 action->setData(path);
196 menu->addAction(action);
197 }
198 }
199
200 static void updateRenderPreferences(
201 Ui_MainWindow* ui,
202 const gl::RenderPreferences* renderPreferences)
203 {
204 for (int i = 0; i < ui->tabs->count(); i += 1) {
205 EditorTabWidget* tab = qobject_cast<EditorTabWidget*>(ui->tabs->widget(i));
206 if (tab != nullptr) {
207 tab->canvas->setRenderPreferences(*renderPreferences);
208 }
209 }
210 for (auto data : ::renderStyleButtons) {
211 QAction* action = data.memberInstance(ui);
212 action->setChecked(renderPreferences->style == data.payload);
213 }
214 ui->actionDrawAxes->setChecked(renderPreferences->drawAxes);
215 };
216
217 static gl::RenderPreferences loadRenderPreferences(Configuration* settings)
218 {
219 return gl::RenderPreferences{
220 .style = static_cast<gl::RenderStyle>(settings->renderStyle()),
221 .mainColor = settings->mainColor(),
222 .backgroundColor = settings->backgroundColor(),
223 .selectedColor = settings->selectedColor(),
224 .lineThickness = settings->lineThickness(),
225 .lineAntiAliasing = settings->lineAntiAliasing(),
226 .drawAxes = settings->drawAxes(),
227 };
228 }
229
32 int main(int argc, char *argv[]) 230 int main(int argc, char *argv[])
33 { 231 {
34 doQtRegistrations(); 232 doQtRegistrations();
35 QApplication app{argc, argv}; 233 QApplication app{argc, argv};
36 MainWindow mainwindow; 234 QMainWindow mainWindow;
37 mainwindow.show(); 235 Ui_MainWindow ui;
38 return app.exec(); 236 DocumentManager documents{&mainWindow};
39 } 237 QString currentLanguage = "en";
238 QTranslator translator{&mainWindow};
239 Configuration settings;
240 LibraryManager libraries{&mainWindow};
241 QByteArray documentSplitterState;
242 QStringList recentlyOpenedFiles;
243 ldraw::ColorTable colorTable;
244 gl::RenderPreferences renderPreferences;
245 ui.setupUi(&mainWindow);
246 const uiutilities::KeySequenceMap defaultKeyboardShortcuts =
247 uiutilities::makeKeySequenceMap(uiutilities::collectActions(&mainWindow));
248 const auto saveSettings = [&]{
249 settings.setMainWindowGeometry(mainWindow.saveGeometry());
250 settings.setRecentFiles(recentlyOpenedFiles);
251 settings.setMainSplitterState(documentSplitterState);
252 settings.setRenderStyle(static_cast<int>(renderPreferences.style));
253 settings.setDrawAxes(renderPreferences.drawAxes);
254 libraries.storeToSettings(&settings);
255 };
256 const auto updateRecentlyOpenedDocumentsMenu = [&]{
257 rebuildRecentFilesMenu(ui.menuRecentFiles, recentlyOpenedFiles, &mainWindow);
258 for (QAction* action : ui.menuRecentFiles->findChildren<QAction*>()) {
259 QString path = action->data().toString();
260 QObject::connect(
261 action,
262 &QAction::triggered,
263 [path, &libraries, &documents, &mainWindow]() {
264 openModelFromPath(path, &libraries, &documents, &mainWindow);
265 }
266 );
267 }
268 };
269 const auto restoreSettings = [&]{
270 recentlyOpenedFiles = settings.recentFiles();
271 documentSplitterState = settings.mainSplitterState();
272 renderPreferences = loadRenderPreferences(&settings);
273 changeLanguage(settings.locale(), &translator);
274 libraries.restoreFromSettings(&settings);
275 updateRecentlyOpenedDocumentsMenu();
276 colorTable = loadColors(&libraries);
277 updateRenderPreferences(&ui, &renderPreferences);
278 ui.retranslateUi(&mainWindow);
279 };
280 const auto addRecentlyOpenedFile = [&](const QString& path){
281 constexpr int maxRecentlyOpenedFiles = 10;
282 recentlyOpenedFiles.removeAll(path);
283 recentlyOpenedFiles.insert(0, path);
284 while (recentlyOpenedFiles.size() > maxRecentlyOpenedFiles)
285 {
286 recentlyOpenedFiles.removeLast();
287 }
288 saveSettings();
289 updateRecentlyOpenedDocumentsMenu();
290 };
291 const auto openModelForEditing = [&](const ModelId modelId){
292 EditorTabWidget* document = new EditorTabWidget{
293 documents.getModelById(modelId),
294 &documents,
295 colorTable,
296 };
297 document->canvas->setRenderPreferences(renderPreferences);
298 QObject::connect(
299 document,
300 &EditorTabWidget::newStatusText,
301 [&](const QString& newStatusText) {
302 mainWindow.statusBar()->showMessage(newStatusText);
303 });
304 const QFileInfo fileInfo{*documents.modelPath(modelId)};
305 ui.tabs->addTab(document, tabName(fileInfo));
306 ui.tabs->setCurrentWidget(document);
307 document->restoreSplitterState(documentSplitterState);
308 };
309 const auto newModel = [&openModelForEditing](DocumentManager* documents){
310 openModelForEditing(documents->newModel());
311 };
312 QObject::connect(ui.actionNew, &QAction::triggered, [&newModel, &documents]{
313 newModel(&documents);
314 });
315 QObject::connect(ui.actionOpen, &QAction::triggered, [&]{
316 const QString path = getOpenModelPath(&mainWindow);
317 if (not path.isEmpty())
318 {
319 const std::optional<ModelId> id = openModelFromPath(path, &libraries, &documents, &mainWindow);
320 if (id.has_value()) {
321 openModelForEditing(id.value());
322 addRecentlyOpenedFile(path);
323 }
324 }
325 });
326 QObject::connect(ui.actionSettingsEditor, &QAction::triggered, [&]{
327 SettingsEditor settingsEditor{&settings, defaultKeyboardShortcuts, &mainWindow};
328 const int result = settingsEditor.exec();
329 if (result == QDialog::Accepted)
330 {
331 restoreSettings();
332 }
333 });
334 QObject::connect(ui.actionQuit, &QAction::triggered, &mainWindow, &QMainWindow::close);
335 QObject::connect(ui.actionAdjustGridToView, &QAction::triggered, [&ui]{
336 EditorTabWidget* tab = currentTabWidget(&ui);
337 if (tab != nullptr)
338 {
339 adjustGridToView(tab->canvas);
340 }
341 });
342 QObject::connect(ui.actionClose, &QAction::triggered, [&ui, &documents]{
343 EditorTabWidget* tab = currentTabWidget(&ui);
344 if (tab != nullptr)
345 {
346 closeDocument(&documents, tab);
347 }
348 });
349 const auto save = [&](ModelId modelId){
350 QString error;
351 QTextStream errorStream{&error};
352 const bool succeeded = documents.saveModel(modelId, errorStream);
353 if (not succeeded)
354 {
355 QMessageBox::critical(&mainWindow, QObject::tr("Save error"), error);
356 }
357 else
358 {
359 const QString* pathPtr = documents.modelPath(modelId);
360 if (pathPtr != nullptr) {
361 addRecentlyOpenedFile(*pathPtr);
362 }
363 }
364 };
365 const auto actionSaveAs = [&]{
366 const std::optional<ModelId> modelId = findCurrentModelId(&ui, &documents);
367 if (modelId.has_value())
368 {
369 const QString* pathPtr = documents.modelPath(*modelId);
370 QString defaultPath = (pathPtr != nullptr) ? *pathPtr : "";
371 const QString newPath = QFileDialog::getSaveFileName(
372 &mainWindow,
373 QObject::tr("Save as…"),
374 QFileInfo{defaultPath}.absoluteDir().path(),
375 QObject::tr("LDraw files (*.ldr *dat);;All files (*)")
376 );
377 if (not newPath.isEmpty()) {
378 QString error;
379 QTextStream errorStream{&error};
380 documents.setModelPath(*modelId, newPath, libraries, errorStream);
381 ui.tabs->setTabText(ui.tabs->currentIndex(), QFileInfo{newPath}.fileName());
382 save(*modelId);
383 }
384 }
385 };
386 QObject::connect(ui.actionSaveAs, &QAction::triggered, actionSaveAs);
387 QObject::connect(ui.actionSave, &QAction::triggered, [&]{
388 if (currentTabWidget(&ui) != nullptr) {
389 const std::optional<ModelId> modelId = findCurrentModelId(&ui, &documents);
390 if (modelId.has_value()) {
391 const QString* path = documents.modelPath(*modelId);
392 if (path == nullptr or path->isEmpty()) {
393 actionSaveAs();
394 }
395 else {
396 save(*modelId);
397 }
398 }
399 }
400 });
401 QObject::connect(ui.tabs, &QTabWidget::tabCloseRequested, [&](int index){
402 handleTabCloseButton(&ui, &documents, index);
403 });
404 QObject::connect(ui.actionDrawAxes, &QAction::triggered, [&](bool drawAxes){
405 renderPreferences.drawAxes = drawAxes;
406 saveSettings();
407 updateRenderPreferences(&ui, &renderPreferences);
408 });
409 for (auto data : ::renderStyleButtons) {
410 QAction* action = data.memberInstance(&ui);
411 QObject::connect(action, &QAction::triggered, [&, data]{
412 renderPreferences.style = data.payload;
413 saveSettings();
414 updateRenderPreferences(&ui, &renderPreferences);
415 });
416 }
417 mainWindow.setWindowTitle(title());
418 mainWindow.restoreGeometry(settings.mainWindowGeometry());
419 restoreSettings();
420 updateRenderPreferences(&ui, &renderPreferences);
421 newModel(&documents);
422 mainWindow.show();
423 const int result = app.exec();
424 saveSettings();
425 return result;
426 }

mercurial