1 /************************************************************************
2 **
3 ** Copyright (C) 2015-2021 Kevin B. Hendricks, Stratford Ontario Canada
4 ** Copyright (C) 2009-2011 Strahinja Markovic <strahinja.markovic@gmail.com>
5 **
6 ** This file is part of Sigil.
7 **
8 ** Sigil is free software: you can redistribute it and/or modify
9 ** it under the terms of the GNU General Public License as published by
10 ** the Free Software Foundation, either version 3 of the License, or
11 ** (at your option) any later version.
12 **
13 ** Sigil is distributed in the hope that it will be useful,
14 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ** GNU General Public License for more details.
17 **
18 ** You should have received a copy of the GNU General Public License
19 ** along with Sigil. If not, see <http://www.gnu.org/licenses/>.
20 **
21 *************************************************************************/
22
23 #include "EmbedPython/EmbeddedPython.h"
24
25 #include <QtCore/QFileInfo>
26 #include <QtWidgets/QApplication>
27 #include <QtWidgets/QHeaderView>
28 #include <QtWidgets/QTableWidget>
29 #include <QRegularExpression>
30 #include <QVariant>
31
32 #include "BookManipulation/Book.h"
33 #include "BookManipulation/FolderKeeper.h"
34 #include "MainUI/ValidationResultsView.h"
35 #include "Misc/Utility.h"
36 #include "sigil_exception.h"
37
38 #if(0)
39 static const QBrush INFO_BRUSH = QBrush(QColor(224, 255, 255));
40 static const QBrush WARNING_BRUSH = QBrush(QColor(255, 255, 230));
41 static const QBrush ERROR_BRUSH = QBrush(QColor(255, 230, 230));
42 #endif
43
44 const QString ValidationResultsView::SEP = QString(QChar(31));
45
ValidationResultsView(QWidget * parent)46 ValidationResultsView::ValidationResultsView(QWidget *parent)
47 :
48 QDockWidget(tr("Validation Results"), parent),
49 m_ResultTable(new QTableWidget(this)),
50 m_NoProblems(false)
51 {
52 setWidget(m_ResultTable);
53 setAllowedAreas(Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea);
54 SetUpTable();
55 connect(m_ResultTable, SIGNAL(itemDoubleClicked(QTableWidgetItem *)),
56 this, SLOT(ResultDoubleClicked(QTableWidgetItem *)));
57 }
58
showEvent(QShowEvent * event)59 void ValidationResultsView::showEvent(QShowEvent *event)
60 {
61 QDockWidget::showEvent(event);
62 raise();
63 }
64
65
ValidateFile(QString & apath)66 QStringList ValidationResultsView::ValidateFile(QString &apath)
67 {
68 int rv = 0;
69 QString error_traceback;
70 QStringList results;
71
72 QList<QVariant> args;
73 args.append(QVariant(apath));
74
75 EmbeddedPython * epython = EmbeddedPython::instance();
76
77 QVariant res = epython->runInPython( QString("sanitycheck"),
78 QString("perform_sanity_check"),
79 args,
80 &rv,
81 error_traceback);
82 if (rv != 0) {
83 Utility::DisplayStdWarningDialog(QString("error in sanitycheck perform_sanity_check: ") + QString::number(rv),
84 error_traceback);
85 // an error happened - make no changes
86 return results;
87 }
88 return res.toStringList();
89 }
90
91
ValidateCurrentBook()92 void ValidationResultsView::ValidateCurrentBook()
93 {
94 ClearResults();
95 QList<ValidationResult> results;
96 QApplication::setOverrideCursor(Qt::WaitCursor);
97 m_Book->SaveAllResourcesToDisk();
98
99 QList<Resource *> resources = m_Book->GetFolderKeeper()->GetResourceList();
100 foreach (Resource * resource, resources) {
101 if (resource->Type() == Resource::HTMLResourceType) {
102 QString apath = resource->GetFullPath();
103 QString bookpath = resource->GetRelativePath();
104 QStringList reslst = ValidateFile(apath);
105 if (!reslst.isEmpty()) {
106 foreach (QString res, reslst) {
107 QStringList details = res.split(SEP);
108 ValidationResult::ResType vtype;
109 QString etype = details[0];
110 if (etype == "info") {
111 vtype = ValidationResult::ResType_Info;
112 } else if (etype == "warning") {
113 vtype = ValidationResult::ResType_Warn;
114 } else if (etype == "error") {
115 vtype = ValidationResult::ResType_Error;
116 } else {
117 continue;
118 }
119 QString filename = details[1];
120 int lineno = details[2].toInt();
121 int charoffset = details[3].toInt();
122 QString msg = details[4];
123 results.append(ValidationResult(vtype,bookpath,lineno,charoffset,msg));
124 }
125 }
126 }
127 }
128 QApplication::restoreOverrideCursor();
129 DisplayResults(results);
130 show();
131 raise();
132 }
133
134
LoadResults(const QList<ValidationResult> & results)135 void ValidationResultsView::LoadResults(const QList<ValidationResult> &results)
136 {
137 ClearResults();
138 DisplayResults(results);
139 show();
140 raise();
141 }
142
143
ClearResults()144 void ValidationResultsView::ClearResults()
145 {
146 m_ResultTable->clearContents();
147 m_ResultTable->setRowCount(0);
148 }
149
150
SetBook(QSharedPointer<Book> book)151 void ValidationResultsView::SetBook(QSharedPointer<Book> book)
152 {
153 m_Book = book;
154 ClearResults();
155 }
156
157
ResultDoubleClicked(QTableWidgetItem * item)158 void ValidationResultsView::ResultDoubleClicked(QTableWidgetItem *item)
159 {
160 Q_ASSERT(item);
161 int row = item->row();
162 QTableWidgetItem *path_item = m_ResultTable->item(row, 0);
163
164 if (!path_item) {
165 return;
166 }
167
168 QString shortname = path_item->text();
169 QString bookpath = path_item->data(Qt::UserRole+1).toString();
170 QTableWidgetItem *line_item = m_ResultTable->item(row, 1);
171 QTableWidgetItem *offset_item = m_ResultTable->item(row, 2);
172
173 if (!line_item || !offset_item) {
174 return;
175 }
176
177
178 int line = line_item->text().toInt();
179 int charoffset = offset_item->text().toInt();
180
181 try {
182 Resource *resource = m_Book->GetFolderKeeper()->GetResourceByBookPath(bookpath);
183 // if character offset info exists, use it in preference to just the line number
184 if (charoffset != -1) {
185 emit OpenResourceRequest(resource, line, charoffset, QString());
186 } else {
187 emit OpenResourceRequest(resource, line, -1, QString());
188 }
189 } catch (ResourceDoesNotExist&) {
190 return;
191 }
192 }
193
194
SetUpTable()195 void ValidationResultsView::SetUpTable()
196 {
197 m_ResultTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
198 m_ResultTable->setTabKeyNavigation(false);
199 m_ResultTable->setDropIndicatorShown(false);
200 m_ResultTable->horizontalHeader()->setStretchLastSection(true);
201 m_ResultTable->verticalHeader()->setVisible(false);
202 }
203
204
DisplayResults(const QList<ValidationResult> & results)205 void ValidationResultsView::DisplayResults(const QList<ValidationResult> &results)
206 {
207 m_ResultTable->clear();
208 m_NoProblems = false;
209
210 if (results.empty()) {
211 m_NoProblems = true;
212 DisplayNoProblemsMessage();
213 return;
214 }
215
216 ConfigureTableForResults();
217
218 Q_FOREACH(ValidationResult result, results) {
219 int rownum = m_ResultTable->rowCount();
220 QTableWidgetItem *item = NULL;
221
222 QBrush row_brush = Utility::ValidationResultBrush(Utility::INFO_BRUSH);
223 if (result.Type() == ValidationResult::ResType_Warn) {
224 row_brush = Utility::ValidationResultBrush(Utility::WARNING_BRUSH);
225 } else if (result.Type() == ValidationResult::ResType_Error) {
226 row_brush = Utility::ValidationResultBrush(Utility::ERROR_BRUSH);
227 }
228
229 m_ResultTable->insertRow(rownum);
230
231 QString path;
232 QString bookpath = result.BookPath();
233 try {
234 Resource * resource = m_Book->GetFolderKeeper()->GetResourceByBookPath(bookpath);
235 path = resource->ShortPathName();
236 } catch (ResourceDoesNotExist&) {
237 if (bookpath.isEmpty()) {
238 path = "***Invalid Book Path Provided ***";
239 } else {
240 path = bookpath;
241 }
242 }
243
244 item = new QTableWidgetItem(RemoveEpubPathPrefix(path));
245 item->setData(Qt::UserRole+1, bookpath);
246 SetItemPalette(item, row_brush);
247 m_ResultTable->setItem(rownum, 0, item);
248
249 item = result.LineNumber() > 0 ? new QTableWidgetItem(QString::number(result.LineNumber())) : new QTableWidgetItem(tr("N/A"));
250 SetItemPalette(item, row_brush);
251 m_ResultTable->setItem(rownum, 1, item);
252
253 item = result.CharOffset() > 0 ? new QTableWidgetItem(QString::number(result.CharOffset())) : new QTableWidgetItem(tr("N/A"));
254 SetItemPalette(item, row_brush);
255 m_ResultTable->setItem(rownum, 2, item);
256
257 item = new QTableWidgetItem(result.Message());
258 SetItemPalette(item, row_brush);
259 m_ResultTable->setItem(rownum, 3, item);
260 }
261
262 // Make Line and Offset columns as small as possible
263 // Ditto for Filename
264 m_ResultTable->resizeColumnToContents(0);
265 m_ResultTable->resizeColumnToContents(1);
266 m_ResultTable->resizeColumnToContents(2);
267 //m_ResultTable->resizeColumnsToContents();
268 }
269
ResultCount()270 int ValidationResultsView::ResultCount()
271 {
272 if (m_NoProblems) return 0;
273 return m_ResultTable->rowCount();
274 }
275
DisplayNoProblemsMessage()276 void ValidationResultsView::DisplayNoProblemsMessage()
277 {
278 m_ResultTable->setRowCount(1);
279 m_ResultTable->setColumnCount(1);
280 m_ResultTable->setHorizontalHeaderLabels(
281 QStringList() << tr("Message"));
282 QTableWidgetItem *item = new QTableWidgetItem(tr("No problems found!"));
283 item->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
284 QFont font = item->font();
285 font.setPointSize(16);
286 item->setFont(font);
287 m_ResultTable->setItem(0, 0, item);
288 m_ResultTable->resizeRowToContents(0);
289 }
290
291
ConfigureTableForResults()292 void ValidationResultsView::ConfigureTableForResults()
293 {
294 m_ResultTable->setRowCount(0);
295 m_ResultTable->setColumnCount(4);
296 m_ResultTable->setHorizontalHeaderLabels(
297 QStringList() << tr("File") << tr("Line") << tr("Offset") << tr("Message"));
298 m_ResultTable->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
299 }
300
301
RemoveEpubPathPrefix(const QString & path)302 QString ValidationResultsView::RemoveEpubPathPrefix(const QString &path)
303 {
304 return QString(path).remove(QRegularExpression("^[\\w-]+\\.epub/?"));
305 }
306
SetItemPalette(QTableWidgetItem * item,QBrush & row_brush)307 void ValidationResultsView::SetItemPalette(QTableWidgetItem * item, QBrush &row_brush)
308 {
309 if (Utility::IsDarkMode()) {
310 item->setForeground(row_brush);
311 } else {
312 item->setBackground(row_brush);
313 }
314 }
315
316