BibleTime
btconfig.cpp
Go to the documentation of this file.
1/*********
2*
3* In the name of the Father, and of the Son, and of the Holy Spirit.
4*
5* This file is part of BibleTime's source code, https://bibletime.info/
6*
7* Copyright 1999-2025 by the BibleTime developers.
8* The BibleTime source code is licensed under the GNU General Public License
9* version 2.0.
10*
11**********/
12
13#include "btconfig.h"
14
15#include <cstddef>
16#include <limits>
17#include <QByteArray>
18#include <QDir>
19#include <QFile>
20#include <QKeySequence>
21#include <QLocale>
22#include <QSettings>
23#include <QVariant>
24#include <memory>
25#include <type_traits>
26#include <utility>
27#include "../../util/btassert.h"
28#include "../../util/directory.h"
29#include "../btglobal.h"
30#include "../drivers/cswordmoduleinfo.h"
31#include "../language.h"
32#include "../managers/cswordbackend.h"
33
34
35// Sword includes:
36#pragma GCC diagnostic push
37#pragma GCC diagnostic ignored "-Wextra-semi"
38#pragma GCC diagnostic ignored "-Wsuggest-override"
39#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
40#ifdef __clang__
41#pragma clang diagnostic push
42#pragma clang diagnostic ignored "-Wsuggest-destructor-override"
43#endif
44#include <swkey.h>
45#include <swmodule.h>
46#include <listkey.h>
47#include <versekey.h> // For search scope configuration
48#ifdef __clang__
49#pragma clang diagnostic pop
50#endif
51#pragma GCC diagnostic pop
52
53
54#define BTCONFIG_API_VERSION 1
55namespace {
56auto const BTCONFIG_API_VERSION_KEY = QStringLiteral("btconfig_api_version");
57auto const GROUP_SESSIONS = QStringLiteral("sessions");
58auto const GROUP_SESSIONS_PREFIX = QStringLiteral("sessions/");
59auto const KEY_CURRENT_SESSION = QStringLiteral("sessions/currentSession");
60auto const KEY_SESSION_NAME = QStringLiteral("sessions/%1/name");
61} // anonymous namespace
62
63/*
64 * set the instance variable initially to 0, so it can be safely checked
65 * whether the variable has been initialized yet.
66 */
68
70
71
72BtConfig::BtConfig(const QString & settingsFile)
74 std::make_shared<QSettings>(settingsFile, QSettings::IniFormat))
75{
76 BT_ASSERT(!m_instance && "BtConfig already initialized!");
77 m_instance = this;
78
79 if (m_defaultSearchScopes.isEmpty()) {
80 m_defaultSearchScopes.insert(QT_TR_NOOP("Old testament"),
81 QStringLiteral("Gen - Mal"));
82 m_defaultSearchScopes.insert(QT_TR_NOOP("Moses/Pentateuch/Torah"),
83 QStringLiteral("Gen - Deut"));
84 m_defaultSearchScopes.insert(QT_TR_NOOP("History"),
85 QStringLiteral("Jos - Est"));
86 m_defaultSearchScopes.insert(QT_TR_NOOP("Prophets"),
87 QStringLiteral("Isa - Mal"));
88 m_defaultSearchScopes.insert(QT_TR_NOOP("New testament"),
89 QStringLiteral("Mat - Rev"));
90 m_defaultSearchScopes.insert(QT_TR_NOOP("Gospels"),
91 QStringLiteral("Mat - Joh"));
92 m_defaultSearchScopes.insert(QT_TR_NOOP("Letters/Epistles"),
93 QStringLiteral("Rom - Jude"));
94 m_defaultSearchScopes.insert(QT_TR_NOOP("Paul's Epistles"),
95 QStringLiteral("Rom - Phile"));
96 }
97
98#ifdef Q_OS_WIN
99 const double minPointSize = 14.0;
100 double pointSize = m_defaultFont.pointSizeF();
101 if (pointSize < minPointSize)
102 m_defaultFont.setPointSizeF(minPointSize);
103#endif
104
105 // Read all session keys and names:
106 auto sessionsConf = group(GROUP_SESSIONS);
107 for (auto && sessionKey : sessionsConf.childGroups()) {
108 // Skip empty//keys just in case:
109 if (sessionKey.isEmpty())
110 continue;
111
112 auto sessionName =
113 sessionsConf.value<QString>(
114 sessionKey + QStringLiteral("/name"));
115 if (!sessionName.isEmpty())
116 m_sessionNames.insert(std::move(sessionKey),
117 std::move(sessionName));
118 }
119
120 // Get current session key:
121 m_currentSessionKey = value<QString>(KEY_CURRENT_SESSION);
122
123 /*
124 If no session with the current session key exists, default to the first
125 session found. If no sessions were found, create a default session.
126 */
127 if (m_currentSessionKey.isEmpty()
129 {
130 if (m_sessionNames.isEmpty()) {
131 m_currentSessionKey = QString::number(0, 36);
132 setValue(KEY_CURRENT_SESSION, m_currentSessionKey);
133 setValue(KEY_SESSION_NAME.arg(m_currentSessionKey),
134 tr("Default Session"));
135 } else {
136 m_currentSessionKey = m_sessionNames.keys().first();
137 }
138 }
139}
140
143
144 const QString confFileName = util::directory::getUserBaseDir().absolutePath()
145 + QStringLiteral("/bibletimerc");
146 bool confExisted = QFile::exists(confFileName);
147 m_instance = new BtConfig(confFileName);
148 if (!confExisted) {
149 m_instance->setValue<int>(BTCONFIG_API_VERSION_KEY, BTCONFIG_API_VERSION);
150 return INIT_OK;
151 }
152
153 int btConfigOldApi = m_instance->value<int>(BTCONFIG_API_VERSION_KEY, 0);
154 if (btConfigOldApi == BTCONFIG_API_VERSION)
155 return INIT_OK;
156 return (btConfigOldApi < BTCONFIG_API_VERSION)
159}
160
162{ m_instance->setValue<int>(BTCONFIG_API_VERSION_KEY, BTCONFIG_API_VERSION); }
163
165 BT_ASSERT(m_instance && "BtConfig not yet initialized!");
166 return *m_instance;
167}
168
169void BtConfig::setCurrentSession(QString const & key) {
170 BT_ASSERT(!key.isEmpty());
171 BT_ASSERT(m_sessionNames.contains(key));
173
174 setValue(KEY_CURRENT_SESSION, key);
175}
176
177QString BtConfig::addSession(QString const & name) {
178 BT_ASSERT(!name.isEmpty());
179
180 // Generate a new session key:
181 QString key = QString::number(0u, 36);
182 if (m_sessionNames.contains(key)) {
183 QString keyPrefix;
184 std::size_t i = 1u;
185 for (;;) {
186 key = QString::number(i, 36);
187 if (!m_sessionNames.contains(keyPrefix + key))
188 break;
189 if (i == std::numeric_limits<std::size_t>::max()) {
190 i = 0u;
191 keyPrefix.append('_');
192 } else {
193 i++;
194 }
195 }
196 }
197 BT_ASSERT(!m_sessionNames.contains(key));
198 m_sessionNames.insert(key, name);
199
200 setValue(KEY_SESSION_NAME.arg(key), name);
201 return key;
202}
203
204
205void BtConfig::deleteSession(QString const & key) {
206 BT_ASSERT(m_sessionNames.contains(key));
208 m_sessionNames.remove(key);
209
210 remove(GROUP_SESSIONS_PREFIX + key);
211}
212
214{ return group(GROUP_SESSIONS_PREFIX + m_currentSessionKey); }
215
217 delete m_instance;
218 m_instance = nullptr;
219}
220
221void BtConfig::setModuleEncryptionKey(const QString & name,
222 const QString & key)
223{
224 BT_ASSERT(!name.isEmpty());
225 setValue(QStringLiteral("Module keys/") + name, key);
226}
227
228QString BtConfig::getModuleEncryptionKey(const QString & name) {
229 BT_ASSERT(!name.isEmpty());
230 return value<QString>(QStringLiteral("Module keys/") + name, QString());
231}
232
233BtConfig::ShortcutsMap BtConfig::getShortcuts(QString const & shortcutGroup) {
234 ShortcutsMap allShortcuts;
235 auto shortcutsConf = group(shortcutGroup);
236 for (QString const & key : shortcutsConf.childKeys()) {
237 auto const variant = shortcutsConf.qVariantValue(key);
238
239 QList<QKeySequence> shortcuts;
240 auto const typeId = variant.typeId();
241 if (typeId == QMetaType::QVariantList) { // For BibleTime before 2.9
242 for (QVariant const & shortcut : variant.toList())
243 shortcuts.append(shortcut.toString());
244 } else if (typeId == QMetaType::QStringList
245 || typeId == QMetaType::QString)
246 { // a StringList with one element is recognized as a QVariant::String
247 for (QString const & shortcut : variant.toStringList())
248 shortcuts.append(shortcut);
249 } else { // it's something we don't know, skip it
250 continue;
251 }
252
253 allShortcuts.insert(key, shortcuts);
254 }
255 return allShortcuts;
256}
257
258void BtConfig::setShortcuts(QString const & shortcutGroup,
259 ShortcutsMap const & shortcuts)
260{
261 auto shortcutsConf = group(shortcutGroup);
262 for (auto it = shortcuts.begin(); it != shortcuts.end(); ++it) {
263 // Write beautiful string lists (since 2.9):
264 /// \note saving QKeySequences directly doesn't appear to work!
265 QStringList varList;
266 for (QKeySequence const & shortcut : it.value())
267 varList.append(shortcut.toString());
268
269 if (!varList.empty())
270 shortcutsConf.setValue(it.key(), varList);
271 }
272}
273
275 FilterOptions os;
276 auto const subConf = group.group(QStringLiteral("presentation"));
277 os.footnotes = subConf.value<bool>(QStringLiteral("footnotes"), true);
278 os.strongNumbers = subConf.value<bool>(QStringLiteral("strongNumbers"), true);
279 os.headings = subConf.value<bool>(QStringLiteral("headings"), true);
280 os.morphTags = subConf.value<bool>(QStringLiteral("morphTags"), true);
281 os.lemmas = subConf.value<bool>(QStringLiteral("lemmas"), true);
282 os.redLetterWords = subConf.value<bool>(QStringLiteral("redLetterWords"), true);
283 os.hebrewPoints = subConf.value<bool>(QStringLiteral("hebrewPoints"), true);
284 os.hebrewCantillation = subConf.value<bool>(QStringLiteral("hebrewCantillation"), true);
285 os.greekAccents = subConf.value<bool>(QStringLiteral("greekAccents"), true);
286 os.textualVariants = subConf.value<bool>(QStringLiteral("textualVariants"), false);
287 os.scriptureReferences = subConf.value<bool>(QStringLiteral("scriptureReferences"), true);
288 os.morphSegmentation = subConf.value<bool>(QStringLiteral("morphSegmentation"), true);
289 return os;
290}
291
293 BtConfigCore & group)
294{
295 auto subConf = group.group(QStringLiteral("presentation"));
296 subConf.setValue(QStringLiteral("footnotes"), static_cast<bool>(os.footnotes));
297 subConf.setValue(QStringLiteral("strongNumbers"), static_cast<bool>(os.strongNumbers));
298 subConf.setValue(QStringLiteral("headings"), static_cast<bool>(os.headings));
299 subConf.setValue(QStringLiteral("morphTags"), static_cast<bool>(os.morphTags));
300 subConf.setValue(QStringLiteral("lemmas"), static_cast<bool>(os.lemmas));
301 subConf.setValue(QStringLiteral("redLetterWords"), static_cast<bool>(os.redLetterWords));
302 subConf.setValue(QStringLiteral("hebrewPoints"), static_cast<bool>(os.hebrewPoints));
303 subConf.setValue(QStringLiteral("hebrewCantillation"), static_cast<bool>(os.hebrewCantillation));
304 subConf.setValue(QStringLiteral("greekAccents"), static_cast<bool>(os.greekAccents));
305 subConf.setValue(QStringLiteral("textualVariants"), static_cast<bool>(os.textualVariants));
306 subConf.setValue(QStringLiteral("scriptureReferences"), static_cast<bool>(os.scriptureReferences));
307 subConf.setValue(QStringLiteral("morphSegmentation"), static_cast<bool>(os.morphSegmentation));
308}
309
313 auto const subConf = group.group(QStringLiteral("presentation"));
314 os.lineBreaks = subConf.value<bool>(QStringLiteral("lineBreaks"), false);
315 os.verseNumbers = subConf.value<bool>(QStringLiteral("verseNumbers"), true);
316 return os;
317}
318
320 BtConfigCore & group)
321{
322 auto subConf = group.group(QStringLiteral("presentation"));
323 subConf.setValue(QStringLiteral("lineBreaks"),
324 static_cast<bool>(os.lineBreaks));
325 subConf.setValue(QStringLiteral("verseNumbers"),
326 static_cast<bool>(os.verseNumbers));
327}
328
330 FontSettingsPair const & fontSettings)
331{
332 auto fontAsString = fontSettings.second.toString();
333
334 const QString & englishName = language.englishName();
335 BT_ASSERT(!englishName.isEmpty());
336
337 // write the language to the settings
338 setValue(QStringLiteral("fonts/") + englishName, fontAsString);
339 setValue(QStringLiteral("font standard settings/") + englishName,
340 fontSettings.first);
341
342 auto const & abbrev = language.abbrev();
343 BT_ASSERT(!abbrev.isEmpty());
344
345 // (over-)write the language to the settings using abbreviation:
346 setValue(QStringLiteral("fonts/") + abbrev, std::move(fontAsString));
347 setValue(QStringLiteral("font standard settings/") + abbrev,
348 fontSettings.first);
349
350 // Update cache:
351 m_fontCache[&language] = fontSettings;
352}
353
356 // Check the cache first:
357 auto it(m_fontCache.find(&language));
358 if (it != m_fontCache.end())
359 return *it;
360
361 // Retrieve the font from the settings
362 FontSettingsPair fontSettings;
363
364 const QString & englishName = language.englishName();
365 BT_ASSERT(!englishName.isEmpty());
366 auto const & abbrev = language.abbrev();
367 BT_ASSERT(!abbrev.isEmpty());
368
369 if (auto const v =
370 qVariantValue(QStringLiteral("font standard settings/") + abbrev);
371 v.canConvert<bool>())
372 {
373 fontSettings.first = v.value<bool>();
374 } else {
375 fontSettings.first =
376 value<bool>(
377 QStringLiteral("font standard settings/") + englishName,
378 false);
379 }
380
381 QFont font;
382 if (fontSettings.first) {
383 auto const v = qVariantValue(QStringLiteral("fonts/") + abbrev);
384 auto fontName =
385 v.canConvert<QString>()
386 ? v.value<QString>()
387 : value<QString>(QStringLiteral("fonts/") + englishName,
388 getDefaultFont().toString());
389 if (!font.fromString(std::move(fontName))) {
390 /// \todo
391 }
392 } else {
393 font = getDefaultFont();
394 }
395 fontSettings.second = font;
396
397 // Cache the value:
398 m_fontCache.insert(&language, fontSettings);
399
400 return fontSettings;
401}
402
404 auto const storedMap =
405 value<BtConfig::StringMap>(
406 QStringLiteral("properties/searchScopes"),
408 StringMap map;
409
410 // Apply translation for default search scope names:
411 for (auto it = storedMap.cbegin(); it != storedMap.cend(); ++it) {
412 if (m_defaultSearchScopes.contains(it.key())) {
413 map.insert(tr(it.key().toUtf8()), it.value());
414 } else {
415 map.insert(it.key(), it.value());
416 }
417 }
418
419 // Convert map to current locale:
420 static auto const separator = QStringLiteral("; ");
421 for (auto & data : map) {
422 sword::ListKey list = parseVerseListWithModules(data, scopeModules);
423 data.clear();
424 for (int i = 0; i < list.getCount(); i++) {
425 data.append(QString::fromUtf8(list.getElement(i)->getRangeText()));
426 data.append(separator);
427 }
428 }
429 return map;
430}
431
432void BtConfig::setSearchScopesWithCurrentLocale(const QStringList& scopeModules, StringMap searchScopes) {
433 /**
434 * We want to make sure that the search scopes are saved with english
435 * key names so loading them will always work with each locale set.
436 */
437 auto iter(searchScopes.begin());
438 while (iter != searchScopes.end()) {
439 QString &data = iter.value();
440 bool parsingWorked = true;
441 sword::ListKey list = parseVerseListWithModules(data, scopeModules);
442 data.clear();
443 for (int i = 0; i < list.getCount(); i++) {
444 sword::VerseKey * verse(dynamic_cast<sword::VerseKey *>(list.getElement(i)));
445
446 if (verse != nullptr) {
447 verse->setLocale("en");
448 data.append(QString::fromUtf8(verse->getRangeText()));
449 data.append(';');
450 } else {
451 parsingWorked = false;
452 break;
453 }
454 }
455
456 if (parsingWorked)
457 iter++;
458 else
459 iter = searchScopes.erase(iter);
460 }
461 setValue(QStringLiteral("properties/searchScopes"), searchScopes);
462}
463
465 static auto const key = QStringLiteral("GUI/booknameLanguage");
466 auto r = value<QString>(key, QLocale().name());
467
468 // Maintain backwards compatibility with BibleTime versions older than 3.1:
469 bool const updateConfig = r.contains('_');
470 r.replace('_', '-'); // BCP 47
471 if (updateConfig)
472 setValue(key, r);
473
474 return r;
475}
476
477sword::ListKey BtConfig::parseVerseListWithModules(const QString& data, const QStringList& scopeModules) {
478 for (auto const & moduleName : scopeModules) {
479 auto module = CSwordBackend::instance().findModuleByName(moduleName);
480 if (module == nullptr)
481 continue;
482 sword::VerseKey vk = module->swordModule().getKey();
483 sword::ListKey list(vk.parseVerseList(data.toUtf8(), "Genesis 1:1", true));
484 if (list.getCount() > 0)
485 return list;
486 }
487 return sword::ListKey();
488}
489
491 remove(QStringLiteral("properties/searchScopes"));
492}
493
495 auto const moduleName =
496 value<QString>(QStringLiteral("settings/defaults/") + moduleType);
497 if (moduleName.isEmpty())
498 return nullptr;
499
500 return CSwordBackend::instance().findModuleByName(moduleName);
501}
502
503void BtConfig::setDefaultSwordModuleByType(const QString &moduleType,
504 const CSwordModuleInfo * const module)
505{
506 setValue(QStringLiteral("settings/defaults/") + moduleType,
507 module != nullptr ? module->name() : QString());
508}
509
510/**
511 \todo -CDisplayWindow gets a construct method that reads from config and constructs and
512 returns the respective child window (check whether module is installed...)
513 -CDisplayWindows get a new variable "id" or something, which is a unique identifier.
514 The path in the configuration will use this id as name. (who gives out the IDs?)
515 -values are updated as they are changed, just like the rest of bibletime
516 -QMdiArea::subWindowActivated signal will trigger reading the window order and saving
517 it to the config.
518 Action Plan:
519 1. get current code to work with old session system
520 2. move complete code over to BtConfig
521 3. remove CBTConfig
522 4. implement BtConfig infrastructure for saving window configuration
523 - function to add a window
524 - function to remove a window
525 - specify how to save ordering
526 5. change CDisplayWindows to write all state changes to the configuration
527 6. implement BtConfig::readSession and callers
528 7. make session handling code work with QSetting paths instead of properties
529 8. add gui for new session handling
530 9. remove old gui for session handling
531*/
#define BT_ASSERT(...)
Definition btassert.h:17
#define BTCONFIG_API_VERSION
Definition btconfig.cpp:54
friend class BtConfig
void remove(QString const &key)
removes a key (and its children) from the current group.
BtConfigCore group(Prefix &&prefix) const &
T value(QString const &key, T const &defaultValue=T()) const
Returns the settings value for the given global key.
void setValue(QString const &key, T const &value)
Sets a value for a key.
QVariant qVariantValue(QString const &key, QVariant const &defaultValue=QVariant()) const
Returns the settings value for the given global key as a QVariant.
QMap< QString, QString > StringMap
Definition btconfig.h:48
QString getModuleEncryptionKey(const QString &name)
Function to get a module decryption key.
Definition btconfig.cpp:228
static void storeFilterOptionsToGroup(FilterOptions const &options, BtConfigCore &group)
Saves the current filter options.
Definition btconfig.cpp:292
BtConfigCore session() const
Definition btconfig.cpp:213
static FilterOptions loadFilterOptionsFromGroup(BtConfigCore const &group)
Definition btconfig.cpp:274
QFont const & getDefaultFont() const
Definition btconfig.h:192
void setFontForLanguage(Language const &language, FontSettingsPair const &fontSettings)
Set font for a language.
Definition btconfig.cpp:329
QPair< bool, QFont > FontSettingsPair
Definition btconfig.h:47
static DisplayOptions loadDisplayOptionsFromGroup(BtConfigCore const &group)
Definition btconfig.cpp:311
QHash< QString, QString > m_sessionNames
Definition btconfig.h:292
void setDefaultSwordModuleByType(const QString &moduleType, const CSwordModuleInfo *const module)
Sets the default sword module for a module type.
Definition btconfig.cpp:503
void setSearchScopesWithCurrentLocale(const QStringList &scopeModules, StringMap searchScopes)
Definition btconfig.cpp:432
QString m_currentSessionKey
Definition btconfig.h:293
static InitState initBtConfig()
Definition btconfig.cpp:141
static void destroyInstance()
Definition btconfig.cpp:216
QString addSession(const QString &name)
Creates a new session with the given name.
Definition btconfig.cpp:177
void deleteSearchScopesWithCurrentLocale()
Definition btconfig.cpp:490
static void forceMigrate()
Definition btconfig.cpp:161
static sword::ListKey parseVerseListWithModules(const QString &data, const QStringList &scopeModules)
Definition btconfig.cpp:477
void setModuleEncryptionKey(const QString &name, const QString &key)
Function to set a module decryption key.
Definition btconfig.cpp:221
QHash< Language const *, FontSettingsPair > m_fontCache
a cache for the fonts saved in the configuration file for speed
Definition btconfig.h:288
void setShortcuts(QString const &shortcutGroup, ShortcutsMap const &shortcuts)
Sets the shortcuts for the given group.
Definition btconfig.cpp:258
static BtConfig * m_instance
singleton instance
Definition btconfig.h:285
QFont m_defaultFont
default font used when no special one is set
Definition btconfig.h:287
FontSettingsPair getFontForLanguage(Language const &language)
Get font for a language.
Definition btconfig.cpp:355
@ INIT_NEED_UNIMPLEMENTED_FORWARD_MIGRATE
Definition btconfig.h:56
@ INIT_NEED_UNIMPLEMENTED_BACKWARD_MIGRATE
Definition btconfig.h:54
@ INIT_OK
Definition btconfig.h:55
void setCurrentSession(const QString &key)
Notifies the configuration system that the session settings should be read from and saved to the give...
Definition btconfig.cpp:169
void deleteSession(const QString &key)
Deletes the session with the given key.
Definition btconfig.cpp:205
QString booknameLanguage()
Definition btconfig.cpp:464
CSwordModuleInfo * getDefaultSwordModuleByType(const QString &moduleType)
Returns default sword module info class for a given module type.
Definition btconfig.cpp:494
ShortcutsMap getShortcuts(QString const &shortcutGroup)
Gets the shortcuts for the given group.
Definition btconfig.cpp:233
static void storeDisplayOptionsToGroup(DisplayOptions const &options, BtConfigCore &group)
Saves the current display options.
Definition btconfig.cpp:319
static StringMap m_defaultSearchScopes
Definition btconfig.h:290
static BtConfig & getInstance()
Definition btconfig.cpp:164
StringMap getSearchScopesForCurrentLocale(const QStringList &scopeModules)
Definition btconfig.cpp:403
CSwordModuleInfo * findModuleByName(const QString &name) const
Searches for a module with the given name.
static CSwordBackend & instance() noexcept
QString const & name() const
QString const & abbrev() const
Definition language.h:34
QString const & englishName() const noexcept
Definition language.h:43
QStringList r(content.left(bodyIndex))
const QDir & getUserBaseDir()
int morphSegmentation
Definition btglobal.h:37
int hebrewCantillation
Definition btglobal.h:32
int textualVariants
Definition btglobal.h:34
int hebrewPoints
Definition btglobal.h:31
int scriptureReferences
Definition btglobal.h:36
int greekAccents
Definition btglobal.h:33
int redLetterWords
Definition btglobal.h:35
int strongNumbers
Definition btglobal.h:27