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
140 m_selection.reset();
141 Q_EMIT selectionChanged({});
142}
143
145{ return m_selection.has_value() ? m_selection->selectedText : QString(); }
146
147void BtQmlInterface::openEditor(int row, int column) {
149 wiz.setTitle(tr("Edit %1").arg(m_moduleTextModel->indexToKeyName(row)));
150 wiz.setText(
152 m_moduleTextModel->index(row, 0),
153 ModuleEntry::Edit0Role + column).toString());
154 wiz.setFont(m_fonts.at(column));
155 if (wiz.exec() == QDialog::Accepted)
156 setRawText(row, column, wiz.text());
157}
158
160 return m_moduleTextModel->indexToVerse(index);
161}
162
163void BtQmlInterface::setHoveredLink(QString const & link) {
164 if (QGuiApplication::keyboardModifiers() & Qt::ShiftModifier)
165 return;
167
168 if (m_activeLink != link) {
169 m_activeLink = link;
170 activeLinkChanged(link);
171 }
172}
173
174QString BtQmlInterface::getLemmaFromLink(const QString& url) {
175 static QRegularExpression const rx(
176 QStringLiteral(R"PCRE(sword://lemmamorph/([a-s]+)=([GH][0-9]+))PCRE"));
177 if (auto const match = rx.match(url); match.hasMatch())
178 return match.captured(2);
179 return {};
180}
181
182QString BtQmlInterface::getBibleUrlFromLink(const QString& url) {
183 static QRegularExpression const rx(
184 QStringLiteral(R"PCRE((sword://Bible/.*)\|\|(.*)=(.*))PCRE"));
185 if (auto const match = rx.match(url); match.hasMatch())
186 return match.captured(1);
187 return {};
188}
189
190QString BtQmlInterface::getReferenceFromUrl(const QString& url) {
191 {
192 static QRegularExpression const rx(
193 QStringLiteral(
194 R"PCRE(sword://(bible|lexicon)/(.*)/(.*)(\|\|))PCRE"),
195 QRegularExpression::CaseInsensitiveOption);
196 if (auto const match = rx.match(url); match.hasMatch())
197 return QStringLiteral("href=sword://%1/%2/%3")
198 .arg(match.capturedView(1),
199 match.capturedView(2),
200 match.capturedView(3));
201 }{
202 static QRegularExpression const rx(
203 QStringLiteral(R"PCRE(sword://(bible|lexicon)/(.*)/(.*))PCRE"),
204 QRegularExpression::CaseInsensitiveOption);
205 if (auto const match = rx.match(url); match.hasMatch())
206 return QStringLiteral("href=sword://%1/%2/%3")
207 .arg(match.capturedView(1),
208 match.capturedView(2),
209 match.capturedView(3));
210 }{
211 static QRegularExpression const rx(
212 QStringLiteral(R"PCRE(sword://footnote/(.*)=(.*))PCRE"),
213 QRegularExpression::CaseInsensitiveOption);
214 if (auto const match = rx.match(url); match.hasMatch())
215 return QStringLiteral("note=%1").arg(match.capturedView(1));
216 }{
217 static QRegularExpression const rx(
218 QStringLiteral(R"PCRE(sword://lemmamorph/(.*)=(.*)/(.*))PCRE"),
219 QRegularExpression::CaseInsensitiveOption);
220 if (auto const match = rx.match(url); match.hasMatch())
221 return QStringLiteral("%1=%2").arg(match.capturedView(1),
222 match.capturedView(2));
223 }
224 return {};
225}
226
227QString BtQmlInterface::rawText(int const row, int const column) {
228 return m_moduleTextModel->data(m_moduleTextModel->index(row, 0),
229 ModuleEntry::Text0Role + column).toString();
230}
231
232void BtQmlInterface::setRawText(int row, int column, const QString& text) {
233 QModelIndex index = m_moduleTextModel->index(row, 0);
234 int const role = ModuleEntry::Edit0Role + column;
235 Q_ASSERT(column < m_moduleNames.size());
236 if (m_moduleTextModel->setData(index, text, role)) {
237 if (auto * const module =
238 CSwordBackend::instance().findModuleByName(
239 m_moduleNames[column]))
240 {
241 if (module->hasIndex()) {
242 module->deleteIndex();
244 }
245 }
246 }
247}
248
250 killTimer(m_linkTimerId);
251 m_linkTimerId = 0;
252}
253
254void BtQmlInterface::setMagReferenceByUrl(const QString& url) {
255 if (url.isEmpty())
256 return;
257 m_timeoutUrl = url;
259 m_linkTimerId = startTimer(400);
260}
261
267
269 decltype(m_fonts) newFonts;
270 newFonts.reserve(m_moduleNames.size());
271 for (auto const & moduleName : m_moduleNames) {
272 if (auto const * const m =
274 {
275 if (auto const lang = m->language()) {
277 if (fontPair.first) {
278 newFonts.append(fontPair.second);
279 continue;
280 }
281 }
282 }
283 newFonts.append(btConfig().getDefaultFont());
284 }
285 m_fonts = std::move(newFonts);
286 Q_EMIT fontChanged();
287}
288
289void BtQmlInterface::setBibleKey(const QString& link) {
290 static QRegularExpression const rx(
291 QStringLiteral(R"PCRE(sword://Bible/(.*)/(.*)\|\|(.*)=(.*))PCRE"));
292 if (auto const match = rx.match(link); match.hasMatch())
293 Q_EMIT setBibleReference(match.captured(2));
294}
295
307
308void BtQmlInterface::setModules(const QStringList &modules) {
309 m_moduleNames = modules;
312 Q_EMIT numModulesChanged();
313}
314
316 QString reference = m_moduleTextModel->indexToKeyName(i);
317 Q_EMIT updateReference(reference);
318}
319
321 QString moduleName;
322 QString keyName;
323
324 static QRegularExpression const rx(
325 QStringLiteral(R"PCRE(sword://Bible/(.*)/(.*)\|\|(.*)=(.*))PCRE"));
326
327 if (auto const match = rx.match(m_activeLink); match.hasMatch()) {
328 moduleName = match.captured(1);
329 keyName = match.captured(2);
330 } else {
331 moduleName = m_moduleNames.at(0);
332 keyName = m_moduleTextModel->indexToKeyName(index);
333 }
334
335 Q_EMIT dragOccuring(moduleName, keyName);
336}
337
341
342QFont BtQmlInterface::font(int column) const {
343 if (column >= 0 && column < m_fonts.count())
344 return m_fonts.at(column);
345 return QApplication::font();
346}
347
348QFont BtQmlInterface::getFont0() const { return font(0); }
349QFont BtQmlInterface::getFont1() const { return font(1); }
350QFont BtQmlInterface::getFont2() const { return font(2); }
351QFont BtQmlInterface::getFont3() const { return font(3); }
352QFont BtQmlInterface::getFont4() const { return font(4); }
353QFont BtQmlInterface::getFont5() const { return font(5); }
354QFont BtQmlInterface::getFont6() const { return font(6); }
355QFont BtQmlInterface::getFont7() const { return font(7); }
356QFont BtQmlInterface::getFont8() const { return font(8); }
357QFont BtQmlInterface::getFont9() const { return font(9); }
358
360 QVariant var;
361 var.setValue(m_moduleTextModel);
362 return var;
363}
364
366 return m_moduleTextModel;
367}
368
371
373 if (column >= m_moduleNames.count())
374 return false;
375 QString moduleName = m_moduleNames.at(column);
376 auto * const module =
377 CSwordBackend::instance().findModuleByName(moduleName);
378 BT_ASSERT(module);
379 return module->isWritable();
380}
381
387
388void BtQmlInterface::copyRange(int index1, int index2) const {
389 QString text;
390 std::unique_ptr<CSwordKey> key(m_swordKey->copy());
391
392 for (int i=index1; i<=index2; ++i) {
393 QString keyName = m_moduleTextModel->indexToKeyName(i);
394 key->setKey(keyName);
395 text.append(QStringLiteral("%1\n%2\n\n")
396 .arg(keyName, key->strippedText()));
397 }
398 QClipboard *clipboard = QGuiApplication::clipboard();
399 clipboard->setText(text);
400}
401
403 CSwordVerseKey const & key2)
404{
405 BT_ASSERT(key1.module());
406 BT_ASSERT(key1.module() == key2.module());
407
409 {
410 DisplayOptions displayOptions;
411 displayOptions.lineBreaks = true;
412 displayOptions.verseNumbers = true;
413 render.setDisplayOptions(displayOptions);
414 }{
415 FilterOptions filterOptions;
416 filterOptions.footnotes = 0;
417 filterOptions.greekAccents = 1;
418 filterOptions.headings = 1;
419 filterOptions.hebrewCantillation = 1;
420 filterOptions.hebrewPoints = 1;
421 filterOptions.lemmas = 0;
422 filterOptions.morphSegmentation = 1;
423 filterOptions.morphTags = 0;
424 filterOptions.redLetterWords = 1;
425 filterOptions.scriptureReferences = 0;
426 filterOptions.strongNumbers = 0;
427 filterOptions.textualVariants = 0;
428 render.setFilterOptions(filterOptions);
429 }
430 QGuiApplication::clipboard()->setText(
431 render.renderKeyRange(key1, key2, {key1.module()}));
432}
433
434void BtQmlInterface::setHighlightWords(const QString& words, bool caseSensitive) {
435 QApplication::setOverrideCursor(Qt::WaitCursor);
436 m_moduleTextModel->setHighlightWords(words, caseSensitive);
437 m_findState.reset();
439 QApplication::restoreOverrideCursor();
440}
441
442void BtQmlInterface::timerEvent(QTimerEvent * const event) {
443 auto const timerId = event->timerId();
444 BT_ASSERT(timerId);
445 if (timerId == m_linkTimerId) {
446 event->accept();
449 if (!infoList.isEmpty())
450 BibleTime::instance()->infoDisplay()->setInfo(std::move(infoList));
451 } else {
452 QObject::timerEvent(event);
453 }
454}
455
456void BtQmlInterface::findText(bool const backward) {
457 QApplication::setOverrideCursor(Qt::WaitCursor);
458 if (!m_findState)
460
461 auto const countHighlightsInItem =
462 [this](int const index) {
463 return m_moduleTextModel->data(m_moduleTextModel->index(index),
465 .toString().count(QStringLiteral("\"highlightwords"));
466 };
467
468 auto const num = countHighlightsInItem(m_findState->index);
469 if (backward) { // get previous matching item:
470 if (num > 0 && m_findState->subIndex == 0) {
471 // Found within m_findState->index item
472 m_findState->subIndex = 1;
473 } else if (auto index = m_findState->index; index > 0) {
474 if (m_findState->subIndex == 0)
475 --index;
476 for (int i = 0; i < 1000; ++i, --index) {
477 if (auto const num2 = countHighlightsInItem(index)) {
478 m_findState->index = index;
479 if (m_findState->subIndex == 0) {
480 m_findState->subIndex = num2;
481 } else {
482 --m_findState->subIndex;
483 }
484 if (m_findState->subIndex != 0)
485 break;
486 }
487 }
488 }
489 } else { // get next matching item:
490 if (num > m_findState->subIndex) {
491 // Found within m_findState->index item
492 ++m_findState->subIndex;
493 } else if (m_findState->index < m_moduleTextModel->rowCount()) {
494 auto index = m_findState->index + 1;
495 for (int i = 0; i < 1000; ++i, ++index) {
496 if (countHighlightsInItem(index)) {
497 m_findState->index = index;
498 m_findState->subIndex = 1;
499 break;
500 }
501 }
502 }
503 }
504
506 Q_EMIT positionItemOnScreen(m_findState->index);
507 QApplication::restoreOverrideCursor();
508}
509
#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:225
InfoDisplay::CInfoDisplay * infoDisplay() const noexcept
Definition bibletime.h:241
QPair< bool, QFont > FontSettingsPair
Definition btconfig.h:47
FontSettingsPair getFontForLanguage(Language const &language)
Get font for a language.
Definition btconfig.cpp:354
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