1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights. These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
32 **************************************************************************/
34 #include "pathchooser.h"
36 #include "basevalidatinglineedit.h"
37 #include "environment.h"
38 #include "qtcassert.h"
40 #include <QtCore/QDebug>
41 #include <QtCore/QDir>
42 #include <QtCore/QFileInfo>
43 #include <QtCore/QSettings>
45 #include <QtGui/QDesktopServices>
46 #include <QtGui/QFileDialog>
47 #include <QtGui/QHBoxLayout>
48 #include <QtGui/QLineEdit>
49 #include <QtGui/QToolButton>
50 #include <QtGui/QPushButton>
52 /*static*/ const char * const Utils::PathChooser::browseButtonLabel =
54 QT_TRANSLATE_NOOP("Utils::PathChooser", "Choose...");
56 QT_TRANSLATE_NOOP("Utils::PathChooser", "Browse...");
61 // ------------------ PathValidatingLineEdit
63 class PathValidatingLineEdit : public BaseValidatingLineEdit
66 explicit PathValidatingLineEdit(PathChooser *chooser, QWidget *parent = 0);
69 virtual bool validate(const QString &value, QString *errorMessage) const;
72 PathChooser *m_chooser;
75 PathValidatingLineEdit::PathValidatingLineEdit(PathChooser *chooser, QWidget *parent) :
76 BaseValidatingLineEdit(parent),
79 QTC_ASSERT(chooser, return);
82 bool PathValidatingLineEdit::validate(const QString &value, QString *errorMessage) const
84 return m_chooser->validatePath(value, errorMessage);
87 // ------------------ PathChooserPrivate
89 class PathChooserPrivate
92 PathChooserPrivate(PathChooser *chooser);
94 QString expandedPath(const QString &path) const;
96 QHBoxLayout *m_hLayout;
97 PathValidatingLineEdit *m_lineEdit;
98 PathChooser::Kind m_acceptingKind;
99 QString m_dialogTitleOverride;
100 QString m_dialogFilter;
101 QString m_initialBrowsePathOverride;
102 QString m_baseDirectory;
103 Environment m_environment;
106 PathChooserPrivate::PathChooserPrivate(PathChooser *chooser) :
107 m_hLayout(new QHBoxLayout),
108 m_lineEdit(new PathValidatingLineEdit(chooser)),
109 m_acceptingKind(PathChooser::Directory)
113 QString PathChooserPrivate::expandedPath(const QString &input) const
117 const QString path = QDir::fromNativeSeparators(m_environment.expandVariables(input));
121 switch (m_acceptingKind) {
122 case PathChooser::Command:
123 case PathChooser::ExistingCommand: {
124 const QString expanded = m_environment.searchInPath(path, QStringList(m_baseDirectory));
125 return expanded.isEmpty() && m_acceptingKind == PathChooser::Command ? path : expanded;
127 case PathChooser::Any:
129 case PathChooser::Directory:
130 case PathChooser::File:
131 if (!m_baseDirectory.isEmpty() && QFileInfo(path).isRelative())
132 return QFileInfo(m_baseDirectory + QLatin1Char('/') + path).absoluteFilePath();
138 PathChooser::PathChooser(QWidget *parent) :
140 m_d(new PathChooserPrivate(this))
142 m_d->m_hLayout->setContentsMargins(0, 0, 0, 0);
144 connect(m_d->m_lineEdit, SIGNAL(validReturnPressed()), this, SIGNAL(returnPressed()));
145 connect(m_d->m_lineEdit, SIGNAL(textChanged(QString)), this, SIGNAL(changed(QString)));
146 connect(m_d->m_lineEdit, SIGNAL(validChanged()), this, SIGNAL(validChanged()));
147 connect(m_d->m_lineEdit, SIGNAL(validChanged(bool)), this, SIGNAL(validChanged(bool)));
148 connect(m_d->m_lineEdit, SIGNAL(editingFinished()), this, SIGNAL(editingFinished()));
150 m_d->m_lineEdit->setMinimumWidth(200);
151 m_d->m_hLayout->addWidget(m_d->m_lineEdit);
152 m_d->m_hLayout->setSizeConstraint(QLayout::SetMinimumSize);
154 addButton(tr(browseButtonLabel), this, SLOT(slotBrowse()));
156 setLayout(m_d->m_hLayout);
157 setFocusProxy(m_d->m_lineEdit);
158 setEnvironment(Environment::systemEnvironment());
161 PathChooser::~PathChooser()
166 void PathChooser::addButton(const QString &text, QObject *receiver, const char *slotFunc)
169 QPushButton *button = new QPushButton;
171 QToolButton *button = new QToolButton;
173 button->setText(text);
174 connect(button, SIGNAL(clicked()), receiver, slotFunc);
175 m_d->m_hLayout->addWidget(button);
178 QAbstractButton *PathChooser::buttonAtIndex(int index) const
180 return findChildren<QAbstractButton*>().at(index);
183 QString PathChooser::baseDirectory() const
185 return m_d->m_baseDirectory;
188 void PathChooser::setBaseDirectory(const QString &directory)
190 m_d->m_baseDirectory = directory;
193 void PathChooser::setEnvironment(const Utils::Environment &env)
195 QString oldExpand = path();
196 m_d->m_environment = env;
197 if (path() != oldExpand)
198 emit changed(rawPath());
201 QString PathChooser::path() const
203 return m_d->expandedPath(QDir::fromNativeSeparators(m_d->m_lineEdit->text()));
206 QString PathChooser::rawPath() const
208 return QDir::fromNativeSeparators(m_d->m_lineEdit->text());
211 void PathChooser::setPath(const QString &path)
213 m_d->m_lineEdit->setText(QDir::toNativeSeparators(path));
216 void PathChooser::slotBrowse()
218 emit beforeBrowsing();
220 QString predefined = path();
221 if ((predefined.isEmpty() || !QFileInfo(predefined).isDir())
222 && !m_d->m_initialBrowsePathOverride.isNull()) {
223 predefined = m_d->m_initialBrowsePathOverride;
224 if (!QFileInfo(predefined).isDir())
228 // Prompt for a file/dir
230 switch (m_d->m_acceptingKind) {
231 case PathChooser::Directory:
232 newPath = QFileDialog::getExistingDirectory(this,
233 makeDialogTitle(tr("Choose Directory")), predefined);
235 case PathChooser::ExistingCommand:
236 case PathChooser::Command:
237 newPath = QFileDialog::getOpenFileName(this,
238 makeDialogTitle(tr("Choose Executable")), predefined,
239 m_d->m_dialogFilter);
241 case PathChooser::File: // fall through
242 newPath = QFileDialog::getOpenFileName(this,
243 makeDialogTitle(tr("Choose File")), predefined,
244 m_d->m_dialogFilter);
246 case PathChooser::Any: {
247 QFileDialog dialog(this);
248 dialog.setFileMode(QFileDialog::AnyFile);
249 dialog.setWindowTitle(makeDialogTitle(tr("Choose File")));
250 QFileInfo fi(predefined);
252 dialog.setDirectory(fi.absolutePath());
253 // FIXME: fix QFileDialog so that it filters properly: lib*.a
254 dialog.setNameFilter(m_d->m_dialogFilter);
255 if (dialog.exec() == QDialog::Accepted) {
256 // probably loop here until the *.framework dir match
257 QStringList paths = dialog.selectedFiles();
258 if (!paths.isEmpty())
259 newPath = paths.at(0);
268 // Delete trailing slashes unless it is "/"|"\\", only
269 if (!newPath.isEmpty()) {
270 newPath = QDir::toNativeSeparators(newPath);
271 if (newPath.size() > 1 && newPath.endsWith(QDir::separator()))
272 newPath.truncate(newPath.size() - 1);
276 emit browsingFinished();
277 m_d->m_lineEdit->triggerChanged();
280 bool PathChooser::isValid() const
282 return m_d->m_lineEdit->isValid();
285 QString PathChooser::errorMessage() const
287 return m_d->m_lineEdit->errorMessage();
290 bool PathChooser::validatePath(const QString &path, QString *errorMessage)
292 QString expandedPath = m_d->expandedPath(path);
294 QString displayPath = expandedPath;
295 if (expandedPath.isEmpty())
296 //: Selected path is not valid:
297 displayPath = tr("<not valid>");
299 if (expandedPath.isEmpty()) {
301 *errorMessage = tr("The path must not be empty.");
305 const QFileInfo fi(expandedPath);
308 switch (m_d->m_acceptingKind) {
309 case PathChooser::Directory: // fall through
310 case PathChooser::File: // fall through
311 case PathChooser::ExistingCommand:
314 *errorMessage = tr("The path '%1' does not exist.").arg(QDir::toNativeSeparators(expandedPath));
319 case PathChooser::Command: // fall through
324 // Check expected kind
325 switch (m_d->m_acceptingKind) {
326 case PathChooser::Directory:
329 *errorMessage = tr("The path <b>%1</b> is not a directory.").arg(QDir::toNativeSeparators(expandedPath));
334 case PathChooser::File:
337 *errorMessage = tr("The path <b>%1</b> is not a file.").arg(QDir::toNativeSeparators(expandedPath));
342 case PathChooser::ExistingCommand:
343 if (!fi.isFile() || !fi.isExecutable()) {
345 *errorMessage = tr("The path <b>%1</b> is not a executable file.").arg(QDir::toNativeSeparators(expandedPath));
349 case PathChooser::Command:
352 case PathChooser::Any:
359 *errorMessage = tr("Full path: <b>%1</b>").arg(QDir::toNativeSeparators(expandedPath));
363 QString PathChooser::label()
368 QString PathChooser::homePath()
371 // Return 'users/<name>/Documents' on Windows, since Windows explorer
372 // does not let people actually display the contents of their home
373 // directory. Alternatively, create a QtCreator-specific directory?
374 return QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation);
376 return QDir::homePath();
380 void PathChooser::setExpectedKind(Kind expected)
382 m_d->m_acceptingKind = expected;
385 PathChooser::Kind PathChooser::expectedKind() const
387 return m_d->m_acceptingKind;
390 void PathChooser::setPromptDialogTitle(const QString &title)
392 m_d->m_dialogTitleOverride = title;
395 QString PathChooser::promptDialogTitle() const
397 return m_d->m_dialogTitleOverride;
400 void PathChooser::setPromptDialogFilter(const QString &filter)
402 m_d->m_dialogFilter = filter;
405 QString PathChooser::promptDialogFilter() const
407 return m_d->m_dialogFilter;
410 void PathChooser::setInitialBrowsePathBackup(const QString &path)
412 m_d->m_initialBrowsePathOverride = path;
415 QString PathChooser::makeDialogTitle(const QString &title)
417 if (m_d->m_dialogTitleOverride.isNull())
420 return m_d->m_dialogTitleOverride;
423 QLineEdit *PathChooser::lineEdit() const
425 // HACK: Make it work with HistoryCompleter.
426 if (m_d->m_lineEdit->objectName().isEmpty())
427 m_d->m_lineEdit->setObjectName(objectName() + QLatin1String("LineEdit"));
428 return m_d->m_lineEdit;