qBittorrent
fspathedit_p.cpp
Go to the documentation of this file.
1 /*
2  * Bittorrent Client using Qt and libtorrent.
3  * Copyright (C) 2016 Eugene Shalygin
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 02110-1301, USA.
18  *
19  * In addition, as a special exception, the copyright holders give permission to
20  * link this program with the OpenSSL project's "OpenSSL" library (or with
21  * modified versions of it that use the same license as the "OpenSSL" library),
22  * and distribute the linked executables. You must obey the GNU General Public
23  * License in all respects for all of the code used other than "OpenSSL". If you
24  * modify file(s), you may extend this exception to your version of the file(s),
25  * but you are not obligated to do so. If you do not wish to do so, delete this
26  * exception statement from your version.
27  */
28 
29 #include "fspathedit_p.h"
30 
31 #include <QCompleter>
32 #include <QContextMenuEvent>
33 #include <QDir>
34 #include <QFileInfo>
35 #include <QFileSystemModel>
36 #include <QMenu>
37 #include <QStringList>
38 #include <QStyle>
39 
40 // -------------------- FileSystemPathValidator ----------------------------------------
42  : QValidator(parent)
43  , m_strictMode(false)
44  , m_existingOnly(false)
45  , m_directoriesOnly(false)
46  , m_checkReadPermission(false)
47  , m_checkWritePermission(false)
48  , m_lastTestResult(TestResult::DoesNotExist)
49  , m_lastValidationState(QValidator::Invalid)
50 {
51 }
52 
54 {
55  return m_strictMode;
56 }
57 
59 {
60  m_strictMode = v;
61 }
62 
64 {
65  return m_existingOnly;
66 }
67 
69 {
70  m_existingOnly = v;
71 }
72 
74 {
75  return m_directoriesOnly;
76 }
77 
79 {
80  m_directoriesOnly = v;
81 }
82 
84 {
85  return m_checkReadPermission;
86 }
87 
89 {
90  m_checkReadPermission = v;
91 }
92 
94 {
95  return m_checkWritePermission;
96 }
97 
99 {
100  m_checkWritePermission = v;
101 }
102 
103 QValidator::State Private::FileSystemPathValidator::validate(QString &input, int &pos) const
104 {
105  if (input.isEmpty())
106  return m_strictMode ? QValidator::Invalid : QValidator::Intermediate;
107 
108  // we test path components from beginning to the one with cursor location in strict mode
109  // and the one with cursor and beyond in non-strict mode
110  QList<QStringView> components = QStringView(input).split(QDir::separator(), Qt::KeepEmptyParts);
111  // find index of the component that contains pos
112  int componentWithCursorIndex = 0;
113  int componentWithCursorPosition = 0;
114  int pathLength = 0;
115 
116  // components.size() - 1 because when path ends with QDir::separator(), we will not see the last
117  // character in the components array, yet everything past the one before the last delimiter
118  // belongs to the last component
119  for (; (componentWithCursorIndex < (components.size() - 1)) && (pathLength < pos); ++componentWithCursorIndex)
120  {
121  pathLength = componentWithCursorPosition + components[componentWithCursorIndex].size();
122  componentWithCursorPosition += components[componentWithCursorIndex].size() + 1;
123  }
124 
125  Q_ASSERT(componentWithCursorIndex < components.size());
126 
127  m_lastValidationState = QValidator::Acceptable;
128  if (componentWithCursorIndex > 0)
129  m_lastValidationState = validate(components, m_strictMode, 0, componentWithCursorIndex - 1);
130  if ((m_lastValidationState == QValidator::Acceptable) && (componentWithCursorIndex < components.size()))
131  m_lastValidationState = validate(components, false, componentWithCursorIndex, components.size() - 1);
132  return m_lastValidationState;
133 }
134 
135 QValidator::State Private::FileSystemPathValidator::validate(const QList<QStringView> &pathComponents, bool strict,
136  int firstComponentToTest, int lastComponentToTest) const
137 {
138  Q_ASSERT(firstComponentToTest >= 0);
139  Q_ASSERT(lastComponentToTest >= firstComponentToTest);
140  Q_ASSERT(lastComponentToTest < pathComponents.size());
141 
142  m_lastTestResult = TestResult::DoesNotExist;
143  if (pathComponents.empty())
144  return strict ? QValidator::Invalid : QValidator::Intermediate;
145 
146  for (int i = firstComponentToTest; i <= lastComponentToTest; ++i)
147  {
148  const bool isFinalPath = (i == (pathComponents.size() - 1));
149  const QStringView componentPath = pathComponents[i];
150  if (componentPath.isEmpty()) continue;
151 
152  m_lastTestResult = testPath(pathComponents[i], isFinalPath);
153  if (m_lastTestResult != TestResult::OK)
154  {
155  m_lastTestedPath = componentPath.toString();
156  return strict ? QValidator::Invalid : QValidator::Intermediate;
157  }
158  }
159 
160  return QValidator::Acceptable;
161 }
162 
164 Private::FileSystemPathValidator::testPath(const QStringView path, bool pathIsComplete) const
165 {
166  QFileInfo fi(path.toString());
167  if (m_existingOnly && !fi.exists())
168  return TestResult::DoesNotExist;
169 
170  if ((!pathIsComplete || m_directoriesOnly) && !fi.isDir())
171  return TestResult::NotADir;
172 
173  if (pathIsComplete)
174  {
175  if (!m_directoriesOnly && fi.isDir())
176  return TestResult::NotAFile;
177 
178  if (m_checkWritePermission && (fi.exists() && !fi.isWritable()))
179  return TestResult::CantWrite;
180  if (m_checkReadPermission && !fi.isReadable())
181  return TestResult::CantRead;
182  }
183 
184  return TestResult::OK;
185 }
186 
188 {
189  return m_lastTestResult;
190 }
191 
193 {
194  return m_lastValidationState;
195 }
196 
198 {
199  return m_lastTestedPath;
200 }
201 
203  : QLineEdit {parent}
204  , m_completerModel {new QFileSystemModel(this)}
205  , m_completer {new QCompleter(this)}
206  , m_browseAction {nullptr}
207  , m_warningAction {nullptr}
208 {
209  m_completerModel->setRootPath("");
210  m_completerModel->setIconProvider(&m_iconProvider);
211  m_completer->setModel(m_completerModel);
212  m_completer->setCompletionMode(QCompleter::PopupCompletion);
213  setCompleter(m_completer);
214 }
215 
217 {
218  delete m_completerModel; // has to be deleted before deleting the m_iconProvider object
219 }
220 
222 {
223  QDir::Filters filters = completeDirsOnly ? QDir::Dirs : QDir::AllEntries;
224  filters |= QDir::NoDotAndDotDot;
225  m_completerModel->setFilter(filters);
226 }
227 
228 void Private::FileLineEdit::setFilenameFilters(const QStringList &filters)
229 {
230  m_completerModel->setNameFilters(filters);
231 }
232 
234 {
235  m_browseAction = action;
236 }
237 
238 void Private::FileLineEdit::setValidator(QValidator *validator)
239 {
240  QLineEdit::setValidator(validator);
241 }
242 
244 {
245  return placeholderText();
246 }
247 
249 {
250  setPlaceholderText(val);
251 }
252 
254 {
255  return this;
256 }
257 
259 {
260  QLineEdit::keyPressEvent(e);
261  if ((e->key() == Qt::Key_Space) && (e->modifiers() == Qt::CTRL))
262  {
263  m_completerModel->setRootPath(QFileInfo(text()).absoluteDir().absolutePath());
264  showCompletionPopup();
265  }
266 
267  auto *validator = qobject_cast<const FileSystemPathValidator *>(this->validator());
268  if (validator)
269  {
270  FileSystemPathValidator::TestResult lastTestResult = validator->lastTestResult();
271  QValidator::State lastState = validator->lastValidationState();
272  if (lastTestResult == FileSystemPathValidator::TestResult::OK)
273  {
274  delete m_warningAction;
275  m_warningAction = nullptr;
276  }
277  else
278  {
279  if (!m_warningAction)
280  {
281  m_warningAction = new QAction(this);
282  addAction(m_warningAction, QLineEdit::TrailingPosition);
283  }
284  }
285 
286  if (m_warningAction)
287  {
288  if (lastState == QValidator::Invalid)
289  m_warningAction->setIcon(style()->standardIcon(QStyle::SP_MessageBoxCritical));
290  else if (lastState == QValidator::Intermediate)
291  m_warningAction->setIcon(style()->standardIcon(QStyle::SP_MessageBoxWarning));
292  m_warningAction->setToolTip(warningText(lastTestResult).arg(validator->lastTestedPath()));
293  }
294  }
295 }
296 
297 void Private::FileLineEdit::contextMenuEvent(QContextMenuEvent *event)
298 {
299  QMenu *menu = createStandardContextMenu();
300  menu->setAttribute(Qt::WA_DeleteOnClose);
301 
302  if (m_browseAction)
303  {
304  menu->addSeparator();
305  menu->addAction(m_browseAction);
306  }
307 
308  menu->popup(event->globalPos());
309 }
310 
312 {
313  m_completer->setCompletionPrefix(text());
314  m_completer->complete();
315 }
316 
318 {
319  using TestResult = FileSystemPathValidator::TestResult;
320  switch (r)
321  {
322  case TestResult::DoesNotExist:
323  return tr("'%1' does not exist");
324  case TestResult::NotADir:
325  return tr("'%1' does not point to a directory");
326  case TestResult::NotAFile:
327  return tr("'%1' does not point to a file");
328  case TestResult::CantRead:
329  return tr("Does not have read permission in '%1'");
330  case TestResult::CantWrite:
331  return tr("Does not have write permission in '%1'");
332  default:
333  return {};
334  }
335 }
336 
338  : QComboBox {parent}
339 {
340  setEditable(true);
341  setLineEdit(new FileLineEdit(this));
342 }
343 
345 {
346  static_cast<FileLineEdit *>(lineEdit())->completeDirectoriesOnly(completeDirsOnly);
347 }
348 
350 {
351  static_cast<FileLineEdit *>(lineEdit())->setBrowseAction(action);
352 }
353 
354 void Private::FileComboEdit::setValidator(QValidator *validator)
355 {
356  lineEdit()->setValidator(validator);
357 }
358 
360 {
361  return lineEdit()->placeholderText();
362 }
363 
365 {
366  lineEdit()->setPlaceholderText(val);
367 }
368 
369 void Private::FileComboEdit::setFilenameFilters(const QStringList &filters)
370 {
371  static_cast<FileLineEdit *>(lineEdit())->setFilenameFilters(filters);
372 }
373 
375 {
376  return this;
377 }
378 
380 {
381  return currentText();
382 }
void completeDirectoriesOnly(bool completeDirsOnly) override
QString text() const
void setValidator(QValidator *validator) override
void setPlaceholder(const QString &val) override
FileComboEdit(QWidget *parent=nullptr)
void setBrowseAction(QAction *action) override
QWidget * widget() override
void setFilenameFilters(const QStringList &filters) override
QString placeholder() const override
void keyPressEvent(QKeyEvent *event) override
static QString warningText(FileSystemPathValidator::TestResult r)
QFileSystemModel * m_completerModel
Definition: fspathedit_p.h:138
QCompleter * m_completer
Definition: fspathedit_p.h:139
FileLineEdit(QWidget *parent=nullptr)
void setBrowseAction(QAction *action) override
QFileIconProvider m_iconProvider
Definition: fspathedit_p.h:141
void setValidator(QValidator *validator) override
QString placeholder() const override
void contextMenuEvent(QContextMenuEvent *event) override
void completeDirectoriesOnly(bool completeDirsOnly) override
void setFilenameFilters(const QStringList &filters) override
QWidget * widget() override
void setPlaceholder(const QString &val) override
QValidator::State validate(QString &input, int &pos) const override
TestResult testPath(QStringView path, bool pathIsComplete) const
FileSystemPathValidator(QObject *parent=nullptr)
QValidator::State lastValidationState() const
action
Definition: tstool.py:143