BibleTime
csearchanalysisscene.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
14
15#include <algorithm>
16#include <QApplication>
17#include <QFileDialog>
18#include <QMap>
19#include <QTextStream>
20#include <QTextDocument>
21#include <type_traits>
22#include <utility>
23#include "../../../backend/drivers/cswordmoduleinfo.h"
24#include "../../../backend/keys/cswordversekey.h"
25#include "../../../util/btassert.h"
26#include "../../../util/tool.h"
27
28
29namespace Search {
30
31const int SPACE_BETWEEN_PARTS = 5;
32const int RIGHT_BORDER = 15;
33const int LEFT_BORDER = 15;
34const int LOWER_BORDER = 10;
35const int UPPER_BORDER = 10;
36
37const int ITEM_TEXT_SIZE = 8;
38
39//used for the shift between the bars
40const int BAR_DELTAX = 4;
41const int BAR_DELTAY = 2;
42const int BAR_WIDTH = 2 + (2*BAR_DELTAX); //should be equal or bigger than the label font size
43// Used for the text below the bars
44const int BAR_LOWER_BORDER = 90;
45
46const int LEGEND_INNER_BORDER = 5;
47const int LEGEND_DELTAY = 4;
48const int LEGEND_WIDTH = 85;
49
51 QString searchedText,
52 CSwordModuleSearch::Results const & results,
53 QObject * parent)
54 : QGraphicsScene(parent)
55 , m_searchedText(std::move(searchedText))
56{
57 setBackgroundBrush(QBrush(Qt::white));
58 setSceneRect(0, 0, 1, 1);
59
60 for (auto const & result : results)
61 if ((result.module->type() == CSwordModuleInfo::Bible)
62 || (result.module->type() == CSwordModuleInfo::Commentary))
63 m_results.emplace_back(result);
64
65 auto const numberOfModules = m_results.size();
66 if (!numberOfModules)
67 return;
68 m_legend = std::make_unique<CSearchAnalysisLegendItem>(&m_results);
69 addItem(m_legend.get());
71 LEGEND_WIDTH, LEGEND_INNER_BORDER*2 + ITEM_TEXT_SIZE*numberOfModules + LEGEND_DELTAY*(numberOfModules - 1) );
72 m_legend->show();
73
74 int moduleIndex = 0;
75 for (auto const & result : m_results) {
76 qApp->processEvents(QEventLoop::AllEvents);
77 for (auto const & moduleresultPtr : result.results) {
78 /* m_results only contains results from Bibles and
79 Commentaries, as filtered above. */
80 BT_ASSERT(dynamic_cast<sword::VerseKey const *>(
81 moduleresultPtr.get()));
82 auto const * const vk =
83 static_cast<sword::VerseKey const *>(
84 moduleresultPtr.get());
85 auto key = std::tuple(vk->getTestament(), vk->getBook());
86 CSearchAnalysisItem * analysisItem;
87 static_assert(std::is_same_v<decltype(key),
88 decltype(m_itemList)::key_type>, "");
89 if (auto const it = m_itemList.find(key); it != m_itemList.end()) {
90 analysisItem = it->second;
91 } else {
92 analysisItem =
94 QString::fromUtf8(vk->getBookName()),
95 m_results.size());
96 m_itemList.emplace(std::move(key), analysisItem);
97 }
98 auto const count = ++analysisItem->counts()[moduleIndex];
99 m_maxCount = std::max(m_maxCount, count);
100 }
101
102 ++moduleIndex;
103 }
104
105 int xPos = static_cast<int>(LEFT_BORDER
106 + m_legend->rect().width()
108 for (auto const & vp : m_itemList) {
109 auto & analysisItem = *vp.second;
110 addItem(&analysisItem);
111 analysisItem.setRect(xPos,
113 analysisItem.rect().width(),
114 analysisItem.rect().height());
115 xPos += static_cast<int>(analysisItem.width() + SPACE_BETWEEN_PARTS);
116
117 QStringList toolTipItems;
118 int i = 0;
119 for (auto const & result : m_results) {
120 auto const * const info = result.module;
121
122 auto const count = result.results.size();
123 double const percent =
124 (info && count)
125 ? ((static_cast<double>(analysisItem.counts()[i])
126 * static_cast<double>(100.0))
127 / static_cast<double>(count))
128 : 0.0;
129 toolTipItems.append(
130 QStringLiteral("%1\">%2</span></b></td><td>%3 (%4")
131 .arg(getColor(i).name(),
132 info ? info->name() : QString(),
133 QString::number(analysisItem.counts()[i]),
134 QString::number(percent, 'g', 2)));
135 ++i;
136 }
137 #define ROW_START "<tr bgcolor=\"white\"><td><b><span style=\"color:"
138 #define ROW_END "%)</td></tr>"
139 analysisItem.setToolTip(
140 QStringLiteral(
141 "<center><b>%1</b></center><hr/>"
142 "<table cellspacing=\"0\" cellpadding=\"3\" "
143 "width=\"100%\" height=\"100%\" align=\"center\">"
144 ROW_START "%2" ROW_END "</table>")
145 .arg(analysisItem.bookName().toHtmlEscaped(),
146 toolTipItems.join(QStringLiteral(ROW_END ROW_START))));
147 #undef ROW_START
148 #undef ROW_END
149 }
150 setSceneRect(0, 0, xPos + BAR_WIDTH + (m_results.size() - 1)*BAR_DELTAX + RIGHT_BORDER, height() );
151 slotResized();
152}
153
154/** No descriptions */
156 double scaleFactor;
157 if (m_maxCount <= 0) {
158 scaleFactor = 0.0;
159 } else {
160 scaleFactor = static_cast<double>(height() - UPPER_BORDER - LOWER_BORDER - BAR_LOWER_BORDER - 100 - (m_results.size() - 1) * BAR_DELTAY)
161 / static_cast<double>(m_maxCount);
162 }
163 for (auto const & vp : m_itemList) {
164 auto & analysisItem = *vp.second;
165 analysisItem.setScaleFactor(scaleFactor);
166 analysisItem.setRect(analysisItem.rect().x(),
168 BAR_WIDTH + (m_results.size() - 1) * BAR_DELTAX,
169 height() - LOWER_BORDER - BAR_LOWER_BORDER);
170 }
171 update();
172}
173
174/** This function returns a color for each module */
176 switch (index) {
177 case 0:
178 return Qt::red;
179 case 1:
180 return Qt::darkGreen;
181 case 2:
182 return Qt::blue;
183 case 3:
184 return Qt::cyan;
185 case 4:
186 return Qt::magenta;
187 case 5:
188 return Qt::darkRed;
189 case 6:
190 return Qt::darkGray;
191 case 7:
192 return Qt::black;
193 case 8:
194 return Qt::darkCyan;
195 case 9:
196 return Qt::darkMagenta;
197 default:
198 return Qt::red;
199 }
200}
201
203 auto const fileName =
204 QFileDialog::getSaveFileName(
205 nullptr,
206 tr("Save Search Analysis"),
207 QString(),
208 QObject::tr("HTML files") + QStringLiteral(" (*.html *.htm);;")
209 + QObject::tr("All files") + QStringLiteral(" (*)"));
210 if (fileName.isEmpty())
211 return;
212
213 struct UserData { CSearchAnalysisScene const & obj; } userData{*this};
214 static auto const writer =
215 +[](QTextStream & out, void * userPtr)
216 { static_cast<UserData const * >(userPtr)->obj.saveAsHTML(out); };
217 util::tool::savePlainFile(fileName, *writer, &userData);
218}
219
220void CSearchAnalysisScene::saveAsHTML(QTextStream & out) const {
221 auto const title(tr("BibleTime Search Analysis"));
222 out << QStringLiteral(
223 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
224 "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" "
225 "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"
226 "<html xmlns=\"http://www.w3.org/1999/xhtml\"><head><title>")
227 << title
228 << QStringLiteral(
229 "</title>"
230 "<style type=\"text/css\">"
231 "body{background-color:#fff;color:#000}"
232 "table{border-collapse:collapse}"
233 "td{border:1px solid #333}"
234 "th{font-size:130%;text-align:left;vertical-align:top}"
235 "td,th{text-align:left;padding:0.2em 0.5em}"
236 ".r{text-align:right}"
237 "</style>"
238 "<meta http-equiv=\"Content-Type\" "
239 "content=\"text/html; charset=utf-8\"/>"
240 "</head><body><h1>")
241 << title
242 << QStringLiteral("</h1><p><span style=\"font-weight:bold\">")
243 << tr("Search text:")
244 << QStringLiteral("</span>&nbsp;")
245 << m_searchedText.toHtmlEscaped()
246 << QStringLiteral("</p><table><caption>")
247 << tr("Results by work and book")
248 << QStringLiteral("</caption><tr><th>")
249 << tr("Book")
250 << QStringLiteral("</th>");
251
252 for (auto const & result : m_results)
253 out << QStringLiteral("<th>")
254 << result.module->name().toHtmlEscaped()
255 << QStringLiteral("</th>");
256 out << QStringLiteral("</tr>");
257
258 for (auto const & vp : m_itemList) {
259 auto const & analysisItem = *vp.second;
260 out << QStringLiteral("<tr><td>")
261 << analysisItem.bookName().toHtmlEscaped()
262 << QStringLiteral("</td>");
263 for (auto const count : analysisItem.counts())
264 out << QStringLiteral("<td class=\"r\">")
265 << QString::number(count)
266 << QStringLiteral("</td>");
267 out << QStringLiteral("</tr>");
268 }
269 out << QStringLiteral("<tr><th class=\"r\">")
270 << tr("Total hits")
271 << QStringLiteral("</th>");
272
273 for (auto const & result : m_results) {
274 out << QStringLiteral("<td class=\"r\">")
275 << QString::number(result.results.size())
276 << QStringLiteral("</td>");
277 }
278
279 out << QStringLiteral("</tr></table><p style=\"text-align:center;"
280 "font-size:x-small\">")
281 << tr("Created by <a href=\"%1\">BibleTime</a>").arg(BT_HOMEPAGE)
282 << QStringLiteral("</p></body></html>");
283}
284
286 setSceneRect(0, 0, sceneRect().width(), height);
287 slotResized();
288}
289
290} // namespace Search {
#define BT_ASSERT(...)
Definition btassert.h:17
std::unique_ptr< CSearchAnalysisLegendItem > m_legend
std::map< std::tuple< char, char >, CSearchAnalysisItem * > m_itemList
CSwordModuleSearch::Results m_results
CSearchAnalysisScene(QString searchedText, CSwordModuleSearch::Results const &results, QObject *parent)
static QColor getColor(int index)
#define ROW_START
#define ROW_END
std::vector< ModuleSearchResult > Results
const int RIGHT_BORDER
const int ITEM_TEXT_SIZE
const int LOWER_BORDER
const int BAR_DELTAY
const int SPACE_BETWEEN_PARTS
const int BAR_LOWER_BORDER
const int UPPER_BORDER
const int BAR_DELTAX
const int LEFT_BORDER
const int LEGEND_INNER_BORDER
const int BAR_WIDTH
const int LEGEND_WIDTH
bool savePlainFile(const QString &filename, void(&writer)(QTextStream &, void *), void *userPtr)
Definition tool.cpp:33