1 /************************************************************************
2 **
3 **  Copyright (C) 2015-2021 Kevin B. Hendricks, Stratford, Ontario
4 **  Copyright (C) 2012      John Schember <john@nachtimwald.com>
5 **  Copyright (C) 2012-2013 Dave Heiland
6 **
7 **  This file is part of Sigil.
8 **
9 **  Sigil is free software: you can redistribute it and/or modify
10 **  it under the terms of the GNU General Public License as published by
11 **  the Free Software Foundation, either version 3 of the License, or
12 **  (at your option) any later version.
13 **
14 **  Sigil is distributed in the hope that it will be useful,
15 **  but WITHOUT ANY WARRANTY; without even the implied warranty of
16 **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 **  GNU General Public License for more details.
18 **
19 **  You should have received a copy of the GNU General Public License
20 **  along with Sigil.  If not, see <http://www.gnu.org/licenses/>.
21 **
22 *************************************************************************/
23 
24 #include <QtCore/QFile>
25 #include <QtCore/QFileInfo>
26 #include <QtWidgets/QFileDialog>
27 #include <QtGui/QFont>
28 #include <QtWidgets/QMessageBox>
29 #include <QtWidgets/QPushButton>
30 
31 #include "sigil_exception.h"
32 #include "BookManipulation/FolderKeeper.h"
33 #include "Dialogs/ReportsWidgets/AllFilesWidget.h"
34 #include "Misc/HTMLSpellCheck.h"
35 #include "Misc/NumericItem.h"
36 #include "Misc/SettingsStore.h"
37 #include "Misc/Utility.h"
38 #include "ResourceObjects/HTMLResource.h"
39 #include "ResourceObjects/NavProcessor.h"
40 
41 static const QString SETTINGS_GROUP = "reports";
42 static const QString DEFAULT_REPORT_FILE = "AllFilesReport.csv";
43 
44 
AllFilesWidget()45 AllFilesWidget::AllFilesWidget()
46     :
47     m_ItemModel(new QStandardItemModel),
48     m_LastDirSaved(QString()),
49     m_LastFileSaved(QString())
50 {
51     ui.setupUi(this);
52     connectSignalsSlots();
53 }
54 
~AllFilesWidget()55 AllFilesWidget::~AllFilesWidget()
56 {
57   delete m_ItemModel;
58 }
59 
CreateReport(QSharedPointer<Book> book)60 void AllFilesWidget::CreateReport(QSharedPointer<Book> book)
61 {
62     m_Book = book;
63     SetupTable();
64 }
65 
SetupTable(int sort_column,Qt::SortOrder sort_order)66 void AllFilesWidget::SetupTable(int sort_column, Qt::SortOrder sort_order)
67 {
68     // Need to rebuild m_AllResources since deletes can happen behind the scenes
69     m_AllResources = m_Book->GetAllResources();
70     QString version = m_Book->GetOPF()->GetEpubVersion();
71     m_ItemModel->clear();
72     QStringList header;
73     header.append(tr("Directory"));
74     header.append(tr("Name"));
75     header.append(tr("File Size (KB)"));
76     header.append(tr("Type"));
77     header.append(tr("Semantics"));
78     if (version.startsWith("3")) {
79         header.append(tr("Properties"));
80     }
81     m_ItemModel->setHorizontalHeaderLabels(header);
82     ui.fileTree->setSelectionBehavior(QAbstractItemView::SelectRows);
83     ui.fileTree->setModel(m_ItemModel);
84     ui.fileTree->header()->setSortIndicatorShown(true);
85     double total_size = 0;
86     QString main_folder = m_Book->GetFolderKeeper()->GetFullPathToMainFolder();
87     foreach(Resource *resource, m_AllResources) {
88         QString fullpath = resource->GetFullPath();
89         QString filepath = resource->GetRelativePath();
90         QString directory = resource->GetFolder();
91         QString file_spname = resource->ShortPathName();
92         QList<QStandardItem *> rowItems;
93         QStandardItem *item;
94         // Directory
95         item = new QStandardItem();
96         item->setText(directory);
97         rowItems << item;
98         // Filename
99         item = new QStandardItem();
100         item->setText(file_spname);
101         item->setData(filepath);
102         item->setToolTip(filepath);
103         rowItems << item;
104         // File Size
105         double ffsize = QFile(fullpath).size() / 1024.0;
106         total_size += ffsize;
107         QString fsize = QString::number(ffsize, 'f', 2);
108         NumericItem *size_item = new NumericItem();
109         size_item->setText(fsize);
110         size_item->setTextAlignment(Qt::AlignRight);
111         rowItems << size_item;
112         // Type
113         item = new QStandardItem();
114         item ->setText(GetType(resource));
115         rowItems << item;
116         // Semantics
117         item = new QStandardItem();
118         if (version.startsWith('3')) {
119             NavProcessor navproc(m_Book->GetConstOPF()->GetNavResource());
120             item->setText(navproc.GetLandmarkNameForResource(resource));
121         } else {
122             item->setText(m_Book->GetOPF()->GetGuideSemanticNameForResource(resource));
123         }
124         rowItems << item;
125         // Manifest Properties
126         if (version.startsWith('3')) {
127             item = new QStandardItem();
128             item->setText(m_Book->GetOPF()->GetManifestPropertiesForResource(resource));
129             rowItems << item;
130         }
131         // Add item to table
132         m_ItemModel->appendRow(rowItems);
133         for (int i = 0; i < rowItems.count(); i++) {
134             rowItems[i]->setEditable(false);
135         }
136     }
137     // Sort before adding the totals row
138     // Since sortIndicator calls this routine, must disconnect/reconnect while resorting
139     disconnect(ui.fileTree->header(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), this, SLOT(Sort(int, Qt::SortOrder)));
140     ui.fileTree->header()->setSortIndicator(sort_column, sort_order);
141     connect(ui.fileTree->header(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), this, SLOT(Sort(int, Qt::SortOrder)));
142     // Totals
143     NumericItem *nitem;
144     QList<QStandardItem *> rowItems;
145     // Directory
146     nitem = new NumericItem();
147     rowItems << nitem;
148     // Files
149     nitem = new NumericItem();
150     nitem->setText(QString(tr("%n file(s)", "", m_AllResources.count())));
151     rowItems << nitem;
152     // File size
153     nitem = new NumericItem();
154     nitem->setText(QLocale().toString(total_size, 'f', 2));
155     nitem->setTextAlignment(Qt::AlignRight);
156     rowItems << nitem;
157 
158     QFont font;
159     font.setWeight(QFont::Bold);
160     for (int i = 0; i < rowItems.count(); i++) {
161         rowItems[i]->setEditable(false);
162         rowItems[i]->setFont(font);
163     }
164 
165     m_ItemModel->appendRow(rowItems);
166 
167     for (int i = 0; i < ui.fileTree->header()->count(); i++) {
168         ui.fileTree->resizeColumnToContents(i);
169     }
170 }
171 
172 
FilterEditTextChangedSlot(const QString & text)173 void AllFilesWidget::FilterEditTextChangedSlot(const QString &text)
174 {
175     const QString lowercaseText = text.toLower();
176     QStandardItem *root_item = m_ItemModel->invisibleRootItem();
177     QModelIndex parent_index;
178     // Hide rows that don't contain the filter text
179     int first_visible_row = -1;
180 
181     for (int row = 0; row < root_item->rowCount(); row++) {
182         if (text.isEmpty() || root_item->child(row, 0)->text().toLower().contains(lowercaseText)) {
183             ui.fileTree->setRowHidden(row, parent_index, false);
184 
185             if (first_visible_row == -1) {
186                 first_visible_row = row;
187             }
188         } else {
189             ui.fileTree->setRowHidden(row, parent_index, true);
190         }
191     }
192 
193     if (!text.isEmpty() && first_visible_row != -1) {
194         // Select the first non-hidden row
195         ui.fileTree->setCurrentIndex(root_item->child(first_visible_row, 0)->index());
196     } else {
197         // Clear current and selection, which clears preview image
198         ui.fileTree->setCurrentIndex(QModelIndex());
199     }
200 }
201 
Sort(int logicalindex,Qt::SortOrder order)202 void AllFilesWidget::Sort(int logicalindex, Qt::SortOrder order)
203 {
204     SetupTable(logicalindex, order);
205 }
206 
DoubleClick()207 void AllFilesWidget::DoubleClick()
208 {
209     QModelIndex index = ui.fileTree->selectionModel()->selectedRows(1).first();
210 
211     if (index.row() != m_ItemModel->rowCount() - 1) {
212         QString filepath = m_ItemModel->itemFromIndex(index)->data().toString();
213         emit OpenFileRequest(filepath, 1, -1);
214     }
215 }
216 
Save()217 void AllFilesWidget::Save()
218 {
219     QStringList report_info;
220     QStringList heading_row;
221 
222     // Get headings
223     for (int col = 0; col < ui.fileTree->header()->count(); col++) {
224         QStandardItem *item = m_ItemModel->horizontalHeaderItem(col);
225         QString text = "";
226         if (item) {
227             text = item->text();
228         }
229         heading_row << text;
230     }
231     report_info << Utility::createCSVLine(heading_row);
232 
233     // Get data from table
234     for (int row = 0; row < m_ItemModel->rowCount(); row++) {
235         QStringList data_row;
236         for (int col = 0; col < ui.fileTree->header()->count(); col++) {
237             QStandardItem *item = m_ItemModel->item(row, col);
238             QString text = "";
239             if (item) {
240                 text = item->text();
241             }
242             data_row << text;
243         }
244         report_info << Utility::createCSVLine(data_row);
245     }
246 
247     QString data = report_info.join('\n') + '\n';
248     // Save the file
249     ReadSettings();
250     QString filter_string = "*.csv;;*.txt;;*.*";
251     QString default_filter = "";
252     QString save_path = m_LastDirSaved + "/" + m_LastFileSaved;
253     QFileDialog::Options options = QFileDialog::Options();
254 #ifdef Q_OS_MAC
255     options = options | QFileDialog::DontUseNativeDialog;
256 #endif
257 
258     QString destination = QFileDialog::getSaveFileName(this,
259                                                        tr("Save Report As Comma Separated File"),
260                                                        save_path,
261                                                        filter_string,
262                                                        &default_filter,
263                                                        options);
264 
265     if (destination.isEmpty()) {
266         return;
267     }
268 
269     try {
270         Utility::WriteUnicodeTextFile(data, destination);
271     } catch (CannotOpenFile&) {
272         QMessageBox::warning(this, tr("Sigil"), tr("Cannot save report file."));
273     }
274 
275     m_LastDirSaved = QFileInfo(destination).absolutePath();
276     m_LastFileSaved = QFileInfo(destination).fileName();
277     WriteSettings();
278 }
279 
GetType(Resource * resource)280 QString AllFilesWidget::GetType(Resource *resource)
281 {
282     QString type;
283 
284     switch (resource->Type()) {
285         case Resource::HTMLResourceType: {
286             type = "HTML";
287             break;
288         }
289 
290         case Resource::CSSResourceType: {
291             type = "CSS";
292             break;
293         }
294 
295         case Resource::ImageResourceType:
296         case Resource::SVGResourceType: {
297             type = tr("Image");
298             break;
299         }
300 
301         case Resource::AudioResourceType: {
302             type = tr("Audio");
303             break;
304         }
305 
306         case Resource::VideoResourceType: {
307             type = tr("Video");
308             break;
309         }
310 
311         case Resource::FontResourceType: {
312             type = tr("Font");
313             break;
314         }
315 
316 
317         case Resource::OPFResourceType: {
318             type = "OPF";
319             break;
320         }
321 
322         case Resource::NCXResourceType: {
323             type = "NCX";
324             break;
325         }
326 
327         case Resource::XMLResourceType: {
328             type = "XML";
329             break;
330         }
331 
332         case Resource::MiscTextResourceType:
333         case Resource::TextResourceType: {
334             type = "Text";
335             break;
336         }
337 
338         default:
339             type = tr("Unknown");
340             break;
341     }
342 
343     return type;
344 }
345 
ReadSettings()346 void AllFilesWidget::ReadSettings()
347 {
348     SettingsStore settings;
349     settings.beginGroup(SETTINGS_GROUP);
350     // Last file open
351     m_LastDirSaved = settings.value("last_dir_saved").toString();
352     m_LastFileSaved = settings.value("last_file_saved_all_files").toString();
353 
354     if (m_LastFileSaved.isEmpty()) {
355         m_LastFileSaved = DEFAULT_REPORT_FILE;
356     }
357 
358     settings.endGroup();
359 }
360 
WriteSettings()361 void AllFilesWidget::WriteSettings()
362 {
363     SettingsStore settings;
364     settings.beginGroup(SETTINGS_GROUP);
365     // Last file open
366     settings.setValue("last_dir_saved", m_LastDirSaved);
367     settings.setValue("last_file_saved_all_files", m_LastFileSaved);
368     settings.endGroup();
369 }
370 
371 
connectSignalsSlots()372 void AllFilesWidget::connectSignalsSlots()
373 {
374     connect(ui.leFilter,  SIGNAL(textChanged(QString)),
375             this,         SLOT(FilterEditTextChangedSlot(QString)));
376     connect(ui.fileTree, SIGNAL(doubleClicked(const QModelIndex &)),
377             this,         SLOT(DoubleClick()));
378     connect(ui.fileTree->header(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), this, SLOT(Sort(int, Qt::SortOrder)));
379     connect(ui.buttonBox->button(QDialogButtonBox::Close), SIGNAL(clicked()), this, SIGNAL(CloseDialog()));
380     connect(ui.buttonBox->button(QDialogButtonBox::Save), SIGNAL(clicked()), this, SLOT(Save()));
381 }
382 
383