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