BibleTime
btqmlinterface.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 "btqmlinterface.h"
14 
15 #include <QApplication>
16 #include <QClipboard>
17 #include <QScreen>
18 #include <QRegularExpression>
19 #include <QRegularExpressionMatch>
20 #include <QTimerEvent>
21 #include <utility>
22 #include "../../../backend/config/btconfig.h"
23 #include "../../../backend/drivers/cswordbookmoduleinfo.h"
24 #include "../../../backend/drivers/cswordlexiconmoduleinfo.h"
25 #include "../../../backend/drivers/cswordmoduleinfo.h"
26 #include "../../../backend/keys/cswordkey.h"
27 #include "../../../backend/managers/colormanager.h"
28 #include "../../../backend/managers/cswordbackend.h"
29 #include "../../../backend/models/btmoduletextmodel.h"
30 #include "../../../backend/rendering/btinforendering.h"
31 #include "../../../backend/rendering/cplaintextexportrendering.h"
32 #include "../../../backend/rendering/ctextrendering.h"
33 #include "../../../backend/rendering/btinforendering.h"
34 #include "../../../util/btassert.h"
35 #include "../../bibletime.h"
36 #include "../../cinfodisplay.h"
37 #include "../../edittextwizard/btedittextwizard.h"
38 
39 
41  : QObject(parent)
42  , m_moduleTextModel(new BtModuleTextModel(this))
44 
46 
48  if (!m_swordKey)
49  return false;
50  switch (m_swordKey->module()->type()) {
51  case CSwordModuleInfo::Bible: [[fallthrough]];
52  case CSwordModuleInfo::Commentary: return true;
53  default: return false;
54  }
55 }
56 
58  m_moduleTextModel->setFilterOptions(filterOptions);
59 }
60 
62  return m_contextMenuIndex;
63 }
64 
66  m_contextMenuIndex = index;
67  Q_EMIT contextMenuIndexChanged();
68 }
69 
71  return m_contextMenuColumn;
72 }
73 
75  m_contextMenuColumn = index;
76  Q_EMIT contextMenuColumnChanged();
77 }
78 
81 
84 
87 }
88 
91 
93  if (m_swordKey == nullptr)
94  return 0;
95  auto const * const keyModule = m_swordKey->module();
96  auto const moduleType = keyModule->type();
97  if (moduleType == CSwordModuleInfo::Bible
98  || moduleType == CSwordModuleInfo::Commentary)
99  {
101  *static_cast<CSwordVerseKey *>(m_swordKey));
102  } else if (moduleType == CSwordModuleInfo::GenericBook) {
103  auto const m = static_cast<CSwordBookModuleInfo const *>(keyModule);
104  CSwordTreeKey key(m->tree(), m);
105  key.setKey(m_swordKey->key());
106  CSwordTreeKey p(key);
107  p.positionToRoot();
108  if(p != key)
109  return static_cast<int>(key.offset() / 4u); /// \todo Check range!
110  } else if (moduleType == CSwordModuleInfo::Lexicon) {
111  return static_cast<CSwordLexiconModuleInfo const *>(
112  keyModule)->entries().indexOf(m_swordKey->key());
113  }
114  return 0;
115 }
116 
118  return m_moduleNames.count();
119 }
120 
122  constexpr static double const millimetersPerInch = 25.4;
123  return QGuiApplication::screens().first()->physicalDotsPerInchX()
124  / millimetersPerInch;
125 }
126 
127 void BtQmlInterface::setSelection(int const column,
128  int const startIndex,
129  int const endIndex,
130  QString const & selectedText)
131 {
132  BT_ASSERT(column >= 0);
133  BT_ASSERT(startIndex >= 0);
134  BT_ASSERT(endIndex >= 0);
135  BT_ASSERT(!selectedText.isEmpty());
136  m_selection.emplace(Selection{column, startIndex, endIndex, selectedText});
137 }
138 
139 void BtQmlInterface::clearSelection() noexcept { m_selection.reset(); }
140 
141 bool BtQmlInterface::hasSelectedText() const noexcept
142 { return m_selection.has_value(); }
143 
145 { return m_selection.has_value() ? m_selection->selectedText : QString(); }
146 
147 QString BtQmlInterface::getRawText(int row, int column) {
148  BT_ASSERT(column >= 0 && column <= m_moduleNames.count());
150  QString moduleName = m_moduleNames.at(column);
151  auto * const module =
153  CSwordVerseKey mKey(module);
154  mKey.setKey(key.key());
155  auto rawText = mKey.rawText();
156 
157  /* Since rawText is a complete HTML page at the moment, strip away headers
158  and footers of a HTML page, keeping only the contents of <body>: */
159  static auto const reFlags =
160  QRegularExpression::CaseInsensitiveOption
161  | QRegularExpression::DotMatchesEverythingOption
162  | QRegularExpression::DontCaptureOption
163  | QRegularExpression::UseUnicodePropertiesOption;
164  static QRegularExpression const reBefore(
165  QStringLiteral("^.*?<body(\\s[^>]*?)?>"), reFlags);
166  static QRegularExpression const reAfter(
167  QStringLiteral("</body>.*?$"), reFlags);
168  if (auto const m = reBefore.match(rawText); m.hasMatch())
169  rawText.remove(0, m.capturedLength());
170  if (auto const m = reAfter.match(rawText); m.hasMatch())
171  rawText.chop(m.capturedLength());
172  return rawText;
173 }
174 
175 void BtQmlInterface::openEditor(int row, int column) {
176  BtEditTextWizard wiz;
177  wiz.setTitle(tr("Edit %1").arg(m_moduleTextModel->indexToKeyName(row)));
178  wiz.setText(getRawText(row, column));
179  wiz.setFont(m_fonts.at(column));
180  if (wiz.exec() == QDialog::Accepted)
181  setRawText(row, column, wiz.text());
182 }
183 
185  return m_moduleTextModel->indexToVerse(index);
186 }
187 
188 void BtQmlInterface::setHoveredLink(QString const & link) {
189  if (QGuiApplication::keyboardModifiers() & Qt::ShiftModifier)
190  return;
191  setMagReferenceByUrl(link);
192  m_activeLink = link;
193 }
194 
195 QString BtQmlInterface::getLemmaFromLink(const QString& url) {
196  static QRegularExpression const rx(
197  QStringLiteral(R"PCRE(sword://lemmamorph/([a-s]+)=([GH][0-9]+))PCRE"));
198  if (auto const match = rx.match(url); match.hasMatch())
199  return match.captured(2);
200  return {};
201 }
202 
203 QString BtQmlInterface::getBibleUrlFromLink(const QString& url) {
204  static QRegularExpression const rx(
205  QStringLiteral(R"PCRE((sword://Bible/.*)\|\|(.*)=(.*))PCRE"));
206  if (auto const match = rx.match(url); match.hasMatch())
207  return match.captured(1);
208  return {};
209 }
210 
211 QString BtQmlInterface::getReferenceFromUrl(const QString& url) {
212  {
213  static QRegularExpression const rx(
214  QStringLiteral(
215  R"PCRE(sword://(bible|lexicon)/(.*)/(.*)(\|\|))PCRE"),
216  QRegularExpression::CaseInsensitiveOption);
217  if (auto const match = rx.match(url); match.hasMatch())
218  return QStringLiteral("href=sword://%1/%2/%3")
219  .arg(match.capturedView(1),
220  match.capturedView(2),
221  match.capturedView(3));
222  }{
223  static QRegularExpression const rx(
224  QStringLiteral(R"PCRE(sword://(bible|lexicon)/(.*)/(.*))PCRE"),
225  QRegularExpression::CaseInsensitiveOption);
226  if (auto const match = rx.match(url); match.hasMatch())
227  return QStringLiteral("href=sword://%1/%2/%3")
228  .arg(match.capturedView(1),
229  match.capturedView(2),
230  match.capturedView(3));
231  }{
232  static QRegularExpression const rx(
233  QStringLiteral(R"PCRE(sword://footnote/(.*)=(.*))PCRE"),
234  QRegularExpression::CaseInsensitiveOption);
235  if (auto const match = rx.match(url); match.hasMatch())
236  return QStringLiteral("note=%1").arg(match.capturedView(1));
237  }{
238  static QRegularExpression const rx(
239  QStringLiteral(R"PCRE(sword://lemmamorph/(.*)=(.*)/(.*))PCRE"),
240  QRegularExpression::CaseInsensitiveOption);
241  if (auto const match = rx.match(url); match.hasMatch())
242  return QStringLiteral("%1=%2").arg(match.capturedView(1),
243  match.capturedView(2));
244  }
245  return {};
246 }
247 
248 QString BtQmlInterface::rawText(int const row, int const column) {
249  return m_moduleTextModel->data(m_moduleTextModel->index(row, 0),
250  ModuleEntry::Text0Role + column).toString();
251 }
252 
253 void BtQmlInterface::setRawText(int row, int column, const QString& text) {
254  QModelIndex index = m_moduleTextModel->index(row, 0);
255  int role = ModuleEntry::Text0Role + column;
256  m_moduleTextModel->setData(index, text, role);
257 }
258 
260  killTimer(m_linkTimerId);
261  m_linkTimerId = 0;
262 }
263 
264 void BtQmlInterface::setMagReferenceByUrl(const QString& url) {
265  if (url.isEmpty())
266  return;
267  m_timeoutUrl = url;
268  cancelMagTimer();
269  m_linkTimerId = startTimer(400);
270 }
271 
275  Q_EMIT textChanged();
276 }
277 
279  decltype(m_fonts) newFonts;
280  newFonts.reserve(m_moduleNames.size());
281  for (auto const & moduleName : m_moduleNames) {
282  if (auto const * const m =
283  CSwordBackend::instance().findModuleByName(moduleName))
284  {
285  if (auto const lang = m->language()) {
287  if (fontPair.first) {
288  newFonts.append(fontPair.second);
289  continue;
290  }
291  }
292  }
293  newFonts.append(btConfig().getDefaultFont());
294  }
295  m_fonts = std::move(newFonts);
296  Q_EMIT fontChanged();
297 }
298 
299 void BtQmlInterface::setBibleKey(const QString& link) {
300  static QRegularExpression const rx(
301  QStringLiteral(R"PCRE(sword://Bible/(.*)/(.*)\|\|(.*)=(.*))PCRE"));
302  if (auto const match = rx.match(link); match.hasMatch())
303  Q_EMIT setBibleReference(match.captured(2));
304 }
305 
308 
309  /* Convert from sword index to ListView index */
312 
314  m_swordKey = key;
315  Q_EMIT currentModelIndexChanged();
316 }
317 
318 void BtQmlInterface::setModules(const QStringList &modules) {
319  m_moduleNames = modules;
320  m_moduleTextModel->setModules(modules);
322  Q_EMIT numModulesChanged();
323 }
324 
326  QString reference = m_moduleTextModel->indexToKeyName(i);
327  Q_EMIT updateReference(reference);
328 }
329 
331  QString moduleName;
332  QString keyName;
333 
334  static QRegularExpression const rx(
335  QStringLiteral(R"PCRE(sword://Bible/(.*)/(.*)\|\|(.*)=(.*))PCRE"));
336 
337  if (auto const match = rx.match(m_activeLink); match.hasMatch()) {
338  moduleName = match.captured(1);
339  keyName = match.captured(2);
340  } else {
341  moduleName = m_moduleNames.at(0);
342  keyName = m_moduleTextModel->indexToKeyName(index);
343  }
344 
345  Q_EMIT dragOccuring(moduleName, keyName);
346 }
347 
350 }
351 
352 QFont BtQmlInterface::font(int column) const {
353  if (column >= 0 && column < m_fonts.count())
354  return m_fonts.at(column);
355  return QApplication::font();
356 }
357 
358 QFont BtQmlInterface::getFont0() const { return font(0); }
359 QFont BtQmlInterface::getFont1() const { return font(1); }
360 QFont BtQmlInterface::getFont2() const { return font(2); }
361 QFont BtQmlInterface::getFont3() const { return font(3); }
362 QFont BtQmlInterface::getFont4() const { return font(4); }
363 QFont BtQmlInterface::getFont5() const { return font(5); }
364 QFont BtQmlInterface::getFont6() const { return font(6); }
365 QFont BtQmlInterface::getFont7() const { return font(7); }
366 QFont BtQmlInterface::getFont8() const { return font(8); }
367 QFont BtQmlInterface::getFont9() const { return font(9); }
368 
370  QVariant var;
371  var.setValue(m_moduleTextModel);
372  return var;
373 }
374 
376  return m_moduleTextModel;
377 }
378 
380 { return m_moduleTextModel; }
381 
383  if (column >= m_moduleNames.count())
384  return false;
385  QString moduleName = m_moduleNames.at(column);
386  auto * const module =
388  BT_ASSERT(module);
389  return module->isWritable();
390 }
391 
394  Q_EMIT backgroundColorChanged();
395  Q_EMIT foregroundColorChanged();
396 }
397 
398 void BtQmlInterface::copyRange(int index1, int index2) const {
399  QString text;
400  std::unique_ptr<CSwordKey> key(m_swordKey->copy());
401 
402  for (int i=index1; i<=index2; ++i) {
403  QString keyName = m_moduleTextModel->indexToKeyName(i);
404  key->setKey(keyName);
405  text.append(QStringLiteral("%1\n%2\n\n")
406  .arg(keyName, key->strippedText()));
407  }
408  QClipboard *clipboard = QGuiApplication::clipboard();
409  clipboard->setText(text);
410 }
411 
413  CSwordVerseKey const & key2)
414 {
415  BT_ASSERT(key1.module());
416  BT_ASSERT(key1.module() == key2.module());
417 
419  {
420  DisplayOptions displayOptions;
421  displayOptions.lineBreaks = true;
422  displayOptions.verseNumbers = true;
423  render.setDisplayOptions(displayOptions);
424  }{
425  FilterOptions filterOptions;
426  filterOptions.footnotes = 0;
427  filterOptions.greekAccents = 1;
428  filterOptions.headings = 1;
429  filterOptions.hebrewCantillation = 1;
430  filterOptions.hebrewPoints = 1;
431  filterOptions.lemmas = 0;
432  filterOptions.morphSegmentation = 1;
433  filterOptions.morphTags = 0;
434  filterOptions.redLetterWords = 1;
435  filterOptions.scriptureReferences = 0;
436  filterOptions.strongNumbers = 0;
437  filterOptions.textualVariants = 0;
438  render.setFilterOptions(filterOptions);
439  }
440  QGuiApplication::clipboard()->setText(
441  render.renderKeyRange(key1, key2, {key1.module()}));
442 }
443 
444 void BtQmlInterface::setHighlightWords(const QString& words, bool caseSensitive) {
445  QApplication::setOverrideCursor(Qt::WaitCursor);
446  m_moduleTextModel->setHighlightWords(words, caseSensitive);
447  m_findState.reset();
449  QApplication::restoreOverrideCursor();
450 }
451 
452 void BtQmlInterface::timerEvent(QTimerEvent * const event) {
453  auto const timerId = event->timerId();
454  BT_ASSERT(timerId);
455  if (timerId == m_linkTimerId) {
456  event->accept();
457  cancelMagTimer();
459  if (!infoList.isEmpty())
460  BibleTime::instance()->infoDisplay()->setInfo(std::move(infoList));
461  } else {
462  QObject::timerEvent(event);
463  }
464 }
465 
466 void BtQmlInterface::findText(bool const backward) {
467  QApplication::setOverrideCursor(Qt::WaitCursor);
468  if (!m_findState)
470 
471  auto const countHighlightsInItem =
472  [this](int const index) {
473  return m_moduleTextModel->data(m_moduleTextModel->index(index),
475  .toString().count(QStringLiteral("\"highlightwords"));
476  };
477 
478  auto const num = countHighlightsInItem(m_findState->index);
479  if (backward) { // get previous matching item:
480  if (num > 0 && m_findState->subIndex == 0) {
481  // Found within m_findState->index item
482  m_findState->subIndex = 1;
483  } else if (auto index = m_findState->index; index > 0) {
484  if (m_findState->subIndex == 0)
485  --index;
486  for (int i = 0; i < 1000; ++i, --index) {
487  if (auto const num2 = countHighlightsInItem(index)) {
488  m_findState->index = index;
489  if (m_findState->subIndex == 0) {
490  m_findState->subIndex = num2;
491  } else {
492  --m_findState->subIndex;
493  }
494  if (m_findState->subIndex != 0)
495  break;
496  }
497  }
498  }
499  } else { // get next matching item:
500  if (num > m_findState->subIndex) {
501  // Found within m_findState->index item
502  ++m_findState->subIndex;
503  } else if (m_findState->index < m_moduleTextModel->rowCount()) {
504  auto index = m_findState->index + 1;
505  for (int i = 0; i < 1000; ++i, ++index) {
506  if (countHighlightsInItem(index)) {
507  m_findState->index = index;
508  m_findState->subIndex = 1;
509  break;
510  }
511  }
512  }
513  }
514 
516  Q_EMIT positionItemOnScreen(m_findState->index);
517  QApplication::restoreOverrideCursor();
518 }
519 
#define BT_ASSERT(...)
Definition: btassert.h:17
BtConfig & btConfig()
This is a shortchand for BtConfig::getInstance().
Definition: btconfig.h:305
InfoDisplay::CInfoDisplay * infoDisplay() const noexcept
Definition: bibletime.h:159
static BibleTime * instance() noexcept
Definition: bibletime.h:143
QPair< bool, QFont > FontSettingsPair
Definition: btconfig.h:47
FontSettingsPair getFontForLanguage(Language const &language)
Get font for a language.
Definition: btconfig.cpp:364
The Edit Text wizard for editing the personal commentary.
QString text() const
void setFont(const QFont &font)
void setText(const QString &text)
void setTitle(const QString &text)
Model that represents the entire text of a given module.
CSwordKey * indexToKey(int index, int moduleNum) const
int rowCount(const QModelIndex &parent=QModelIndex()) const override
int firstEntryIndex() const noexcept
int verseKeyToIndex(const CSwordVerseKey &key) 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 indexToKeyName(int index) const
void setTextFilter(BtModuleTextFilter *textFilter)
virtual bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) override
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
void setFindState(std::optional< FindState > findState)
Q_INVOKABLE void setRawText(int row, int column, const QString &text)
void scrollToSwordKey(CSwordKey *key)
QString getBibleUrlFromLink(const QString &url)
void backgroundColorChanged()
void contextMenuIndexChanged()
Q_INVOKABLE bool moduleIsWritable(int column)
void dragOccuring(const QString &moduleName, const QString &keyName)
QList< QFont > m_fonts
int getContextMenuColumn() const
QFont getFont3() const
void setContextMenuColumn(int index)
Q_INVOKABLE void changeReference(int i)
Q_INVOKABLE void setBibleKey(const QString &link)
QVariant textModel
void setBibleReference(const QString &reference)
void textChanged()
QFont getFont6() const
int getContextMenuIndex() const
QString m_timeoutUrl
QString m_activeLink
void setMagReferenceByUrl(const QString &url)
QFont getFont2() const
void backgroundHighlightColorChanged()
CSwordKey * getMouseClickedKey() const
int m_backgroundHighlightColorIndex
std::optional< Selection > m_selection
QFont getFont0() const
int getCurrentModelIndex() const
QColor getBackgroundColor() const
QFont getFont8() const
QColor getForegroundColor() const
Q_INVOKABLE int indexToVerse(int index)
void setContextMenuIndex(int index)
std::optional< FindState > m_findState
void fontChanged()
static int typeId
int getBackgroundHighlightColorIndex() const
int getNumModules() const
~BtQmlInterface() override
QString getRawText(int row, int column)
Q_INVOKABLE void setHoveredLink(QString const &link)
QString getSelectedText() const
void updateReference(const QString &reference)
void foregroundColorChanged()
void setModules(const QStringList &modules)
QFont getFont9() const
static void copyVerseRange(CSwordVerseKey const &key1, CSwordVerseKey const &key2)
void timerEvent(QTimerEvent *event) final override
void setHighlightWords(const QString &words, bool caseSensitivy)
QFont getFont7() const
void setFilterOptions(FilterOptions filterOptions)
CSwordKey * m_swordKey
bool isBibleOrCommentary()
QString getReferenceFromUrl(const QString &url)
QFont getFont5() const
Q_INVOKABLE void openEditor(int row, int column)
void positionItemOnScreen(int index)
void currentModelIndexChanged()
void findText(bool backward)
QFont getFont4() const
BtTextFilter m_textFilter
void contextMenuColumnChanged()
QVariant getTextModel()
QFont font(int column) const
QColor getBackgroundHighlightColor() const
Q_INVOKABLE void setSelection(int column, int startIndex, int endIndex, QString const &selectedText)
bool hasSelectedText() const noexcept
void numModulesChanged()
double getPixelsPerMM() const
Q_INVOKABLE void dragHandler(int index)
BtModuleTextModel *const m_moduleTextModel
Q_INVOKABLE QString rawText(int row, int column)
void backgroundHighlightColorIndexChanged()
void getFontsFromSettings()
Q_INVOKABLE void clearSelection() noexcept
BtQmlInterface(QObject *parent=nullptr)
QFont getFont1() const
void copyRange(int index1, int index2) const
QStringList m_moduleNames
QString getLemmaFromLink(const QString &url)
CSwordModuleInfo * findModuleByName(const QString &name) const
Searches for a module with the given name.
static CSwordBackend & instance() noexcept
Definition: cswordbackend.h:98
Class for generic book support.
virtual CSwordKey * copy() const =0
CSwordModuleInfo const * module() const
Definition: cswordkey.h:68
QString rawText()
Definition: cswordkey.cpp:45
virtual QString key() const =0
ModuleType type() const
CSwordKey implementation for Sword's TreeKey.
Definition: cswordtreekey.h:43
void positionToRoot()
Definition: cswordtreekey.h:99
Offset offset() const
bool setKey(const QString &key) final override
CSwordKey implementation for Sword's VerseKey.
QString key() const final override
bool setKey(const QString &key) final override
void setInfo(const QString &renderedData, const QString &lang=QString())
QString renderKeyRange(CSwordVerseKey const &lowerBound, CSwordVerseKey const &upperBound, const BtConstModuleList &modules, const QString &hightlightKey=QString(), const KeyTreeItem::Settings &settings=KeyTreeItem::Settings())
void setFilterOptions(FilterOptions const &filterOptions) noexcept
void setDisplayOptions(DisplayOptions const &displayOptions) noexcept
QString getForegroundColor()
QString getBackgroundColor()
QString getBackgroundHighlightColor()
ListInfoData detectInfo(QString const &data)
int lineBreaks
Definition: btglobal.h:47
int verseNumbers
Definition: btglobal.h:48
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
int strongNumbers
Definition: btglobal.h:27