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-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 "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/rendering/btinforendering.h"
30#include "../../../backend/rendering/cplaintextexportrendering.h"
31#include "../../../backend/rendering/ctextrendering.h"
32#include "../../../backend/rendering/btinforendering.h"
33#include "../../../util/btassert.h"
34#include "../../bibletime.h"
35#include "../../btmoduleindexdialog.h"
36#include "../../cinfodisplay.h"
37#include "../../edittextwizard/btedittextwizard.h"
38
39
41 : QObject(parent)
42 , m_moduleTextModel(new BtModuleTextModel(this))
43{}
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
60
65
69
74
77
80
84
87
89 if (m_swordKey == nullptr)
90 return 0;
91 auto const * const keyModule = m_swordKey->module();
92 auto const moduleType = keyModule->type();
93 if (moduleType == CSwordModuleInfo::Bible
94 || moduleType == CSwordModuleInfo::Commentary)
95 {
97 *static_cast<CSwordVerseKey *>(m_swordKey));
98 } else if (moduleType == CSwordModuleInfo::GenericBook) {
99 auto const m = static_cast<CSwordBookModuleInfo const *>(keyModule);
100 CSwordTreeKey key(m->tree(), m);
101 key.setKey(m_swordKey->key());
102 CSwordTreeKey p(key);
103 p.positionToRoot();
104 if(p != key)
105 return static_cast<int>(key.offset() / 4u); /// \todo Check range!
106 } else if (moduleType == CSwordModuleInfo::Lexicon) {
107 return static_cast<CSwordLexiconModuleInfo const *>(
108 keyModule)->entries().indexOf(m_swordKey->key());
109 }
110 return 0;
111}
112
114 return m_moduleNames.count();
115}
116
118 constexpr static double const millimetersPerInch = 25.4;
119 return QGuiApplication::screens().first()->physicalDotsPerInchX()
120 / millimetersPerInch;
121}
122
123void BtQmlInterface::setSelection(int const column,
124 int const startIndex,
125 int const endIndex,
126 QString const & selectedText)
127{
128 BT_ASSERT(column >= 0);
129 BT_ASSERT(startIndex >= 0);
130 BT_ASSERT(endIndex >= 0);
131 BT_ASSERT(!selectedText.isEmpty());
132 Selection newSelection{column, startIndex, endIndex, selectedText};
133 if (m_selection != newSelection) {
134 m_selection.emplace(newSelection);
135 Q_EMIT selectionChanged(std::move(newSelection));
136 }
137}
138
139void BtQmlInterface::clearSelection() noexcept { m_selection.reset(); }
140
142{ return m_selection.has_value() ? m_selection->selectedText : QString(); }
143
144void BtQmlInterface::openEditor(int row, int column) {
146 wiz.setTitle(tr("Edit %1").arg(m_moduleTextModel->indexToKeyName(row)));
147 wiz.setText(
149 m_moduleTextModel->index(row, 0),
150 ModuleEntry::Edit0Role + column).toString());
151 wiz.setFont(m_fonts.at(column));
152 if (wiz.exec() == QDialog::Accepted)
153 setRawText(row, column, wiz.text());
154}
155
157 return m_moduleTextModel->indexToVerse(index);
158}
159
160void BtQmlInterface::setHoveredLink(QString const & link) {
161 if (QGuiApplication::keyboardModifiers() & Qt::ShiftModifier)
162 return;
164
165 if (m_activeLink != link) {
166 m_activeLink = link;
167 activeLinkChanged(link);
168 }
169}
170
171QString BtQmlInterface::getLemmaFromLink(const QString& url) {
172 static QRegularExpression const rx(
173 QStringLiteral(R"PCRE(sword://lemmamorph/([a-s]+)=([GH][0-9]+))PCRE"));
174 if (auto const match = rx.match(url); match.hasMatch())
175 return match.captured(2);
176 return {};
177}
178
179QString BtQmlInterface::getBibleUrlFromLink(const QString& url) {
180 static QRegularExpression const rx(
181 QStringLiteral(R"PCRE((sword://Bible/.*)\|\|(.*)=(.*))PCRE"));
182 if (auto const match = rx.match(url); match.hasMatch())
183 return match.captured(1);
184 return {};
185}
186
187QString BtQmlInterface::getReferenceFromUrl(const QString& url) {
188 {
189 static QRegularExpression const rx(
190 QStringLiteral(
191 R"PCRE(sword://(bible|lexicon)/(.*)/(.*)(\|\|))PCRE"),
192 QRegularExpression::CaseInsensitiveOption);
193 if (auto const match = rx.match(url); match.hasMatch())
194 return QStringLiteral("href=sword://%1/%2/%3")
195 .arg(match.capturedView(1),
196 match.capturedView(2),
197 match.capturedView(3));
198 }{
199 static QRegularExpression const rx(
200 QStringLiteral(R"PCRE(sword://(bible|lexicon)/(.*)/(.*))PCRE"),
201 QRegularExpression::CaseInsensitiveOption);
202 if (auto const match = rx.match(url); match.hasMatch())
203 return QStringLiteral("href=sword://%1/%2/%3")
204 .arg(match.capturedView(1),
205 match.capturedView(2),
206 match.capturedView(3));
207 }{
208 static QRegularExpression const rx(
209 QStringLiteral(R"PCRE(sword://footnote/(.*)=(.*))PCRE"),
210 QRegularExpression::CaseInsensitiveOption);
211 if (auto const match = rx.match(url); match.hasMatch())
212 return QStringLiteral("note=%1").arg(match.capturedView(1));
213 }{
214 static QRegularExpression const rx(
215 QStringLiteral(R"PCRE(sword://lemmamorph/(.*)=(.*)/(.*))PCRE"),
216 QRegularExpression::CaseInsensitiveOption);
217 if (auto const match = rx.match(url); match.hasMatch())
218 return QStringLiteral("%1=%2").arg(match.capturedView(1),
219 match.capturedView(2));
220 }
221 return {};
222}
223
224QString BtQmlInterface::rawText(int const row, int const column) {
225 return m_moduleTextModel->data(m_moduleTextModel->index(row, 0),
226 ModuleEntry::Text0Role + column).toString();
227}
228
229void BtQmlInterface::setRawText(int row, int column, const QString& text) {
230 QModelIndex index = m_moduleTextModel->index(row, 0);
231 int const role = ModuleEntry::Edit0Role + column;
232 Q_ASSERT(column < m_moduleNames.size());
233 if (m_moduleTextModel->setData(index, text, role)) {
234 if (auto * const module =
235 CSwordBackend::instance().findModuleByName(
236 m_moduleNames[column]))
237 {
238 if (module->hasIndex()) {
239 module->deleteIndex();
241 }
242 }
243 }
244}
245
247 killTimer(m_linkTimerId);
248 m_linkTimerId = 0;
249}
250
251void BtQmlInterface::setMagReferenceByUrl(const QString& url) {
252 if (url.isEmpty())
253 return;
254 m_timeoutUrl = url;
256 m_linkTimerId = startTimer(400);
257}
258
264
266 decltype(m_fonts) newFonts;
267 newFonts.reserve(m_moduleNames.size());
268 for (auto const & moduleName : m_moduleNames) {
269 if (auto const * const m =
271 {
272 if (auto const lang = m->language()) {
274 if (fontPair.first) {
275 newFonts.append(fontPair.second);
276 continue;
277 }
278 }
279 }
280 newFonts.append(btConfig().getDefaultFont());
281 }
282 m_fonts = std::move(newFonts);
283 Q_EMIT fontChanged();
284}
285
286void BtQmlInterface::setBibleKey(const QString& link) {
287 static QRegularExpression const rx(
288 QStringLiteral(R"PCRE(sword://Bible/(.*)/(.*)\|\|(.*)=(.*))PCRE"));
289 if (auto const match = rx.match(link); match.hasMatch())
290 Q_EMIT setBibleReference(match.captured(2));
291}
292
304
305void BtQmlInterface::setModules(const QStringList &modules) {
306 m_moduleNames = modules;
309 Q_EMIT numModulesChanged();
310}
311
313 QString reference = m_moduleTextModel->indexToKeyName(i);
314 Q_EMIT updateReference(reference);
315}
316
318 QString moduleName;
319 QString keyName;
320
321 static QRegularExpression const rx(
322 QStringLiteral(R"PCRE(sword://Bible/(.*)/(.*)\|\|(.*)=(.*))PCRE"));
323
324 if (auto const match = rx.match(m_activeLink); match.hasMatch()) {
325 moduleName = match.captured(1);
326 keyName = match.captured(2);
327 } else {
328 moduleName = m_moduleNames.at(0);
329 keyName = m_moduleTextModel->indexToKeyName(index);
330 }
331
332 Q_EMIT dragOccuring(moduleName, keyName);
333}
334
338
339QFont BtQmlInterface::font(int column) const {
340 if (column >= 0 && column < m_fonts.count())
341 return m_fonts.at(column);
342 return QApplication::font();
343}
344
345QFont BtQmlInterface::getFont0() const { return font(0); }
346QFont BtQmlInterface::getFont1() const { return font(1); }
347QFont BtQmlInterface::getFont2() const { return font(2); }
348QFont BtQmlInterface::getFont3() const { return font(3); }
349QFont BtQmlInterface::getFont4() const { return font(4); }
350QFont BtQmlInterface::getFont5() const { return font(5); }
351QFont BtQmlInterface::getFont6() const { return font(6); }
352QFont BtQmlInterface::getFont7() const { return font(7); }
353QFont BtQmlInterface::getFont8() const { return font(8); }
354QFont BtQmlInterface::getFont9() const { return font(9); }
355
357 QVariant var;
358 var.setValue(m_moduleTextModel);
359 return var;
360}
361
363 return m_moduleTextModel;
364}
365
368
370 if (column >= m_moduleNames.count())
371 return false;
372 QString moduleName = m_moduleNames.at(column);
373 auto * const module =
374 CSwordBackend::instance().findModuleByName(moduleName);
375 BT_ASSERT(module);
376 return module->isWritable();
377}
378
384
385void BtQmlInterface::copyRange(int index1, int index2) const {
386 QString text;
387 std::unique_ptr<CSwordKey> key(m_swordKey->copy());
388
389 for (int i=index1; i<=index2; ++i) {
390 QString keyName = m_moduleTextModel->indexToKeyName(i);
391 key->setKey(keyName);
392 text.append(QStringLiteral("%1\n%2\n\n")
393 .arg(keyName, key->strippedText()));
394 }
395 QClipboard *clipboard = QGuiApplication::clipboard();
396 clipboard->setText(text);
397}
398
400 CSwordVerseKey const & key2)
401{
402 BT_ASSERT(key1.module());
403 BT_ASSERT(key1.module() == key2.module());
404
406 {
407 DisplayOptions displayOptions;
408 displayOptions.lineBreaks = true;
409 displayOptions.verseNumbers = true;
410 render.setDisplayOptions(displayOptions);
411 }{
412 FilterOptions filterOptions;
413 filterOptions.footnotes = 0;
414 filterOptions.greekAccents = 1;
415 filterOptions.headings = 1;
416 filterOptions.hebrewCantillation = 1;
417 filterOptions.hebrewPoints = 1;
418 filterOptions.lemmas = 0;
419 filterOptions.morphSegmentation = 1;
420 filterOptions.morphTags = 0;
421 filterOptions.redLetterWords = 1;
422 filterOptions.scriptureReferences = 0;
423 filterOptions.strongNumbers = 0;
424 filterOptions.textualVariants = 0;
425 render.setFilterOptions(filterOptions);
426 }
427 QGuiApplication::clipboard()->setText(
428 render.renderKeyRange(key1, key2, {key1.module()}));
429}
430
431void BtQmlInterface::setHighlightWords(const QString& words, bool caseSensitive) {
432 QApplication::setOverrideCursor(Qt::WaitCursor);
433 m_moduleTextModel->setHighlightWords(words, caseSensitive);
434 m_findState.reset();
436 QApplication::restoreOverrideCursor();
437}
438
439void BtQmlInterface::timerEvent(QTimerEvent * const event) {
440 auto const timerId = event->timerId();
441 BT_ASSERT(timerId);
442 if (timerId == m_linkTimerId) {
443 event->accept();
446 if (!infoList.isEmpty())
447 BibleTime::instance()->infoDisplay()->setInfo(std::move(infoList));
448 } else {
449 QObject::timerEvent(event);
450 }
451}
452
453void BtQmlInterface::findText(bool const backward) {
454 QApplication::setOverrideCursor(Qt::WaitCursor);
455 if (!m_findState)
457
458 auto const countHighlightsInItem =
459 [this](int const index) {
460 return m_moduleTextModel->data(m_moduleTextModel->index(index),
462 .toString().count(QStringLiteral("\"highlightwords"));
463 };
464
465 auto const num = countHighlightsInItem(m_findState->index);
466 if (backward) { // get previous matching item:
467 if (num > 0 && m_findState->subIndex == 0) {
468 // Found within m_findState->index item
469 m_findState->subIndex = 1;
470 } else if (auto index = m_findState->index; index > 0) {
471 if (m_findState->subIndex == 0)
472 --index;
473 for (int i = 0; i < 1000; ++i, --index) {
474 if (auto const num2 = countHighlightsInItem(index)) {
475 m_findState->index = index;
476 if (m_findState->subIndex == 0) {
477 m_findState->subIndex = num2;
478 } else {
479 --m_findState->subIndex;
480 }
481 if (m_findState->subIndex != 0)
482 break;
483 }
484 }
485 }
486 } else { // get next matching item:
487 if (num > m_findState->subIndex) {
488 // Found within m_findState->index item
489 ++m_findState->subIndex;
490 } else if (m_findState->index < m_moduleTextModel->rowCount()) {
491 auto index = m_findState->index + 1;
492 for (int i = 0; i < 1000; ++i, ++index) {
493 if (countHighlightsInItem(index)) {
494 m_findState->index = index;
495 m_findState->subIndex = 1;
496 break;
497 }
498 }
499 }
500 }
501
503 Q_EMIT positionItemOnScreen(m_findState->index);
504 QApplication::restoreOverrideCursor();
505}
506
#define BT_ASSERT(...)
Definition btassert.h:17
BtConfig & btConfig()
This is a shortchand for BtConfig::getInstance().
Definition btconfig.h:305
static BibleTime * instance() noexcept
Definition bibletime.h:222
InfoDisplay::CInfoDisplay * infoDisplay() const noexcept
Definition bibletime.h:238
QPair< bool, QFont > FontSettingsPair
Definition btconfig.h:47
FontSettingsPair getFontForLanguage(Language const &language)
Get font for a language.
Definition btconfig.cpp:355
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)
static bool indexAllModules(QList< CSwordModuleInfo * > const &modules)
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
QString indexToKeyName(int index) const
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)
void selectionChanged(std::optional< Selection > newSelection)
Q_INVOKABLE void setBibleKey(const QString &link)
void setBibleReference(const QString &reference)
void activeLinkChanged(QString newActiveLink)
void textChanged()
QFont getFont6() const
int getContextMenuIndex() const
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
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
CSwordKey * m_swordKey
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
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)
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()
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
Class for generic book support.
CSwordModuleInfo const * module() const
Definition cswordkey.h:68
virtual CSwordKey * copy() const =0
virtual QString key() const =0
ModuleType type() const
CSwordKey implementation for Sword's TreeKey.
void positionToRoot()
Offset offset() const
bool setKey(const QString &key) final override
CSwordKey implementation for Sword's VerseKey.
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 morphSegmentation
Definition btglobal.h:37
int hebrewCantillation
Definition btglobal.h:32
int textualVariants
Definition btglobal.h:34
int hebrewPoints
Definition btglobal.h:31
int scriptureReferences
Definition btglobal.h:36
int greekAccents
Definition btglobal.h:33
int redLetterWords
Definition btglobal.h:35
int strongNumbers
Definition btglobal.h:27