OSDN Git Service

It's 2011 now.
[qt-creator-jp/qt-creator-jp.git] / src / libs / utils / pathchooser.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
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
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
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.
24 **
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.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33
34 #include "pathchooser.h"
35
36 #include "basevalidatinglineedit.h"
37 #include "environment.h"
38 #include "qtcassert.h"
39
40 #include <QtCore/QDebug>
41 #include <QtCore/QDir>
42 #include <QtCore/QFileInfo>
43 #include <QtCore/QSettings>
44
45 #include <QtGui/QDesktopServices>
46 #include <QtGui/QFileDialog>
47 #include <QtGui/QHBoxLayout>
48 #include <QtGui/QLineEdit>
49 #include <QtGui/QToolButton>
50 #include <QtGui/QPushButton>
51
52 /*static*/ const char * const Utils::PathChooser::browseButtonLabel =
53 #ifdef Q_WS_MAC
54                    QT_TRANSLATE_NOOP("Utils::PathChooser", "Choose...");
55 #else
56                    QT_TRANSLATE_NOOP("Utils::PathChooser", "Browse...");
57 #endif
58
59 namespace Utils {
60
61 // ------------------ PathValidatingLineEdit
62
63 class PathValidatingLineEdit : public BaseValidatingLineEdit
64 {
65 public:
66     explicit PathValidatingLineEdit(PathChooser *chooser, QWidget *parent = 0);
67
68 protected:
69     virtual bool validate(const QString &value, QString *errorMessage) const;
70
71 private:
72     PathChooser *m_chooser;
73 };
74
75 PathValidatingLineEdit::PathValidatingLineEdit(PathChooser *chooser, QWidget *parent) :
76     BaseValidatingLineEdit(parent),
77     m_chooser(chooser)
78 {
79     QTC_ASSERT(chooser, return);
80 }
81
82 bool PathValidatingLineEdit::validate(const QString &value, QString *errorMessage) const
83 {
84     return m_chooser->validatePath(value, errorMessage);
85 }
86
87 // ------------------ PathChooserPrivate
88
89 class PathChooserPrivate
90 {
91 public:
92     PathChooserPrivate(PathChooser *chooser);
93
94     QString expandedPath(const QString &path) const;
95
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;
104 };
105
106 PathChooserPrivate::PathChooserPrivate(PathChooser *chooser) :
107     m_hLayout(new QHBoxLayout),
108     m_lineEdit(new PathValidatingLineEdit(chooser)),
109     m_acceptingKind(PathChooser::Directory)
110 {
111 }
112
113 QString PathChooserPrivate::expandedPath(const QString &input) const
114 {
115     if (input.isEmpty())
116         return input;
117     const QString path = QDir::fromNativeSeparators(m_environment.expandVariables(input));
118     if (path.isEmpty())
119         return path;
120
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;
126     }
127     case PathChooser::Any:
128         break;
129     case PathChooser::Directory:
130     case PathChooser::File:
131         if (!m_baseDirectory.isEmpty() && QFileInfo(path).isRelative())
132             return QFileInfo(m_baseDirectory + QLatin1Char('/') + path).absoluteFilePath();
133         break;
134     }
135     return path;
136 }
137
138 PathChooser::PathChooser(QWidget *parent) :
139     QWidget(parent),
140     m_d(new PathChooserPrivate(this))
141 {
142     m_d->m_hLayout->setContentsMargins(0, 0, 0, 0);
143
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()));
149
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);
153
154     addButton(tr(browseButtonLabel), this, SLOT(slotBrowse()));
155
156     setLayout(m_d->m_hLayout);
157     setFocusProxy(m_d->m_lineEdit);
158     setEnvironment(Environment::systemEnvironment());
159 }
160
161 PathChooser::~PathChooser()
162 {
163     delete m_d;
164 }
165
166 void PathChooser::addButton(const QString &text, QObject *receiver, const char *slotFunc)
167 {
168 #ifdef Q_WS_MAC
169     QPushButton *button = new QPushButton;
170 #else
171     QToolButton *button = new QToolButton;
172 #endif
173     button->setText(text);
174     connect(button, SIGNAL(clicked()), receiver, slotFunc);
175     m_d->m_hLayout->addWidget(button);
176 }
177
178 QAbstractButton *PathChooser::buttonAtIndex(int index) const
179 {
180     return findChildren<QAbstractButton*>().at(index);
181 }
182
183 QString PathChooser::baseDirectory() const
184 {
185     return m_d->m_baseDirectory;
186 }
187
188 void PathChooser::setBaseDirectory(const QString &directory)
189 {
190     m_d->m_baseDirectory = directory;
191 }
192
193 void PathChooser::setEnvironment(const Utils::Environment &env)
194 {
195     QString oldExpand = path();
196     m_d->m_environment = env;
197     if (path() != oldExpand)
198         emit changed(rawPath());
199 }
200
201 QString PathChooser::path() const
202 {
203     return m_d->expandedPath(QDir::fromNativeSeparators(m_d->m_lineEdit->text()));
204 }
205
206 QString PathChooser::rawPath() const
207 {
208     return QDir::fromNativeSeparators(m_d->m_lineEdit->text());
209 }
210
211 void PathChooser::setPath(const QString &path)
212 {
213     m_d->m_lineEdit->setText(QDir::toNativeSeparators(path));
214 }
215
216 void PathChooser::slotBrowse()
217 {
218     emit beforeBrowsing();
219
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())
225             predefined.clear();
226     }
227
228     // Prompt for a file/dir
229     QString newPath;
230     switch (m_d->m_acceptingKind) {
231     case PathChooser::Directory:
232         newPath = QFileDialog::getExistingDirectory(this,
233                 makeDialogTitle(tr("Choose Directory")), predefined);
234         break;
235     case PathChooser::ExistingCommand:
236     case PathChooser::Command:
237         newPath = QFileDialog::getOpenFileName(this,
238                 makeDialogTitle(tr("Choose Executable")), predefined,
239                 m_d->m_dialogFilter);
240         break;
241     case PathChooser::File: // fall through
242         newPath = QFileDialog::getOpenFileName(this,
243                 makeDialogTitle(tr("Choose File")), predefined,
244                 m_d->m_dialogFilter);
245         break;
246     case PathChooser::Any: {
247         QFileDialog dialog(this);
248         dialog.setFileMode(QFileDialog::AnyFile);
249         dialog.setWindowTitle(makeDialogTitle(tr("Choose File")));
250         QFileInfo fi(predefined);
251         if (fi.exists())
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);
260         }
261         break;
262         }
263
264     default:
265         break;
266     }
267
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);
273         setPath(newPath);
274     }
275
276     emit browsingFinished();
277     m_d->m_lineEdit->triggerChanged();
278 }
279
280 bool PathChooser::isValid() const
281 {
282     return m_d->m_lineEdit->isValid();
283 }
284
285 QString PathChooser::errorMessage() const
286 {
287     return m_d->m_lineEdit->errorMessage();
288 }
289
290 bool PathChooser::validatePath(const QString &path, QString *errorMessage)
291 {
292     QString expandedPath = m_d->expandedPath(path);
293
294     QString displayPath = expandedPath;
295     if (expandedPath.isEmpty())
296         //: Selected path is not valid:
297         displayPath = tr("<not valid>");
298
299     if (expandedPath.isEmpty()) {
300         if (errorMessage)
301             *errorMessage = tr("The path must not be empty.");
302         return false;
303     }
304
305     const QFileInfo fi(expandedPath);
306
307     // Check if existing
308     switch (m_d->m_acceptingKind) {
309     case PathChooser::Directory: // fall through
310     case PathChooser::File: // fall through
311     case PathChooser::ExistingCommand:
312         if (!fi.exists()) {
313             if (errorMessage)
314                 *errorMessage = tr("The path '%1' does not exist.").arg(QDir::toNativeSeparators(expandedPath));
315             return false;
316         }
317         break;
318
319     case PathChooser::Command: // fall through
320     default:
321         ;
322     }
323
324     // Check expected kind
325     switch (m_d->m_acceptingKind) {
326     case PathChooser::Directory:
327         if (!fi.isDir()) {
328             if (errorMessage)
329                 *errorMessage = tr("The path <b>%1</b> is not a directory.").arg(QDir::toNativeSeparators(expandedPath));
330             return false;
331         }
332         break;
333
334     case PathChooser::File:
335         if (!fi.isFile()) {
336             if (errorMessage)
337                 *errorMessage = tr("The path <b>%1</b> is not a file.").arg(QDir::toNativeSeparators(expandedPath));
338             return false;
339         }
340         break;
341
342     case PathChooser::ExistingCommand:
343         if (!fi.isFile() || !fi.isExecutable()) {
344             if (errorMessage)
345                 *errorMessage = tr("The path <b>%1</b> is not a executable file.").arg(QDir::toNativeSeparators(expandedPath));
346             return false;
347         }
348
349     case PathChooser::Command:
350         break;
351
352     case PathChooser::Any:
353         break;
354
355     default:
356         ;
357     }
358     if (errorMessage)
359         *errorMessage = tr("Full path: <b>%1</b>").arg(QDir::toNativeSeparators(expandedPath));
360     return true;
361 }
362
363 QString PathChooser::label()
364 {
365     return tr("Path:");
366 }
367
368 QString PathChooser::homePath()
369 {
370 #ifdef Q_OS_WIN
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);
375 #else
376     return QDir::homePath();
377 #endif
378 }
379
380 void PathChooser::setExpectedKind(Kind expected)
381 {
382     m_d->m_acceptingKind = expected;
383 }
384
385 PathChooser::Kind PathChooser::expectedKind() const
386 {
387     return m_d->m_acceptingKind;
388 }
389
390 void PathChooser::setPromptDialogTitle(const QString &title)
391 {
392     m_d->m_dialogTitleOverride = title;
393 }
394
395 QString PathChooser::promptDialogTitle() const
396 {
397     return m_d->m_dialogTitleOverride;
398 }
399
400 void PathChooser::setPromptDialogFilter(const QString &filter)
401 {
402     m_d->m_dialogFilter = filter;
403 }
404
405 QString PathChooser::promptDialogFilter() const
406 {
407     return m_d->m_dialogFilter;
408 }
409
410 void PathChooser::setInitialBrowsePathBackup(const QString &path)
411 {
412     m_d->m_initialBrowsePathOverride = path;
413 }
414
415 QString PathChooser::makeDialogTitle(const QString &title)
416 {
417     if (m_d->m_dialogTitleOverride.isNull())
418         return title;
419     else
420         return m_d->m_dialogTitleOverride;
421 }
422
423 QLineEdit *PathChooser::lineEdit() const
424 {
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;
429 }
430
431 } // namespace Utils