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