BibleTime
btmoduletextmodel.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 <QTextEdit>
14 #include "btmoduletextmodel.h"
15 
16 #include "../../util/btassert.h"
17 #include "../drivers/cswordmoduleinfo.h"
18 #include "../drivers/cswordbiblemoduleinfo.h"
19 #include "../drivers/cswordbookmoduleinfo.h"
20 #include "../drivers/cswordlexiconmoduleinfo.h"
21 #include "../cswordmodulesearch.h"
22 #include "../keys/cswordtreekey.h"
23 #include "../keys/cswordversekey.h"
24 #include "../managers/colormanager.h"
25 #include "../managers/cswordbackend.h"
26 #include "../rendering/ctextrendering.h"
27 
28 
29 namespace {
30 
31 DisplayOptions const defaultDisplayOptions = []() noexcept {
32  DisplayOptions opts;
33  opts.lineBreaks = 1;
34  opts.verseNumbers = 1;
35  return opts;
36 }();
37 
38 FilterOptions const defaultFilterOptions = []() noexcept {
39  FilterOptions opts;
40  opts.footnotes = 0;
41  opts.greekAccents = 1;
42  opts.headings = 1;
43  opts.hebrewCantillation = 1;
44  opts.hebrewPoints = 1;
45  opts.lemmas = 0;
46  opts.morphSegmentation = 1;
47  opts.morphTags = 1;
48  opts.redLetterWords = 1;
49  opts.scriptureReferences = 0;
50  opts.strongNumbers = 0;
51  opts.textualVariants = 0;
52  return opts;
53 }();
54 
55 } // anonymous namespace
56 
58 
60  : QAbstractListModel(parent)
61  , m_firstEntry(0)
62  , m_maxEntries(0)
63  , m_textFilter(nullptr)
64  , m_displayRendering(defaultDisplayOptions, defaultFilterOptions)
65 {}
66 
68  m_moduleInfoList.clear();
69  for (auto const & moduleName : m_modules)
70  m_moduleInfoList.append(
71  CSwordBackend::instance().findModuleByName(
72  moduleName));
73 
74  beginResetModel();
75  const CSwordModuleInfo* firstModule = m_moduleInfoList.at(0);
76  if (isBible() || isCommentary()) {
77  CSwordBibleModuleInfo const * const m =
78  static_cast<const CSwordBibleModuleInfo *>(firstModule);
79  m_firstEntry = m->lowerBound().index();
81  } else if(isLexicon()) {
82  m_maxEntries =
83  static_cast<CSwordLexiconModuleInfo const *>(firstModule)
84  ->entries().size();
85  } else if(isBook()) {
86  sword::TreeKeyIdx tk(
87  *static_cast<CSwordBookModuleInfo const *>(firstModule)
88  ->tree());
89  tk.root();
90  tk.firstChild();
91  BT_ASSERT(tk.getOffset() == 4);
92  tk.setPosition(sword::BOTTOM);
93  m_maxEntries = tk.getOffset() / 4;
94  }
95 
96  endResetModel();
97 }
98 
99 void BtModuleTextModel::setModules(const QStringList& modules) {
100  m_modules = modules;
101  reloadModules();
102 }
103 
104 
105 QVariant BtModuleTextModel::data(const QModelIndex & index, int role) const {
106 
107  QString text;
108  if (isBible() || isCommentary())
109  text = verseData(index, role);
110  else if (isBook())
111  text = bookData(index, role);
112  else if (isLexicon())
113  text = lexiconData(index, role);
114  else
115  text = QStringLiteral("invalid");
116 
117  if (m_textFilter) {
118  text = m_textFilter->processText(text);
119  }
120 
121  if ( ! m_highlightWords.isEmpty()) {
124  true);
125  if (m_findState && index.row() == m_findState->index) {
126  // t = highlightFindPreviousNextField(t); now inlined:
127  int from = 0;
128  for (int i = 0; i < m_findState->subIndex; ++i) {
129  int pos = t.indexOf(QStringLiteral("\"highlightwords\""), from);
130  if (pos == -1)
131  return t;
132  else {
133  from = pos + 1;
134  }
135  }
136  int position = from + 14; // highlightwords = 14, quote was already added
137  t.insert(position, '2');
138  }
139  return QVariant(t);
140  }
141 
142  return QVariant(text);
143 }
144 
145 QString BtModuleTextModel::lexiconData(const QModelIndex & index, int role) const {
146  int row = index.row();
147 
148  const CSwordLexiconModuleInfo *lexiconModule = qobject_cast<const CSwordLexiconModuleInfo*>(m_moduleInfoList.at(0));
149  BtConstModuleList moduleList;
150  moduleList << lexiconModule;
151  QString keyName = lexiconModule->entries()[row];
152 
153  if (role == ModuleEntry::TextRole || role == ModuleEntry::Text0Role) {
154  if (keyName.isEmpty())
155  return {};
156  auto text = m_displayRendering.renderDisplayEntry(moduleList, keyName);
157  text.replace(QStringLiteral("#CHAPTERTITLE#"), QString());
158  text.replace(QStringLiteral("#TEXT_ALIGN#"), QStringLiteral("left"));
159  text = ColorManager::replaceColors(text);
160  return text;
161  }
162  else if (role == ModuleEntry::ReferenceRole){
163  return keyName;
164  }
165  return QString();
166 }
167 
168 QString BtModuleTextModel::bookData(const QModelIndex & index, int role) const {
169  if (role == ModuleEntry::TextRole ||
170  role == ModuleEntry::Text0Role) {
171  const CSwordBookModuleInfo *bookModule = qobject_cast<const CSwordBookModuleInfo*>(m_moduleInfoList.at(0));
172  CSwordTreeKey key(bookModule->tree(), bookModule);
173  int bookIndex = index.row() * 4;
174  key.setOffset(bookIndex);
175  BtConstModuleList moduleList;
176  moduleList << bookModule;
177  if (key.key().isEmpty())
178  return {};
179  auto text =
181  moduleList,
182  key.key(),
186  text.replace(QStringLiteral("#CHAPTERTITLE#"), QString());
187  text.replace(QStringLiteral("#TEXT_ALIGN#"), QStringLiteral("left"));
188  return text;
189  }
190  return QString();
191 }
192 
193 static int getColumnFromRole(int role) {
194  if (role >= ModuleEntry::Text0Role && role <= ModuleEntry::Text9Role)
195  return role - ModuleEntry::Text0Role;
196  if (role >= ModuleEntry::Title0Role && role <= ModuleEntry::Title9Role)
197  return role - ModuleEntry::Title0Role;
198  return 0;
199 }
200 
201 QString BtModuleTextModel::verseData(const QModelIndex & index, int role) const {
202  int row = index.row();
203  CSwordVerseKey key = indexToVerseKey(row);
204  int verse = key.verse();
205 
206  if (role >= ModuleEntry::TextRole && role <= ModuleEntry::Title9Role) {
207  if (verse == 0)
208  return QString();
209  QString text;
210 
211  QString chapterTitle;
212  if (verse == 1)
213  chapterTitle =
214  QStringLiteral("%1 %2").arg(key.bookName(),
215  QString::number(key.chapter()));
216 
217  BtConstModuleList modules;
218  if ( role == ModuleEntry::TextRole) {
219  modules = m_moduleInfoList;
220  } else {
221  int column = getColumnFromRole(role);
222  CSwordModuleInfo const * module;
223  if ((column + 1) > m_moduleInfoList.count())
224  module = m_moduleInfoList.at(0);
225  else
226  module = m_moduleInfoList.at(column);
227  modules.append(module);
228  CSwordVerseKey mKey(module);
229  mKey.setKey(key.key());
230 
231  // Title only for verse 1 of Personal commentary
232  if (role >= ModuleEntry::Title0Role && role <= ModuleEntry::Title9Role) {
233  if (module->isWritable() && verse == 1)
234  return QStringLiteral("<center><h3>%1</h3></center>")
235  .arg(chapterTitle);
236  return {};
237  }
238 
239  // Personal commentary
240  if (module->isWritable()) {
241  auto const & rawText = mKey.rawText();
242  auto text =
243  QStringLiteral("%1 %2")
244  .arg(QString::number(verse),
245  rawText.isEmpty()
246  ? QStringLiteral("<span style=\"color:gray\">"
247  "<small>%1</small></span>")
248  .arg(tr("Click to edit"))
249  : rawText);
251  ColorManager::replaceColors(std::move(text)),
253  }
254  }
255 
256  if (!key.key().isEmpty())
257  text +=
259  modules,
260  key.key(),
264 
265  text.replace(QStringLiteral("#CHAPTERTITLE#"), chapterTitle);
266  text.replace(QStringLiteral("#TEXT_ALIGN#"), QStringLiteral("left"));
267  text = ColorManager::replaceColors(text);
268  return text;
269  }
270  return QString();
271 }
272 
273 int BtModuleTextModel::columnCount(const QModelIndex & /*parent*/) const {
274  return 1;
275 }
276 
277 int BtModuleTextModel::rowCount(const QModelIndex & /*parent*/) const {
278  return m_maxEntries;
279 }
280 
282  static auto const roleNames_ =
283  []() {
285  r[ModuleEntry::ReferenceRole] = "keyName"; // reference
286  r[ModuleEntry::TextRole] = "line"; // not used
287  r[ModuleEntry::Text0Role] = "text0"; // text in column 0
288  r[ModuleEntry::Text1Role] = "text1"; // text in column 1
289  r[ModuleEntry::Text2Role] = "text2"; // text in column 2
290  r[ModuleEntry::Text3Role] = "text3"; // text in column 3
291  r[ModuleEntry::Text4Role] = "text4"; // text in column 4
292  r[ModuleEntry::Text5Role] = "text5"; // text in column 5
293  r[ModuleEntry::Text6Role] = "text6"; // text in column 6
294  r[ModuleEntry::Text7Role] = "text7"; // text in column 7
295  r[ModuleEntry::Text8Role] = "text8"; // text in column 8
296  r[ModuleEntry::Text9Role] = "text9"; // text in column 9
297  r[ModuleEntry::Title0Role] = "title0"; // title in column 0
298  r[ModuleEntry::Title1Role] = "title1"; // title in column 1
299  r[ModuleEntry::Title2Role] = "title2"; // title in column 2
300  r[ModuleEntry::Title3Role] = "title3"; // title in column 3
301  r[ModuleEntry::Title4Role] = "title4"; // title in column 4
302  r[ModuleEntry::Title5Role] = "title5"; // title in column 5
303  r[ModuleEntry::Title6Role] = "title6"; // title in column 6
304  r[ModuleEntry::Title7Role] = "title7"; // title in column 7
305  r[ModuleEntry::Title8Role] = "title8"; // title in column 8
306  r[ModuleEntry::Title9Role] = "title9"; // title in column 9
307  return r;
308  }();
309  return roleNames_;
310 }
311 
313  const CSwordModuleInfo* module = m_moduleInfoList.at(0);
314  if (module == nullptr)
315  return false;
316  return module->type() == CSwordModuleInfo::Bible;
317 }
318 
320  const CSwordModuleInfo* module = m_moduleInfoList.at(0);
321  if (module == nullptr)
322  return false;
323  return module->type() == CSwordModuleInfo::GenericBook;
324 }
325 
327  const CSwordModuleInfo* module = m_moduleInfoList.at(0);
328  if (module == nullptr)
329  return false;
330  return module->type() == CSwordModuleInfo::Commentary;
331 }
332 
334  const CSwordModuleInfo* module = m_moduleInfoList.at(0);
335  if (module == nullptr)
336  return false;
337  return module->type() == CSwordModuleInfo::Lexicon;
338 }
339 
340 // Function is valid for Bibles, Commentaries, and Books
341 int BtModuleTextModel::keyToIndex(CSwordKey const & key) const {
342  if (auto const * const treeKey = dynamic_cast<CSwordTreeKey const *>(&key))
343  return treeKey->offset() / 4u;
344  if (auto const * const vKey = dynamic_cast<CSwordVerseKey const *>(&key))
345  return vKey->index();
346  return 0;
347 }
348 
350  int index = key.index() - m_firstEntry;
351  return index;
352 }
353 
355 { return indexToVerseKey(index, *m_moduleInfoList.front()); }
356 
359  CSwordModuleInfo const & module) const
360 {
361  CSwordVerseKey key(&module);
362  key.setIntros(true);
363  key.setIndex(index + m_firstEntry);
364  return key;
365 }
366 
368 {
369  const CSwordModuleInfo* module = m_moduleInfoList.at(0);
370  CSwordVerseKey key(module);
371 
372  key.setIntros(true);
373  key.setIndex(index + m_firstEntry);
374  return key.verse();
375 }
376 
377 CSwordKey* BtModuleTextModel::indexToKey(int index, int moduleNum) const
378 {
379  const CSwordModuleInfo* module = m_moduleInfoList.at(moduleNum);
380  auto * const key = module->createKey();
381  QString keyName = indexToKeyName(index);
382  key->setKey(keyName);
383  return key;
384 }
385 
387 {
388  const CSwordModuleInfo* module = m_moduleInfoList.at(0);
389  const CSwordBookModuleInfo *bookModule = qobject_cast<const CSwordBookModuleInfo*>(module);
390  CSwordTreeKey key(bookModule->tree(), bookModule);
391  int bookIndex = index * 4;
392  key.setOffset(bookIndex);
393  return key;
394 }
395 
396 QString BtModuleTextModel::indexToKeyName(int index) const {
397  if (index < 0)
398  return {};
399  if (isBible() || isCommentary())
400  return indexToVerseKey(index).key();
401  if (isBook())
402  return indexToBookKey(index).key();
403  if (isLexicon())
404  return qobject_cast<CSwordLexiconModuleInfo const *>(
405  m_moduleInfoList.at(0))->entries()[index];
406  return QStringLiteral("???");
407 }
408 
409 void BtModuleTextModel::setFindState(std::optional<FindState> findState) {
410  if (m_findState
411  && (!findState || (m_findState->index != findState->index)))
412  {
413  QModelIndex oldIndexToClear = index(m_findState->index, 0);
414  m_findState = std::move(findState);
415  Q_EMIT dataChanged(oldIndexToClear, oldIndexToClear);
416  } else {
417  m_findState = std::move(findState);
418  }
419  if (m_findState) {
420  QModelIndex index = this->index(m_findState->index, 0);
421  Q_EMIT dataChanged(index, index);
422  }
423 }
425  const QString& highlightWords, bool /* caseSensitive */) {
426  beginResetModel();
427  m_highlightWords = highlightWords;
428  endResetModel();
429 }
430 
433  return;
434  beginResetModel();
435  m_displayRendering.setDisplayOptions(displayOptions);
436  endResetModel();
437 }
438 
441  return;
442  beginResetModel();
443  m_displayRendering.setFilterOptions(filterOptions);
444  endResetModel();
445 }
446 
448  BT_ASSERT(m_textFilter == nullptr);
449  m_textFilter = textFilter;
450 }
451 
453  const QModelIndex &index,
454  const QVariant &value,
455  int role) {
456  auto const & module = *m_moduleInfoList.at(getColumnFromRole(role));
457  CSwordVerseKey mKey(indexToVerseKey(index.row(), module));
458  const_cast<CSwordModuleInfo &>(module).write(&mKey, value.toString());
459  Q_EMIT dataChanged(index, index);
460  return true;
461 }
#define BT_ASSERT(...)
Definition: btassert.h:17
QList< CSwordModuleInfo const * > BtConstModuleList
Definition: btmodulelist.h:21
static int getColumnFromRole(int role)
virtual QString processText(const QString &text)=0
virtual ~BtModuleTextFilter()=0
QString bookData(const QModelIndex &index, int role=Qt::DisplayRole) const
CSwordKey * indexToKey(int index, int moduleNum) const
int rowCount(const QModelIndex &parent=QModelIndex()) const override
void setDisplayOptions(const DisplayOptions &displayOptions)
std::optional< FindState > m_findState
int verseKeyToIndex(const CSwordVerseKey &key) const
int columnCount(const QModelIndex &parent=QModelIndex()) const override
CSwordTreeKey indexToBookKey(int index) const
BtConstModuleList m_moduleInfoList
BtModuleTextModel(QObject *parent=nullptr)
QString lexiconData(const QModelIndex &index, int role=Qt::DisplayRole) const
void setModules(const QStringList &modules)
void setHighlightWords(const QString &highlightWords, bool caseSensitive)
int keyToIndex(CSwordKey const &key) const
int indexToVerse(int index) const
void setFilterOptions(FilterOptions filterOptions)
CSwordVerseKey indexToVerseKey(int index) const
QString verseData(const QModelIndex &index, int role=Qt::DisplayRole) const
QString indexToKeyName(int index) const
void setTextFilter(BtModuleTextFilter *textFilter)
QHash< int, QByteArray > roleNames() const override
virtual bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) override
BtModuleTextFilter * m_textFilter
Rendering::CDisplayRendering m_displayRendering
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
void setFindState(std::optional< FindState > findState)
static CSwordBackend & instance() noexcept
Definition: cswordbackend.h:98
Implementation for Sword Bibles.
CSwordVerseKey const & lowerBound() const
CSwordVerseKey const & upperBound() const
Class for generic book support.
sword::TreeKeyIdx * tree() const
QString rawText()
Definition: cswordkey.cpp:45
const QStringList & entries() const
virtual CSwordKey * createKey() const =0
ModuleType type() const
virtual bool isWritable() const
CSwordKey implementation for Sword's TreeKey.
Definition: cswordtreekey.h:43
QString key() const final override
void setOffset(Offset value)
CSwordKey implementation for Sword's VerseKey.
QString bookName() const
void setIndex(long v)
void setIntros(bool v)
QString key() const final override
bool setKey(const QString &key) final override
int verse() const
long index() const
int chapter() const
QString renderDisplayEntry(BtConstModuleList const &modules, QString const &key, CTextRendering::KeyTreeItem::Settings::KeyRenderingFace keyRendering=CTextRendering::KeyTreeItem::Settings::CompleteShort) const
DisplayOptions const & displayOptions() const noexcept
void setFilterOptions(FilterOptions const &filterOptions) noexcept
void setDisplayOptions(DisplayOptions const &displayOptions) noexcept
FilterOptions const & filterOptions() const noexcept
QStringList r(content.left(bodyIndex))
QString highlightSearchedText(QString const &content, QString const &searchedText, bool plainSearchedText)
QString replaceColors(QString content)
int lineBreaks
Definition: btglobal.h:47
int verseNumbers
Definition: btglobal.h:48
bool displayOptionsAreEqual(DisplayOptions const &opts) const noexcept
Definition: btglobal.cpp:55
int morphSegmentation
Definition: btglobal.h:37
int hebrewCantillation
Definition: btglobal.h:32
int morphTags
Definition: btglobal.h:29
int textualVariants
Definition: btglobal.h:34
int hebrewPoints
Definition: btglobal.h:31
int footnotes
Definition: btglobal.h:26
int scriptureReferences
Definition: btglobal.h:36
int greekAccents
Definition: btglobal.h:33
int headings
Definition: btglobal.h:28
int redLetterWords
Definition: btglobal.h:35
bool filterOptionsAreEqual(FilterOptions const &opts) const noexcept
Definition: btglobal.cpp:38
int strongNumbers
Definition: btglobal.h:27
@ SimpleKey
means only versenumber or only lexicon entry name