1
// SPDX-License-Identifier: LGPL-2.1-or-later
3
/***************************************************************************
4
* Copyright (c) 2024 Werner Mayer <wmayer[at]users.sourceforge.net> *
6
* This file is part of FreeCAD. *
8
* FreeCAD is free software: you can redistribute it and/or modify it *
9
* under the terms of the GNU Lesser General Public License as *
10
* published by the Free Software Foundation, either version 2.1 of the *
11
* License, or (at your option) any later version. *
13
* FreeCAD is distributed in the hope that it will be useful, but *
14
* WITHOUT ANY WARRANTY; without even the implied warranty of *
15
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
16
* Lesser General Public License for more details. *
18
* You should have received a copy of the GNU Lesser General Public *
19
* License along with FreeCAD. If not, see *
20
* <https://www.gnu.org/licenses/>. *
22
**************************************************************************/
24
#include "PreCompiled.h"
26
#include <QApplication>
28
#include <QImageReader>
30
#include <QOpenGLContext>
31
#include <QOpenGLFunctions>
34
#include <Inventor/SoDB.h>
37
#include "StartupProcess.h"
38
#include "Application.h"
40
#include "DlgCheckableMessageBox.h"
41
#include "FileDialog.h"
42
#include "GuiApplication.h"
43
#include "MainWindow.h"
44
#include "Language/Translator.h"
45
#include <App/Application.h>
46
#include <Base/Console.h>
52
StartupProcess::StartupProcess() = default;
54
void StartupProcess::setupApplication()
56
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
58
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
59
QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL);
62
// Automatic scaling for legacy apps (disable once all parts of GUI are aware of HiDpi)
63
ParameterGrp::handle hDPI =
64
App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/HighDPI");
65
bool disableDpiScaling = hDPI->GetBool("DisableDpiScaling", false);
66
if (disableDpiScaling) {
68
SetProcessDPIAware(); // call before the main event loop
70
#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
71
QApplication::setAttribute(Qt::AA_DisableHighDpiScaling);
75
// Enable automatic scaling based on pixel density of display (added in Qt 5.6)
76
#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
77
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
79
#if QT_VERSION >= QT_VERSION_CHECK(5,14,0) && defined(Q_OS_WIN)
80
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
84
#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
85
//Enable support for highres images (added in Qt 5.1, but off by default)
86
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
89
// Use software rendering for OpenGL
90
ParameterGrp::handle hOpenGL =
91
App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/OpenGL");
92
bool useSoftwareOpenGL = hOpenGL->GetBool("UseSoftwareOpenGL", false);
93
if (useSoftwareOpenGL) {
94
QApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);
97
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
98
// By default (on platforms that support it, see docs for
99
// Qt::AA_CompressHighFrequencyEvents) QT applies compression
100
// for high frequency events (mouse move, touch, window resizes)
101
// to keep things smooth even when handling the event takes a
102
// while (e.g. to calculate snapping).
103
// However, tablet pen move events (and mouse move events
104
// synthesised from those) are not compressed by default (to
105
// allow maximum precision when e.g. hand-drawing curves),
106
// leading to unacceptable slowdowns using a tablet pen. Enable
107
// compression for tablet events here to solve that.
108
QCoreApplication::setAttribute(Qt::AA_CompressTabletEvents);
112
void StartupProcess::execute()
115
setStyleSheetPaths();
122
void StartupProcess::setLibraryPath()
125
plugin = QString::fromStdString(App::Application::getHomePath());
126
plugin += QLatin1String("/plugins");
127
QCoreApplication::addLibraryPath(plugin);
130
void StartupProcess::setStyleSheetPaths()
132
// setup the search paths for Qt style sheets
133
QStringList qssPaths;
134
qssPaths << QString::fromUtf8(
135
(App::Application::getUserAppDataDir() + "Gui/Stylesheets/").c_str())
136
<< QString::fromUtf8((App::Application::getResourceDir() + "Gui/Stylesheets/").c_str())
137
<< QLatin1String(":/stylesheets");
138
QDir::setSearchPaths(QString::fromLatin1("qss"), qssPaths);
139
// setup the search paths for Qt overlay style sheets
140
QStringList qssOverlayPaths;
141
qssOverlayPaths << QString::fromUtf8((App::Application::getUserAppDataDir()
142
+ "Gui/Stylesheets/overlay").c_str())
143
<< QString::fromUtf8((App::Application::getResourceDir()
144
+ "Gui/Stylesheets/overlay").c_str());
145
QDir::setSearchPaths(QStringLiteral("overlay"), qssOverlayPaths);
148
void StartupProcess::setImagePaths()
150
// set search paths for images
151
QStringList imagePaths;
152
imagePaths << QString::fromUtf8((App::Application::getUserAppDataDir() + "Gui/images").c_str())
153
<< QString::fromUtf8((App::Application::getUserAppDataDir() + "pixmaps").c_str())
154
<< QLatin1String(":/icons");
155
QDir::setSearchPaths(QString::fromLatin1("images"), imagePaths);
158
void StartupProcess::registerEventType()
160
// register action style event type
161
ActionStyleEvent::EventType = QEvent::registerEventType(QEvent::User + 1);
164
void StartupProcess::setThemePaths()
166
#if !defined(Q_OS_LINUX)
167
QIcon::setThemeSearchPaths(QIcon::themeSearchPaths()
168
<< QString::fromLatin1(":/icons/FreeCAD-default"));
171
ParameterGrp::handle hTheme = App::GetApplication().GetParameterGroupByPath(
172
"User parameter:BaseApp/Preferences/Bitmaps/Theme");
174
std::string searchpath = hTheme->GetASCII("SearchPath");
175
if (!searchpath.empty()) {
176
QStringList searchPaths = QIcon::themeSearchPaths();
177
searchPaths.prepend(QString::fromUtf8(searchpath.c_str()));
178
QIcon::setThemeSearchPaths(searchPaths);
181
std::string name = hTheme->GetASCII("Name");
183
QIcon::setThemeName(QString::fromLatin1(name.c_str()));
187
void StartupProcess::setupFileDialog()
189
#if defined(FC_OS_LINUX)
191
QString path = FileDialog::restoreLocation();
192
FileDialog::setWorkingDirectory(QDir::currentPath());
193
FileDialog::saveLocation(path);
195
FileDialog::setWorkingDirectory(FileDialog::restoreLocation());
199
// ------------------------------------------------------------------------------------------------
201
StartupPostProcess::StartupPostProcess(MainWindow* mw, Application& guiApp, QApplication* app)
208
void StartupPostProcess::setLoadFromPythonModule(bool value)
210
loadFromPythonModule = value;
213
void StartupPostProcess::execute()
216
setProcessMessages();
218
setToolBarIconSize();
219
setWheelEventFilter();
231
void StartupPostProcess::setWindowTitle()
233
// empty window title QString sets default title (app + version)
234
mainWindow->setWindowTitle(QString());
237
void StartupPostProcess::setProcessMessages()
239
if (!loadFromPythonModule) {
240
QObject::connect(qtApp, SIGNAL(messageReceived(const QList<QString> &)),
241
mainWindow, SLOT(processMessages(const QList<QString> &)));
245
void StartupPostProcess::setAutoSaving()
247
ParameterGrp::handle hDocGrp = WindowParameter::getDefaultParameter()->GetGroup("Document");
248
int timeout = int(hDocGrp->GetInt("AutoSaveTimeout", 15L)); // 15 min
249
if (!hDocGrp->GetBool("AutoSaveEnabled", true)) {
253
AutoSaver::instance()->setTimeout(timeout * 60000); // NOLINT
254
AutoSaver::instance()->setCompressed(hDocGrp->GetBool("AutoSaveCompressed", true));
257
void StartupPostProcess::setToolBarIconSize()
259
// set toolbar icon size
260
ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("General");
261
int size = int(hGrp->GetInt("ToolbarIconSize", 0));
262
// must not be lower than this
263
if (size >= 16) { // NOLINT
264
mainWindow->setIconSize(QSize(size,size));
268
void StartupPostProcess::setWheelEventFilter()
270
// filter wheel events for combo boxes
271
ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("General");
272
if (hGrp->GetBool("ComboBoxWheelEventFilter", false)) {
273
auto filter = new WheelEventFilter(qtApp);
274
qtApp->installEventFilter(filter);
278
void StartupPostProcess::setLocale()
280
// For values different to 1 and 2 use the OS locale settings
281
ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("General");
282
auto localeFormat = hGrp->GetInt("UseLocaleFormatting", 0);
283
if (localeFormat == 1) {
284
Translator::instance()->setLocale(
285
hGrp->GetASCII("Language", Translator::instance()->activeLanguage().c_str()));
287
else if (localeFormat == 2) {
288
Translator::instance()->setLocale("C");
292
void StartupPostProcess::setCursorFlashing()
294
// set text cursor blinking state
295
ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("General");
296
int blinkTime = hGrp->GetBool("EnableCursorBlinking", true) ? -1 : 0;
297
QApplication::setCursorFlashTime(blinkTime);
300
void StartupPostProcess::setQtStyle()
302
ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("MainWindow");
303
auto qtStyle = hGrp->GetASCII("QtStyle");
304
QApplication::setStyle(QString::fromStdString(qtStyle));
307
void StartupPostProcess::checkOpenGL()
310
window.setSurfaceType(QWindow::OpenGLSurface);
313
QOpenGLContext context;
314
if (context.create()) {
315
context.makeCurrent(&window);
316
if (!context.functions()->hasOpenGLFeature(QOpenGLFunctions::Framebuffers)) {
317
Base::Console().Log("This system does not support framebuffer objects\n");
319
if (!context.functions()->hasOpenGLFeature(QOpenGLFunctions::NPOTTextures)) {
320
Base::Console().Log("This system does not support NPOT textures\n");
323
int major = context.format().majorVersion();
324
int minor = context.format().minorVersion();
327
// In release mode, issue a warning to users that their version of OpenGL is
328
// potentially going to cause problems
331
QObject::tr("This system is running OpenGL %1.%2. "
332
"FreeCAD requires OpenGL 2.0 or above. "
333
"Please upgrade your graphics driver and/or card as required.")
336
+ QStringLiteral("\n");
337
Base::Console().Warning(message.toStdString().c_str());
338
Dialog::DlgCheckableMessageBox::showMessage(
339
QCoreApplication::applicationName() + QStringLiteral(" - ")
340
+ QObject::tr("Invalid OpenGL Version"),
344
const char* glVersion = reinterpret_cast<const char*>(glGetString(GL_VERSION));
345
Base::Console().Log("OpenGL version is: %d.%d (%s)\n", major, minor, glVersion);
349
void StartupPostProcess::loadOpenInventor()
351
bool loadedInventor = false;
352
if (loadFromPythonModule) {
353
loadedInventor = SoDB::isInitialized();
356
if (!loadedInventor) {
357
// init the Inventor subsystem
358
Application::initOpenInventor();
362
void StartupPostProcess::setBranding()
364
QString home = QString::fromStdString(App::Application::getHomePath());
366
const std::map<std::string,std::string>& cfg = App::Application::Config();
367
std::map<std::string,std::string>::const_iterator it;
368
it = cfg.find("WindowTitle");
369
if (it != cfg.end()) {
370
QString title = QString::fromUtf8(it->second.c_str());
371
mainWindow->setWindowTitle(title);
373
it = cfg.find("WindowIcon");
374
if (it != cfg.end()) {
375
QString path = QString::fromUtf8(it->second.c_str());
376
if (QDir(path).isRelative()) {
377
path = QFileInfo(QDir(home), path).absoluteFilePath();
379
QApplication::setWindowIcon(QIcon(path));
381
it = cfg.find("ProgramLogo");
382
if (it != cfg.end()) {
383
QString path = QString::fromUtf8(it->second.c_str());
384
if (QDir(path).isRelative()) {
385
path = QFileInfo(QDir(home), path).absoluteFilePath();
389
auto logo = new QLabel();
390
logo->setPixmap(px.scaledToHeight(32));
391
mainWindow->statusBar()->addPermanentWidget(logo, 0);
392
logo->setFrameShape(QFrame::NoFrame);
397
void StartupPostProcess::setImportImageFormats()
399
QList<QByteArray> supportedFormats = QImageReader::supportedImageFormats();
400
std::stringstream str;
401
str << "Image formats (";
402
for (const auto& ext : supportedFormats) {
403
str << "*." << ext.constData() << " *." << ext.toUpper().constData() << " ";
407
std::string filter = str.str();
408
App::GetApplication().addImportType(filter.c_str(), "FreeCADGui");
411
bool StartupPostProcess::hiddenMainWindow() const
413
const std::map<std::string,std::string>& cfg = App::Application::Config();
415
auto it = cfg.find("StartHidden");
416
if (it != cfg.end()) {
423
void StartupPostProcess::showMainWindow()
425
bool hidden = hiddenMainWindow();
427
// show splasher while initializing the GUI
428
if (!hidden && !loadFromPythonModule) {
429
mainWindow->startSplasher();
432
// running the GUI init script
434
Base::Console().Log("Run Gui init script\n");
435
Application::runInitGuiScript();
436
setImportImageFormats();
438
catch (const Base::Exception& e) {
439
Base::Console().Error("Error in FreeCADGuiInit.py: %s\n", e.what());
440
mainWindow->stopSplasher();
445
// stop splash screen and set immediately the active window that may be of interest
446
// for scripts using Python binding for Qt
447
mainWindow->stopSplasher();
448
mainWindow->activateWindow();
451
void StartupPostProcess::activateWorkbench()
453
// Activate the correct workbench
454
std::string start = App::Application::Config()["StartWorkbench"];
455
Base::Console().Log("Init: Activating default workbench %s\n", start.c_str());
456
std::string autoload =
457
App::GetApplication()
458
.GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")
459
->GetASCII("AutoloadModule", start.c_str());
460
if ("$LastModule" == autoload) {
461
start = App::GetApplication()
462
.GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")
463
->GetASCII("LastModule", start.c_str());
468
// if the auto workbench is not visible then force to use the default workbech
469
// and replace the wrong entry in the parameters
470
QStringList wb = guiApp.workbenches();
471
if (!wb.contains(QString::fromLatin1(start.c_str()))) {
472
start = App::Application::Config()["StartWorkbench"];
473
if ("$LastModule" == autoload) {
474
App::GetApplication()
475
.GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")
476
->SetASCII("LastModule", start.c_str());
479
App::GetApplication()
480
.GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")
481
->SetASCII("AutoloadModule", start.c_str());
485
// Call this before showing the main window because otherwise:
486
// 1. it shows a white window for a few seconds which doesn't look nice
487
// 2. the layout of the toolbars is completely broken
488
guiApp.activateWorkbench(start.c_str());
490
// show the main window
491
if (!hiddenMainWindow()) {
492
Base::Console().Log("Init: Showing main window\n");
493
mainWindow->loadWindowSettings();
496
//initialize spaceball.
497
if (auto fcApp = qobject_cast<GUIApplicationNativeEventAware*>(qtApp)) {
498
fcApp->initSpaceball(mainWindow);
503
// Now run the background autoload, for workbenches that should be loaded at startup, but not
504
// displayed to the user immediately
507
// Reactivate the startup workbench
508
guiApp.activateWorkbench(start.c_str());
511
void StartupPostProcess::setStyleSheet()
513
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
514
"User parameter:BaseApp/Preferences/MainWindow");
515
std::string style = hGrp->GetASCII("StyleSheet");
517
// check the branding settings
518
const auto& config = App::Application::Config();
519
auto it = config.find("StyleSheet");
520
if (it != config.end()) {
525
guiApp.setStyleSheet(QLatin1String(style.c_str()), hGrp->GetBool("TiledBackground", false));
528
void StartupPostProcess::autoloadModules(const QStringList& wb)
530
// Now run the background autoload, for workbenches that should be loaded at startup, but not
531
// displayed to the user immediately
532
std::string autoloadCSV =
533
App::GetApplication()
534
.GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")
535
->GetASCII("BackgroundAutoloadModules", "");
537
// Tokenize the comma-separated list and load the requested workbenches if they exist in this
539
std::stringstream stream(autoloadCSV);
540
std::string workbench;
541
while (std::getline(stream, workbench, ',')) {
542
if (wb.contains(QString::fromLatin1(workbench.c_str()))) {
543
guiApp.activateWorkbench(workbench.c_str());
548
void StartupPostProcess::checkParameters()
550
if (App::GetApplication().GetSystemParameter().IgnoreSave()) {
551
Base::Console().Warning("System parameter file couldn't be opened.\n"
552
"Continue with an empty configuration that won't be saved.\n");
554
if (App::GetApplication().GetUserParameter().IgnoreSave()) {
555
Base::Console().Warning("User parameter file couldn't be opened.\n"
556
"Continue with an empty configuration that won't be saved.\n");