pathchooser.cpp 12 KB
Newer Older
1
/**************************************************************************
con's avatar
con committed
2
3
4
**
** This file is part of Qt Creator
**
hjk's avatar
hjk committed
5
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
con's avatar
con committed
6
**
7
** Contact: Nokia Corporation (qt-info@nokia.com)
con's avatar
con committed
8
**
9
** Commercial Usage
10
**
11
12
13
14
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
15
**
16
** GNU Lesser General Public License Usage
17
**
18
19
20
21
22
23
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24
**
25
** If you are unsure which license is appropriate for your use, please
hjk's avatar
hjk committed
26
** contact the sales department at http://qt.nokia.com/contact.
con's avatar
con committed
27
**
28
**************************************************************************/
hjk's avatar
hjk committed
29

con's avatar
con committed
30
31
#include "pathchooser.h"

hjk's avatar
hjk committed
32
#include "basevalidatinglineedit.h"
33
#include "environment.h"
hjk's avatar
hjk committed
34
#include "qtcassert.h"
con's avatar
con committed
35

hjk's avatar
hjk committed
36
#include <QtCore/QDebug>
con's avatar
con committed
37
#include <QtCore/QDir>
hjk's avatar
hjk committed
38
#include <QtCore/QFileInfo>
con's avatar
con committed
39
#include <QtCore/QSettings>
hjk's avatar
hjk committed
40
41
42
43
44
45

#include <QtGui/QDesktopServices>
#include <QtGui/QFileDialog>
#include <QtGui/QHBoxLayout>
#include <QtGui/QLineEdit>
#include <QtGui/QToolButton>
46
#include <QtGui/QPushButton>
con's avatar
con committed
47

48
/*static*/ const char * const Utils::PathChooser::browseButtonLabel =
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
49
#ifdef Q_WS_MAC
50
                   QT_TRANSLATE_NOOP("Utils::PathChooser", "Choose...");
51
#else
52
                   QT_TRANSLATE_NOOP("Utils::PathChooser", "Browse...");
53
54
#endif

55
56
namespace Utils {

con's avatar
con committed
57
// ------------------ PathValidatingLineEdit
58

hjk's avatar
hjk committed
59
60
class PathValidatingLineEdit : public BaseValidatingLineEdit
{
con's avatar
con committed
61
public:
62
    explicit PathValidatingLineEdit(PathChooser *chooser, QWidget *parent = 0);
con's avatar
con committed
63
64
65

protected:
    virtual bool validate(const QString &value, QString *errorMessage) const;
66
67
68

private:
    PathChooser *m_chooser;
con's avatar
con committed
69
70
};

71
72
73
PathValidatingLineEdit::PathValidatingLineEdit(PathChooser *chooser, QWidget *parent) :
    BaseValidatingLineEdit(parent),
    m_chooser(chooser)
con's avatar
con committed
74
{
hjk's avatar
hjk committed
75
    QTC_ASSERT(chooser, return);
con's avatar
con committed
76
77
78
79
}

bool PathValidatingLineEdit::validate(const QString &value, QString *errorMessage) const
{
80
    return m_chooser->validatePath(value, errorMessage);
con's avatar
con committed
81
82
83
}

// ------------------ PathChooserPrivate
84
85

class PathChooserPrivate
hjk's avatar
hjk committed
86
{
87
public:
88
    PathChooserPrivate(PathChooser *chooser);
con's avatar
con committed
89

90
91
    QString expandedPath(const QString &path) const;

92
    QHBoxLayout *m_hLayout;
con's avatar
con committed
93
    PathValidatingLineEdit *m_lineEdit;
94
95
    PathChooser::Kind m_acceptingKind;
    QString m_dialogTitleOverride;
96
    QString m_dialogFilter;
97
    QString m_initialBrowsePathOverride;
dt's avatar
dt committed
98
    QString m_baseDirectory;
99
    Environment m_environment;
con's avatar
con committed
100
101
};

102
PathChooserPrivate::PathChooserPrivate(PathChooser *chooser) :
103
    m_hLayout(new QHBoxLayout),
104
105
    m_lineEdit(new PathValidatingLineEdit(chooser)),
    m_acceptingKind(PathChooser::Directory)
con's avatar
con committed
106
107
108
{
}

109
110
QString PathChooserPrivate::expandedPath(const QString &input) const
{
111
112
    if (input.isEmpty())
        return input;
113
    const QString path = QDir::fromNativeSeparators(m_environment.expandVariables(input));
114
    if (path.isEmpty())
115
116
        return path;

117
118
119
120
121
122
123
124
125
126
127
128
129
130
    switch (m_acceptingKind) {
    case PathChooser::Command:
    case PathChooser::ExistingCommand: {
        const QString expanded = m_environment.searchInPath(path, QStringList(m_baseDirectory));
        return expanded.isEmpty() && m_acceptingKind == PathChooser::Command ? path : expanded;
    }
    case PathChooser::Any:
        break;
    case PathChooser::Directory:
    case PathChooser::File:
        if (!m_baseDirectory.isEmpty() && QFileInfo(path).isRelative())
            return QFileInfo(m_baseDirectory + QLatin1Char('/') + path).absoluteFilePath();
        break;
    }
131
132
133
    return path;
}

con's avatar
con committed
134
135
PathChooser::PathChooser(QWidget *parent) :
    QWidget(parent),
136
    m_d(new PathChooserPrivate(this))
con's avatar
con committed
137
{
138
    m_d->m_hLayout->setContentsMargins(0, 0, 0, 0);
con's avatar
con committed
139
140

    connect(m_d->m_lineEdit, SIGNAL(validReturnPressed()), this, SIGNAL(returnPressed()));
con's avatar
con committed
141
    connect(m_d->m_lineEdit, SIGNAL(textChanged(QString)), this, SIGNAL(changed(QString)));
con's avatar
con committed
142
    connect(m_d->m_lineEdit, SIGNAL(validChanged()), this, SIGNAL(validChanged()));
143
    connect(m_d->m_lineEdit, SIGNAL(validChanged(bool)), this, SIGNAL(validChanged(bool)));
144
    connect(m_d->m_lineEdit, SIGNAL(editingFinished()), this, SIGNAL(editingFinished()));
con's avatar
con committed
145

con's avatar
con committed
146
    m_d->m_lineEdit->setMinimumWidth(200);
147
148
    m_d->m_hLayout->addWidget(m_d->m_lineEdit);
    m_d->m_hLayout->setSizeConstraint(QLayout::SetMinimumSize);
con's avatar
con committed
149

150
    addButton(tr(browseButtonLabel), this, SLOT(slotBrowse()));
con's avatar
con committed
151

152
    setLayout(m_d->m_hLayout);
con's avatar
con committed
153
    setFocusProxy(m_d->m_lineEdit);
154
    setEnvironment(Environment::systemEnvironment());
con's avatar
con committed
155
156
157
158
159
160
161
}

PathChooser::~PathChooser()
{
    delete m_d;
}

162
163
void PathChooser::addButton(const QString &text, QObject *receiver, const char *slotFunc)
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
164
#ifdef Q_WS_MAC
165
166
167
168
169
170
171
172
173
    QPushButton *button = new QPushButton;
#else
    QToolButton *button = new QToolButton;
#endif
    button->setText(text);
    connect(button, SIGNAL(clicked()), receiver, slotFunc);
    m_d->m_hLayout->addWidget(button);
}

con's avatar
con committed
174
175
176
177
178
QAbstractButton *PathChooser::buttonAtIndex(int index) const
{
    return findChildren<QAbstractButton*>().at(index);
}

dt's avatar
dt committed
179
180
181
182
183
184
185
186
187
188
QString PathChooser::baseDirectory() const
{
    return m_d->m_baseDirectory;
}

void PathChooser::setBaseDirectory(const QString &directory)
{
    m_d->m_baseDirectory = directory;
}

189
190
void PathChooser::setEnvironment(const Utils::Environment &env)
{
191
    QString oldExpand = path();
192
    m_d->m_environment = env;
193
194
    if (path() != oldExpand)
        emit changed(rawPath());
195
196
}

con's avatar
con committed
197
198
QString PathChooser::path() const
{
199
    return m_d->expandedPath(QDir::fromNativeSeparators(m_d->m_lineEdit->text()));
200
201
202
203
204
}

QString PathChooser::rawPath() const
{
    return QDir::fromNativeSeparators(m_d->m_lineEdit->text());
con's avatar
con committed
205
206
207
208
}

void PathChooser::setPath(const QString &path)
{
209
    m_d->m_lineEdit->setText(QDir::toNativeSeparators(path));
con's avatar
con committed
210
211
212
213
}

void PathChooser::slotBrowse()
{
214
215
    emit beforeBrowsing();

con's avatar
con committed
216
    QString predefined = path();
217
218
219
220
221
222
    if ((predefined.isEmpty() || !QFileInfo(predefined).isDir())
            && !m_d->m_initialBrowsePathOverride.isNull()) {
        predefined = m_d->m_initialBrowsePathOverride;
        if (!QFileInfo(predefined).isDir())
            predefined.clear();
    }
223
224
225
226
227
228

    // Prompt for a file/dir
    QString newPath;
    switch (m_d->m_acceptingKind) {
    case PathChooser::Directory:
        newPath = QFileDialog::getExistingDirectory(this,
229
                makeDialogTitle(tr("Choose Directory")), predefined);
230
        break;
231
    case PathChooser::ExistingCommand:
232
    case PathChooser::Command:
233
234
235
236
237
        newPath = QFileDialog::getOpenFileName(this,
                makeDialogTitle(tr("Choose Executable")), predefined,
                m_d->m_dialogFilter);
        break;
    case PathChooser::File: // fall through
238
        newPath = QFileDialog::getOpenFileName(this,
239
                makeDialogTitle(tr("Choose File")), predefined,
240
                m_d->m_dialogFilter);
241
        break;
242
243
244
245
246
247
248
    case PathChooser::Any: {
        QFileDialog dialog(this);
        dialog.setFileMode(QFileDialog::AnyFile);
        dialog.setWindowTitle(makeDialogTitle(tr("Choose File")));
        QFileInfo fi(predefined);
        if (fi.exists())
            dialog.setDirectory(fi.absolutePath());
249
250
251
252
        // FIXME: fix QFileDialog so that it filters properly: lib*.a
        dialog.setNameFilter(m_d->m_dialogFilter);
        if (dialog.exec() == QDialog::Accepted) {
            // probably loop here until the *.framework dir match
253
254
255
256
257
258
            QStringList paths = dialog.selectedFiles();
            if (!paths.isEmpty())
                newPath = paths.at(0);
        }
        break;
        }
259
260

    default:
261
        break;
262
263
    }

264
    // Delete trailing slashes unless it is "/"|"\\", only
hjk's avatar
hjk committed
265
    if (!newPath.isEmpty()) {
266
        newPath = QDir::toNativeSeparators(newPath);
hjk's avatar
hjk committed
267
268
        if (newPath.size() > 1 && newPath.endsWith(QDir::separator()))
            newPath.truncate(newPath.size() - 1);
con's avatar
con committed
269
270
        setPath(newPath);
    }
271
272

    emit browsingFinished();
273
    m_d->m_lineEdit->triggerChanged();
con's avatar
con committed
274
275
276
277
278
279
280
281
282
}

bool PathChooser::isValid() const
{
    return m_d->m_lineEdit->isValid();
}

QString PathChooser::errorMessage() const
{
hjk's avatar
hjk committed
283
    return m_d->m_lineEdit->errorMessage();
con's avatar
con committed
284
285
286
287
}

bool PathChooser::validatePath(const QString &path, QString *errorMessage)
{
288
289
290
291
292
293
294
295
    QString expandedPath = m_d->expandedPath(path);

    QString displayPath = expandedPath;
    if (expandedPath.isEmpty())
        //: Selected path is not valid:
        displayPath = tr("<not valid>");

    if (expandedPath.isEmpty()) {
con's avatar
con committed
296
297
298
299
        if (errorMessage)
            *errorMessage = tr("The path must not be empty.");
        return false;
    }
300

301
    const QFileInfo fi(expandedPath);
con's avatar
con committed
302

303
304
305
    // Check if existing
    switch (m_d->m_acceptingKind) {
    case PathChooser::Directory: // fall through
306
307
    case PathChooser::File: // fall through
    case PathChooser::ExistingCommand:
308
309
        if (!fi.exists()) {
            if (errorMessage)
310
                *errorMessage = tr("The path '%1' does not exist.").arg(QDir::toNativeSeparators(expandedPath));
311
312
313
314
315
316
317
318
319
320
321
322
            return false;
        }
        break;

    case PathChooser::Command: // fall through
    default:
        ;
    }

    // Check expected kind
    switch (m_d->m_acceptingKind) {
    case PathChooser::Directory:
323
        if (!fi.isDir()) {
324
            if (errorMessage)
325
                *errorMessage = tr("The path <b>%1</b> is not a directory.").arg(QDir::toNativeSeparators(expandedPath));
326
            return false;
hjk's avatar
hjk committed
327
        }
328
329
330
        break;

    case PathChooser::File:
331
        if (!fi.isFile()) {
332
            if (errorMessage)
333
                *errorMessage = tr("The path <b>%1</b> is not a file.").arg(QDir::toNativeSeparators(expandedPath));
334
            return false;
hjk's avatar
hjk committed
335
        }
336
337
        break;

338
339
340
341
342
343
344
    case PathChooser::ExistingCommand:
        if (!fi.isFile() || !fi.isExecutable()) {
            if (errorMessage)
                *errorMessage = tr("The path <b>%1</b> is not a executable file.").arg(QDir::toNativeSeparators(expandedPath));
            return false;
        }

345
346
347
    case PathChooser::Command:
        break;

348
349
350
    case PathChooser::Any:
        break;

351
352
    default:
        ;
con's avatar
con committed
353
    }
Friedemann Kleint's avatar
Friedemann Kleint committed
354
355
    if (errorMessage)
        *errorMessage = tr("Full path: <b>%1</b>").arg(QDir::toNativeSeparators(expandedPath));
356
    return true;
con's avatar
con committed
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
}

QString PathChooser::label()
{
    return tr("Path:");
}

QString PathChooser::homePath()
{
#ifdef Q_OS_WIN
    // Return 'users/<name>/Documents' on Windows, since Windows explorer
    // does not let people actually display the contents of their home
    // directory. Alternatively, create a QtCreator-specific directory?
    return QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation);
#else
    return QDir::homePath();
#endif
}

376
377
378
379
380
void PathChooser::setExpectedKind(Kind expected)
{
    m_d->m_acceptingKind = expected;
}

381
382
383
384
385
PathChooser::Kind PathChooser::expectedKind() const
{
    return m_d->m_acceptingKind;
}

386
387
388
389
390
void PathChooser::setPromptDialogTitle(const QString &title)
{
    m_d->m_dialogTitleOverride = title;
}

391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
QString PathChooser::promptDialogTitle() const
{
    return m_d->m_dialogTitleOverride;
}

void PathChooser::setPromptDialogFilter(const QString &filter)
{
    m_d->m_dialogFilter = filter;
}

QString PathChooser::promptDialogFilter() const
{
    return m_d->m_dialogFilter;
}

406
407
408
409
410
void PathChooser::setInitialBrowsePathBackup(const QString &path)
{
    m_d->m_initialBrowsePathOverride = path;
}

411
412
413
414
415
416
417
418
QString PathChooser::makeDialogTitle(const QString &title)
{
    if (m_d->m_dialogTitleOverride.isNull())
        return title;
    else
        return m_d->m_dialogTitleOverride;
}

419
420
421
422
423
424
425
426
QLineEdit *PathChooser::lineEdit() const
{
    // HACK: Make it work with HistoryCompleter.
    if (m_d->m_lineEdit->objectName().isEmpty())
        m_d->m_lineEdit->setObjectName(objectName() + QLatin1String("LineEdit"));
    return m_d->m_lineEdit;
}

hjk's avatar
hjk committed
427
} // namespace Utils