cfgfilepaths.cpp
1 //------------------------------------------------------------------------------
2 // cfgfilepaths.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) 2009 "Zalewa" <zalewapl@gmail.com>
22 //------------------------------------------------------------------------------
23 #include "cfgfilepaths.h"
24 
25 #include "configuration/doomseekerconfig.h"
26 #include "datapaths.h"
27 #include "pathfinder/filesearchpath.h"
28 #include "templatedpathresolver.h"
29 #include "ui_cfgfilepaths.h"
30 #include <QApplication>
31 #include <QFileDialog>
32 #include <QStyle>
33 
34 static const int COL_PATH = 0;
35 static const int COL_RECURSE = 1;
36 static const int NUM_COLS = 2;
37 
38 DClass<CFGFilePaths> : public Ui::CFGFilePaths
39 {
40 public:
41  struct SavedState
42  {
43  bool bResolveTemplatedPathsPlaceholders;
44  } savedState;
45 
46  void resetSavedState()
47  {
48  savedState.bResolveTemplatedPathsPlaceholders = gConfig.doomseeker.bResolveTemplatedPathsPlaceholders;
49  }
50 };
51 
52 DPointered(CFGFilePaths)
53 
54 CFGFilePaths::CFGFilePaths(QWidget *parent)
55  : ConfigPage(parent)
56 {
57  set_reject(&CFGFilePaths::reject_);
58  d->setupUi(this);
59 
60  QStringList labels;
61  labels << CFGFilePaths::tr("Path") << CFGFilePaths::tr("Recurse");
62  d->tblFilePaths->setColumnCount(NUM_COLS);
63  d->tblFilePaths->setHorizontalHeaderLabels(labels);
64 
65  QHeaderView *header = d->tblFilePaths->horizontalHeader();
66  header->setSectionResizeMode(COL_PATH, QHeaderView::Stretch);
67  header->setSectionResizeMode(COL_RECURSE, QHeaderView::ResizeToContents);
68 
69  QString templatedPathsToolTip = QString("<p>%1</p>").arg(CFGFilePaths::tr(
70  "Doomseeker supports special characters in user-configurable "
71  "paths. This affects all paths everywhere, not only the file "
72  "paths configured on this page. The placeholders are:"));
73  templatedPathsToolTip += "<ul>";
74 
75  templatedPathsToolTip += QString("<li>%1</li>").arg(CFGFilePaths::tr(
76  "<b>~</b> - if at the beginning of the path, resolved to "
77  "the home directory of the current user"));
78 #ifdef DOOMSEEKER_TEMPLATED_PATH_RESOLVER_TILDEUSER
79  templatedPathsToolTip += QString("<li>%1</li>").arg(CFGFilePaths::tr(
80  "<b>~<i>user</i></b> - if at the beginning of the path, resolved to "
81  "the home directory of the specified <i>user</i>"));
82 #endif
83  templatedPathsToolTip += QString("<li>%1</li>").arg(CFGFilePaths::tr(
84  "<b>$PROGDIR</b> - resolved to the directory where Doomseeker's "
85  "executable is located; this may create invalid paths if "
86  "used in the middle of the path"));
87  templatedPathsToolTip += QString("<li>%1</li>").arg(CFGFilePaths::tr(
88  "<b>$<i>NAME</i></b> - any <i>NAME</i> is resolved to the environment "
89  "variable of the same <i>NAME</i>, or to empty if the environment "
90  "variable is absent"));
91  templatedPathsToolTip += "</ul>";
92 
93  templatedPathsToolTip += QString("<p>%1</p>").arg(CFGFilePaths::tr(
94  "This option controls whether the <b>$PROGDIR</b> "
95  "and <b>$<i>NAME</i></b> will be resolved or ignored. "
96  "If this option is <b>checked</b>, both are resolved as explained. "
97  "If this option is <b>unchecked</b>, both will be taken literally. "
98  "The <b>~</b> placeholders are always resolved regardless."));
99 
100  d->cbResolveTemplatedPathsPlaceholders->setToolTip(templatedPathsToolTip);
101  d->btnBrowseWadPath->setIcon(style()->standardIcon(QStyle::SP_DirOpenIcon));
102 
103  connect(d->btnAddWadPath, SIGNAL(clicked()), this, SLOT(btnAddWadPath_Click()));
104  connect(d->btnBrowseWadPath, &QPushButton::clicked, this, &CFGFilePaths::btnBrowseWadPath_Click);
105  connect(d->btnRemoveWadPath, SIGNAL(clicked()), this, SLOT(btnRemoveWadPath_Click()));
106  this->connect(d->tblFilePaths->itemDelegate(),
107  SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)),
108  SIGNAL(validationRequested()));
109  connect(d->cbResolveTemplatedPathsPlaceholders, &QAbstractButton::toggled,
110  this, &CFGFilePaths::cbResolveTemplatedPathsPlaceholders_Toggled);
111  d->tblFilePaths->connect(d->tblFilePaths,
112  &TableWidgetReorderable::rowsReordered,
113  &QTableWidget::resizeRowsToContents);
114 }
115 
116 CFGFilePaths::~CFGFilePaths()
117 {
118 }
119 
120 void CFGFilePaths::addPath(const FileSearchPath &fileSearchPath)
121 {
122  if (fileSearchPath.isValid() && !isPathAlreadyDefined(fileSearchPath.path()))
123  addPathToTable(fileSearchPath);
124 }
125 
126 void CFGFilePaths::addPathToTable(const FileSearchPath &fileSearchPath)
127 {
128  d->tblFilePaths->setSortingEnabled(false);
129  int newRow = d->tblFilePaths->rowCount();
130  d->tblFilePaths->insertRow(newRow);
131  auto path = new QTableWidgetItem(fileSearchPath.path());
132  path->setData(Qt::ToolTipRole, fileSearchPath.path());
133  auto recurse = new QTableWidgetItem();
134  recurse->setCheckState(fileSearchPath.isRecursive() ? Qt::Checked : Qt::Unchecked);
135  recurse->setData(Qt::TextAlignmentRole, Qt::AlignCenter);
136 
137  d->tblFilePaths->setItem(newRow, COL_PATH, path);
138  d->tblFilePaths->setItem(newRow, COL_RECURSE, recurse);
139  d->tblFilePaths->resizeRowsToContents();
140  d->tblFilePaths->setSortingEnabled(true);
141 }
142 
143 void CFGFilePaths::btnAddWadPath_Click()
144 {
145  addPathToTable(QString());
146  emit validationRequested();
147 }
148 
149 void CFGFilePaths::btnBrowseWadPath_Click()
150 {
151  QString strDir = QFileDialog::getExistingDirectory(this, tr("Doomseeker - Add game mod path"));
152  addPath(gDefaultDataPaths->portablizePath(strDir));
153  emit validationRequested();
154 }
155 
156 void CFGFilePaths::btnRemoveWadPath_Click()
157 {
158  QSet<int> uniqueRows;
159  for (auto *item : d->tblFilePaths->selectedItems())
160  uniqueRows.insert(item->row());
161  QList<int> rows = uniqueRows.values();
162  // Reverse the order of rows.
163  std::sort(rows.begin(), rows.end(), [](int a, int b) { return b < a; });
164  for (int row : rows)
165  d->tblFilePaths->removeRow(row);
166  emit validationRequested();
167 }
168 
169 void CFGFilePaths::cbResolveTemplatedPathsPlaceholders_Toggled(bool state)
170 {
171  gConfig.doomseeker.bResolveTemplatedPathsPlaceholders = state;
172  emit validationRequested();
173 }
174 
175 QIcon CFGFilePaths::icon() const
176 {
177  return style()->standardIcon(QStyle::SP_DirOpenIcon);
178 }
179 
180 bool CFGFilePaths::isPathAlreadyDefined(const QString &path) const
181 {
182  Qt::CaseSensitivity caseSensitivity;
183 
184  #ifdef Q_OS_WIN32
185  caseSensitivity = Qt::CaseInsensitive;
186  #else
187  caseSensitivity = Qt::CaseSensitive;
188  #endif
189 
190  for (int i = 0; i < d->tblFilePaths->rowCount(); ++i)
191  {
192  const QTableWidgetItem *item = d->tblFilePaths->item(i, COL_PATH);
193  QString dir = item->text();
194 
195  if (dir.compare(path, caseSensitivity) == 0)
196  return true;
197  }
198 
199  return false;
200 }
201 
203 {
204  d->resetSavedState();
205 
206  const QList<FileSearchPath> &wadPaths = gConfig.doomseeker.wadPaths;
207  for (int i = 0; i < wadPaths.count(); ++i)
208  addPath(wadPaths[i]);
209 
210  d->cbTellMeWhereAreMyWads->setChecked(gConfig.doomseeker.bTellMeWhereAreTheWADsWhenIHoverCursorOverWADSColumn);
211  d->cbCheckTheIntegrityOfWads->setChecked(gConfig.doomseeker.bCheckTheIntegrityOfWads);
212  d->cbResolveTemplatedPathsPlaceholders->setChecked(gConfig.doomseeker.bResolveTemplatedPathsPlaceholders);
213 }
214 
216 {
217  QList<FileSearchPath> wadPaths;
218 
219  for (int i = 0; i < d->tblFilePaths->rowCount(); ++i)
220  {
221  QTableWidgetItem *itemPath = d->tblFilePaths->item(i, COL_PATH);
222  QTableWidgetItem *itemRecurse = d->tblFilePaths->item(i, COL_RECURSE);
223  FileSearchPath fileSearchPath(itemPath->text());
224  fileSearchPath.setRecursive(itemRecurse->checkState() == Qt::Checked);
225  wadPaths << fileSearchPath;
226  }
227 
228  gConfig.doomseeker.wadPaths = wadPaths;
229  gConfig.doomseeker.bTellMeWhereAreTheWADsWhenIHoverCursorOverWADSColumn = d->cbTellMeWhereAreMyWads->isChecked();
230  gConfig.doomseeker.bCheckTheIntegrityOfWads = d->cbCheckTheIntegrityOfWads->isChecked();
231  gConfig.doomseeker.bResolveTemplatedPathsPlaceholders = d->cbResolveTemplatedPathsPlaceholders->isChecked();
232 
233  d->resetSavedState();
234 }
235 
236 void CFGFilePaths::reject_()
237 {
238  gConfig.doomseeker.bResolveTemplatedPathsPlaceholders = d->savedState.bResolveTemplatedPathsPlaceholders;
239 }
240 
242 {
243  bool allPathsValid = true;
244  for (int i = 0; i < d->tblFilePaths->rowCount(); ++i)
245  {
246  QTableWidgetItem *itemPath = d->tblFilePaths->item(i, COL_PATH);
247 
248  QString validationError = validatePath(gDoomseekerTemplatedPathResolver().resolve(itemPath->text()));
249  bool valid = validationError.isEmpty();
250  allPathsValid = allPathsValid && valid;
251 
252  itemPath->setIcon(valid ? QIcon() : QIcon(":/icons/exclamation_16.png"));
253  itemPath->setToolTip(validationError);
254  }
255  return allPathsValid ? VALIDATION_OK : VALIDATION_ERROR;
256 }
257 
258 QString CFGFilePaths::validatePath(const QString &path) const
259 {
260  if (path.trimmed().isEmpty())
261  return tr("Path empty.");
262 
263  QFileInfo fileInfo(path.trimmed());
264  if (!fileInfo.exists())
265  return tr("Path doesn't exist.");
266 
267  if (!fileInfo.isDir())
268  return tr("Path is not a directory.");
269 
270  return QString();
271 }