BibleTime
cdisplaytemplatemgr.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-2026 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 "cdisplaytemplatemgr.h"
14
15#include <algorithm>
16#include <QDir>
17#include <QFile>
18#include <QFileInfo>
19#include <QFont>
20#include <QIODevice>
21#include <QObject>
22#include <QStringList>
23#include <QTextStream>
24#include "../../util/btassert.h"
25#include "../../util/directory.h"
26#include "../config/btconfig.h"
27#include "../drivers/btmodulelist.h"
28#include "../drivers/cswordmoduleinfo.h"
29#include "cswordbackend.h"
30
31
32
33namespace {
34
35auto const cssTemplateBase = QStringLiteral("Basic.tmpl");
36
37inline QString readFileToString(const QString & filename) {
38 QFile f(filename);
39 return f.open(QIODevice::ReadOnly) ? QTextStream(&f).readAll() : QString();
40}
41
42auto const defaultTemplateName = QStringLiteral("Blue.css");
43
44} // anonymous namespace
45
47
50 m_instance = this;
51
52 {
53 namespace DU = util::directory;
54 const QDir::Filters readableFileFilter(QDir::Files | QDir::Readable);
55 const QDir & td = DU::getDisplayTemplatesDir(); // Global template directory
56 const QDir & utd = DU::getUserDisplayTemplatesDir(); // User template directory
57
58 // Load regular templates:
59 {
60 const QStringList filter(QStringLiteral("*.tmpl"));
61 // Preload global display templates from disk:
62 for (auto const & file : td.entryList(filter, readableFileFilter))
63 loadTemplate(td.canonicalPath() + '/' + file);
64 // Preload user display templates from disk:
65 for (auto const & file : utd.entryList(filter, readableFileFilter))
66 loadTemplate(utd.canonicalPath() + '/' + file);
67 }
68
69 if (!m_templateMap.contains(cssTemplateBase)) {
70 errorMessage = QObject::tr("CSS base template not found!");
71 return;
72 }
73
74 // Load CSS templates:
75 {
76 const QStringList cssfilter(QStringLiteral("*.css"));
77 // Load global app stylesheets
78 for (auto const & file : td.entryList(cssfilter, readableFileFilter))
79 loadCSSTemplate(td.canonicalPath() + '/' + file);
80 // Load user app stylesheets
81 for (auto const & file : utd.entryList(cssfilter, readableFileFilter))
82 loadCSSTemplate(utd.canonicalPath() + '/' + file);
83 }
84 }
85
86 if (!m_cssMap.contains(defaultTemplateName)) {
87 errorMessage = QObject::tr("Default template \"%1\" not found!")
88 .arg(defaultTemplateName);
89 return;
90 }
91
92 // Create template names cache:
94 const bool b = m_availableTemplateNamesCache.removeOne(cssTemplateBase);
95 BT_ASSERT(b);
97 std::sort(m_availableTemplateNamesCache.begin(),
99
100 errorMessage = QString();
101}
102
103QString CDisplayTemplateMgr::fillTemplate(const QString & name,
104 QString content,
105 const Settings & settings) const
106{
107 BT_ASSERT(name != cssTemplateBase);
108 BT_ASSERT(name.endsWith(QStringLiteral(".css"))
109 || name.endsWith(QStringLiteral(".tmpl")));
110 BT_ASSERT(!name.endsWith(QStringLiteral(".css"))
111 || m_cssMap.contains(name));
112 BT_ASSERT(!name.endsWith(QStringLiteral(".tmpl"))
113 || m_templateMap.contains(name));
114 const bool templateIsCss = name.endsWith(QStringLiteral(".css"));
115
116 QString moduleName;
117 auto displayTypeString = settings.pageCSS_ID;
118 if (displayTypeString.isEmpty()) {
119 if (settings.modules.count()) {
120 switch (settings.modules.first()->type()) {
121
123 displayTypeString = QStringLiteral("bible");
124 break;
125
127 displayTypeString = QStringLiteral("book");
128 break;
129
132 default:
133 displayTypeString = QStringLiteral("singleentry");
134 break;
135 }
136 moduleName = settings.modules.first()->name();
137 }
138 else { //use bible as default type if no modules are set
139 displayTypeString = QStringLiteral("bible");
140 }
141 }
142
143 const int moduleCount = settings.modules.count();
144
145 if (moduleCount >= 2) {
146 //create header for the modules
147 // qDebug() << "There were more than 1 module, create headers";
148 QString header;
149
150 for (auto const * const mi : settings.modules)
151 header.append(
152 QStringLiteral("<th style=\"width:%1%;\">%2</th>")
153 .arg(QString::number(static_cast<int>(100.0 / moduleCount)),
154 mi->name()));
155
156 content = QStringLiteral("<table><tr>%1</tr>%2</table>")
157 .arg(header, content);
158 }
159
160 QString langCSS;
161 {
162 const QFont & f = btConfig().getDefaultFont();
163 langCSS.append(
164 QStringLiteral("#content{font-family:%1;font-size:%2pt;"
165 "font-weight:%3;font-style:%4}")
166 .arg(f.family(),
167 QString::number(f.pointSizeF(), 'f'),
168 f.bold()
169 ? QStringLiteral("bold")
170 : QStringLiteral("normal"),
171 f.italic()
172 ? QStringLiteral("italic")
173 : QStringLiteral("normal")));
174 }
175 {
176 auto const availableLanguages =
178 BT_ASSERT(availableLanguages);
179 for (auto const & lang : *availableLanguages) {
180 if (lang->abbrev().isEmpty())
181 continue;
182
184 if (fp.first) {
185 const QFont & f = fp.second;
186
187 /* QFont::weight() returns an int in the range [0, 99] but CSS
188 requires a real number in the range [1, 1000]. No extra
189 checks are needed for floating point precision in the result
190 range. */
191 using U = std::underlying_type_t<decltype(f.weight())>;
192 auto const fontWeight =
193 1.0 + (static_cast<U>(f.weight()) * 999.0) / 99.0;
194
195 auto const fontStyleString =
196 [&f]() {
197 switch ((int) f.style()) {
198 case QFont::StyleItalic:
199 return QStringLiteral("italic");
200 case QFont::StyleOblique:
201 return QStringLiteral("oblique");
202 case QFont::StyleNormal:
203 default:
204 return QStringLiteral("normal");
205 }
206 }();
207
208 auto const textDecorationString =
209 f.underline()
210 ? (f.strikeOut()
211 ? QStringLiteral("underline line-through")
212 : QStringLiteral("underline"))
213 : (f.strikeOut()
214 ? QStringLiteral("line-through")
215 : QStringLiteral("none"));
216
217 /// \todo Add support translating more QFont properties to CSS.
218
219 langCSS.append(
220 QStringLiteral("*[lang=%1]{font-family:%2;"
221 "font-size:%3pt;font-weight:%4;"
222 "font-style:%5;text-decoration:%6}")
223 .arg(lang->abbrev(),
224 f.family(),
225 QString::number(f.pointSizeF(), 'f'),
226 QString::number(fontWeight),
227 fontStyleString,
228 textDecorationString));
229 }
230 }
231 }
232
233 namespace DU = util::directory;
234 auto output = m_templateMap[templateIsCss ? cssTemplateBase : name];
235 output.replace(QStringLiteral("#TITLE#"), settings.title)
236 .replace(QStringLiteral("#LANG_ABBREV#"), settings.langAbbrev)
237 .replace(QStringLiteral("#DISPLAYTYPE#"), displayTypeString)
238 .replace(QStringLiteral("#LANG_CSS#"), langCSS)
239 .replace(QStringLiteral("#PAGE_DIRECTION#"),
241 ? QStringLiteral("ltr")
242 : QStringLiteral("rtl"))
243 .replace(QStringLiteral("#CONTENT#"), content)
244 .replace(QStringLiteral("#BODY_CLASSES#"),
245 QStringLiteral("%1 %1_%2").arg(displayTypeString,
246 moduleName))
247 .replace(QStringLiteral("#DISPLAY_TEMPLATES_PATH#"),
248 DU::getDisplayTemplatesDir().absolutePath());
249
250 if (templateIsCss)
251 output.replace(QStringLiteral("#THEME_STYLE#"), m_cssMap[name]);
252
253 return output;
254}
255
257 auto const tn =
258 btConfig().value<QString>(QStringLiteral("GUI/activeTemplateName"),
259 QString());
260 return (tn.isEmpty()
261 || !instance()->m_availableTemplateNamesCache.contains(tn))
262 ? defaultTemplateName
263 : tn;
264}
265
266void CDisplayTemplateMgr::loadTemplate(const QString & filename) {
267 BT_ASSERT(filename.endsWith(QStringLiteral(".tmpl")));
268 BT_ASSERT(QFileInfo(filename).isFile());
269 const QString templateString = readFileToString(filename);
270 if (!templateString.isEmpty())
271 m_templateMap.insert(QFileInfo(filename).fileName(), templateString);
272}
273
274void CDisplayTemplateMgr::loadCSSTemplate(const QString & filename) {
275 BT_ASSERT(filename.endsWith(QStringLiteral(".css")));
276 const QFileInfo fi(filename);
277 BT_ASSERT(fi.isFile());
278 if (fi.isReadable())
279 m_cssMap.insert(fi.fileName(), readFileToString(filename));
280}
#define BT_ASSERT(...)
Definition btassert.h:17
BtConfig & btConfig()
This is a shortchand for BtConfig::getInstance().
Definition btconfig.h:305
T value(QString const &key, T const &defaultValue=T()) const
Returns the settings value for the given global key.
QFont const & getDefaultFont() const
Definition btconfig.h:192
QPair< bool, QFont > FontSettingsPair
Definition btconfig.h:47
FontSettingsPair getFontForLanguage(Language const &language)
Get font for a language.
Definition btconfig.cpp:354
CDisplayTemplateMgr(QString &errorMessage)
static QString activeTemplateName()
QHash< QString, QString > m_templateMap
void loadCSSTemplate(const QString &filename)
QHash< QString, QString > m_cssMap
static CDisplayTemplateMgr * m_instance
QString fillTemplate(const QString &name, QString content, const Settings &settings) const
Fills the template.
static CDisplayTemplateMgr * instance()
void loadTemplate(const QString &filename)
QStringList m_availableTemplateNamesCache
std::shared_ptr< AvailableLanguagesCacheContainer const > availableLanguages() noexcept
static CSwordBackend & instance() noexcept
#define U(C)
QString readFileToString(const QString &filename)
CSwordModuleInfo::TextDirection textDirection