freedoomdialog.cpp
1 //------------------------------------------------------------------------------
2 // freedoomdialog.cpp
3 //------------------------------------------------------------------------------
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 // 02110-1301 USA
19 //
20 //------------------------------------------------------------------------------
21 // Copyright (C) 2015 "Zalewa" <zalewapl@gmail.com>
22 //------------------------------------------------------------------------------
23 #include "freedoomdialog.h"
24 
25 #include "configuration/doomseekerconfig.h"
26 #include "gui/commongui.h"
27 #include "fileutils.h"
28 #include "pathfinder/filesearchpath.h"
29 #include "pathfinder/pathfinder.h"
30 #include "pathfinder/wadpathfinder.h"
31 #include "templatedpathresolver.h"
32 #include "ui_freedoomdialog.h"
33 #include <QCheckBox>
34 #include <QCompleter>
35 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
36 #include <QFileSystemModel>
37 #else
38 #include <QDirModel>
39 #endif
40 #include <QMessageBox>
41 #include <QPushButton>
42 #include <wadseeker/entities/checksum.h>
43 #include <wadseeker/entities/modset.h>
44 #include <wadseeker/freedoom.h>
45 #include <wadseeker/modinstall.h>
46 
47 DClass<FreedoomDialog> : public Ui::FreedoomDialog
48 {
49 public:
50  QPushButton *btnInstall;
51  QPushButton *btnRetry;
52  Freedoom *freedoom;
53  ModInstall *modInstall;
54  QString targetDir;
55 };
56 DPointered(FreedoomDialog)
57 
58 FreedoomDialog::FreedoomDialog(QWidget *parent)
59  : QDialog(parent)
60 {
61  d->setupUi(this);
63 
64  d->freedoom = new Freedoom(this);
65  this->connect(d->freedoom, SIGNAL(finished()), SLOT(applyFreedoomVersionInfo()));
66 
67  d->modInstall = new ModInstall(this);
68  this->connect(d->modInstall, SIGNAL(finished()), SLOT(onModInstallFinished()));
69  this->connect(d->modInstall, SIGNAL(fileDownloadProgress(QString,qint64,qint64)),
70  SLOT(showFileDownloadProgress(QString,qint64,qint64)));
71 
72  d->btnInstall = d->buttonBox->button(QDialogButtonBox::Yes);
73  d->btnInstall->setDefault(true);
74  d->btnInstall->setEnabled(false);
75  d->btnInstall->setText(FreedoomDialog::tr("Install"));
76  connect(d->btnInstall, &QAbstractButton::clicked,
77  this, &QDialog::accept);
78  d->btnRetry = d->buttonBox->button(QDialogButtonBox::Retry);
79  connect(d->btnRetry, &QAbstractButton::clicked,
80  this, &FreedoomDialog::fetchInfo);
81 
82  setupInstallPaths();
83  setupWadsTable();
84  fetchInfo();
85 }
86 
87 FreedoomDialog::~FreedoomDialog()
88 {
89 }
90 
91 void FreedoomDialog::accept()
92 {
93  ModSet modSet = selectedModFiles();
94  if (modSet.isEmpty())
95  {
96  QMessageBox::critical(this, tr("Install Freedoom"),
97  tr("Select at least one file."));
98  return;
99  }
100  resetProgressBar();
101  showStatus(tr("Downloading & installing ..."));
102  d->targetDir = gDoomseekerTemplatedPathResolver().resolve(
103  d->cboInstallPath->currentText());
104  d->modInstall->install(d->targetDir, modSet);
105 }
106 
107 void FreedoomDialog::onModInstallFinished()
108 {
109  if (!d->modInstall->isError())
110  {
111  updateConfig();
112  fetchInfo();
113  }
114  else
115  showError(d->modInstall->error());
116 }
117 
118 void FreedoomDialog::updateConfig()
119 {
120  gConfig.doomseeker.enableFreedoomInstallation(d->targetDir);
121  gConfig.saveToFile();
122 }
123 
124 void FreedoomDialog::fetchInfo()
125 {
126  resetProgressBar();
127  d->btnRetry->hide();
128  showStatus(tr("Downloading Freedoom version info ..."));
129  d->freedoom->requestModSet();
130 }
131 
132 void FreedoomDialog::resetProgressBar()
133 {
134  d->progressBar->setFormat(tr("Working ..."));
135  d->progressBar->show();
136  d->progressBar->setMaximum(0);
137  d->progressBar->setValue(0);
138 }
139 
140 void FreedoomDialog::applyFreedoomVersionInfo()
141 {
142  if (!d->freedoom->isError())
143  showModInfo(d->freedoom->modSet());
144  else
145  showError(tr("Error: %1").arg(d->freedoom->error()));
146 }
147 
148 void FreedoomDialog::showModInfo(const ModSet &modSet)
149 {
150  d->wadsArea->show();
151  d->workInProgressArea->hide();
152 
153  d->wadsTable->setSortingEnabled(false);
154  d->wadsTable->clearContents();
155  while (d->wadsTable->rowCount() > 0)
156  {
157  d->wadsTable->removeRow(d->wadsTable->rowCount() - 1);
158  }
159  for (const ModFile &file : modSet.modFiles())
160  {
161  insertModFile(file);
162  }
163  d->wadsTable->setSortingEnabled(true);
164  d->btnInstall->setEnabled(true);
165  d->btnInstall->setFocus();
166 }
167 
168 void FreedoomDialog::insertModFile(const ModFile &file)
169 {
170  WadPathFinder pathFinder = WadPathFinder(PathFinder());
171  pathFinder.setAllowAliases(false);
172  QString location = pathFinder.find(file.fileName()).path();
173 
174  QString status = tr("OK");
175  bool needsInstall = false;
176  if (location.isEmpty())
177  {
178  needsInstall = true;
179  status = tr("Missing");
180  }
181  else
182  {
183  QByteArray md5 = FileUtils::md5(location);
184  for (Checksum checksum : file.checksums())
185  {
186  if (md5 != checksum.hash() && checksum.algorithm() == QCryptographicHash::Md5)
187  {
188  status = tr("Different");
189  needsInstall = true;
190  break;
191  }
192  }
193  }
194  if (file.fileName().compare("freedm.wad", Qt::CaseInsensitive) == 0)
195  needsInstall = false;
196 
197  d->wadsTable->insertRow(d->wadsTable->rowCount());
198  int row = d->wadsTable->rowCount() - 1;
199  d->wadsTable->setItem(row, ColName, new QTableWidgetItem(file.fileName()));
200  d->wadsTable->setItem(row, ColStatus, new QTableWidgetItem(status));
201 
202  auto checkBox = new QCheckBox();
203  checkBox->setChecked(needsInstall);
204  d->wadsTable->setCellWidget(row, ColInstall, checkBox);
205 
206  QString tooltip = tr("<p>File: %1<br>Version: %2<br>"
207  "Description: %3<br>Location: %4</p>")
208  .arg(file.name(), file.version(), file.description(), location);
209 
210  for (int col = 0; col < d->wadsTable->columnCount(); ++col)
211  {
212  QTableWidgetItem *item = d->wadsTable->item(row, col);
213  if (item != nullptr)
214  item->setToolTip(tooltip);
215  }
216 }
217 
218 void FreedoomDialog::showError(const QString &text)
219 {
220  d->btnRetry->setVisible(true);
221  d->btnRetry->setFocus();
222  showStatus(text);
223  d->progressBar->hide();
224 }
225 
226 void FreedoomDialog::showStatus(const QString &text)
227 {
228  d->wadsArea->hide();
229  d->workInProgressArea->show();
230  d->btnInstall->setEnabled(false);
231  d->lblWorkInProgress->setText(text);
232 }
233 
234 ModSet FreedoomDialog::selectedModFiles() const
235 {
236  ModSet modSet;
237  for (int row = 0; row < d->wadsTable->rowCount(); ++row)
238  {
239  auto checkbox = static_cast<QCheckBox *>(d->wadsTable->cellWidget(row, ColInstall));
240  if (checkbox->isChecked())
241  {
242  QString fileName = d->wadsTable->item(row, ColName)->text();
243  modSet.addModFile(d->freedoom->modSet().findFileName(fileName));
244  }
245  }
246  return modSet;
247 }
248 
249 void FreedoomDialog::setupInstallPaths()
250 {
251  auto completer = new QCompleter(this);
252 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
253  // NOTE: Although QFileSystemModel is available since Qt 4, there were some
254  // issues with it and QCompleter until at least Qt 5.12 (i.e. QTBUG-38014).
255  // We'll conservatively use QFileSystemModel on Qt 6 only.
256  auto model = new QFileSystemModel(completer);
257  model->setRootPath(gDoomseekerTemplatedPathResolver().resolve(gConfig.wadseeker.targetDirectory));
258  completer->setModel(model);
259 #else
260  completer->setModel(new QDirModel(completer));
261 #endif
262  d->cboInstallPath->setCompleter(completer);
263 
264  for (const FileSearchPath &path : gConfig.combinedWadseekPaths())
265  {
266  d->cboInstallPath->addItem(path.path());
267  }
268  d->cboInstallPath->setCurrentText(gConfig.wadseeker.targetDirectory);
269 }
270 
271 void FreedoomDialog::setupWadsTable()
272 {
273  QHeaderView *header = d->wadsTable->horizontalHeader();
274  header->setSectionResizeMode(ColName, QHeaderView::Stretch);
275  header->setSectionResizeMode(ColStatus, QHeaderView::ResizeToContents);
276  header->setSectionResizeMode(ColInstall, QHeaderView::ResizeToContents);
277  d->wadsTable->setColumnWidth(ColName, 150);
278 }
279 
280 void FreedoomDialog::showFileDownloadProgress(const QString &file,
281  qint64 current, qint64 total)
282 {
283  d->progressBar->setFormat(tr("%1 %p%").arg(file));
284  d->progressBar->setMaximum(total);
285  d->progressBar->setValue(current);
286 }