1 /*
2  * LibrePCB - Professional EDA for everyone!
3  * Copyright (C) 2013 LibrePCB Developers, see AUTHORS.md for contributors.
4  * https://librepcb.org/
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 /*******************************************************************************
21  *  Includes
22  ******************************************************************************/
23 #include "application.h"
24 
25 #include "build_env.h"
26 #include "dialogs/aboutdialog.h"
27 #include "fileio/transactionalfilesystem.h"
28 #include "font/strokefontpool.h"
29 #include "units/all_length_units.h"
30 
31 #include <QtCore>
32 
33 /*******************************************************************************
34  *  Namespace
35  ******************************************************************************/
36 namespace librepcb {
37 
38 /*******************************************************************************
39  *  Constructors / Destructor
40  ******************************************************************************/
41 
Application(int & argc,char ** argv)42 Application::Application(int& argc, char** argv) noexcept
43   : QApplication(argc, argv),
44     mAppVersion(
45         Version::fromString(QString(LIBREPCB_APP_VERSION).section('-', 0, 0))),
46     mAppVersionLabel(QString(LIBREPCB_APP_VERSION).section('-', 1, 1)),
47     mGitRevision(GIT_COMMIT_SHA),
48     mFileFormatVersion(Version::fromString(LIBREPCB_FILE_FORMAT_VERSION)),
49     mIsFileFormatStable(LIBREPCB_FILE_FORMAT_STABLE) {
50   // register meta types
51   qRegisterMetaType<FilePath>();
52   qRegisterMetaType<Point>();
53   qRegisterMetaType<Length>();
54   qRegisterMetaType<Angle>();
55 
56   // set application version
57   QApplication::setApplicationVersion(LIBREPCB_APP_VERSION);
58 
59   // set build timestamp
60   QDate buildDate =
61       QLocale(QLocale::C)
62           .toDate(QString(__DATE__).simplified(), QLatin1String("MMM d yyyy"));
63   QTime buildTime = QTime::fromString(__TIME__, Qt::TextDate);
64   mBuildDate = QDateTime(buildDate, buildTime);
65 
66   // check file format version
67   if (!mFileFormatVersion.isPrefixOf(mAppVersion)) {
68     qFatal(
69         "The file format version is not a prefix of the application version!");
70   }
71 
72   // get the directory of the currently running executable
73   FilePath executableFilePath(QApplication::applicationFilePath());
74   Q_ASSERT(executableFilePath.isValid());
75 
76   // determine the path to the resources directory (e.g. /usr/share/librepcb)
77 #if defined(LIBREPCB_BINARY_DIR) && defined(LIBREPCB_SHARE_SOURCE)
78   // TODO: The following code checks for paths related to the application
79   // binary, even though this code is located in the library source. This is a
80   // bit of a layer violation and should be refactored.
81   FilePath buildOutputDirPath(LIBREPCB_BINARY_DIR);
82   bool runningFromBuildOutput =
83       executableFilePath.isLocatedInDir(buildOutputDirPath);
84   if (runningFromBuildOutput) {
85     // The executable is located inside the build output directory, so we assume
86     // this is a developer build and thus we use the "share" directory from the
87     // repository root.
88     mResourcesDir = FilePath(LIBREPCB_SHARE_SOURCE).getPathTo("librepcb");
89   }
90 #endif
91   if (!mResourcesDir.isValid()) {
92     if (QDir::isAbsolutePath(LIBREPCB_SHARE)) {
93       mResourcesDir.setPath(LIBREPCB_SHARE);
94     } else {
95       mResourcesDir =
96           executableFilePath.getParentDir().getPathTo(LIBREPCB_SHARE);
97     }
98   }
99 
100   // warn if runtime resource files are not found
101   if (!getResourcesFilePath("README.md").isExistingFile()) {
102     qCritical()
103         << "Could not find resource files! Probably packaging went wrong?!";
104     qCritical() << "Expected resources location:" << mResourcesDir.toNative();
105     qCritical() << "Executable location:        "
106                 << executableFilePath.toNative();
107     qCritical() << "LIBREPCB_SHARE:             " << QString(LIBREPCB_SHARE);
108 #ifdef LIBREPCB_BINARY_DIR
109     qCritical() << "LIBREPCB_BINARY_DIR:        "
110                 << QString(LIBREPCB_BINARY_DIR);
111 #endif
112 #ifdef LIBREPCB_SHARE_SOURCE
113     qCritical() << "LIBREPCB_SHARE_SOURCE:      "
114                 << QString(LIBREPCB_SHARE_SOURCE);
115 #endif
116   }
117 
118   // load all bundled TrueType/OpenType fonts
119   QDir fontsDir(getResourcesFilePath("fonts").toStr());
120   fontsDir.setFilter(QDir::Files);
121   fontsDir.setNameFilters({"*.ttf", "*.otf"});
122   foreach (const QFileInfo& info, fontsDir.entryInfoList()) {
123     QString fp = info.absoluteFilePath();
124     int id = QFontDatabase::addApplicationFont(fp);
125     if (id < 0) {
126       qCritical() << "Failed to load font" << fp;
127     }
128   }
129 
130   // set default sans serif font
131   mSansSerifFont.setStyleStrategy(
132       QFont::StyleStrategy(QFont::OpenGLCompatible | QFont::PreferQuality));
133   mSansSerifFont.setStyleHint(QFont::SansSerif);
134   mSansSerifFont.setFamily("Noto Sans");
135 
136   // set default monospace font
137   mMonospaceFont.setStyleStrategy(
138       QFont::StyleStrategy(QFont::OpenGLCompatible | QFont::PreferQuality));
139   mMonospaceFont.setStyleHint(QFont::TypeWriter);
140   mMonospaceFont.setFamily("Noto Sans Mono");
141 
142   // load all stroke fonts
143   TransactionalFileSystem strokeFontsDir(
144       getResourcesFilePath("fontobene"), false,
145       &TransactionalFileSystem::RestoreMode::no);
146   mStrokeFontPool.reset(new StrokeFontPool(strokeFontsDir));
147   getDefaultStrokeFont();  // ensure that the default font is available (aborts
148                            // if not)
149 }
150 
~Application()151 Application::~Application() noexcept {
152   // Not sure if needed, but let's unregister translators before destroying
153   // (maybe otherwise QCoreApplication has dangling pointers to translators).
154   removeAllTranslators();
155 }
156 
157 /*******************************************************************************
158  *  Getters
159  ******************************************************************************/
160 
getResourcesFilePath(const QString & filepath) const161 FilePath Application::getResourcesFilePath(const QString& filepath) const
162     noexcept {
163   return mResourcesDir.getPathTo(filepath);
164 }
165 
getAvailableTranslationLocales() const166 QStringList Application::getAvailableTranslationLocales() const noexcept {
167   QStringList locales;
168   QDir dir(getResourcesFilePath("i18n").toStr());
169   foreach (QString filename, dir.entryList({"*.qm"}, QDir::Files, QDir::Name)) {
170     filename.remove("librepcb_");
171     filename.remove(".qm");
172     locales.append(filename);
173   }
174   return locales;
175 }
176 
getDefaultStrokeFont() const177 const StrokeFont& Application::getDefaultStrokeFont() const noexcept {
178   try {
179     return mStrokeFontPool->getFont(getDefaultStrokeFontName());
180   } catch (const Exception& e) {
181     qFatal("Default stroke font could not be loaded!");  // aborts the
182                                                          // application!!!
183   }
184 }
185 
186 /*******************************************************************************
187  *  Setters
188  ******************************************************************************/
189 
setTranslationLocale(const QLocale & locale)190 void Application::setTranslationLocale(const QLocale& locale) noexcept {
191   // First, remove all currently installed translations to avoid falling back to
192   // wrong languages. The fallback language must always be en_US, i.e.
193   // untranslated strings. See https://github.com/LibrePCB/LibrePCB/issues/611
194   removeAllTranslators();
195 
196   // Install Qt translations
197   auto qtTranslator = std::make_shared<QTranslator>(this);
198   qtTranslator->load("qt_" % locale.name(),
199                      QLibraryInfo::location(QLibraryInfo::TranslationsPath));
200   installTranslator(qtTranslator.get());
201   mTranslators.append(qtTranslator);
202 
203   // Install system language translations (all system languages defined in the
204   // system settings, in the defined order)
205   const QString dir = getResourcesFilePath("i18n").toStr();
206   auto systemTranslator = std::make_shared<QTranslator>(this);
207   systemTranslator->load(locale, "librepcb", "_", dir);
208   installTranslator(systemTranslator.get());
209   mTranslators.append(systemTranslator);
210 
211   // Install language translations (like "de" for German)
212   auto appTranslator1 = std::make_shared<QTranslator>(this);
213   appTranslator1->load("librepcb_" % locale.name().split("_").at(0), dir);
214   installTranslator(appTranslator1.get());
215   mTranslators.append(appTranslator1);
216 
217   // Install language/country translations (like "de_ch" for German/Switzerland)
218   auto appTranslator2 = std::make_shared<QTranslator>(this);
219   appTranslator2->load("librepcb_" % locale.name(), dir);
220   installTranslator(appTranslator2.get());
221   mTranslators.append(appTranslator2);
222 }
223 
224 /*******************************************************************************
225  *  Reimplemented from QApplication
226  ******************************************************************************/
227 
notify(QObject * receiver,QEvent * e)228 bool Application::notify(QObject* receiver, QEvent* e) {
229   try {
230     return QApplication::notify(receiver, e);
231   } catch (...) {
232     qCritical() << "Exception caught in Application::notify()!";
233   }
234   return false;
235 }
236 
237 /*******************************************************************************
238  *  Static Methods
239  ******************************************************************************/
240 
instance()241 Application* Application::instance() noexcept {
242   Application* app = dynamic_cast<Application*>(QCoreApplication::instance());
243   Q_ASSERT(app);
244   return app;
245 }
246 
247 /*******************************************************************************
248  *  Slots
249  ******************************************************************************/
250 
about()251 void Application::about() noexcept {
252   QWidget* parent = QApplication::activeWindow();
253   AboutDialog aboutDialog(parent);
254   aboutDialog.exec();
255 }
256 
257 /*******************************************************************************
258  *  Private Methods
259  ******************************************************************************/
260 
removeAllTranslators()261 void Application::removeAllTranslators() noexcept {
262   foreach (auto& translator, mTranslators) {
263     if (!qApp->removeTranslator(translator.get())) {
264       qWarning() << "Failed to remove translator.";
265     }
266   }
267   mTranslators.clear();
268 }
269 
270 /*******************************************************************************
271  *  End of File
272  ******************************************************************************/
273 
274 }  // namespace librepcb
275