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