517 lines
18 KiB
C++
517 lines
18 KiB
C++
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|
|
|
/***************************************************************************
|
|
* Copyright (c) 2004 Werner Mayer <wmayer[at]users.sourceforge.net> *
|
|
* *
|
|
* 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 <cstdlib>
|
|
#include <QApplication>
|
|
#include <QClipboard>
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QLocale>
|
|
#include <QProcessEnvironment>
|
|
#include <QRegularExpression>
|
|
#include <QRegularExpressionMatch>
|
|
#include <QScreen>
|
|
#include <QSettings>
|
|
#include <QSysInfo>
|
|
#include <QTextBrowser>
|
|
#include <QTextStream>
|
|
#include <QTimer>
|
|
#include <Inventor/C/basic.h>
|
|
|
|
#include <App/Application.h>
|
|
#include <App/Metadata.h>
|
|
#include <Base/Console.h>
|
|
#include <Base/Interpreter.h>
|
|
#include <CXX/WrapPython.h>
|
|
#include <Gui/Application.h>
|
|
|
|
#include <filesystem>
|
|
#include <LibraryVersions.h>
|
|
#include <zlib.h>
|
|
|
|
#include "BitmapFactory.h"
|
|
#include "Dialogs/DlgAbout.h"
|
|
#include "MainWindow.h"
|
|
#include "SplashScreen.h"
|
|
#include "ui_AboutApplication.h"
|
|
|
|
using namespace Gui;
|
|
using namespace Gui::Dialog;
|
|
namespace fs = std::filesystem;
|
|
|
|
// ------------------------------------------------------------------------------
|
|
|
|
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<float>(image.width()) / static_cast<float>(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<int>(width / scale));
|
|
width = static_cast<int>(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(QStringLiteral("images:about_image.png"));
|
|
if (fi.isFile() && fi.exists()) {
|
|
about_image.load(fi.filePath(), "PNG");
|
|
}
|
|
|
|
const std::string about_path = App::Application::Config()["AboutImage"];
|
|
if (!about_path.empty() && about_image.isNull()) {
|
|
QString path = QString::fromStdString(about_path);
|
|
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(
|
|
QStringLiteral("Gui--Dialog--AboutDialog QLabel {font-size: %1pt;}").arg(fontSize)
|
|
);
|
|
}
|
|
|
|
QString exeName = qApp->applicationName();
|
|
std::map<std::string, std::string>& config = App::Application::Config();
|
|
std::map<std::string, std::string>::iterator it;
|
|
QString banner = QString::fromStdString(config["CopyrightInfo"]);
|
|
banner = banner.left(banner.indexOf(QLatin1Char('\n')));
|
|
QString major = QString::fromStdString(config["BuildVersionMajor"]);
|
|
QString minor = QString::fromStdString(config["BuildVersionMinor"]);
|
|
QString point = QString::fromStdString(config["BuildVersionPoint"]);
|
|
QString suffix = QString::fromStdString(config["BuildVersionSuffix"]);
|
|
QString build = QString::fromStdString(config["BuildRevision"]);
|
|
QString disda = QString::fromStdString(config["BuildRevisionDate"]);
|
|
QString mturl = QString::fromStdString(config["MaintainerUrl"]);
|
|
|
|
// we use replace() to keep label formatting, so a label with text "<b>Unknown</b>"
|
|
// gets replaced to "<b>FreeCAD</b>", for example
|
|
|
|
QString author = ui->labelAuthor->text();
|
|
author.replace(QStringLiteral("Unknown Application"), exeName);
|
|
author.replace(QStringLiteral("(c) Unknown Author"), banner);
|
|
ui->labelAuthor->setText(author);
|
|
ui->labelAuthor->setUrl(mturl);
|
|
|
|
if (qApp->styleSheet().isEmpty()) {
|
|
ui->labelAuthor->setStyleSheet(QStringLiteral(
|
|
"Gui--UrlLabel {color: #0000FF;text-decoration: underline;font-weight: 600;}"
|
|
));
|
|
}
|
|
|
|
QString version = ui->labelBuildVersion->text();
|
|
version.replace(
|
|
QStringLiteral("Unknown"),
|
|
QStringLiteral("%1.%2.%3%4").arg(major, minor, point, suffix)
|
|
);
|
|
ui->labelBuildVersion->setText(version);
|
|
|
|
QString revision = ui->labelBuildRevision->text();
|
|
revision.replace(QStringLiteral("Unknown"), build);
|
|
ui->labelBuildRevision->setText(revision);
|
|
|
|
QString date = ui->labelBuildDate->text();
|
|
date.replace(QStringLiteral("Unknown"), disda);
|
|
ui->labelBuildDate->setText(date);
|
|
|
|
QString os = ui->labelBuildOS->text();
|
|
os.replace(QStringLiteral("Unknown"), App::Application::prettyProductInfoWrapper());
|
|
ui->labelBuildOS->setText(os);
|
|
|
|
QString architecture = ui->labelBuildRunArchitecture->text();
|
|
if (QSysInfo::buildCpuArchitecture() == QSysInfo::currentCpuArchitecture()) {
|
|
architecture.replace(QStringLiteral("Unknown"), QSysInfo::buildCpuArchitecture());
|
|
}
|
|
else {
|
|
architecture.replace(
|
|
QStringLiteral("Unknown"),
|
|
QStringLiteral("%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(QStringLiteral("Unknown"), QString::fromStdString(it->second));
|
|
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(
|
|
QStringLiteral("Unknown"),
|
|
QString::fromStdString(it->second).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 == QStringLiteral("Unknown")) {
|
|
url = QStringLiteral("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(QStringLiteral("git://"), QStringLiteral("https://"));
|
|
https.replace(QStringLiteral(".git"), QStringLiteral(""));
|
|
ui->labelBuildHash->setUrl(
|
|
https + QStringLiteral("/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(QStringLiteral("tab_credits"));
|
|
ui->tabWidget->addTab(tab_credits, tr("Credits"));
|
|
auto hlayout = new QVBoxLayout(tab_credits);
|
|
auto textField = new QTextBrowser(tab_credits);
|
|
textField->setOpenLinks(false);
|
|
hlayout->addWidget(textField);
|
|
|
|
//: Header for the Credits tab of the About screen
|
|
QString creditsHTML
|
|
= QStringLiteral("<html><body><h1>%1</h1><p>%2</p><h2>%3</h2><ul>")
|
|
.arg(tr("Credits", "Header for the Credits tab of the About screen"))
|
|
.arg(tr("FreeCAD would not be possible without the contributions of:"))
|
|
.arg(tr("Individuals", "Header for the list of individual people in the Credits list."));
|
|
|
|
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 == QStringLiteral("Firms")) {
|
|
creditsHTML += QStringLiteral("</ul><h2>");
|
|
//: Header for the list of companies/organizations in the Credits list.
|
|
creditsHTML += tr("Organizations");
|
|
creditsHTML += QStringLiteral("</h2><ul>");
|
|
}
|
|
else {
|
|
creditsHTML += QStringLiteral("<li>") + line + QStringLiteral("</li>");
|
|
}
|
|
}
|
|
}
|
|
creditsHTML += QStringLiteral("</ul></body></html>");
|
|
textField->setHtml(creditsHTML);
|
|
}
|
|
|
|
void AboutDialog::showLicenseInformation()
|
|
{
|
|
QString licenseFileURL = QStringLiteral("%1/LICENSE.html")
|
|
.arg(QString::fromStdString(App::Application::getHelpDir()));
|
|
QFile licenseFile(licenseFileURL);
|
|
|
|
if (licenseFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
QString licenseHTML = QString::fromUtf8(licenseFile.readAll());
|
|
const auto placeholder = QStringLiteral(
|
|
"<!--PLACEHOLDER_FOR_ADDITIONAL_LICENSE_INFORMATION-->"
|
|
);
|
|
licenseHTML.replace(placeholder, getAdditionalLicenseInformation());
|
|
|
|
ui->tabWidget->removeTab(1); // Hide the license placeholder widget
|
|
|
|
auto tab_license = new QWidget();
|
|
tab_license->setObjectName(QStringLiteral("tab_license"));
|
|
ui->tabWidget->addTab(tab_license, tr("License"));
|
|
auto hlayout = new QVBoxLayout(tab_license);
|
|
auto textField = new QTextBrowser(tab_license);
|
|
textField->setOpenExternalLinks(true);
|
|
hlayout->addWidget(textField);
|
|
|
|
textField->setHtml(licenseHTML);
|
|
}
|
|
else {
|
|
QString info(QLatin1String("SUCH DAMAGES.<hr/>"));
|
|
info += getAdditionalLicenseInformation();
|
|
QString lictext = ui->textBrowserLicense->toHtml();
|
|
lictext.replace(QStringLiteral("SUCH DAMAGES.<hr/>"), 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 <h2> tags, license text in <p> tags
|
|
// and add an <hr/> tag at the end to nicely separate license blocks
|
|
QString info;
|
|
#ifdef _USE_3DCONNEXION_SDK
|
|
info += QStringLiteral(
|
|
"<h2>3D Mouse Support</h2>"
|
|
"<p>Development tools and related technology provided under license from 3Dconnexion.<br/>"
|
|
"Copyright © 1992–2012 3Dconnexion. All rights reserved.</p>"
|
|
"<hr/>"
|
|
);
|
|
#endif
|
|
return info;
|
|
}
|
|
|
|
void AboutDialog::showLibraryInformation()
|
|
{
|
|
auto tab_library = new QWidget();
|
|
tab_library->setObjectName(QStringLiteral("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 = QStringLiteral("file:///%1/ThirdPartyLibraries.html")
|
|
.arg(QString::fromStdString(App::Application::getHelpDir()));
|
|
|
|
textField->setSource(QUrl(baseurl));
|
|
}
|
|
|
|
void AboutDialog::showCollectionInformation()
|
|
{
|
|
QString doc = QString::fromStdString(App::Application::getHelpDir());
|
|
QString path = doc + QLatin1String("Collection.html");
|
|
if (!QFile::exists(path)) {
|
|
return;
|
|
}
|
|
|
|
auto tab_collection = new QWidget();
|
|
tab_collection->setObjectName(QStringLiteral("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(QStringLiteral("tabPrivacyPolicy"));
|
|
ui->tabWidget->addTab(tabPrivacyPolicy, tr("Privacy Policy"));
|
|
auto hLayout = new QVBoxLayout(tabPrivacyPolicy);
|
|
auto textField = new QTextBrowser(tabPrivacyPolicy);
|
|
textField->setOpenExternalLinks(true);
|
|
hLayout->addWidget(textField);
|
|
textField->setMarkdown(text);
|
|
}
|
|
|
|
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 = QStringLiteral("%1 %2").arg(prefix, title);
|
|
}
|
|
licenseView->setWindowTitle(title);
|
|
getMainWindow()->addWindow(licenseView);
|
|
licenseView->setSource(link);
|
|
}
|
|
|
|
void AboutDialog::copyToClipboard()
|
|
{
|
|
QString data;
|
|
QTextStream str(&data);
|
|
std::map<std::string, std::string>& config = App::Application::Config();
|
|
App::Application::getVerboseCommonInfo(str, config);
|
|
Gui::Application::getVerboseDPIStyleInfo(str);
|
|
App::Application::getVerboseAddOnsInfo(str, config);
|
|
|
|
QClipboard* cb = QApplication::clipboard();
|
|
cb->setText(data);
|
|
|
|
auto copytext = ui->copyButton->text();
|
|
ui->copyButton->setText(tr("Copied!"));
|
|
const int timeout = 2000;
|
|
QTimer::singleShot(timeout, this, [this, copytext]() { ui->copyButton->setText(copytext); });
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/* 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"
|