BibleTime
cmoduleresultview.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 "cmoduleresultview.h"
14
15#include <QAction>
16#include <QContextMenuEvent>
17#include <QMenu>
18#include <QProgressDialog>
19#include <QStringList>
20#include <QtAlgorithms>
21#include <QTreeWidget>
22#include <QTreeWidgetItem>
23#include "../../backend/config/btconfig.h"
24#include "../../backend/cswordmodulesearch.h"
25#include "../../backend/drivers/cswordmoduleinfo.h"
26#include "../../backend/managers/cswordbackend.h"
27#include "../../backend/rendering/cdisplayrendering.h"
28#include "../../backend/rendering/ctextrendering.h"
29#include "../../util/btassert.h"
30#include "../../util/btconnect.h"
31#include "../../util/cresmgr.h"
32#include "../../util/tool.h"
33#include "../cexportmanager.h"
34#include "btsearchresultarea.h"
35
36
37namespace Search {
38namespace {
39
40QString getStrongsNumberText(QString const & verseContent,
41 int & startIndex,
42 QString const & lemmaText)
43{
44 static constexpr Qt::CaseSensitivity const cs = Qt::CaseInsensitive;
45
46 int index;
47 if (startIndex == 0) {
48 index = verseContent.indexOf(QStringLiteral("<body"));
49 } else {
50 index = startIndex;
51 }
52
53 // find all the "lemma=" inside the the content
54 while ((index = verseContent.indexOf(QStringLiteral("lemma="), index, cs))
55 != -1)
56 {
57 // get the strongs number after the lemma and compare it with the
58 // strongs number we are looking for
59 auto const idx1 = verseContent.indexOf('"', index) + 1;
60 auto idx2 = verseContent.indexOf('"', idx1 + 1);
61 auto const sNumber = verseContent.mid(idx1, idx2 - idx1);
62 if (sNumber == lemmaText) {
63 // strongs number is found now we need to get the text of this node
64 // search right until the '>' is found. Get the text from here to
65 // the next '<'.
66 index = verseContent.indexOf('>', index, cs) + 1;
67 idx2 = verseContent.indexOf('<', index, cs);
68 startIndex = idx2;
69 return verseContent.mid(index, idx2 - index);
70 } else {
71 index += 6; // 6 is the length of "lemma="
72 }
73 }
74 return {};
75}
76
78 QList<StrongsResult> & list,
79 CSwordModuleInfo const * module,
81 QString const & strongsNumber)
82{
83 using namespace Rendering;
84
85 auto const count = result.size();
86 if (!count)
87 return;
88
90 BtConstModuleList modules;
91 modules.append(module);
92
93 // for whatever reason the text "Parsing...translations." does not appear.
94 // this is not critical but the text is necessary to get the dialog box
95 // to be wide enough.
96 QProgressDialog progress(QObject::tr("Parsing Strong's Numbers"), nullptr, 0, count);
97 //0, "progressDialog", tr("Parsing Strong's Numbers"), tr("Parsing Strong's numbers for translations."), true);
98 //progress->setAllowCancel(false);
99 //progress->setMinimumDuration(0);
100 progress.show();
101 progress.raise();
102
103 qApp->processEvents(QEventLoop::AllEvents, 1); //1 ms only
104
105 int index = 0;
106 for (auto const & keyPtr : result) {
107 progress.setValue(index++);
108 qApp->processEvents(QEventLoop::AllEvents, 1); //1 ms only
109
110 QString key = QString::fromUtf8(keyPtr->getText());
111 QString text = CDisplayRendering().renderSingleKey(key, modules, settings);
112 for (int sIndex = 0;;) {
113 continueloop:
114 QString rText = getStrongsNumberText(text, sIndex, strongsNumber);
115 if (rText.isEmpty()) break;
116
117 for (auto & result : list) {
118 if (result.keyText() == rText) {
119 result.addKeyName(key);
120 goto continueloop; // break, then continue
121 }
122 }
123 list.append(StrongsResult(rText, key));
124 }
125 }
126}
127
128} // anonymous namespace
129
130/********************************************
131************ ModuleResultList **************
132********************************************/
133
139
141
142/** Initializes this widget. */
144 // see also csearchresultview.cpp
145 setToolTip(tr("Works chosen for the search and the number of the hits in each work"));
146 setHeaderLabels( QStringList(tr("Work")) << tr("Hits") );
147
148 setColumnWidth(0, util::tool::mWidth(*this, 8));
149 setColumnWidth(1, util::tool::mWidth(*this, 4));
150 QSize sz(util::tool::mWidth(*this, 13), util::tool::mWidth(*this, 5));
151 //setMinimumSize(sz);
152 m_size = sz;
153 /// \todo sorting
154 //setSorting(0, true);
155 //setSorting(1, true);
156
157 //setup the popup menu
158 m_popup = new QMenu(this);
159
160 m_actions.copyMenu = new QMenu(tr("Copy..."), m_popup);
161 m_actions.copyMenu->setIcon(CResMgr::searchdialog::result::moduleList::copyMenu::icon());
162 m_actions.copy.result = new QAction(tr("Reference only"), this);
163 BT_CONNECT(m_actions.copy.result, &QAction::triggered,
164 [this]{
165 if (auto * const m = activeModule())
166 CExportManager(true, tr("Copying search result"))
167 .copyKeyList(m_results[m],
168 m,
169 CExportManager::Text,
170 false);
171 });
172 m_actions.copyMenu->addAction(m_actions.copy.result);
173 m_actions.copy.resultWithText = new QAction(tr("Reference with text"), this);
174 BT_CONNECT(m_actions.copy.resultWithText, &QAction::triggered,
175 [this]{
176 if (auto * const m = activeModule())
177 CExportManager(true, tr("Copying search result"))
178 .copyKeyList(m_results[m],
179 m,
180 CExportManager::Text,
181 true);
182 });
183 m_actions.copyMenu->addAction(m_actions.copy.resultWithText);
184 m_popup->addMenu(m_actions.copyMenu);
185
186 m_actions.saveMenu = new QMenu(tr("Save..."), m_popup);
187 m_actions.saveMenu->setIcon(CResMgr::searchdialog::result::moduleList::saveMenu::icon());
188 m_actions.save.result = new QAction(tr("Reference only"), this);
189 BT_CONNECT(m_actions.save.result, &QAction::triggered,
190 [this]{
191 if (auto * const m = activeModule())
192 CExportManager(true, tr("Saving search result"))
193 .saveKeyList(m_results[m],
194 m,
195 CExportManager::Text,
196 false);
197 });
198 m_actions.saveMenu->addAction(m_actions.save.result);
199 m_actions.save.resultWithText = new QAction(tr("Reference with text"), this);
200 BT_CONNECT(m_actions.save.resultWithText, &QAction::triggered,
201 [this]{
202 if (auto * const m = activeModule())
203 CExportManager(true, tr("Saving search result"))
204 .saveKeyList(m_results[m],
205 m,
206 CExportManager::Text,
207 true);
208 });
209 m_actions.saveMenu->addAction(m_actions.save.resultWithText);
210 m_popup->addMenu(m_actions.saveMenu);
211
212 m_actions.printMenu = new QMenu(tr("Print..."), m_popup);
213 m_actions.printMenu->setIcon(CResMgr::searchdialog::result::moduleList::printMenu::icon());
214 m_actions.print.result = new QAction(tr("Reference with text"), this);
215 BT_CONNECT(m_actions.print.result, &QAction::triggered,
216 [this]{
217 if (auto * const m = activeModule())
218 CExportManager(true, tr("Printing search result"))
219 .printKeyList(m_results[m],
220 m,
221 btConfig().getDisplayOptions(),
222 btConfig().getFilterOptions());
223 });
224 m_actions.printMenu->addAction(m_actions.print.result);
225 m_popup->addMenu(m_actions.printMenu);
226}
227
228/** Initializes the connections of this widget, */
230 /// \todo
231 BT_CONNECT(this, &CModuleResultView::currentItemChanged,
233}
234
236 const QString & searchedText)
237{
238 /// \todo implement sorting in this method.
239
240 clear();
241 m_results.clear();
242 m_strongsResults.clear();
243
244 bool strongsAvailable = false;
245
246 for (auto const & result : results) {
247 auto const * const m = result.module;
248 BT_ASSERT(!m_results.contains(m));
249 m_results.insert(m, result.results);
250 QTreeWidgetItem * const item =
251 new QTreeWidgetItem(this,
252 QStringList(m->name())
253 << QString::number(result.results.size()));
254
255 item->setIcon(0, m->moduleIcon());
256 /*
257 We need to make a decision here. Either don't show any Strong's
258 number translations, or show the first one in the search text, or
259 figure out how to show them all. I choose option number 2 at this time.
260 */
261
262 // strong search text index for finding "strong:"
263 int sstIndex = searchedText.indexOf(QStringLiteral("strong:"), 0);
264 if (sstIndex != -1) {
265 /*
266 Get the strongs number from the search text. First find the first
267 space after "strong:". This should indicate a change in search
268 token
269 */
270 sstIndex += 7;
271 const int sTokenIndex = searchedText.indexOf(' ', sstIndex);
272 const QString sNumber(searchedText.mid(sstIndex, sTokenIndex - sstIndex));
273
274 QList<StrongsResult> strongResultList;
275 populateStrongsResultList(strongResultList,
276 m,
277 result.results,
278 sNumber);
279 for (auto const & strongResult : strongResultList)
280 new QTreeWidgetItem(
281 item,
282 QStringList{strongResult.keyText(),
283 QString::number(strongResult.keyCount())});
284 m_strongsResults[m] = std::move(strongResultList);
285
286 /// \todo item->setOpen(true);
287 strongsAvailable = true;
288 }
289 }
290
291 // Allow to hide the module strongs if there are any available
292 setRootIsDecorated( strongsAvailable );
293}
294
295/// \todo
296/** Is executed when an item was selected in the list. */
297void CModuleResultView::executed( QTreeWidgetItem* i, QTreeWidgetItem*) {
298 if (!i) {
299 //Clear list
300 Q_EMIT moduleChanged();
301 return;
302 }
303
304 auto const & itemText = i->text(0);
305 if (auto * const m = CSwordBackend::instance().findModuleByName(itemText)) {
306 Q_EMIT moduleChanged();
307 Q_EMIT moduleSelected(m, m_results.value(m));
308 return;
309 }
310
311 for (auto const & strongsResult : m_strongsResults[activeModule()]) {
312 if (strongsResult.keyText() == itemText) {
313 //clear the verses list
314 Q_EMIT moduleChanged();
316 strongsResult.getKeyList());
317 return;
318 }
319 }
320}
321
322/** Returns the currently active module. */
324 QTreeWidgetItem * item = currentItem();
325 BT_ASSERT(item);
326
327 // we need to find the parent most node because that is the node
328 // that is the module name.
329 while (item->parent())
330 item = item->parent();
331
332 return CSwordBackend::instance().findModuleByName(item->text(0));
333}
334
335/** Reimplementation from QWidget. */
336void CModuleResultView::contextMenuEvent( QContextMenuEvent * event ) {
337 //make sure that all entries have the correct status
338 m_popup->exec(event->globalPos());
339}
340
341} //end of namespace Search
#define BT_ASSERT(...)
Definition btassert.h:17
#define BT_CONNECT(...)
Definition btconnect.h:20
QList< CSwordModuleInfo const * > BtConstModuleList
CSwordModuleInfo * findModuleByName(const QString &name) const
Searches for a module with the given name.
static CSwordBackend & instance() noexcept
Rendering for the html display widget.
QString renderSingleKey(const QString &key, const BtConstModuleList &modules, const KeyTreeItem::Settings &settings=KeyTreeItem::Settings())
struct Search::CModuleResultView::@17 m_actions
CSwordModuleInfo * activeModule()
void strongsSelected(CSwordModuleInfo *, const QStringList &)
void setupTree(const CSwordModuleSearch::Results &results, const QString &searchedText)
QHash< CSwordModuleInfo const *, CSwordModuleSearch::ModuleResultList > m_results
void contextMenuEvent(QContextMenuEvent *event) override
QHash< CSwordModuleInfo const *, QList< StrongsResult > > m_strongsResults
void executed(QTreeWidgetItem *, QTreeWidgetItem *)
void moduleSelected(CSwordModuleInfo const *, CSwordModuleSearch::ModuleResultList const &)
std::vector< std::shared_ptr< sword::SWKey const > > ModuleResultList
std::vector< ModuleSearchResult > Results
void populateStrongsResultList(QList< StrongsResult > &list, CSwordModuleInfo const *module, CSwordModuleSearch::ModuleResultList const &result, QString const &strongsNumber)
QString getStrongsNumberText(QString const &verseContent, int &startIndex, QString const &lemmaText)
int mWidth(QWidget const &widget, int const mCount)
Calculates a maximum rendered text width for a widget and a string with the a given length.
Definition tool.cpp:117