Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
c136b73e0This 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/workspace/controlpanel/controlpanel.h>
#include <librepcb/editor/workspace/initializeworkspacewizard/initializeworkspacewizard.h>
#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);
static int appExec() noexcept;
/*******************************************************************************
* main()
******************************************************************************/
int main(int argc, char* argv[]) {
Application app(argc, argv);
// 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();
// Install translation files. This must be done before any widget is shown.
app.setTranslationLocale(QLocale::system());
// 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; }");
// Start network access manager thread
QScopedPointer<NetworkAccessManager> networkAccessManager(
new NetworkAccessManager());
// 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 {
Application::setOrganizationName("LibrePCB");
Application::setOrganizationDomain("librepcb.org");
Application::setApplicationName("LibrePCB");
#if (QT_VERSION >= QT_VERSION_CHECK(5, 7, 0))
Application::setDesktopFileName("org.librepcb.LibrePCB");
#endif
}
/*******************************************************************************
* 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(qApp->applicationVersion(),
qApp->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:" << qApp->getResourcesDir().toNative();
// write application settings directory to log (nice to know for users)
qInfo() << "Application settings:"
<< FilePath(QSettings().fileName()).toNative();
}
/*******************************************************************************
* openWorkspace()
******************************************************************************/
static int runApplication() noexcept {
// 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, Application::translate("Workspace", "Error"),
QString(Application::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 (qApp->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) {
InitializeWorkspaceWizard wizard(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());
// 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);
qApp->setTranslationLocale(locale);
EditorCommandSet::instance().updateTranslations();
}
// 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);
// Open the control panel.
ControlPanel p(ws, wizard.getWorkspaceContainsNewerFileFormats());
QObject::connect(qApp, &Application::quitTriggered, &p, &ControlPanel::close);
p.show();
return appExec();
}
/*******************************************************************************
* appExec()
******************************************************************************/
static int appExec() noexcept {
// please note that we shouldn't show a dialog or message box in the catch()
// blocks! from http://qt-project.org/doc/qt-5/exceptionsafety.html:
// "After an exception is thrown, the connection to the windowing server
// might already be closed. It is not safe to call a GUI related function
// after catching an exception."
try {
return Application::exec();
} catch (std::exception& e) {
qFatal("UNCAUGHT EXCEPTION: %s --- PROGRAM EXITED", e.what());
} catch (...) {
qFatal("UNCAUGHT EXCEPTION --- PROGRAM EXITED");
}
return -1;
}