wadspicker.cpp
1 //------------------------------------------------------------------------------
2 // wadspicker.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) 2014 "Zalewa" <zalewapl@gmail.com>
22 //------------------------------------------------------------------------------
23 #include "ui_wadspicker.h"
24 #include "wadspicker.h"
25 
26 #include "configuration/doomseekerconfig.h"
27 #include "datapaths.h"
28 #include "gui/commongui.h"
29 #include "gui/icons.h"
30 #include "templatedpathresolver.h"
31 #include <QAction>
32 #include <QApplication>
33 #include <QKeyEvent>
34 #include <QFileDialog>
35 #include <QIcon>
36 #include <QSignalBlocker>
37 #include <QStandardItemModel>
38 #include <QStyle>
39 
40 DClass<WadsPicker> : public Ui::WadsPicker
41 {
42 public:
43  QStandardItemModel *model;
44 };
45 
46 DPointered(WadsPicker)
47 
48 WadsPicker::WadsPicker(QWidget *parent)
49 {
50  Q_UNUSED(parent);
51  d->setupUi(this);
52  d->model = new QStandardItemModel(this);
53  connect(d->model, &QStandardItemModel::itemChanged,
54  this, &WadsPicker::checkItemPathExists);
55  d->lstAdditionalFiles->setModel(d->model);
56  d->lstAdditionalFiles->installEventFilter(this);
57 
58  d->btnBrowsePwad->setIcon(style()->standardIcon(QStyle::SP_DirOpenIcon));
59  d->btnClearPwadList->setIcon(Icons::clear());
60 
61  auto *checkPathsAction = new QAction(QIcon(":/icons/refresh.png"),
62  WadsPicker::tr("Check paths"), this);
63  d->lstAdditionalFiles->addAction(checkPathsAction);
64  connect(checkPathsAction, &QAction::triggered,
65  this, &WadsPicker::checkAllItemsPathsExist);
66  d->lstAdditionalFiles->setContextMenuPolicy(Qt::ActionsContextMenu);
67 }
68 
69 WadsPicker::~WadsPicker()
70 {
71 }
72 
73 void WadsPicker::addEmptyPath()
74 {
75  addPathToTable(QString(), true);
76 }
77 
78 void WadsPicker::addWadPath(const QString &wadPath, bool required)
79 {
80  if (wadPath.isEmpty())
81  return;
82  QFileInfo fileInfo(gDoomseekerTemplatedPathResolver().resolve(wadPath));
83  QStandardItem *existing = findPath(wadPath);
84  if (existing != nullptr)
85  {
86  // Update the path check if it's re-added.
87  checkItemPathExists(existing);
88  }
89  else
90  {
91  addPathToTable(wadPath, required);
92  }
93 }
94 
95 void WadsPicker::addPathToTable(const QString &path, bool required)
96 {
97  auto it = new QStandardItem(path);
98 
99  it->setDragEnabled(true);
100  it->setDropEnabled(false);
101  it->setToolTip(path);
102  it->setCheckable(true);
103  it->setCheckState(required ? Qt::Checked : Qt::Unchecked);
104  it->setData(path, Qt::EditRole);
105  checkItemPathExists(it);
106 
107  d->model->appendRow(it);
108 }
109 
110 void WadsPicker::browseAndAdd()
111 {
112  QString dialogDir = gConfig.doomseeker.previousCreateServerWadDir;
113  QStringList filesNames = QFileDialog::getOpenFileNames(this,
114  tr("Doomseeker - Add file(s)"), dialogDir);
115 
116  if (!filesNames.isEmpty())
117  {
118  // Remember the directory of the first file. This directory will be
119  // restored the next time this dialog is opened.
120  QFileInfo fi(filesNames[0]);
121  gConfig.doomseeker.previousCreateServerWadDir = fi.absolutePath();
122 
123  for (const QString &strFile : filesNames)
124  {
125  addWadPath(gDefaultDataPaths->portablizePath(strFile));
126  }
127  }
128 }
129 
130 void WadsPicker::checkAllItemsPathsExist()
131 {
132  for (int i = 0; i < d->model->rowCount(); ++i)
133  {
134  checkItemPathExists(d->model->item(i));
135  }
136 }
137 
138 void WadsPicker::checkItemPathExists(QStandardItem *item)
139 {
140  QFont font = item->font();
141  QString path = item->data(Qt::EditRole).toString().trimmed();
142  QFileInfo fileInfo(gDoomseekerTemplatedPathResolver().resolve(path));
143  // Squelch the model signals because this check is triggered by
144  // item edit, but it also does an item edit, which triggers the
145  // signal again, which causes an infinite loop. Also I **hope** this
146  // will never break anything in any version of Qt because who the
147  // hell knows how the internals of the model and widgets are
148  // implemented and if some future widget will come to rely on these
149  // signals and if they're blocked, then the changes will not be
150  // displayed, even though blocking the signals on the model is the
151  // way to do this as suggested by everyone on the net
152  // roflmao-through-tears.
153  QSignalBlocker signalBlocker(d->model);
154  if (fileInfo.exists())
155  {
156  font.setItalic(false);
157  item->setToolTip(path);
158  item->setIcon(QIcon());
159  }
160  else
161  {
162  font.setItalic(true);
163  item->setToolTip(QString("<font color=\"#ff0000\">")
164  + tr("%1 MISSING").arg(path)
165  + QString("</font>"));
166  item->setIcon(QIcon(":/icons/exclamation_16.png"));
167  }
168  item->setFont(font);
169 }
170 
171 bool WadsPicker::eventFilter(QObject *obj, QEvent *event)
172 {
173  if (obj == d->lstAdditionalFiles)
174  {
175  if (event->type() == QEvent::KeyPress)
176  {
177  QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
178  switch (keyEvent->key())
179  {
180  case Qt::Key_Delete:
181  case Qt::Key_Minus:
182  removeSelected();
183  return true;
184  case Qt::Key_Plus:
185  addEmptyPath();
186  return true;
187  default:
188  // no-op
189  break;
190  }
191  }
192  else if (event->type() == QEvent::Show)
193  {
194  checkAllItemsPathsExist();
195  }
196  }
197  return false;
198 }
199 
200 QList<PickedGameFile> WadsPicker::files() const
201 {
202  QList<PickedGameFile> list;
203  for (int i = 0; i < d->model->rowCount(); ++i)
204  {
205  auto item = d->model->item(i);
206  // Filter out all the empty entries from the list.
207  if (!item->data(Qt::EditRole).toString().trimmed().isEmpty())
208  {
209  list << PickedGameFile { item->text(), (item->checkState() == Qt::Unchecked) };
210  }
211  }
212 
213  return list;
214 }
215 
216 QStandardItem *WadsPicker::findPath(const QString &path)
217 {
218  #ifdef Q_OS_WIN32
219  static const Qt::CaseSensitivity cs = Qt::CaseInsensitive;
220  #else
221  static const Qt::CaseSensitivity cs = Qt::CaseSensitive;
222  #endif
223 
224  for (int i = 0; i < d->model->rowCount(); ++i)
225  {
226  QStandardItem *item = d->model->item(i);
227  if (item->text().compare(path, cs) == 0)
228  return item;
229  }
230  return nullptr;
231 }
232 
233 void WadsPicker::setFiles(const QList<PickedGameFile> &files)
234 {
235  removeAll();
236  for (auto file : files)
237  addWadPath(file.path, !file.optional);
238 }
239 
240 void WadsPicker::removeAll()
241 {
242  d->model->clear();
243 }
244 
245 void WadsPicker::removeSelected()
246 {
247  const bool bSelectNextLowest = true;
248  CommonGUI::removeSelectedRowsFromStandardItemView(d->lstAdditionalFiles, bSelectNextLowest);
249 }