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-2021 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 "csearchanalysisscene.h"
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 
30 namespace Search {
31 
32 const int SPACE_BETWEEN_PARTS = 5;
33 const int RIGHT_BORDER = 15;
34 const int LEFT_BORDER = 15;
35 const int LOWER_BORDER = 10;
36 const int UPPER_BORDER = 10;
37 
38 const int ITEM_TEXT_SIZE = 8;
39 
40 //used for the shift between the bars
41 const int BAR_DELTAX = 4;
42 const int BAR_DELTAY = 2;
43 const 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
45 const int BAR_LOWER_BORDER = 90;
46 
47 const int LEGEND_INNER_BORDER = 5;
48 const int LEGEND_DELTAY = 4;
49 const 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,
113  UPPER_BORDER,
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(),
168  UPPER_BORDER,
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 
221 void 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=\"https://bibletime.info/\">BibleTime</a>")
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