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