From 0a9e1bc9e8c81f9c433d3ce1a86f86b056dae9da Mon Sep 17 00:00:00 2001 From: Ladislav Michl Date: Wed, 11 Sep 2024 10:40:10 +0200 Subject: [PATCH] Gui: refactor Splashscreen Move About dialog class to the separate source file. --- src/Gui/CMakeLists.txt | 6 +- src/Gui/CommandStd.cpp | 2 +- src/Gui/{Splashscreen.cpp => DlgAbout.cpp} | 1554 +++++++++----------- src/Gui/{Splashscreen.h => DlgAbout.h} | 229 ++- src/Gui/MainWindow.cpp | 219 +-- src/Gui/MainWindow.h | 4 - src/Gui/SplashScreen.cpp | 408 +++++ src/Gui/SplashScreen.h | 58 + 8 files changed, 1308 insertions(+), 1172 deletions(-) rename src/Gui/{Splashscreen.cpp => DlgAbout.cpp} (64%) rename src/Gui/{Splashscreen.h => DlgAbout.h} (69%) create mode 100644 src/Gui/SplashScreen.cpp create mode 100644 src/Gui/SplashScreen.h diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index 642f58568f..e792d070a2 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -444,6 +444,7 @@ SOURCE_GROUP("Command" FILES ${Command_SRCS}) SET(Dialog_CPP_SRCS Clipping.cpp DemoMode.cpp + DlgAbout.cpp DlgActivateWindowImp.cpp DlgCreateNewPreferencePackImp.cpp DlgUnitsCalculatorImp.cpp @@ -484,6 +485,7 @@ SET(Dialog_CPP_SRCS SET(Dialog_HPP_SRCS Clipping.h DemoMode.h + DlgAbout.h DlgActivateWindowImp.h DlgCreateNewPreferencePackImp.h DlgUnitsCalculatorImp.h @@ -1088,7 +1090,7 @@ SET(Widget_CPP_SRCS ProgressDialog.cpp QuantitySpinBox.cpp SpinBox.cpp - Splashscreen.cpp + SplashScreen.cpp PythonWrapper.cpp UiLoader.cpp WidgetFactory.cpp @@ -1109,7 +1111,7 @@ SET(Widget_HPP_SRCS QuantitySpinBox.h QuantitySpinBox_p.h SpinBox.h - Splashscreen.h + SplashScreen.h PythonWrapper.h UiLoader.h WidgetFactory.h diff --git a/src/Gui/CommandStd.cpp b/src/Gui/CommandStd.cpp index a6104093ea..e0fa98730e 100644 --- a/src/Gui/CommandStd.cpp +++ b/src/Gui/CommandStd.cpp @@ -42,6 +42,7 @@ #include "Action.h" #include "BitmapFactory.h" #include "Command.h" +#include "DlgAbout.h" #include "DlgCustomizeImp.h" #include "DlgParameterImp.h" #include "DlgPreferencesImp.h" @@ -50,7 +51,6 @@ #include "MainWindow.h" #include "OnlineDocumentation.h" #include "Selection.h" -#include "Splashscreen.h" #include "WhatsThis.h" #include "Workbench.h" #include "WorkbenchManager.h" diff --git a/src/Gui/Splashscreen.cpp b/src/Gui/DlgAbout.cpp similarity index 64% rename from src/Gui/Splashscreen.cpp rename to src/Gui/DlgAbout.cpp index 85e21dcbd0..cf424c9471 100644 --- a/src/Gui/Splashscreen.cpp +++ b/src/Gui/DlgAbout.cpp @@ -1,823 +1,731 @@ -/*************************************************************************** - * Copyright (c) 2004 Werner Mayer * - * * - * This file is part of the FreeCAD CAx development system. * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Library General Public * - * License as published by the Free Software Foundation; either * - * version 2 of the License, or (at your option) any later version. * - * * - * This library is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU Library General Public License for more details. * - * * - * You should have received a copy of the GNU Library General Public * - * License along with this library; see the file COPYING.LIB. If not, * - * write to the Free Software Foundation, Inc., 59 Temple Place, * - * Suite 330, Boston, MA 02111-1307, USA * - * * - ***************************************************************************/ - -#include "PreCompiled.h" - -#ifndef _PreComp_ -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -#endif - -#include -#include -#include -#include - -#include -#include -#include - -#include "Splashscreen.h" -#include "ui_AboutApplication.h" -#include "MainWindow.h" - - -using namespace Gui; -using namespace Gui::Dialog; -namespace fs = boost::filesystem; - -namespace Gui { - -QString prettyProductInfoWrapper() -{ - auto productName = QSysInfo::prettyProductName(); -#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) -#ifdef FC_OS_MACOSX - auto macosVersionFile = QString::fromUtf8("/System/Library/CoreServices/.SystemVersionPlatform.plist"); - auto fi = QFileInfo (macosVersionFile); - if (fi.exists() && fi.isReadable()) { - auto plistFile = QFile(macosVersionFile); - plistFile.open(QIODevice::ReadOnly); - while (!plistFile.atEnd()) { - auto line = plistFile.readLine(); - if (line.contains("ProductUserVisibleVersion")) { - auto nextLine = plistFile.readLine(); - if (nextLine.contains("")) { - QRegularExpression re(QString::fromUtf8("\\s*(.*)")); - auto matches = re.match(QString::fromUtf8(nextLine)); - if (matches.hasMatch()) { - productName = QString::fromUtf8("macOS ") + matches.captured(1); - break; - } - } - } - } - } -#endif -#endif -#ifdef FC_OS_WIN64 - QSettings regKey {QString::fromUtf8("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), QSettings::NativeFormat}; - if (regKey.contains(QString::fromUtf8("CurrentBuildNumber"))) { - auto buildNumber = regKey.value(QString::fromUtf8("CurrentBuildNumber")).toInt(); - if (buildNumber > 0) { - if (buildNumber < 9200) { - productName = QString::fromUtf8("Windows 7 build %1").arg(buildNumber); - } - else if (buildNumber < 10240) { - productName = QString::fromUtf8("Windows 8 build %1").arg(buildNumber); - } - else if (buildNumber < 22000) { - productName = QString::fromUtf8("Windows 10 build %1").arg(buildNumber); - } - else { - productName = QString::fromUtf8("Windows 11 build %1").arg(buildNumber); - } - } - } -#endif - return productName; -} - -/** Displays all messages at startup inside the splash screen. - * \author Werner Mayer - */ -class SplashObserver : public Base::ILogger -{ -public: - SplashObserver(const SplashObserver&) = delete; - SplashObserver(SplashObserver&&) = delete; - SplashObserver& operator= (const SplashObserver&) = delete; - SplashObserver& operator= (SplashObserver&&) = delete; - - explicit SplashObserver(QSplashScreen* splasher=nullptr) - : splash(splasher) - , alignment(Qt::AlignBottom|Qt::AlignLeft) - , textColor(Qt::black) - { - Base::Console().AttachObserver(this); - - // allow to customize text position and color - const std::map& cfg = App::Application::Config(); - auto al = cfg.find("SplashAlignment"); - if (al != cfg.end()) { - QString alt = QString::fromLatin1(al->second.c_str()); - int align=0; - if (alt.startsWith(QLatin1String("VCenter"))) { - align = Qt::AlignVCenter; - } - else if (alt.startsWith(QLatin1String("Top"))) { - align = Qt::AlignTop; - } - else { - align = Qt::AlignBottom; - } - - if (alt.endsWith(QLatin1String("HCenter"))) { - align += Qt::AlignHCenter; - } - else if (alt.endsWith(QLatin1String("Right"))) { - align += Qt::AlignRight; - } - else { - align += Qt::AlignLeft; - } - - alignment = align; - } - - // choose text color - auto tc = cfg.find("SplashTextColor"); - if (tc != cfg.end()) { - QColor col(QString::fromStdString(tc->second)); - if (col.isValid()) { - textColor = col; - } - } - } - ~SplashObserver() override - { - Base::Console().DetachObserver(this); - } - const char* Name() override - { - return "SplashObserver"; - } - void SendLog(const std::string& notifiername, const std::string& msg, Base::LogStyle level, - Base::IntendedRecipient recipient, Base::ContentType content) override - { - Q_UNUSED(notifiername) - Q_UNUSED(recipient) - Q_UNUSED(content) - -#ifdef FC_DEBUG - Q_UNUSED(level) - Log(msg); -#else - if (level == Base::LogStyle::Log) { - Log(msg); - } -#endif - } - void Log(const std::string& text) - { - QString msg(QString::fromStdString(text)); - QRegularExpression rx; - // ignore 'Init:' and 'Mod:' prefixes - rx.setPattern(QLatin1String("^\\s*(Init:|Mod:)\\s*")); - auto match = rx.match(msg); - if (match.hasMatch()) { - msg = msg.mid(match.capturedLength()); - } - else { - // ignore activation of commands - rx.setPattern(QLatin1String(R"(^\s*(\+App::|Create|CmdC:|CmdG:|Act:)\s*)")); - match = rx.match(msg); - if (match.hasMatch() && match.capturedStart() == 0) - return; - } - - splash->showMessage(msg.replace(QLatin1String("\n"), QString()), alignment, textColor); - QMutex mutex; - QMutexLocker ml(&mutex); - QWaitCondition().wait(&mutex, 50); - } - -private: - QSplashScreen* splash; - int alignment; - QColor textColor; -}; -} // namespace Gui - -// ------------------------------------------------------------------------------ - -/** - * Constructs a splash screen that will display the pixmap. - */ -SplashScreen::SplashScreen( const QPixmap & pixmap , Qt::WindowFlags f ) - : QSplashScreen(pixmap, f) -{ - // write the messages to splasher - messages = new SplashObserver(this); -} - -/** Destruction. */ -SplashScreen::~SplashScreen() -{ - delete messages; -} - -/** - * Draws the contents of the splash screen using painter \a painter. The default - * implementation draws the message passed by message(). - */ -void SplashScreen::drawContents ( QPainter * painter ) -{ - QSplashScreen::drawContents(painter); -} - -void SplashScreen::setShowMessages(bool on) -{ - messages->bErr = on; - messages->bMsg = on; - messages->bLog = on; - messages->bWrn = on; -} - -// ------------------------------------------------------------------------------ - -AboutDialogFactory* AboutDialogFactory::factory = nullptr; - -AboutDialogFactory::~AboutDialogFactory() = default; - -QDialog *AboutDialogFactory::create(QWidget *parent) const -{ - return new AboutDialog(parent); -} - -const AboutDialogFactory *AboutDialogFactory::defaultFactory() -{ - static const AboutDialogFactory this_factory; - if (factory) - return factory; - return &this_factory; -} - -void AboutDialogFactory::setDefaultFactory(AboutDialogFactory *f) -{ - if (factory != f) - delete factory; - factory = f; -} - -// ------------------------------------------------------------------------------ - -/* TRANSLATOR Gui::Dialog::AboutDialog */ - -/** - * Constructs an AboutDialog which is a child of 'parent', with the - * name 'name' and widget flags set to 'WStyle_Customize|WStyle_NoBorder|WType_Modal' - * - * The dialog will be modal. - */ -AboutDialog::AboutDialog(QWidget* parent) - : QDialog(parent), ui(new Ui_AboutApplication) -{ - setModal(true); - ui->setupUi(this); - connect(ui->copyButton, &QPushButton::clicked, - this, &AboutDialog::copyToClipboard); - - // remove the automatic help button in dialog title since we don't use it - setWindowFlag(Qt::WindowContextHelpButtonHint, false); - - layout()->setSizeConstraint(QLayout::SetFixedSize); - QRect rect = QApplication::primaryScreen()->availableGeometry(); - - // See if we have a custom About screen image set - QPixmap image = getMainWindow()->aboutImage(); - - // Fallback to the splashscreen image - if (image.isNull()) { - image = getMainWindow()->splashImage(); - } - - // Make sure the image is not too big - int denom = 2; - if (image.height() > rect.height()/denom || image.width() > rect.width()/denom) { - float scale = static_cast(image.width()) / static_cast(image.height()); - int width = std::min(image.width(), rect.width()/denom); - int height = std::min(image.height(), rect.height()/denom); - height = std::min(height, static_cast(width / scale)); - width = static_cast(scale * height); - - image = image.scaled(width, height); - } - ui->labelSplashPicture->setPixmap(image); - ui->tabWidget->setCurrentIndex(0); // always start on the About tab - - setupLabels(); - showCredits(); - showLicenseInformation(); - showLibraryInformation(); - showCollectionInformation(); - showPrivacyPolicy(); - showOrHideImage(rect); -} - -/** - * Destroys the object and frees any allocated resources - */ -AboutDialog::~AboutDialog() -{ - // no need to delete child widgets, Qt does it all for us - delete ui; -} - -void AboutDialog::showOrHideImage(const QRect& rect) -{ - adjustSize(); - if (height() > rect.height()) { - ui->labelSplashPicture->hide(); - } -} - -void AboutDialog::setupLabels() -{ - //fonts are rendered smaller on Mac so point size can't be the same for all platforms - int fontSize = 8; -#ifdef Q_OS_MAC - fontSize = 11; -#endif - //avoid overriding user set style sheet - if (qApp->styleSheet().isEmpty()) { - setStyleSheet(QString::fromLatin1("Gui--Dialog--AboutDialog QLabel {font-size: %1pt;}").arg(fontSize)); - } - - QString exeName = qApp->applicationName(); - std::map& config = App::Application::Config(); - std::map::iterator it; - QString banner = QString::fromUtf8(config["CopyrightInfo"].c_str()); - banner = banner.left( banner.indexOf(QLatin1Char('\n')) ); - QString major = QString::fromLatin1(config["BuildVersionMajor"].c_str()); - QString minor = QString::fromLatin1(config["BuildVersionMinor"].c_str()); - QString point = QString::fromLatin1(config["BuildVersionPoint"].c_str()); - QString suffix = QString::fromLatin1(config["BuildVersionSuffix"].c_str()); - QString build = QString::fromLatin1(config["BuildRevision"].c_str()); - QString disda = QString::fromLatin1(config["BuildRevisionDate"].c_str()); - QString mturl = QString::fromLatin1(config["MaintainerUrl"].c_str()); - - // we use replace() to keep label formatting, so a label with text "Unknown" - // gets replaced to "FreeCAD", for example - - QString author = ui->labelAuthor->text(); - author.replace(QString::fromLatin1("Unknown Application"), exeName); - author.replace(QString::fromLatin1("(c) Unknown Author"), banner); - ui->labelAuthor->setText(author); - ui->labelAuthor->setUrl(mturl); - - if (qApp->styleSheet().isEmpty()) { - ui->labelAuthor->setStyleSheet(QString::fromLatin1("Gui--UrlLabel {color: #0000FF;text-decoration: underline;font-weight: 600;}")); - } - - QString version = ui->labelBuildVersion->text(); - version.replace(QString::fromLatin1("Unknown"), QString::fromLatin1("%1.%2.%3%4").arg(major, minor, point, suffix)); - ui->labelBuildVersion->setText(version); - - QString revision = ui->labelBuildRevision->text(); - revision.replace(QString::fromLatin1("Unknown"), build); - ui->labelBuildRevision->setText(revision); - - QString date = ui->labelBuildDate->text(); - date.replace(QString::fromLatin1("Unknown"), disda); - ui->labelBuildDate->setText(date); - - QString os = ui->labelBuildOS->text(); - os.replace(QString::fromLatin1("Unknown"), prettyProductInfoWrapper()); - ui->labelBuildOS->setText(os); - - QString architecture = ui->labelBuildRunArchitecture->text(); - if (QSysInfo::buildCpuArchitecture() == QSysInfo::currentCpuArchitecture()) { - architecture.replace(QString::fromLatin1("Unknown"), QSysInfo::buildCpuArchitecture()); - } - else { - architecture.replace( - QString::fromLatin1("Unknown"), - QString::fromLatin1("%1 (running on: %2)") - .arg(QSysInfo::buildCpuArchitecture(), QSysInfo::currentCpuArchitecture())); - } - ui->labelBuildRunArchitecture->setText(architecture); - - // branch name - it = config.find("BuildRevisionBranch"); - if (it != config.end()) { - QString branch = ui->labelBuildBranch->text(); - branch.replace(QString::fromLatin1("Unknown"), QString::fromUtf8(it->second.c_str())); - ui->labelBuildBranch->setText(branch); - } - else { - ui->labelBranch->hide(); - ui->labelBuildBranch->hide(); - } - - // hash id - it = config.find("BuildRevisionHash"); - if (it != config.end()) { - QString hash = ui->labelBuildHash->text(); - hash.replace(QString::fromLatin1("Unknown"), QString::fromLatin1(it->second.c_str()).left(7)); // Use the 7-char abbreviated hash - ui->labelBuildHash->setText(hash); - if (auto url_itr = config.find("BuildRepositoryURL"); url_itr != config.end()) { - auto url = QString::fromStdString(url_itr->second); - - if (int space = url.indexOf(QChar::fromLatin1(' ')); space != -1) - url = url.left(space); // Strip off the branch information to get just the repo - - if (url == QString::fromUtf8("Unknown")) - url = QString::fromUtf8("https://github.com/FreeCAD/FreeCAD"); // Just take a guess - - // This may only create valid URLs for Github, but some other hosts use the same format so give it a shot... - auto https = url.replace(QString::fromUtf8("git://"), QString::fromUtf8("https://")); - https.replace(QString::fromUtf8(".git"), QString::fromUtf8("")); - ui->labelBuildHash->setUrl(https + QString::fromUtf8("/commit/") + QString::fromStdString(it->second)); - } - } - else { - ui->labelHash->hide(); - ui->labelBuildHash->hide(); - } -} - -void AboutDialog::showCredits() -{ - auto creditsFileURL = QLatin1String(":/doc/CONTRIBUTORS"); - QFile creditsFile(creditsFileURL); - - if (!creditsFile.open(QIODevice::ReadOnly | QIODevice::Text)) { - return; - } - - auto tab_credits = new QWidget(); - tab_credits->setObjectName(QString::fromLatin1("tab_credits")); - ui->tabWidget->addTab(tab_credits, tr("Credits")); - auto hlayout = new QVBoxLayout(tab_credits); - auto textField = new QTextBrowser(tab_credits); - textField->setOpenExternalLinks(false); - textField->setOpenLinks(false); - hlayout->addWidget(textField); - - QString creditsHTML = QString::fromLatin1("

"); - //: Header for bgbsww - creditsHTML += tr("This version of FreeCAD is dedicated to the memory of Brad McLean, aka bgbsww."); - //: Header for the Credits tab of the About screen - creditsHTML += QString::fromLatin1("

"); - creditsHTML += tr("Credits"); - creditsHTML += QString::fromLatin1("

"); - creditsHTML += tr("FreeCAD would not be possible without the contributions of"); - creditsHTML += QString::fromLatin1(":

"); - //: Header for the list of individual people in the Credits list. - creditsHTML += tr("Individuals"); - creditsHTML += QString::fromLatin1("

    "); - - QTextStream stream(&creditsFile); -#if QT_VERSION < QT_VERSION_CHECK(6,0,0) - stream.setCodec("UTF-8"); -#endif - QString line; - while (stream.readLineInto(&line)) { - if (!line.isEmpty()) { - if (line == QString::fromLatin1("Firms")) { - creditsHTML += QString::fromLatin1("

"); - //: Header for the list of companies/organizations in the Credits list. - creditsHTML += tr("Organizations"); - creditsHTML += QString::fromLatin1("

    "); - } - else { - creditsHTML += QString::fromLatin1("
  • ") + line + QString::fromLatin1("
  • "); - } - } - } - creditsHTML += QString::fromLatin1("
"); - textField->setHtml(creditsHTML); -} - -void AboutDialog::showLicenseInformation() -{ - QString licenseFileURL = QString::fromLatin1("%1/LICENSE.html") - .arg(QString::fromUtf8(App::Application::getHelpDir().c_str())); - QFile licenseFile(licenseFileURL); - - if (licenseFile.open(QIODevice::ReadOnly | QIODevice::Text)) { - QString licenseHTML = QString::fromUtf8(licenseFile.readAll()); - const auto placeholder = QString::fromUtf8(""); - licenseHTML.replace(placeholder, getAdditionalLicenseInformation()); - - ui->tabWidget->removeTab(1); // Hide the license placeholder widget - - auto tab_license = new QWidget(); - tab_license->setObjectName(QString::fromLatin1("tab_license")); - ui->tabWidget->addTab(tab_license, tr("License")); - auto hlayout = new QVBoxLayout(tab_license); - auto textField = new QTextBrowser(tab_license); - textField->setOpenExternalLinks(true); - textField->setOpenLinks(true); - hlayout->addWidget(textField); - - textField->setHtml(licenseHTML); - } - else { - QString info(QLatin1String("SUCH DAMAGES.
")); - info += getAdditionalLicenseInformation(); - QString lictext = ui->textBrowserLicense->toHtml(); - lictext.replace(QString::fromLatin1("SUCH DAMAGES.
"), info); - ui->textBrowserLicense->setHtml(lictext); - } -} - -QString AboutDialog::getAdditionalLicenseInformation() const -{ - // Any additional piece of text to be added after the main license text goes below. - // Please set title in

tags, license text in

tags - // and add an


tag at the end to nicely separate license blocks - QString info; -#ifdef _USE_3DCONNEXION_SDK - info += QString::fromUtf8( - "

3D Mouse Support

" - "

Development tools and related technology provided under license from 3Dconnexion.
" - "Copyright © 1992–2012 3Dconnexion. All rights reserved.

" - "
" - ); -#endif - return info; -} - -void AboutDialog::showLibraryInformation() -{ - auto tab_library = new QWidget(); - tab_library->setObjectName(QString::fromLatin1("tab_library")); - ui->tabWidget->addTab(tab_library, tr("Libraries")); - auto hlayout = new QVBoxLayout(tab_library); - auto textField = new QTextBrowser(tab_library); - textField->setOpenExternalLinks(true); - hlayout->addWidget(textField); - - QString baseurl = QString::fromLatin1("file:///%1/ThirdPartyLibraries.html") - .arg(QString::fromUtf8(App::Application::getHelpDir().c_str())); - QUrl librariesFileUrl = QUrl(baseurl); - - textField->setSource(librariesFileUrl); -} - -void AboutDialog::showCollectionInformation() -{ - QString doc = QString::fromUtf8(App::Application::getHelpDir().c_str()); - QString path = doc + QLatin1String("Collection.html"); - if (!QFile::exists(path)) - return; - - auto tab_collection = new QWidget(); - tab_collection->setObjectName(QString::fromLatin1("tab_collection")); - ui->tabWidget->addTab(tab_collection, tr("Collection")); - auto hlayout = new QVBoxLayout(tab_collection); - auto textField = new QTextBrowser(tab_collection); - textField->setOpenExternalLinks(true); - hlayout->addWidget(textField); - textField->setSource(path); -} - -void AboutDialog::showPrivacyPolicy() -{ - auto policyFileURL = QLatin1String(":/doc/PRIVACY_POLICY"); - QFile policyFile(policyFileURL); - - if (!policyFile.open(QIODevice::ReadOnly | QIODevice::Text)) { - return; - } - auto text = QString::fromUtf8(policyFile.readAll()); - auto tabPrivacyPolicy = new QWidget(); - tabPrivacyPolicy->setObjectName(QString::fromLatin1("tabPrivacyPolicy")); - ui->tabWidget->addTab(tabPrivacyPolicy, tr("Privacy Policy")); - auto hLayout = new QVBoxLayout(tabPrivacyPolicy); - auto textField = new QTextBrowser(tabPrivacyPolicy); - textField->setOpenExternalLinks(true); - hLayout->addWidget(textField); - -#if QT_VERSION < QT_VERSION_CHECK(5,15,0) - // We can't actually render the markdown, so just display it as text - textField->setText(text); -#else - textField->setMarkdown(text); -#endif -} - -void AboutDialog::linkActivated(const QUrl& link) -{ - auto licenseView = new LicenseView(); - licenseView->setAttribute(Qt::WA_DeleteOnClose); - licenseView->show(); - QString title = tr("License"); - QString fragment = link.fragment(); - if (fragment.startsWith(QLatin1String("_Toc"))) { - QString prefix = fragment.mid(4); - title = QString::fromLatin1("%1 %2").arg(prefix, title); - } - licenseView->setWindowTitle(title); - getMainWindow()->addWindow(licenseView); - licenseView->setSource(link); -} - -void AboutDialog::copyToClipboard() -{ - QString data; - QTextStream str(&data); - std::map& config = App::Application::Config(); - std::map::iterator it; - QString exe = QString::fromStdString(App::Application::getExecutableName()); - - QString major = QString::fromLatin1(config["BuildVersionMajor"].c_str()); - QString minor = QString::fromLatin1(config["BuildVersionMinor"].c_str()); - QString point = QString::fromLatin1(config["BuildVersionPoint"].c_str()); - QString suffix = QString::fromLatin1(config["BuildVersionSuffix"].c_str()); - QString build = QString::fromLatin1(config["BuildRevision"].c_str()); - - QString deskEnv = QProcessEnvironment::systemEnvironment().value(QStringLiteral("XDG_CURRENT_DESKTOP"), QString()); - QString deskSess = QProcessEnvironment::systemEnvironment().value(QStringLiteral("DESKTOP_SESSION"), QString()); - QStringList deskInfoList; - QString deskInfo; - - if (!deskEnv.isEmpty()) { - deskInfoList.append(deskEnv); - } - if (!deskSess.isEmpty()) { - deskInfoList.append(deskSess); - } - if (qGuiApp->platformName() != QLatin1String("windows") && qGuiApp->platformName() != QLatin1String("cocoa")) { - deskInfoList.append(qGuiApp->platformName()); - } - if(!deskInfoList.isEmpty()) { - deskInfo = QLatin1String(" (") + deskInfoList.join(QLatin1String("/")) + QLatin1String(")"); - } - - str << "OS: " << prettyProductInfoWrapper() << deskInfo << '\n'; - if (QSysInfo::buildCpuArchitecture() == QSysInfo::currentCpuArchitecture()){ - str << "Architecture: " << QSysInfo::buildCpuArchitecture() << "\n"; - } else { - str << "Architecture: " << QSysInfo::buildCpuArchitecture() << "(running on: " << QSysInfo::currentCpuArchitecture() << ")\n"; - } - str << "Version: " << major << "." << minor << "." << point << suffix << "." << build; -#ifdef FC_CONDA - str << " Conda"; -#endif -#ifdef FC_FLATPAK - str << " Flatpak"; -#endif - char* appimage = getenv("APPIMAGE"); - if (appimage) { - str << " AppImage"; - } - char* snap = getenv("SNAP_REVISION"); - if (snap) { - str << " Snap " << snap; - } - str << '\n'; - -#if defined(_DEBUG) || defined(DEBUG) - str << "Build type: Debug\n"; -#elif defined(NDEBUG) - str << "Build type: Release\n"; -#elif defined(CMAKE_BUILD_TYPE) - str << "Build type: " << CMAKE_BUILD_TYPE << '\n'; -#else - str << "Build type: Unknown\n"; -#endif - it = config.find("BuildRevisionBranch"); - if (it != config.end()) - str << "Branch: " << QString::fromUtf8(it->second.c_str()) << '\n'; - it = config.find("BuildRevisionHash"); - if (it != config.end()) - str << "Hash: " << it->second.c_str() << '\n'; - // report also the version numbers of the most important libraries in FreeCAD - str << "Python " << PY_VERSION << ", "; - str << "Qt " << QT_VERSION_STR << ", "; - str << "Coin " << COIN_VERSION << ", "; - str << "Vtk " << fcVtkVersion << ", "; -#if defined(HAVE_OCC_VERSION) - str << "OCC " - << OCC_VERSION_MAJOR << "." - << OCC_VERSION_MINOR << "." - << OCC_VERSION_MAINTENANCE -#ifdef OCC_VERSION_DEVELOPMENT - << "." OCC_VERSION_DEVELOPMENT -#endif - << '\n'; -#endif - QLocale loc; - str << "Locale: " << QLocale::languageToString(loc.language()) << "/" -#if QT_VERSION < QT_VERSION_CHECK(6,6,0) - << QLocale::countryToString(loc.country()) -#else - << QLocale::territoryToString(loc.territory()) -#endif - << " (" << loc.name() << ")"; - if (loc != QLocale::system()) { - loc = QLocale::system(); - str << " [ OS: " << QLocale::languageToString(loc.language()) << "/" -#if QT_VERSION < QT_VERSION_CHECK(6,6,0) - << QLocale::countryToString(loc.country()) -#else - << QLocale::territoryToString(loc.territory()) -#endif - << " (" << loc.name() << ") ]"; - } - str << "\n"; - - // Add Stylesheet/Theme/Qtstyle information - std::string styleSheet = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/MainWindow")->GetASCII("StyleSheet"); - std::string theme = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/MainWindow")->GetASCII("Theme"); - - #if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0) - std::string style = qApp->style()->name().toStdString(); - #else - std::string style = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/MainWindow")->GetASCII("QtStyle"); - if(style.empty()) { - style = "Qt default"; - } - #endif - - if(styleSheet.empty()) { - styleSheet = "unset"; - } - if(theme.empty()) { - theme = "unset"; - } - - str << "Stylesheet/Theme/QtStyle: " - << QString::fromStdString(styleSheet) << "/" - << QString::fromStdString(theme) << "/" - << QString::fromStdString(style) << "\n"; - - // Add installed module information: - auto modDir = fs::path(App::Application::getUserAppDataDir()) / "Mod"; - bool firstMod = true; - if (fs::exists(modDir) && fs::is_directory(modDir)) { - for (const auto& mod : fs::directory_iterator(modDir)) { - auto dirName = mod.path().filename().string(); - if (dirName[0] == '.') // Ignore dot directories - continue; - if (firstMod) { - firstMod = false; - str << "Installed mods: \n"; - } - str << " * " << QString::fromStdString(mod.path().filename().string()); - auto metadataFile = mod.path() / "package.xml"; - if (fs::exists(metadataFile)) { - App::Metadata metadata(metadataFile); - if (metadata.version() != App::Meta::Version()) - str << QLatin1String(" ") + QString::fromStdString(metadata.version().str()); - } - auto disablingFile = mod.path() / "ADDON_DISABLED"; - if (fs::exists(disablingFile)) - str << " (Disabled)"; - - str << "\n"; - } - } - - QClipboard* cb = QApplication::clipboard(); - cb->setText(data); -} - -// ---------------------------------------------------------------------------- - -/* TRANSLATOR Gui::LicenseView */ - -LicenseView::LicenseView(QWidget* parent) - : MDIView(nullptr,parent,Qt::WindowFlags()) -{ - browser = new QTextBrowser(this); - browser->setOpenExternalLinks(true); - browser->setOpenLinks(true); - setCentralWidget(browser); -} - -LicenseView::~LicenseView() = default; - -void LicenseView::setSource(const QUrl& url) -{ - browser->setSource(url); -} - -#include "moc_Splashscreen.cpp" +/*************************************************************************** + * Copyright (c) 2004 Werner Mayer * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#include "PreCompiled.h" + +#ifndef _PreComp_ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include + +#include "BitmapFactory.h" +#include "DlgAbout.h" +#include "MainWindow.h" +#include "SplashScreen.h" +#include "ui_AboutApplication.h" + +using namespace Gui; +using namespace Gui::Dialog; +namespace fs = boost::filesystem; + +static QString prettyProductInfoWrapper() +{ + auto productName = QSysInfo::prettyProductName(); +#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) +#ifdef FC_OS_MACOSX + auto macosVersionFile = + QString::fromUtf8("/System/Library/CoreServices/.SystemVersionPlatform.plist"); + auto fi = QFileInfo(macosVersionFile); + if (fi.exists() && fi.isReadable()) { + auto plistFile = QFile(macosVersionFile); + plistFile.open(QIODevice::ReadOnly); + while (!plistFile.atEnd()) { + auto line = plistFile.readLine(); + if (line.contains("ProductUserVisibleVersion")) { + auto nextLine = plistFile.readLine(); + if (nextLine.contains("")) { + QRegularExpression re(QString::fromUtf8("\\s*(.*)")); + auto matches = re.match(QString::fromUtf8(nextLine)); + if (matches.hasMatch()) { + productName = QString::fromUtf8("macOS ") + matches.captured(1); + break; + } + } + } + } + } +#endif +#endif +#ifdef FC_OS_WIN64 + QSettings regKey { + QString::fromUtf8("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), + QSettings::NativeFormat}; + if (regKey.contains(QString::fromUtf8("CurrentBuildNumber"))) { + auto buildNumber = regKey.value(QString::fromUtf8("CurrentBuildNumber")).toInt(); + if (buildNumber > 0) { + if (buildNumber < 9200) { + productName = QString::fromUtf8("Windows 7 build %1").arg(buildNumber); + } + else if (buildNumber < 10240) { + productName = QString::fromUtf8("Windows 8 build %1").arg(buildNumber); + } + else if (buildNumber < 22000) { + productName = QString::fromUtf8("Windows 10 build %1").arg(buildNumber); + } + else { + productName = QString::fromUtf8("Windows 11 build %1").arg(buildNumber); + } + } + } +#endif + return productName; +} + +// ------------------------------------------------------------------------------ + +AboutDialogFactory* AboutDialogFactory::factory = nullptr; + +AboutDialogFactory::~AboutDialogFactory() = default; + +QDialog* AboutDialogFactory::create(QWidget* parent) const +{ + return new AboutDialog(parent); +} + +const AboutDialogFactory* AboutDialogFactory::defaultFactory() +{ + static const AboutDialogFactory this_factory; + if (factory) { + return factory; + } + return &this_factory; +} + +void AboutDialogFactory::setDefaultFactory(AboutDialogFactory* f) +{ + if (factory != f) { + delete factory; + } + factory = f; +} + +// ------------------------------------------------------------------------------ + +/* TRANSLATOR Gui::Dialog::AboutDialog */ + +/** + * Constructs an AboutDialog which is a child of 'parent', with the + * name 'name' and widget flags set to 'WStyle_Customize|WStyle_NoBorder|WType_Modal' + * + * The dialog will be modal. + */ +AboutDialog::AboutDialog(QWidget* parent) + : QDialog(parent) + , ui(new Ui_AboutApplication) +{ + setModal(true); + ui->setupUi(this); + connect(ui->copyButton, &QPushButton::clicked, this, &AboutDialog::copyToClipboard); + + // remove the automatic help button in dialog title since we don't use it + setWindowFlag(Qt::WindowContextHelpButtonHint, false); + + layout()->setSizeConstraint(QLayout::SetFixedSize); + QRect rect = QApplication::primaryScreen()->availableGeometry(); + + // See if we have a custom About screen image set + QPixmap image = aboutImage(); + + // Fallback to the splashscreen image + if (image.isNull()) { + image = SplashScreen::splashImage(); + } + + // Make sure the image is not too big + int denom = 2; + if (image.height() > rect.height() / denom || image.width() > rect.width() / denom) { + float scale = static_cast(image.width()) / static_cast(image.height()); + int width = std::min(image.width(), rect.width() / denom); + int height = std::min(image.height(), rect.height() / denom); + height = std::min(height, static_cast(width / scale)); + width = static_cast(scale * height); + + image = image.scaled(width, height); + } + ui->labelSplashPicture->setPixmap(image); + ui->tabWidget->setCurrentIndex(0); // always start on the About tab + + setupLabels(); + showCredits(); + showLicenseInformation(); + showLibraryInformation(); + showCollectionInformation(); + showPrivacyPolicy(); + showOrHideImage(rect); +} + +/** + * Destroys the object and frees any allocated resources + */ +AboutDialog::~AboutDialog() +{ + // no need to delete child widgets, Qt does it all for us + delete ui; +} + +QPixmap AboutDialog::aboutImage() const +{ + // See if we have a custom About screen image set + QPixmap about_image; + QFileInfo fi(QString::fromLatin1("images:about_image.png")); + if (fi.isFile() && fi.exists()) { + about_image.load(fi.filePath(), "PNG"); + } + + std::string about_path = App::Application::Config()["AboutImage"]; + if (!about_path.empty() && about_image.isNull()) { + QString path = QString::fromUtf8(about_path.c_str()); + if (QDir(path).isRelative()) { + QString home = QString::fromStdString(App::Application::getHomePath()); + path = QFileInfo(QDir(home), path).absoluteFilePath(); + } + about_image.load(path); + + // Now try the icon paths + if (about_image.isNull()) { + about_image = Gui::BitmapFactory().pixmap(about_path.c_str()); + } + } + + return about_image; +} + +void AboutDialog::showOrHideImage(const QRect& rect) +{ + adjustSize(); + if (height() > rect.height()) { + ui->labelSplashPicture->hide(); + } +} + +void AboutDialog::setupLabels() +{ + // fonts are rendered smaller on Mac so point size can't be the same for all platforms + int fontSize = 8; +#ifdef Q_OS_MAC + fontSize = 11; +#endif + // avoid overriding user set style sheet + if (qApp->styleSheet().isEmpty()) { + setStyleSheet(QString::fromLatin1("Gui--Dialog--AboutDialog QLabel {font-size: %1pt;}") + .arg(fontSize)); + } + + QString exeName = qApp->applicationName(); + std::map& config = App::Application::Config(); + std::map::iterator it; + QString banner = QString::fromUtf8(config["CopyrightInfo"].c_str()); + banner = banner.left(banner.indexOf(QLatin1Char('\n'))); + QString major = QString::fromLatin1(config["BuildVersionMajor"].c_str()); + QString minor = QString::fromLatin1(config["BuildVersionMinor"].c_str()); + QString point = QString::fromLatin1(config["BuildVersionPoint"].c_str()); + QString suffix = QString::fromLatin1(config["BuildVersionSuffix"].c_str()); + QString build = QString::fromLatin1(config["BuildRevision"].c_str()); + QString disda = QString::fromLatin1(config["BuildRevisionDate"].c_str()); + QString mturl = QString::fromLatin1(config["MaintainerUrl"].c_str()); + + // we use replace() to keep label formatting, so a label with text "Unknown" + // gets replaced to "FreeCAD", for example + + QString author = ui->labelAuthor->text(); + author.replace(QString::fromLatin1("Unknown Application"), exeName); + author.replace(QString::fromLatin1("(c) Unknown Author"), banner); + ui->labelAuthor->setText(author); + ui->labelAuthor->setUrl(mturl); + + if (qApp->styleSheet().isEmpty()) { + ui->labelAuthor->setStyleSheet(QString::fromLatin1( + "Gui--UrlLabel {color: #0000FF;text-decoration: underline;font-weight: 600;}")); + } + + QString version = ui->labelBuildVersion->text(); + version.replace(QString::fromLatin1("Unknown"), + QString::fromLatin1("%1.%2.%3%4").arg(major, minor, point, suffix)); + ui->labelBuildVersion->setText(version); + + QString revision = ui->labelBuildRevision->text(); + revision.replace(QString::fromLatin1("Unknown"), build); + ui->labelBuildRevision->setText(revision); + + QString date = ui->labelBuildDate->text(); + date.replace(QString::fromLatin1("Unknown"), disda); + ui->labelBuildDate->setText(date); + + QString os = ui->labelBuildOS->text(); + os.replace(QString::fromLatin1("Unknown"), prettyProductInfoWrapper()); + ui->labelBuildOS->setText(os); + + QString architecture = ui->labelBuildRunArchitecture->text(); + if (QSysInfo::buildCpuArchitecture() == QSysInfo::currentCpuArchitecture()) { + architecture.replace(QString::fromLatin1("Unknown"), QSysInfo::buildCpuArchitecture()); + } + else { + architecture.replace( + QString::fromLatin1("Unknown"), + QString::fromLatin1("%1 (running on: %2)") + .arg(QSysInfo::buildCpuArchitecture(), QSysInfo::currentCpuArchitecture())); + } + ui->labelBuildRunArchitecture->setText(architecture); + + // branch name + it = config.find("BuildRevisionBranch"); + if (it != config.end()) { + QString branch = ui->labelBuildBranch->text(); + branch.replace(QString::fromLatin1("Unknown"), QString::fromUtf8(it->second.c_str())); + ui->labelBuildBranch->setText(branch); + } + else { + ui->labelBranch->hide(); + ui->labelBuildBranch->hide(); + } + + // hash id + it = config.find("BuildRevisionHash"); + if (it != config.end()) { + QString hash = ui->labelBuildHash->text(); + hash.replace( + QString::fromLatin1("Unknown"), + QString::fromLatin1(it->second.c_str()).left(7)); // Use the 7-char abbreviated hash + ui->labelBuildHash->setText(hash); + if (auto url_itr = config.find("BuildRepositoryURL"); url_itr != config.end()) { + auto url = QString::fromStdString(url_itr->second); + + if (int space = url.indexOf(QChar::fromLatin1(' ')); space != -1) { + url = url.left(space); // Strip off the branch information to get just the repo + } + + if (url == QString::fromUtf8("Unknown")) { + url = QString::fromUtf8("https://github.com/FreeCAD/FreeCAD"); // Just take a guess + } + + // This may only create valid URLs for Github, but some other hosts use the same format + // so give it a shot... + auto https = url.replace(QString::fromUtf8("git://"), QString::fromUtf8("https://")); + https.replace(QString::fromUtf8(".git"), QString::fromUtf8("")); + ui->labelBuildHash->setUrl(https + QString::fromUtf8("/commit/") + + QString::fromStdString(it->second)); + } + } + else { + ui->labelHash->hide(); + ui->labelBuildHash->hide(); + } +} + +void AboutDialog::showCredits() +{ + auto creditsFileURL = QLatin1String(":/doc/CONTRIBUTORS"); + QFile creditsFile(creditsFileURL); + + if (!creditsFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + return; + } + + auto tab_credits = new QWidget(); + tab_credits->setObjectName(QString::fromLatin1("tab_credits")); + ui->tabWidget->addTab(tab_credits, tr("Credits")); + auto hlayout = new QVBoxLayout(tab_credits); + auto textField = new QTextBrowser(tab_credits); + textField->setOpenExternalLinks(true); + hlayout->addWidget(textField); + + QString creditsHTML = QString::fromLatin1("

"); + //: Header for bgbsww + creditsHTML += + tr("This version of FreeCAD is dedicated to the memory of Brad McLean, aka bgbsww."); + //: Header for the Credits tab of the About screen + creditsHTML += QString::fromLatin1("

"); + creditsHTML += tr("Credits"); + creditsHTML += QString::fromLatin1("

"); + creditsHTML += tr("FreeCAD would not be possible without the contributions of"); + creditsHTML += QString::fromLatin1(":

"); + //: Header for the list of individual people in the Credits list. + creditsHTML += tr("Individuals"); + creditsHTML += QString::fromLatin1("

    "); + + QTextStream stream(&creditsFile); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + stream.setCodec("UTF-8"); +#endif + QString line; + while (stream.readLineInto(&line)) { + if (!line.isEmpty()) { + if (line == QString::fromLatin1("Firms")) { + creditsHTML += QString::fromLatin1("

"); + //: Header for the list of companies/organizations in the Credits list. + creditsHTML += tr("Organizations"); + creditsHTML += QString::fromLatin1("

    "); + } + else { + creditsHTML += QString::fromLatin1("
  • ") + line + QString::fromLatin1("
  • "); + } + } + } + creditsHTML += QString::fromLatin1("
"); + textField->setHtml(creditsHTML); +} + +void AboutDialog::showLicenseInformation() +{ + QString licenseFileURL = QString::fromLatin1("%1/LICENSE.html") + .arg(QString::fromUtf8(App::Application::getHelpDir().c_str())); + QFile licenseFile(licenseFileURL); + + if (licenseFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + QString licenseHTML = QString::fromUtf8(licenseFile.readAll()); + const auto placeholder = + QString::fromUtf8(""); + licenseHTML.replace(placeholder, getAdditionalLicenseInformation()); + + ui->tabWidget->removeTab(1); // Hide the license placeholder widget + + auto tab_license = new QWidget(); + tab_license->setObjectName(QString::fromLatin1("tab_license")); + ui->tabWidget->addTab(tab_license, tr("License")); + auto hlayout = new QVBoxLayout(tab_license); + auto textField = new QTextBrowser(tab_license); + textField->setOpenExternalLinks(true); + textField->setOpenLinks(true); + hlayout->addWidget(textField); + + textField->setHtml(licenseHTML); + } + else { + QString info(QLatin1String("SUCH DAMAGES.
")); + info += getAdditionalLicenseInformation(); + QString lictext = ui->textBrowserLicense->toHtml(); + lictext.replace(QString::fromLatin1("SUCH DAMAGES.
"), info); + ui->textBrowserLicense->setHtml(lictext); + } +} + +QString AboutDialog::getAdditionalLicenseInformation() const +{ + // Any additional piece of text to be added after the main license text goes below. + // Please set title in

tags, license text in

tags + // and add an


tag at the end to nicely separate license blocks + QString info; +#ifdef _USE_3DCONNEXION_SDK + info += QString::fromUtf8( + "

3D Mouse Support

" + "

Development tools and related technology provided under license from 3Dconnexion.
" + "Copyright © 1992–2012 3Dconnexion. All rights reserved.

" + "
"); +#endif + return info; +} + +void AboutDialog::showLibraryInformation() +{ + auto tab_library = new QWidget(); + tab_library->setObjectName(QString::fromLatin1("tab_library")); + ui->tabWidget->addTab(tab_library, tr("Libraries")); + auto hlayout = new QVBoxLayout(tab_library); + auto textField = new QTextBrowser(tab_library); + textField->setOpenExternalLinks(false); + textField->setOpenLinks(false); + hlayout->addWidget(textField); + + QString baseurl = QString::fromLatin1("file:///%1/ThirdPartyLibraries.html") + .arg(QString::fromUtf8(App::Application::getHelpDir().c_str())); + + textField->setSource(QUrl(baseurl)); +} + +void AboutDialog::showCollectionInformation() +{ + QString doc = QString::fromUtf8(App::Application::getHelpDir().c_str()); + QString path = doc + QLatin1String("Collection.html"); + if (!QFile::exists(path)) { + return; + } + + auto tab_collection = new QWidget(); + tab_collection->setObjectName(QString::fromLatin1("tab_collection")); + ui->tabWidget->addTab(tab_collection, tr("Collection")); + auto hlayout = new QVBoxLayout(tab_collection); + auto textField = new QTextBrowser(tab_collection); + textField->setOpenExternalLinks(true); + hlayout->addWidget(textField); + textField->setSource(path); +} + +void AboutDialog::showPrivacyPolicy() +{ + auto policyFileURL = QLatin1String(":/doc/PRIVACY_POLICY"); + QFile policyFile(policyFileURL); + + if (!policyFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + return; + } + auto text = QString::fromUtf8(policyFile.readAll()); + auto tabPrivacyPolicy = new QWidget(); + tabPrivacyPolicy->setObjectName(QString::fromLatin1("tabPrivacyPolicy")); + ui->tabWidget->addTab(tabPrivacyPolicy, tr("Privacy Policy")); + auto hLayout = new QVBoxLayout(tabPrivacyPolicy); + auto textField = new QTextBrowser(tabPrivacyPolicy); + textField->setOpenExternalLinks(true); + hLayout->addWidget(textField); + +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) + // We can't actually render the markdown, so just display it as text + textField->setText(text); +#else + textField->setMarkdown(text); +#endif +} + +void AboutDialog::linkActivated(const QUrl& link) +{ + auto licenseView = new LicenseView(); + licenseView->setAttribute(Qt::WA_DeleteOnClose); + licenseView->show(); + QString title = tr("License"); + QString fragment = link.fragment(); + if (fragment.startsWith(QLatin1String("_Toc"))) { + QString prefix = fragment.mid(4); + title = QString::fromLatin1("%1 %2").arg(prefix, title); + } + licenseView->setWindowTitle(title); + getMainWindow()->addWindow(licenseView); + licenseView->setSource(link); +} + +void AboutDialog::copyToClipboard() +{ + QString data; + QTextStream str(&data); + std::map& config = App::Application::Config(); + std::map::iterator it; + QString exe = QString::fromStdString(App::Application::getExecutableName()); + + QString major = QString::fromLatin1(config["BuildVersionMajor"].c_str()); + QString minor = QString::fromLatin1(config["BuildVersionMinor"].c_str()); + QString point = QString::fromLatin1(config["BuildVersionPoint"].c_str()); + QString suffix = QString::fromLatin1(config["BuildVersionSuffix"].c_str()); + QString build = QString::fromLatin1(config["BuildRevision"].c_str()); + + QString deskEnv = + QProcessEnvironment::systemEnvironment().value(QStringLiteral("XDG_CURRENT_DESKTOP"), + QString()); + QString deskSess = + QProcessEnvironment::systemEnvironment().value(QStringLiteral("DESKTOP_SESSION"), + QString()); + QStringList deskInfoList; + QString deskInfo; + + if (!deskEnv.isEmpty()) { + deskInfoList.append(deskEnv); + } + if (!deskSess.isEmpty()) { + deskInfoList.append(deskSess); + } + if (qGuiApp->platformName() != QLatin1String("windows") + && qGuiApp->platformName() != QLatin1String("cocoa")) { + deskInfoList.append(qGuiApp->platformName()); + } + if (!deskInfoList.isEmpty()) { + deskInfo = QLatin1String(" (") + deskInfoList.join(QLatin1String("/")) + QLatin1String(")"); + } + + str << "OS: " << prettyProductInfoWrapper() << deskInfo << '\n'; + if (QSysInfo::buildCpuArchitecture() == QSysInfo::currentCpuArchitecture()) { + str << "Architecture: " << QSysInfo::buildCpuArchitecture() << "\n"; + } + else { + str << "Architecture: " << QSysInfo::buildCpuArchitecture() + << "(running on: " << QSysInfo::currentCpuArchitecture() << ")\n"; + } + str << "Version: " << major << "." << minor << "." << point << suffix << "." << build; +#ifdef FC_CONDA + str << " Conda"; +#endif +#ifdef FC_FLATPAK + str << " Flatpak"; +#endif + char* appimage = getenv("APPIMAGE"); + if (appimage) { + str << " AppImage"; + } + char* snap = getenv("SNAP_REVISION"); + if (snap) { + str << " Snap " << snap; + } + str << '\n'; + +#if defined(_DEBUG) || defined(DEBUG) + str << "Build type: Debug\n"; +#elif defined(NDEBUG) + str << "Build type: Release\n"; +#elif defined(CMAKE_BUILD_TYPE) + str << "Build type: " << CMAKE_BUILD_TYPE << '\n'; +#else + str << "Build type: Unknown\n"; +#endif + it = config.find("BuildRevisionBranch"); + if (it != config.end()) { + str << "Branch: " << QString::fromUtf8(it->second.c_str()) << '\n'; + } + it = config.find("BuildRevisionHash"); + if (it != config.end()) { + str << "Hash: " << it->second.c_str() << '\n'; + } + // report also the version numbers of the most important libraries in FreeCAD + str << "Python " << PY_VERSION << ", "; + str << "Qt " << QT_VERSION_STR << ", "; + str << "Coin " << COIN_VERSION << ", "; + str << "Vtk " << fcVtkVersion << ", "; +#if defined(HAVE_OCC_VERSION) + str << "OCC " << OCC_VERSION_MAJOR << "." << OCC_VERSION_MINOR << "." << OCC_VERSION_MAINTENANCE +#ifdef OCC_VERSION_DEVELOPMENT + << "." OCC_VERSION_DEVELOPMENT +#endif + << '\n'; +#endif + QLocale loc; + str << "Locale: " << QLocale::languageToString(loc.language()) << "/" +#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0) + << QLocale::countryToString(loc.country()) +#else + << QLocale::territoryToString(loc.territory()) +#endif + << " (" << loc.name() << ")"; + if (loc != QLocale::system()) { + loc = QLocale::system(); + str << " [ OS: " << QLocale::languageToString(loc.language()) << "/" +#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0) + << QLocale::countryToString(loc.country()) +#else + << QLocale::territoryToString(loc.territory()) +#endif + << " (" << loc.name() << ") ]"; + } + str << "\n"; + + // Add Stylesheet/Theme/Qtstyle information + std::string styleSheet = + App::GetApplication() + .GetParameterGroupByPath("User parameter:BaseApp/Preferences/MainWindow") + ->GetASCII("StyleSheet"); + std::string theme = + App::GetApplication() + .GetParameterGroupByPath("User parameter:BaseApp/Preferences/MainWindow") + ->GetASCII("Theme"); +#if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0) + std::string style = qApp->style()->name().toStdString(); +#else + std::string style = + App::GetApplication() + .GetParameterGroupByPath("User parameter:BaseApp/Preferences/MainWindow") + ->GetASCII("QtStyle"); + if (style.empty()) { + style = "Qt default"; + } +#endif + if (styleSheet.empty()) { + styleSheet = "unset"; + } + if (theme.empty()) { + theme = "unset"; + } + + str << "Stylesheet/Theme/QtStyle: " << QString::fromStdString(styleSheet) << "/" + << QString::fromStdString(theme) << "/" << QString::fromStdString(style) << "\n"; + + // Add installed module information: + auto modDir = fs::path(App::Application::getUserAppDataDir()) / "Mod"; + bool firstMod = true; + if (fs::exists(modDir) && fs::is_directory(modDir)) { + for (const auto& mod : fs::directory_iterator(modDir)) { + auto dirName = mod.path().filename().string(); + if (dirName[0] == '.') { // Ignore dot directories + continue; + } + if (firstMod) { + firstMod = false; + str << "Installed mods: \n"; + } + str << " * " << QString::fromStdString(mod.path().filename().string()); + auto metadataFile = mod.path() / "package.xml"; + if (fs::exists(metadataFile)) { + App::Metadata metadata(metadataFile); + if (metadata.version() != App::Meta::Version()) { + str << QLatin1String(" ") + QString::fromStdString(metadata.version().str()); + } + } + auto disablingFile = mod.path() / "ADDON_DISABLED"; + if (fs::exists(disablingFile)) { + str << " (Disabled)"; + } + + str << "\n"; + } + } + + QClipboard* cb = QApplication::clipboard(); + cb->setText(data); +} + +// ---------------------------------------------------------------------------- + +/* TRANSLATOR Gui::LicenseView */ + +LicenseView::LicenseView(QWidget* parent) + : MDIView(nullptr, parent, Qt::WindowFlags()) +{ + browser = new QTextBrowser(this); + browser->setOpenExternalLinks(true); + browser->setOpenLinks(true); + setCentralWidget(browser); +} + +LicenseView::~LicenseView() = default; + +void LicenseView::setSource(const QUrl& url) +{ + browser->setSource(url); +} + +#include "moc_DlgAbout.cpp" diff --git a/src/Gui/Splashscreen.h b/src/Gui/DlgAbout.h similarity index 69% rename from src/Gui/Splashscreen.h rename to src/Gui/DlgAbout.h index d423236fdd..6908238dee 100644 --- a/src/Gui/Splashscreen.h +++ b/src/Gui/DlgAbout.h @@ -1,125 +1,104 @@ -/*************************************************************************** - * Copyright (c) 2004 Werner Mayer * - * * - * This file is part of the FreeCAD CAx development system. * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Library General Public * - * License as published by the Free Software Foundation; either * - * version 2 of the License, or (at your option) any later version. * - * * - * This library is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU Library General Public License for more details. * - * * - * You should have received a copy of the GNU Library General Public * - * License along with this library; see the file COPYING.LIB. If not, * - * write to the Free Software Foundation, Inc., 59 Temple Place, * - * Suite 330, Boston, MA 02111-1307, USA * - * * - ***************************************************************************/ - -#ifndef GUI_SPLASHSCREEN_H -#define GUI_SPLASHSCREEN_H - -#include -#include -#include -#include - -namespace Gui { - -class SplashObserver; - -/** This widget provides a splash screen that can be shown during application startup. - * - * \author Werner Mayer - */ -class SplashScreen : public QSplashScreen -{ - Q_OBJECT - -public: - explicit SplashScreen( const QPixmap & pixmap = QPixmap ( ), Qt::WindowFlags f = Qt::WindowFlags() ); - ~SplashScreen() override; - - void setShowMessages(bool on); - -protected: - void drawContents ( QPainter * painter ) override; - -private: - SplashObserver* messages; -}; - -namespace Dialog { -class Ui_AboutApplication; - -class GuiExport AboutDialogFactory -{ -public: - AboutDialogFactory() = default; - virtual ~AboutDialogFactory(); - - virtual QDialog *create(QWidget *parent) const; - - static const AboutDialogFactory *defaultFactory(); - static void setDefaultFactory(AboutDialogFactory *factory); - -private: - static AboutDialogFactory* factory; -}; - -class GuiExport LicenseView : public Gui::MDIView -{ - Q_OBJECT - -public: - explicit LicenseView(QWidget* parent=nullptr); - ~LicenseView() override; - - void setSource(const QUrl & url); - const char *getName() const override { - return "LicenseView"; - } - -private: - QTextBrowser* browser; -}; - -/** This widget provides the "About dialog" of an application. - * This shows the current version, the build number and date. - * \author Werner Mayer - */ -class GuiExport AboutDialog : public QDialog -{ - Q_OBJECT - -public: - explicit AboutDialog(QWidget* parent = nullptr); - ~AboutDialog() override; - -protected: - void setupLabels(); - void showCredits(); - void showLicenseInformation(); - QString getAdditionalLicenseInformation() const; - void showLibraryInformation(); - void showCollectionInformation(); - void showPrivacyPolicy(); - void showOrHideImage(const QRect& rect); - -protected: - virtual void copyToClipboard(); - void linkActivated(const QUrl& link); - -private: - Ui_AboutApplication* ui; -}; - -} // namespace Dialog -} // namespace Gui - - -#endif // GUI_SPLASHSCREEN_H +/*************************************************************************** + * Copyright (c) 2004 Werner Mayer * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#ifndef GUI_DLG_ABOUT_H +#define GUI_DLG_ABOUT_H + +#include +#include +#include + +namespace Gui +{ +namespace Dialog +{ + +class Ui_AboutApplication; + +class GuiExport AboutDialogFactory +{ +public: + AboutDialogFactory() = default; + virtual ~AboutDialogFactory(); + + virtual QDialog* create(QWidget* parent) const; + + static const AboutDialogFactory* defaultFactory(); + static void setDefaultFactory(AboutDialogFactory* factory); + +private: + static AboutDialogFactory* factory; +}; + +class GuiExport LicenseView: public Gui::MDIView +{ + Q_OBJECT + +public: + explicit LicenseView(QWidget* parent = nullptr); + ~LicenseView() override; + + void setSource(const QUrl& url); + const char* getName() const override + { + return "LicenseView"; + } + +private: + QTextBrowser* browser; +}; + +/** This widget provides the "About dialog" of an application. + * This shows the current version, the build number and date. + * \author Werner Mayer + */ +class GuiExport AboutDialog: public QDialog +{ + Q_OBJECT + +public: + explicit AboutDialog(QWidget* parent = nullptr); + ~AboutDialog() override; + +protected: + void setupLabels(); + void showCredits(); + void showLicenseInformation(); + QString getAdditionalLicenseInformation() const; + void showLibraryInformation(); + void showCollectionInformation(); + void showPrivacyPolicy(); + void showOrHideImage(const QRect& rect); + +protected: + QPixmap aboutImage() const; + virtual void copyToClipboard(); + void linkActivated(const QUrl& link); + +private: + Ui_AboutApplication* ui; +}; + +} // namespace Dialog +} // namespace Gui + +#endif // GUI_DLG_ABOUT_H diff --git a/src/Gui/MainWindow.cpp b/src/Gui/MainWindow.cpp index b776bb3c83..6862089d02 100644 --- a/src/Gui/MainWindow.cpp +++ b/src/Gui/MainWindow.cpp @@ -100,7 +100,7 @@ #include "PythonConsole.h" #include "ReportView.h" #include "SelectionView.h" -#include "Splashscreen.h" +#include "SplashScreen.h" #include "ToolBarManager.h" #include "ToolBoxManager.h" #include "Tree.h" @@ -115,7 +115,6 @@ #include "View3DInventor.h" #include "View3DInventorViewer.h" #include "DlgObjectSelection.h" -#include "Tools.h" #include FC_LOG_LEVEL_INIT("MainWindow",false,true,true) @@ -1936,7 +1935,7 @@ void MainWindow::startSplasher() GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("General"); // first search for an external image file if (hGrp->GetBool("ShowSplasher", true)) { - d->splashscreen = new SplashScreen(this->splashImage()); + d->splashscreen = new SplashScreen(SplashScreen::splashImage()); if (!hGrp->GetBool("ShowSplasherMessages", false)) { d->splashscreen->setShowMessages(false); @@ -1959,220 +1958,6 @@ void MainWindow::stopSplasher() } } -QPixmap MainWindow::aboutImage() const -{ - // See if we have a custom About screen image set - QPixmap about_image; - QFileInfo fi(QString::fromLatin1("images:about_image.png")); - if (fi.isFile() && fi.exists()) - about_image.load(fi.filePath(), "PNG"); - - std::string about_path = App::Application::Config()["AboutImage"]; - if (!about_path.empty() && about_image.isNull()) { - QString path = QString::fromUtf8(about_path.c_str()); - if (QDir(path).isRelative()) { - QString home = QString::fromStdString(App::Application::getHomePath()); - path = QFileInfo(QDir(home), path).absoluteFilePath(); - } - about_image.load(path); - - // Now try the icon paths - if (about_image.isNull()) { - about_image = Gui::BitmapFactory().pixmap(about_path.c_str()); - } - } - - return about_image; -} - -/** - * Displays a warning about this being a developer build. Designed for display in the Splashscreen. - * \param painter The painter to draw the warning into - * \param startPosition The painter-space coordinates to start the warning box at. - * \param maxSize The maximum extents for the box that is drawn. If the text exceeds this size it - * will be scaled down to fit. - * \note The text string is translatable, so its length is somewhat unpredictable. It is always - * displayed as two lines, regardless of the length of the text (e.g. no wrapping is done). Only the - * width is considered, the height simply follows from the font size. - */ -void MainWindow::renderDevBuildWarning( - QPainter &painter, - const QPoint startPosition, - const QSize maxSize, - QColor color) -{ - // Create a background box that fades out the artwork for better legibility - QColor fader (Qt::white); - constexpr float halfDensity (0.65F); - fader.setAlphaF(halfDensity); - QBrush fillBrush(fader, Qt::BrushStyle::SolidPattern); - painter.setBrush(fillBrush); - - // Construct the lines of text and figure out how much space they need - const auto devWarningLine1 = tr("WARNING: This is a development version."); - const auto devWarningLine2 = tr("Please do not use it in a production environment."); - QFontMetrics fontMetrics(painter.font()); // Try to use the existing font - int padding = QtTools::horizontalAdvance(fontMetrics, QLatin1String("M")); // Arbitrary - int line1Width = QtTools::horizontalAdvance(fontMetrics, devWarningLine1); - int line2Width = QtTools::horizontalAdvance(fontMetrics, devWarningLine2); - int boxWidth = std::max(line1Width,line2Width) + 2 * padding; - int lineHeight = fontMetrics.lineSpacing(); - if (boxWidth > maxSize.width()) { - // Especially if the text was translated, there is a chance that using the existing font - // will exceed the width of the Splashscreen graphic. Resize down so that it fits, no matter - // how long the text strings are. - float reductionFactor = static_cast(maxSize.width()) / static_cast(boxWidth); - int newFontSize = static_cast(painter.font().pointSize() * reductionFactor); - padding *= reductionFactor; - QFont newFont = painter.font(); - newFont.setPointSize(newFontSize); - painter.setFont(newFont); - lineHeight = painter.fontMetrics().lineSpacing(); - boxWidth = maxSize.width(); - } - constexpr float lineExpansionFactor(2.3F); - int boxHeight = static_cast(lineHeight*lineExpansionFactor); - - // Draw the background rectangle and the text - painter.setPen(color); - painter.drawRect(startPosition.x(), startPosition.y(), boxWidth, boxHeight); - painter.drawText(startPosition.x()+padding, startPosition.y()+lineHeight, devWarningLine1); - painter.drawText(startPosition.x()+padding, startPosition.y()+2*lineHeight, devWarningLine2); -} - -QPixmap MainWindow::splashImage() const -{ - // search in the UserAppData dir as very first - QPixmap splash_image; - QFileInfo fi(QString::fromLatin1("images:splash_image.png")); - if (fi.isFile() && fi.exists()) - splash_image.load(fi.filePath(), "PNG"); - - // if no image was found try the config - std::string splash_path = App::Application::Config()["SplashScreen"]; - if (splash_image.isNull()) { - QString path = QString::fromUtf8(splash_path.c_str()); - if (QDir(path).isRelative()) { - QString home = QString::fromStdString(App::Application::getHomePath()); - path = QFileInfo(QDir(home), path).absoluteFilePath(); - } - - splash_image.load(path); - } - - // now try the icon paths - float pixelRatio (1.0); - if (splash_image.isNull()) { - // determine the count of splashes - QStringList pixmaps = Gui::BitmapFactory().findIconFiles().filter(QString::fromStdString(splash_path)); - // divide by 2 since there's two sets (normal and 2x) - // minus 1 to ignore the default splash that isn't numbered - int splash_count = pixmaps.count()/2 - 1; - - // set a random splash path - if (splash_count > 0) { - int random = rand() % splash_count; - splash_path += std::to_string(random); - } - if (qApp->devicePixelRatio() > 1.0) { - // For HiDPI screens, we have a double-resolution version of the splash image - splash_path += "_2x"; - splash_image = Gui::BitmapFactory().pixmap(splash_path.c_str()); - splash_image.setDevicePixelRatio(2.0); - pixelRatio = 2.0; - } - else { - splash_image = Gui::BitmapFactory().pixmap(splash_path.c_str()); - } - } - - // include application name and version number - std::map::const_iterator tc = App::Application::Config().find("SplashInfoColor"); - std::map::const_iterator wc = App::Application::Config().find("SplashWarningColor"); - if (tc != App::Application::Config().end() && wc != App::Application::Config().end()) { - QString title = qApp->applicationName(); - QString major = QString::fromLatin1(App::Application::Config()["BuildVersionMajor"].c_str()); - QString minor = QString::fromLatin1(App::Application::Config()["BuildVersionMinor"].c_str()); - QString point = QString::fromLatin1(App::Application::Config()["BuildVersionPoint"].c_str()); - QString suffix = QString::fromLatin1(App::Application::Config()["BuildVersionSuffix"].c_str()); - QString version = QString::fromLatin1("%1.%2.%3%4").arg(major, minor, point, suffix); - QString position, fontFamily; - - std::map::const_iterator te = App::Application::Config().find("SplashInfoExeName"); - std::map::const_iterator tv = App::Application::Config().find("SplashInfoVersion"); - std::map::const_iterator tp = App::Application::Config().find("SplashInfoPosition"); - std::map::const_iterator tf = App::Application::Config().find("SplashInfoFont"); - if (te != App::Application::Config().end()) - title = QString::fromUtf8(te->second.c_str()); - if (tv != App::Application::Config().end()) - version = QString::fromUtf8(tv->second.c_str()); - if (tp != App::Application::Config().end()) - position = QString::fromUtf8(tp->second.c_str()); - if (tf != App::Application::Config().end()) - fontFamily = QString::fromUtf8(tf->second.c_str()); - - QPainter painter; - painter.begin(&splash_image); - if (!fontFamily.isEmpty()) { - QFont font = painter.font(); - if (font.fromString(fontFamily)) - painter.setFont(font); - } - - QFont fontExe = painter.font(); - fontExe.setPointSizeF(20.0); - QFontMetrics metricExe(fontExe); - int l = QtTools::horizontalAdvance(metricExe, title); - if (title == QLatin1String("FreeCAD")) { - l = 0.0; // "FreeCAD" text is already part of the splashscreen, version goes below it - } - int w = splash_image.width(); - int h = splash_image.height(); - - QFont fontVer = painter.font(); - fontVer.setPointSizeF(11.0); - QFontMetrics metricVer(fontVer); - int v = QtTools::horizontalAdvance(metricVer, version); - - int x = -1, y = -1; - QRegularExpression rx(QLatin1String("(\\d+).(\\d+)")); - auto match = rx.match(position); - if (match.hasMatch()) { - x = match.captured(1).toInt(); - y = match.captured(2).toInt(); - } - else { - x = w - (l + v + 10); - y = h - 20; - } - - QColor color(QString::fromLatin1(tc->second.c_str())); - if (color.isValid()) { - painter.setPen(color); - painter.setFont(fontExe); - if (title != QLatin1String("FreeCAD")) { - // FreeCAD's Splashscreen already contains the EXE name, no need to draw it - painter.drawText(x, y, title); - } - painter.setFont(fontVer); - painter.drawText(x + (l + 235), y - 7, version); - QColor warningColor(QString::fromLatin1(wc->second.c_str())); - if (suffix == QLatin1String("dev") && warningColor.isValid()) { - fontVer.setPointSizeF(14.0); - painter.setFont(fontVer); - const int lineHeight = metricVer.lineSpacing(); - const int padding {45}; // Distance from the edge of the graphic's bounding box - QPoint startPosition(padding, y + lineHeight*2); - QSize maxSize(w/pixelRatio - 2*padding, lineHeight * 3); - MainWindow::renderDevBuildWarning(painter, startPosition, maxSize, warningColor); - } - painter.end(); - } - } - - return splash_image; -} - /** * Drops the event \a e and tries to open the files. */ diff --git a/src/Gui/MainWindow.h b/src/Gui/MainWindow.h index cf9103adda..c197430dff 100644 --- a/src/Gui/MainWindow.h +++ b/src/Gui/MainWindow.h @@ -142,10 +142,6 @@ public: void startSplasher(); /** Stops the splasher after startup. */ void stopSplasher(); - /* The image of the About dialog, it might be empty. */ - QPixmap aboutImage() const; - /* The image of the splash screen of the application. */ - QPixmap splashImage() const; /** Shows the online documentation. */ void showDocumentation(const QString& help); //@} diff --git a/src/Gui/SplashScreen.cpp b/src/Gui/SplashScreen.cpp new file mode 100644 index 0000000000..9924403f8e --- /dev/null +++ b/src/Gui/SplashScreen.cpp @@ -0,0 +1,408 @@ +/*************************************************************************** + * Copyright (c) 2004 Werner Mayer * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#include "PreCompiled.h" + +#ifndef _PreComp_ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include +#include +#include + +#include "BitmapFactory.h" +#include "SplashScreen.h" +#include "Tools.h" + +using namespace Gui; + +namespace Gui +{ + +/** Displays all messages at startup inside the splash screen. + * \author Werner Mayer + */ +class SplashObserver: public Base::ILogger +{ +public: + SplashObserver(const SplashObserver&) = delete; + SplashObserver(SplashObserver&&) = delete; + SplashObserver& operator=(const SplashObserver&) = delete; + SplashObserver& operator=(SplashObserver&&) = delete; + + explicit SplashObserver(QSplashScreen* splasher = nullptr) + : splash(splasher) + , alignment(Qt::AlignBottom | Qt::AlignLeft) + , textColor(Qt::black) + { + Base::Console().AttachObserver(this); + + // allow to customize text position and color + const std::map& cfg = App::Application::Config(); + auto al = cfg.find("SplashAlignment"); + if (al != cfg.end()) { + QString alt = QString::fromLatin1(al->second.c_str()); + int align = 0; + if (alt.startsWith(QLatin1String("VCenter"))) { + align = Qt::AlignVCenter; + } + else if (alt.startsWith(QLatin1String("Top"))) { + align = Qt::AlignTop; + } + else { + align = Qt::AlignBottom; + } + + if (alt.endsWith(QLatin1String("HCenter"))) { + align += Qt::AlignHCenter; + } + else if (alt.endsWith(QLatin1String("Right"))) { + align += Qt::AlignRight; + } + else { + align += Qt::AlignLeft; + } + + alignment = align; + } + + // choose text color + auto tc = cfg.find("SplashTextColor"); + if (tc != cfg.end()) { + QColor col(QString::fromStdString(tc->second)); + if (col.isValid()) { + textColor = col; + } + } + } + ~SplashObserver() override + { + Base::Console().DetachObserver(this); + } + const char* Name() override + { + return "SplashObserver"; + } + void SendLog(const std::string& notifiername, + const std::string& msg, + Base::LogStyle level, + Base::IntendedRecipient recipient, + Base::ContentType content) override + { + Q_UNUSED(notifiername) + Q_UNUSED(recipient) + Q_UNUSED(content) + +#ifdef FC_DEBUG + Q_UNUSED(level) + Log(msg); +#else + if (level == Base::LogStyle::Log) { + Log(msg); + } +#endif + } + void Log(const std::string& text) + { + QString msg(QString::fromStdString(text)); + QRegularExpression rx; + // ignore 'Init:' and 'Mod:' prefixes + rx.setPattern(QLatin1String("^\\s*(Init:|Mod:)\\s*")); + auto match = rx.match(msg); + if (match.hasMatch()) { + msg = msg.mid(match.capturedLength()); + } + else { + // ignore activation of commands + rx.setPattern(QLatin1String(R"(^\s*(\+App::|Create|CmdC:|CmdG:|Act:)\s*)")); + match = rx.match(msg); + if (match.hasMatch() && match.capturedStart() == 0) { + return; + } + } + + splash->showMessage(msg.replace(QLatin1String("\n"), QString()), alignment, textColor); + QMutex mutex; + QMutexLocker ml(&mutex); + QWaitCondition().wait(&mutex, 50); + } + +private: + QSplashScreen* splash; + int alignment; + QColor textColor; +}; + +/** + * Displays a warning about this being a developer build. Designed for display in the Splashscreen. + * \param painter The painter to draw the warning into + * \param startPosition The painter-space coordinates to start the warning box at. + * \param maxSize The maximum extents for the box that is drawn. If the text exceeds this size it + * will be scaled down to fit. + * \note The text string is translatable, so its length is somewhat unpredictable. It is always + * displayed as two lines, regardless of the length of the text (e.g. no wrapping is done). Only the + * width is considered, the height simply follows from the font size. + */ +static void renderDevBuildWarning(QPainter& painter, + const QPoint startPosition, + const QSize maxSize, + QColor color) +{ + // Create a background box that fades out the artwork for better legibility + QColor fader(Qt::white); + constexpr float halfDensity(0.65F); + fader.setAlphaF(halfDensity); + QBrush fillBrush(fader, Qt::BrushStyle::SolidPattern); + painter.setBrush(fillBrush); + + // Construct the lines of text and figure out how much space they need + const auto devWarningLine1 = QObject::tr("WARNING: This is a development version."); + const auto devWarningLine2 = QObject::tr("Please do not use it in a production environment."); + QFontMetrics fontMetrics(painter.font()); // Try to use the existing font + int padding = QtTools::horizontalAdvance(fontMetrics, QLatin1String("M")); // Arbitrary + int line1Width = QtTools::horizontalAdvance(fontMetrics, devWarningLine1); + int line2Width = QtTools::horizontalAdvance(fontMetrics, devWarningLine2); + int boxWidth = std::max(line1Width, line2Width) + 2 * padding; + int lineHeight = fontMetrics.lineSpacing(); + if (boxWidth > maxSize.width()) { + // Especially if the text was translated, there is a chance that using the existing font + // will exceed the width of the Splashscreen graphic. Resize down so that it fits, no matter + // how long the text strings are. + float reductionFactor = static_cast(maxSize.width()) / static_cast(boxWidth); + int newFontSize = static_cast(painter.font().pointSize() * reductionFactor); + padding *= reductionFactor; + QFont newFont = painter.font(); + newFont.setPointSize(newFontSize); + painter.setFont(newFont); + lineHeight = painter.fontMetrics().lineSpacing(); + boxWidth = maxSize.width(); + } + constexpr float lineExpansionFactor(2.3F); + int boxHeight = static_cast(lineHeight * lineExpansionFactor); + + // Draw the background rectangle and the text + painter.setPen(color); + painter.drawRect(startPosition.x(), startPosition.y(), boxWidth, boxHeight); + painter.drawText(startPosition.x() + padding, startPosition.y() + lineHeight, devWarningLine1); + painter.drawText(startPosition.x() + padding, + startPosition.y() + 2 * lineHeight, + devWarningLine2); +} + +} // namespace Gui + +// ------------------------------------------------------------------------------ + +/** + * Constructs a splash screen that will display the pixmap. + */ +SplashScreen::SplashScreen(const QPixmap& pixmap, Qt::WindowFlags f) + : QSplashScreen(pixmap, f) +{ + // write the messages to splasher + messages = new SplashObserver(this); +} + +/** Destruction. */ +SplashScreen::~SplashScreen() +{ + delete messages; +} + +/** + * Draws the contents of the splash screen using painter \a painter. The default + * implementation draws the message passed by message(). + */ +void SplashScreen::drawContents(QPainter* painter) +{ + QSplashScreen::drawContents(painter); +} + +void SplashScreen::setShowMessages(bool on) +{ + messages->bErr = on; + messages->bMsg = on; + messages->bLog = on; + messages->bWrn = on; +} + +QPixmap SplashScreen::splashImage() +{ + // search in the UserAppData dir as very first + QPixmap splash_image; + QFileInfo fi(QString::fromLatin1("images:splash_image.png")); + if (fi.isFile() && fi.exists()) { + splash_image.load(fi.filePath(), "PNG"); + } + + // if no image was found try the config + std::string splash_path = App::Application::Config()["SplashScreen"]; + if (splash_image.isNull()) { + QString path = QString::fromStdString(splash_path); + if (QDir(path).isRelative()) { + QString home = QString::fromStdString(App::Application::getHomePath()); + path = QFileInfo(QDir(home), path).absoluteFilePath(); + } + + splash_image.load(path); + } + + // now try the icon paths + float pixelRatio(1.0); + if (splash_image.isNull()) { + // determine the count of splashes + QStringList pixmaps = + Gui::BitmapFactory().findIconFiles().filter(QString::fromStdString(splash_path)); + // divide by 2 since there's two sets (normal and 2x) + // minus 1 to ignore the default splash that isn't numbered + int splash_count = pixmaps.count() / 2 - 1; + + // set a random splash path + if (splash_count > 0) { + int random = rand() % splash_count; + splash_path += std::to_string(random); + } + + if (qApp->devicePixelRatio() > 1.0) { + // For HiDPI screens, we have a double-resolution version of the splash image + splash_path += "_2x"; + splash_image = Gui::BitmapFactory().pixmap(splash_path.c_str()); + splash_image.setDevicePixelRatio(2.0); + pixelRatio = 2.0; + } + else { + splash_image = Gui::BitmapFactory().pixmap(splash_path.c_str()); + } + } + + // include application name and version number + std::map::const_iterator tc = + App::Application::Config().find("SplashInfoColor"); + std::map::const_iterator wc = + App::Application::Config().find("SplashWarningColor"); + if (tc != App::Application::Config().end() && wc != App::Application::Config().end()) { + QString title = qApp->applicationName(); + QString major = + QString::fromLatin1(App::Application::Config()["BuildVersionMajor"].c_str()); + QString minor = + QString::fromLatin1(App::Application::Config()["BuildVersionMinor"].c_str()); + QString point = + QString::fromLatin1(App::Application::Config()["BuildVersionPoint"].c_str()); + QString suffix = + QString::fromLatin1(App::Application::Config()["BuildVersionSuffix"].c_str()); + QString version = QString::fromLatin1("%1.%2.%3%4").arg(major, minor, point, suffix); + QString position, fontFamily; + + std::map::const_iterator te = + App::Application::Config().find("SplashInfoExeName"); + std::map::const_iterator tv = + App::Application::Config().find("SplashInfoVersion"); + std::map::const_iterator tp = + App::Application::Config().find("SplashInfoPosition"); + std::map::const_iterator tf = + App::Application::Config().find("SplashInfoFont"); + if (te != App::Application::Config().end()) { + title = QString::fromUtf8(te->second.c_str()); + } + if (tv != App::Application::Config().end()) { + version = QString::fromUtf8(tv->second.c_str()); + } + if (tp != App::Application::Config().end()) { + position = QString::fromUtf8(tp->second.c_str()); + } + if (tf != App::Application::Config().end()) { + fontFamily = QString::fromUtf8(tf->second.c_str()); + } + + QPainter painter; + painter.begin(&splash_image); + if (!fontFamily.isEmpty()) { + QFont font = painter.font(); + if (font.fromString(fontFamily)) { + painter.setFont(font); + } + } + + QFont fontExe = painter.font(); + fontExe.setPointSizeF(20.0); + QFontMetrics metricExe(fontExe); + int l = QtTools::horizontalAdvance(metricExe, title); + if (title == QLatin1String("FreeCAD")) { + l = 0.0; // "FreeCAD" text is already part of the splashscreen, version goes below it + } + int w = splash_image.width(); + int h = splash_image.height(); + + QFont fontVer = painter.font(); + fontVer.setPointSizeF(11.0); + QFontMetrics metricVer(fontVer); + int v = QtTools::horizontalAdvance(metricVer, version); + + int x = -1, y = -1; + QRegularExpression rx(QLatin1String("(\\d+).(\\d+)")); + auto match = rx.match(position); + if (match.hasMatch()) { + x = match.captured(1).toInt(); + y = match.captured(2).toInt(); + } + else { + x = w - (l + v + 10); + y = h - 20; + } + + QColor color(QString::fromStdString(tc->second)); + if (color.isValid()) { + painter.setPen(color); + painter.setFont(fontExe); + if (title != QLatin1String("FreeCAD")) { + // FreeCAD's Splashscreen already contains the EXE name, no need to draw it + painter.drawText(x, y, title); + } + painter.setFont(fontVer); + painter.drawText(x + (l + 235), y - 7, version); + QColor warningColor(QString::fromStdString(wc->second)); + if (suffix == QLatin1String("dev") && warningColor.isValid()) { + fontVer.setPointSizeF(14.0); + painter.setFont(fontVer); + const int lineHeight = metricVer.lineSpacing(); + const int padding {45}; // Distance from the edge of the graphic's bounding box + QPoint startPosition(padding, y + lineHeight * 2); + QSize maxSize(w / pixelRatio - 2 * padding, lineHeight * 3); + renderDevBuildWarning(painter, startPosition, maxSize, warningColor); + } + painter.end(); + } + } + + return splash_image; +} diff --git a/src/Gui/SplashScreen.h b/src/Gui/SplashScreen.h new file mode 100644 index 0000000000..4310abebbc --- /dev/null +++ b/src/Gui/SplashScreen.h @@ -0,0 +1,58 @@ +/*************************************************************************** + * Copyright (c) 2004 Werner Mayer * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#ifndef GUI_SPLASHSCREEN_H +#define GUI_SPLASHSCREEN_H + +#include + +namespace Gui +{ + +class SplashObserver; + +/** This widget provides a splash screen that can be shown during application startup. + * + * \author Werner Mayer + */ +class SplashScreen: public QSplashScreen +{ + Q_OBJECT + +public: + explicit SplashScreen(const QPixmap& pixmap = QPixmap(), Qt::WindowFlags f = Qt::WindowFlags()); + ~SplashScreen() override; + + void setShowMessages(bool on); + + static QPixmap splashImage(); + +protected: + void drawContents(QPainter* painter) override; + +private: + SplashObserver* messages; +}; + +} // namespace Gui + +#endif // GUI_SPLASHSCREEN_H