Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
7e4fdaba9This file is pinned to the selected release. Download the original to open it in your design tool.
/*
* LibrePCB - Professional EDA for everyone!
* Copyright (C) 2013 LibrePCB Developers, see AUTHORS.md for contributors.
* https://librepcb.org/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*******************************************************************************
* Includes
******************************************************************************/
#include <librepcb/core/application.h>
#include <librepcb/core/debug.h>
#include <librepcb/core/exceptions.h>
#include <librepcb/core/network/networkaccessmanager.h>
#include <librepcb/core/workspace/workspace.h>
#include <librepcb/core/workspace/workspacesettings.h>
#include <librepcb/editor/dialogs/directorylockhandlerdialog.h>
#include <librepcb/editor/editorcommandset.h>
#include <librepcb/editor/guiapplication.h>
#include <librepcb/editor/project/partinformationprovider.h>
#include <librepcb/editor/workspace/initializeworkspacewizard/initializeworkspacewizard.h>
#include <QtConcurrent>
#include <QtCore>
#include <QtWidgets>
/*******************************************************************************
* Namespace
******************************************************************************/
using namespace librepcb;
using namespace librepcb::editor;
/*******************************************************************************
* Function Prototypes
******************************************************************************/
static void setApplicationMetadata() noexcept;
static void configureApplicationSettings() noexcept;
static void writeLogHeader() noexcept;
static int runApplication() noexcept;
static bool isFileFormatStableOrAcceptUnstable() noexcept;
static int openWorkspace(FilePath& path);
/*******************************************************************************
* main()
******************************************************************************/
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
// Give the main thread a higher priority than most other threads as GUI
// rendering and event processing are important for a smooth user experience.
QThread::currentThread()->setPriority(QThread::HighPriority);
// Set the organization / application names must be done very early because
// some other classes will use these values (for example QSettings, Debug)!
setApplicationMetadata();
// Creates the Debug object which installs the message handler. This must be
// done as early as possible, but *after* setting application metadata
// (organization + name).
Debug::instance();
// Configure the application settings format and location used by QSettings
configureApplicationSettings();
// Write some information about the application instance to the log.
writeLogHeader();
// Perform global initialization tasks. This must be done before any widget is
// shown.
Application::loadBundledFonts();
Application::setTranslationLocale(QLocale::system());
// Clean up old temporary files since at least on Windows this is not done
// automatically. Let's do it in a thread to avoid delaying application start.
std::ignore = QtConcurrent::run(&Application::cleanTemporaryDirectory);
// This is to remove the ugly frames around widgets in all status bars...
// (from http://www.qtcentre.org/threads/1904)
app.setStyleSheet("QStatusBar::item { border: 0px solid black; }");
// Use Fusion style with custom palette to make the legacy Qt dialogs looking
// similar to the new Slint UI. Can be removed as soon as no Qt widgets are
// used anymore.
{
QPalette palette(QColor("#202020"), QColor("#2a2a2a"));
palette.setColor(QPalette::Window, "#2a2a2a");
palette.setColor(QPalette::WindowText, "#c4c4c4");
palette.setColor(QPalette::Base, "#353535");
palette.setColor(QPalette::AlternateBase, "#2e2e2e");
palette.setColor(QPalette::ToolTipBase, "#2e2e2e");
palette.setColor(QPalette::ToolTipText, "#dedede");
palette.setColor(QPalette::Text, "#c4c4c4");
palette.setColor(QPalette::PlaceholderText, "#959595");
palette.setColor(QPalette::Button, "#202020");
palette.setColor(QPalette::ButtonText, "#c4c4c4");
palette.setColor(QPalette::Link, "#29d682");
palette.setColor(QPalette::LinkVisited, "#29d682");
palette.setColor(QPalette::Highlight, "#29d682");
palette.setColor(QPalette::HighlightedText, "#161616");
palette.setColor(QPalette::Disabled, QPalette::Button, "#1a1a1a");
palette.setColor(QPalette::Disabled, QPalette::ButtonText, "#707070");
palette.setColor(QPalette::Disabled, QPalette::WindowText, "#707070");
palette.setColor(QPalette::Disabled, QPalette::Text, "#707070");
palette.setColor(QPalette::Disabled, QPalette::Light, "#707070");
app.setStyle("fusion");
app.setPalette(palette);
}
// Start network access manager thread with HTTP cache to avoid extensive
// requests (e.g. downloading library pictures each time opening the manager).
QScopedPointer<NetworkAccessManager> networkAccessManager(
new NetworkAccessManager(Application::getCacheDir().getPathTo("http")));
// Run the actual application
int retval = runApplication();
// Stop network access manager thread
networkAccessManager.reset();
qDebug().nospace() << "Exit application with code " << retval << ".";
return retval;
}
/*******************************************************************************
* setApplicationMetadata()
******************************************************************************/
static void setApplicationMetadata() noexcept {
QApplication::setOrganizationName("LibrePCB");
QApplication::setOrganizationDomain("librepcb.org");
QApplication::setApplicationName("LibrePCB");
QApplication::setApplicationVersion(Application::getVersion());
QApplication::setDesktopFileName("org.librepcb.LibrePCB");
}
/*******************************************************************************
* configureApplicationSettings()
******************************************************************************/
static void configureApplicationSettings() noexcept {
// Make sure the INI format is used for settings on all platforms because:
// - Consistent storage format on all platforms
// - Useful for functional testing (control settings by fixtures)
// - Windows Registry is a mess (hard to find, edit and track changes of our
// settings)
QSettings::setDefaultFormat(QSettings::IniFormat);
// Use different configuration directory if supplied by environment variable
// "LIBREPCB_CONFIG_DIR" (useful for functional testing)
QString customConfigDir = qgetenv("LIBREPCB_CONFIG_DIR");
if (!customConfigDir.isEmpty()) {
QSettings::setPath(QSettings::IniFormat, QSettings::UserScope,
customConfigDir);
}
}
/*******************************************************************************
* writeLogHeader()
******************************************************************************/
static void writeLogHeader() noexcept {
// write application name and version to log
qInfo().noquote() << QString("LibrePCB %1 (%2)")
.arg(Application::getVersion(),
Application::getGitRevision());
// write Qt version to log
qInfo().noquote() << QString("Qt version: %1 (compiled against %2)")
.arg(qVersion(), QT_VERSION_STR);
// write resources directory path to log
qInfo() << "Resources directory:"
<< Application::getResourcesDir().toNative();
// write application settings file to log (nice to know for users)
qInfo() << "Application settings:"
<< FilePath(QSettings().fileName()).toNative();
// write cache directory to log (nice to know for users)
qInfo() << "Cache directory:" << Application::getCacheDir().toNative();
}
/*******************************************************************************
* openWorkspace()
******************************************************************************/
static int runApplication() noexcept {
// For deployment testing purposes, exit the application now if the flag
// '--exit-after-startup' is passed. This shall be done *before* any user
// interaction (e.g. message box) to make it working headless.
const char* exitFlagName = "--exit-after-startup";
if (qApp->arguments().contains(exitFlagName)) {
qInfo().nospace() << "Exit requested by flag '" << exitFlagName << "'.";
return 0;
}
// If the file format is unstable (e.g. for nightly builds), ask to abort now.
// This warning *must* come that early to be really sure that no files are
// overwritten with unstable content!
if (!isFileFormatStableOrAcceptUnstable()) {
return 0;
}
// Get the path of the workspace to open. By default, open the recently used
// workspace stored in the user settings.
FilePath path = Workspace::getMostRecentlyUsedWorkspacePath();
qDebug() << "Recently used workspace:" << path.toNative();
// If the workspace path is specified by environment variable, use this one.
const char* wsEnvVarName = "LIBREPCB_WORKSPACE";
QString wsEnvStr = qgetenv(wsEnvVarName);
if (!wsEnvStr.isEmpty()) {
qInfo() << "Workspace path overridden by" << wsEnvVarName
<< "environment variable:" << wsEnvStr;
path.setPath(wsEnvStr);
}
// If creating or opening a workspace failed, allow to choose another
// workspace path until it succeeds or the user aborts.
while (true) {
try {
return openWorkspace(path); // can throw
} catch (const UserCanceled& e) {
return 0; // User canceled -> exit application.
} catch (const Exception& e) {
QMessageBox::critical(
nullptr, QApplication::translate("Workspace", "Error"),
QString(QApplication::translate(
"Workspace", "Could not open the workspace \"%1\":"))
.arg(path.toNative()) %
"\n\n" % e.getMsg());
path = FilePath(); // Make sure the workspace selector wizard is shown.
}
}
}
/*******************************************************************************
* isFileFormatStableOrAcceptUnstable()
******************************************************************************/
static bool isFileFormatStableOrAcceptUnstable() noexcept {
if (Application::isFileFormatStable() ||
(qgetenv("LIBREPCB_DISABLE_UNSTABLE_WARNING") == "1")) {
return true;
} else {
QMessageBox::StandardButton btn = QMessageBox::critical(
nullptr, QCoreApplication::translate("main", "Unstable file format!"),
QCoreApplication::translate(
"main",
"<p><b>ATTENTION: This application version is UNSTABLE!</b></p>"
"<p>Everything you do with this application can break your "
"workspace, libraries or projects! Saved files will not be "
"readable with stable releases of LibrePCB. It's highly "
"recommended to create a backup before proceeding. If you are "
"unsure, please download an official stable release instead.</p>"
"<p>For details, please take a look at LibrePCB's "
"<a href=\"%1\">versioning concept</a>.</p>"
"<p>Are you really sure to continue with the risk of breaking your "
"files?!</p>")
.arg("https://developers.librepcb.org/da/dbc/"
"doc_release_workflow.html"),
QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
return (btn == QMessageBox::Yes);
}
}
/*******************************************************************************
* openWorkspace()
******************************************************************************/
static int openWorkspace(FilePath& path) {
// Run initialize workspace wizard as many times as needed until a valid
// workspace is selected.
std::unique_ptr<InitializeWorkspaceWizard> wizard(
new InitializeWorkspaceWizard(false));
wizard->setWorkspacePath(path); // can throw
while (wizard->getNeedsToBeShown()) {
if (wizard->exec() != QDialog::Accepted) {
throw UserCanceled(__FILE__, __LINE__);
}
Workspace::setMostRecentlyUsedWorkspacePath(wizard->getWorkspacePath());
// Just to be on the safe side that the workspace is now *really* ready
// to open (created, upgraded, initialized, ...), check the status again
// before continue opening the workspace.
wizard->setWorkspacePath(wizard->getWorkspacePath()); // can throw
wizard->restart();
}
// Open the workspace (can throw). If it is locked, a dialog will show
// an error and possibly provides an option to override the lock.
Workspace ws(wizard->getWorkspacePath(), wizard->getDataDir(),
DirectoryLockHandlerDialog::createDirectoryLockCallback());
// We don't need the wizard anymore (and it disturbes the Funq tests).
const bool wsContainsNewerFileFormats =
wizard->getWorkspaceContainsNewerFileFormats();
wizard.reset();
// Now since workspace settings are loaded, switch to the locale defined
// there (until now, the system locale was used).
if (!ws.getSettings().applicationLocale.get().isEmpty()) {
QLocale locale(ws.getSettings().applicationLocale.get());
QLocale::setDefault(locale);
Application::setTranslationLocale(locale);
EditorCommandSet::instance().updateTranslations();
}
// Setup global parts information provider (with cache).
PartInformationProvider::instance().setCacheDir(Application::getCacheDir());
auto applyPartInformationProviderSettings = [&ws]() {
const auto ep = ws.getSettings().getApiEndpointForPartsInfo();
PartInformationProvider::instance().setApiEndpoint(ep ? ep->url : QUrl());
};
applyPartInformationProviderSettings();
QObject::connect(
&ws.getSettings().apiEndpoints, &WorkspaceSettingsItem::edited,
&ws.getSettings().apiEndpoints, applyPartInformationProviderSettings);
// Apply keyboard shortcuts from workspace settings globally.
auto applyKeyboardShortcuts = [&ws]() {
const auto& overrides = ws.getSettings().keyboardShortcuts.get();
EditorCommandSet& set = EditorCommandSet::instance();
foreach (EditorCommandCategory* category, set.getCategories()) {
foreach (EditorCommand* command, set.getCommands(category)) {
QList<QKeySequence> sequences =
overrides.contains(command->getIdentifier())
? overrides.value(command->getIdentifier())
: command->getDefaultKeySequences();
command->setKeySequences(sequences);
}
}
};
applyKeyboardShortcuts();
QObject::connect(&ws.getSettings().keyboardShortcuts,
&WorkspaceSettingsItem::edited,
&ws.getSettings().keyboardShortcuts, applyKeyboardShortcuts);
// Run the application.
GuiApplication app(ws, wsContainsNewerFileFormats);
app.exec();
return 0;
}