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-2021 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 
37 namespace {
38 
39 inline QString readFileToString(const QString & filename) {
40  QFile f(filename);
41  return f.open(QIODevice::ReadOnly) ? QTextStream(&f).readAll() : QString();
42 }
43 
44 auto 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 
106 QString 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#"),
248  settings.textDirectionAsHtmlDirAttr())
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))
269  : tn;
270 }
271 
272 void 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 
280 void 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 
289  m_multiModuleHeaders = visible;
290 }
#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.
Definition: btconfigcore.h:50
QPair< bool, QFont > FontSettingsPair
Definition: btconfig.h:47
QFont const & getDefaultFont() const
Definition: btconfig.h:192
FontSettingsPair getFontForLanguage(Language const &language)
Get font for a language.
Definition: btconfig.cpp:364
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
Definition: cswordbackend.h:98
bool filter(WizardTaskType const taskType, QStringList const &languages, CSwordModuleInfo const *const mInfo)
QString readFileToString(const QString &filename)
const QDir & getDisplayTemplatesDir()
Definition: directory.cpp:330
const QDir & getUserDisplayTemplatesDir()
Definition: directory.cpp:354
char const * textDirectionAsHtmlDirAttr() const noexcept