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