wadseekerinterface.cpp
1 //------------------------------------------------------------------------------
2 // wadseekerinterface.cpp
3 //------------------------------------------------------------------------------
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9 //
10 // This program 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
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; 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) 2009 "Zalewa" <zalewapl@gmail.com>
22 //------------------------------------------------------------------------------
23 #include "gui/wadseekerinterface.h"
24 #include "ui_wadseekerinterface.h"
25 #include "configuration/doomseekerconfig.h"
26 #include "serverapi/server.h"
27 #include "application.h"
28 #include "mainwindow.h"
29 #include "strings.h"
30 
31 #include <QMessageBox>
32 
33 const int WadseekerInterface::UPDATE_INTERVAL_MS = 500;
34 WadseekerInterface *WadseekerInterface::currentInstance = NULL;
35 
36 DClass<WadseekerInterface> : public Ui::WadseekerInterface
37 {
38 public:
39  bool bCompletedSuccessfully;
40 };
41 
42 DPointered(WadseekerInterface)
43 
44 WadseekerInterface::WadseekerInterface(QWidget* parent)
45 : QDialog(parent)
46 {
47  construct();
48  bAutomatic = false;
49 }
50 
51 WadseekerInterface::WadseekerInterface(ServerPtr server, QWidget* parent)
52 : QDialog(parent)
53 {
54  construct();
55  bAutomatic = true;
56  d->lblTop->setText(tr("Downloading WADs for server \"%1\"").arg(server->name()));
57  d->btnDownload->hide();
58  d->leWadName->hide();
59  setCustomSite(server->webSite());
60 }
61 
62 WadseekerInterface::~WadseekerInterface()
63 {
64  currentInstance = NULL;
65 }
66 
67 void WadseekerInterface::accept()
68 {
69  if (isAutomatic())
70  {
71  if (d->bCompletedSuccessfully)
72  {
73  done(QDialog::Accepted);
74  }
75  }
76  else
77  {
78  if (d->leWadName->text().isEmpty())
79  {
80  return;
81  }
82 
83  startSeeking(d->leWadName->text().split(',', QString::SkipEmptyParts));
84  }
85 }
86 
87 void WadseekerInterface::allDone(bool bSuccess)
88 {
89  setStateWaiting();
90  d->bCompletedSuccessfully = bSuccess;
91  QApplication::alert(this);
92  if (bSuccess)
93  {
94  displayMessage(tr("All done. Success."), WadseekerLib::NoticeImportant, false);
95 
96  if (isAutomatic())
97  {
98  if (isActiveWindow())
99  {
100  done(QDialog::Accepted);
101  }
102  else
103  {
104  d->btnStartGame->show();
105  }
106  }
107  }
108  else
109  {
110  QStringList failures = unsuccessfulWads();
111 
112  foreach (const QString& failure, failures)
113  {
114  d->twWads->setFileFailed(failure);
115  }
116 
117  displayMessage(tr("All done. Fail."), WadseekerLib::CriticalError, false);
118  }
119 }
120 
121 void WadseekerInterface::connectWadseekerObject()
122 {
123  // Connect Wadseeker to the dialog box.
124  this->connect(&wadseeker, SIGNAL( allDone(bool) ),
125  SLOT( allDone(bool) ) );
126  this->connect(&wadseeker, SIGNAL( message(const QString&, WadseekerLib::MessageType) ),
127  SLOT( message(const QString&, WadseekerLib::MessageType) ) );
128  this->connect(&wadseeker, SIGNAL( seekStarted(const QStringList&) ),
129  SLOT( seekStarted(const QStringList&) ) );
130  this->connect(&wadseeker, SIGNAL( fileInstalled(const QString&) ),
131  SLOT( fileDownloadSuccessful(const QString&) ) );
132  this->connect(&wadseeker, SIGNAL( siteFinished(const QUrl&) ),
133  SLOT( siteFinished(const QUrl&) ) );
134  this->connect(&wadseeker, SIGNAL( siteProgress(const QUrl&, qint64, qint64) ),
135  SLOT( siteProgress(const QUrl&, qint64, qint64) ) );
136  this->connect(&wadseeker, SIGNAL( siteRedirect(const QUrl&, const QUrl&) ),
137  SLOT( siteRedirect(const QUrl&, const QUrl&) ) );
138  this->connect(&wadseeker, SIGNAL( siteStarted(const QUrl&) ),
139  SLOT( siteStarted(const QUrl&) ) );
140 
141  // Connect Wadseeker to the WADs table widget.
142  d->twWads->connect(&wadseeker, SIGNAL( fileDownloadFinished(const QString&) ),
143  SLOT( setFileDownloadFinished(const QString&) ) );
144  d->twWads->connect(&wadseeker, SIGNAL( fileDownloadProgress(const QString&, qint64, qint64) ),
145  SLOT( setFileProgress(const QString&, qint64, qint64) ) );
146  d->twWads->connect(&wadseeker, SIGNAL( fileDownloadStarted(const QString&, const QUrl&) ),
147  SLOT( setFileUrl(const QString&, const QUrl&) ) );
148 }
149 
150 void WadseekerInterface::construct()
151 {
152  d->setupUi(this);
153  d->bCompletedSuccessfully = false;
154  setStateWaiting();
155 
156  initMessageColors();
157 
158  this->setWindowIcon(QIcon(":/icon.png"));
159  d->btnStartGame->hide();
160  this->connect(&updateTimer, SIGNAL(timeout()), SLOT(registerUpdateRequest()));
161 
162  connectWadseekerObject();
163 
164  // Connect tables.
165  this->connect(d->twWads, SIGNAL( rightMouseClick(const QModelIndex&, const QPoint&) ),
166  SLOT( wadsTableRightClicked(const QModelIndex&, const QPoint&) ) );
167 
168  bAutomatic = false;
169  bFirstShown = false;
170 
171  const QStringList& urlList = gConfig.wadseeker.searchURLs;
172 
173  if (!urlList.isEmpty())
174  {
175  wadseeker.setPrimarySites(urlList);
176  }
177  else
178  {
179  wadseeker.setPrimarySitesToDefault();
180  }
181 
182  updateTimer.setSingleShot(false);
183  updateTimer.start(UPDATE_INTERVAL_MS);
184 }
185 
186 WadseekerInterface *WadseekerInterface::create(QWidget* parent)
187 {
188  if (!isInstantiated())
189  {
190  currentInstance = new WadseekerInterface(parent);
191  return currentInstance;
192  }
193  return NULL;
194 }
195 
196 WadseekerInterface *WadseekerInterface::create(ServerPtr server, QWidget* parent)
197 {
198  if (!isInstantiated())
199  {
200  currentInstance = new WadseekerInterface(server, parent);
201  return currentInstance;
202  }
203  return NULL;
204 }
205 
206 void WadseekerInterface::displayMessage(const QString& message, WadseekerLib::MessageType type, bool bPrependErrorsWithMessageType)
207 {
208  QString strProcessedMessage;
209 
210  bool bPrependWithNewline = false;
211  QString wrapHtmlLeft = "<div style=\"%1\">";
212  QString wrapHtmlRight = "</div>";
213  QString htmlStyle;
214 
215  switch (type)
216  {
217  case WadseekerLib::CriticalError:
218  htmlStyle = QString("color: %1; font-weight: bold;").arg(colorHtmlMessageFatalError);
219  bPrependWithNewline = true;
220 
221  if (bPrependErrorsWithMessageType)
222  {
223  strProcessedMessage = tr("CRITICAL ERROR: %1").arg(message);
224  }
225  else
226  {
227  strProcessedMessage = message;
228  }
229 
230  setStateWaiting();
231  break;
232 
233  case WadseekerLib::Error:
234  htmlStyle = QString("color: %1;").arg(colorHtmlMessageError);
235 
236  if (bPrependErrorsWithMessageType)
237  {
238  strProcessedMessage = tr("Error: %1").arg(message);
239  }
240  else
241  {
242  strProcessedMessage = message;
243  }
244  break;
245 
246  case WadseekerLib::Notice:
247  htmlStyle = QString("color: %1;").arg(colorHtmlMessageNotice);
248 
249  strProcessedMessage = message;
250  break;
251 
252  case WadseekerLib::NoticeImportant:
253  htmlStyle = QString("color: %1; font-weight: bold;").arg(colorHtmlMessageNotice);
254  bPrependWithNewline = true;
255 
256  strProcessedMessage = message;
257  break;
258  }
259 
260  if (bPrependWithNewline && !d->teWadseekerOutput->toPlainText().isEmpty())
261  {
262  strProcessedMessage = "<br>" + strProcessedMessage;
263  }
264 
265  wrapHtmlLeft = wrapHtmlLeft.arg(htmlStyle);
266 
267  strProcessedMessage = wrapHtmlLeft + strProcessedMessage + wrapHtmlRight;
268 
269  d->teWadseekerOutput->append(strProcessedMessage);
270 }
271 
272 void WadseekerInterface::fileDownloadSuccessful(const QString& filename)
273 {
274  successfulWads << filename;
275  d->twWads->setFileSuccessful(filename);
276 }
277 
278 void WadseekerInterface::initMessageColors()
279 {
280  colorHtmlMessageNotice = gConfig.wadseeker.colorMessageNotice;
281  colorHtmlMessageError = gConfig.wadseeker.colorMessageError;
282  colorHtmlMessageFatalError = gConfig.wadseeker.colorMessageCriticalError;
283 }
284 
285 bool WadseekerInterface::isInstantiated()
286 {
287  return currentInstance != NULL;
288 }
289 
290 void WadseekerInterface::message(const QString& message, WadseekerLib::MessageType type)
291 {
292  displayMessage(message, type, true);
293 }
294 
295 void WadseekerInterface::registerUpdateRequest()
296 {
297  updateProgressBar();
298  updateTitle();
299 }
300 
301 void WadseekerInterface::reject()
302 {
303  switch(state)
304  {
305  case Downloading:
306  wadseeker.abort();
307  break;
308 
309  case Waiting:
310  this->done(Rejected);
311  break;
312  }
313 }
314 
315 void WadseekerInterface::resetTitleToDefault()
316 {
317  setWindowTitle(tr("Wadseeker"));
318 }
319 
320 void WadseekerInterface::seekStarted(const QStringList& filenames)
321 {
322  d->teWadseekerOutput->clear();
323  d->pbOverallProgress->setValue(0);
324  displayMessage("Seek started on filenames: " + filenames.join(", "), WadseekerLib::Notice, false);
325 
326  seekedWads = filenames;
327  successfulWads.clear();
328  d->twSites->setRowCount(0);
329  d->twWads->setRowCount(0);
330  setStateDownloading();
331 
332  foreach (const QString& name, filenames)
333  {
334  d->twWads->addFile(name);
335  }
336 }
337 
338 void WadseekerInterface::setStateDownloading()
339 {
340  d->btnClose->setText(tr("Abort"));
341  d->btnDownload->setEnabled(false);
342  state = Downloading;
343 }
344 
345 void WadseekerInterface::setStateWaiting()
346 {
347  d->btnClose->setText(tr("Close"));
348  d->btnDownload->setEnabled(true);
349  state = Waiting;
350 }
351 
352 void WadseekerInterface::setWads(const QStringList& wads)
353 {
354  if (isAutomatic())
355  {
356  seekedWads = wads;
357  }
358  else
359  {
360  d->leWadName->setText(wads.join(", "));
361  }
362 }
363 
364 void WadseekerInterface::setupIdgames()
365 {
366  QString idgamesUrl = Wadseeker::defaultIdgamesUrl();
367  bool useIdgames = true;
368  bool idgamesHasHighPriority = false;
369 
370  useIdgames = gConfig.wadseeker.bSearchInIdgames;
371  idgamesUrl = gConfig.wadseeker.idgamesURL;
372 
373  wadseeker.setIdgamesEnabled(useIdgames);
374  wadseeker.setIdgamesUrl(idgamesUrl);
375  wadseeker.setWadArchiveEnabled(gConfig.wadseeker.bSearchInWadArchive);
376 }
377 
378 void WadseekerInterface::showEvent(QShowEvent* event)
379 {
380  if (!bFirstShown)
381  {
382  bFirstShown = true;
383 
384  if (isAutomatic())
385  {
386  startSeeking(seekedWads);
387  }
388  }
389 }
390 
391 void WadseekerInterface::siteFinished(const QUrl& site)
392 {
393  d->twSites->removeUrl(site);
394  displayMessage("Site finished: " + site.toEncoded(), WadseekerLib::Notice, false);
395 }
396 
397 void WadseekerInterface::siteProgress(const QUrl& site, qint64 bytes, qint64 total)
398 {
399  d->twSites->setUrlProgress(site, bytes, total);
400 }
401 
402 void WadseekerInterface::siteRedirect(const QUrl& oldUrl, const QUrl& newUrl)
403 {
404  d->twSites->removeUrl(oldUrl);
405  d->twSites->addUrl(newUrl);
406  displayMessage("Site redirect: " + oldUrl.toEncoded() + " -> " + newUrl.toEncoded(), WadseekerLib::Notice, false);
407 }
408 
409 void WadseekerInterface::siteStarted(const QUrl& site)
410 {
411  d->twSites->addUrl(site);
412  displayMessage("Site started: " + site.toEncoded(), WadseekerLib::Notice, false);
413 }
414 
415 void WadseekerInterface::startSeeking(const QStringList& seekedFilesList)
416 {
417  if (seekedFilesList.isEmpty())
418  {
419  return;
420  }
421  d->bCompletedSuccessfully = false;
422 
423  // Get rid of the whitespace characters from each filename; we don't want
424  // to be searching " awad.wad".
425  QStringList seekedFilesListFormatted;
426  foreach (QString filenameFormatted, seekedFilesList)
427  {
428  filenameFormatted = filenameFormatted.trimmed();
429 
430  seekedFilesListFormatted << filenameFormatted;
431  }
432 
433  setupIdgames();
434 
435  wadseeker.setTargetDirectory(gConfig.wadseeker.targetDirectory);
436  wadseeker.setCustomSite(customSite);
437  wadseeker.setMaximumConcurrentSeeks(gConfig.wadseeker.maxConcurrentSiteDownloads);
438  wadseeker.setMaximumConcurrentDownloads(gConfig.wadseeker.maxConcurrentWadDownloads);
439  wadseeker.startSeek(seekedFilesListFormatted);
440 }
441 
442 void WadseekerInterface::updateProgressBar()
443 {
444  double totalPercentage = d->twWads->totalDonePercentage();
445  unsigned progressBarValue = (unsigned)(totalPercentage * 100.0);
446 
447  d->pbOverallProgress->setValue(progressBarValue);
448 }
449 
450 void WadseekerInterface::updateTitle()
451 {
452  switch (state)
453  {
454  case Downloading:
455  {
456  double totalPercentage = d->twWads->totalDonePercentage();
457  if (totalPercentage < 0.0)
458  {
459  totalPercentage = 0.0;
460  }
461 
462  setWindowTitle(tr("[%1%] Wadseeker").arg(totalPercentage, 6, 'f', 2));
463  break;
464  }
465 
466  default:
467  case Waiting:
468  resetTitleToDefault();
469  break;
470  }
471 }
472 
473 QStringList WadseekerInterface::unsuccessfulWads() const
474 {
475  QStringList allWads = seekedWads;
476 
477  foreach (const QString& success, successfulWads)
478  {
479  allWads.removeAll(success);
480  }
481 
482  return allWads;
483 }
484 
485 void WadseekerInterface::wadsTableRightClicked(const QModelIndex& index, const QPoint& cursorPosition)
486 {
487  WadseekerWadsTable::ContextMenu* menu = d->twWads->contextMenu(index, cursorPosition);
488 
489  // Disable actions depending on Wadseeker's state.
490  QString fileName = d->twWads->fileNameAtRow(index.row());
491  if (!wadseeker.isDownloadingFile(fileName))
492  {
493  menu->actionSkipCurrentSite->setEnabled(false);
494  }
495 
496  QAction* pResult = menu->exec();
497 
498  if (pResult == menu->actionSkipCurrentSite)
499  {
500  QString wadName = d->twWads->fileNameAtRow(index.row());
501  d->twWads->setFileUrl(fileName, QUrl());
502 
503  wadseeker.skipFileCurrentUrl(wadName);
504  }
505  else if (pResult != NULL)
506  {
507  QMessageBox::warning(this, tr("Context menu error"), tr("Unknown action selected."));
508  }
509 
510  delete menu;
511 }
void setWads(const QStringList &wads)
Sets WADs to seek.
Wadseeker dialog box, only one instance is allowed.