foldernavigationwidget.cpp 17.4 KB
Newer Older
1
/**************************************************************************
con's avatar
con committed
2
3
4
**
** This file is part of Qt Creator
**
con's avatar
con committed
5
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
con's avatar
con committed
6
**
hjk's avatar
hjk committed
7
** Contact: Nokia Corporation (info@qt.nokia.com)
con's avatar
con committed
8
**
9
**
10
** GNU Lesser General Public License Usage
11
**
hjk's avatar
hjk committed
12
13
14
15
16
17
** 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.
18
**
con's avatar
con committed
19
** In addition, as a special exception, Nokia gives you certain additional
hjk's avatar
hjk committed
20
** rights. These rights are described in the Nokia Qt LGPL Exception
con's avatar
con committed
21
22
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
23
24
25
26
27
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
con's avatar
con committed
28
** If you have questions regarding the use of this file, please contact
Tobias Hunger's avatar
Tobias Hunger committed
29
** Nokia at info@qt.nokia.com.
con's avatar
con committed
30
**
31
**************************************************************************/
hjk's avatar
hjk committed
32

con's avatar
con committed
33
34
35
36
37
#include "foldernavigationwidget.h"
#include "projectexplorer.h"
#include "projectexplorerconstants.h"

#include <coreplugin/icore.h>
38
#include <coreplugin/fileiconprovider.h>
con's avatar
con committed
39
40
#include <coreplugin/filemanager.h>
#include <coreplugin/editormanager/editormanager.h>
41
#include <coreplugin/coreconstants.h>
con's avatar
con committed
42

43
#include <utils/environment.h>
con's avatar
con committed
44
#include <utils/pathchooser.h>
45
#include <utils/qtcassert.h>
46
#include <utils/qtcprocess.h>
47
48
#include <utils/unixutils.h>
#include <utils/consoleprocess.h>
con's avatar
con committed
49
50

#include <QtCore/QDebug>
51
#include <QtCore/QProcess>
52
#include <QtCore/QSize>
53
#include <QtGui/QFileSystemModel>
con's avatar
con committed
54
55
#include <QtGui/QVBoxLayout>
#include <QtGui/QToolButton>
56
#include <QtGui/QPushButton>
57
58
59
#include <QtGui/QLabel>
#include <QtGui/QListView>
#include <QtGui/QSortFilterProxyModel>
60
61
62
63
#include <QtGui/QAction>
#include <QtGui/QMenu>
#include <QtGui/QFileDialog>
#include <QtGui/QContextMenuEvent>
64
#include <QtGui/QMessageBox>
con's avatar
con committed
65

66
enum { debug = 0 };
con's avatar
con committed
67
68

namespace ProjectExplorer {
hjk's avatar
hjk committed
69
70
namespace Internal {

71
72
// Hide the '.' entry.
class DotRemovalFilter : public QSortFilterProxyModel
hjk's avatar
hjk committed
73
74
75
{
    Q_OBJECT
public:
76
    explicit DotRemovalFilter(QObject *parent = 0);
hjk's avatar
hjk committed
77
protected:
78
79
    virtual bool filterAcceptsRow(int source_row, const QModelIndex &parent) const;
private:
80
81
82
83
#if defined(Q_OS_UNIX)
    const QVariant m_root;
    const QVariant m_dotdot;
#endif
84
    const QVariant m_dot;
hjk's avatar
hjk committed
85
86
};

87
88
DotRemovalFilter::DotRemovalFilter(QObject *parent) :
    QSortFilterProxyModel(parent),
89
90
91
92
#if defined(Q_OS_UNIX)
    m_root(QString(QLatin1Char('/'))),
    m_dotdot(QLatin1String("..")),
#endif
93
94
95
96
97
98
    m_dot(QString(QLatin1Char('.')))
{
}

bool DotRemovalFilter::filterAcceptsRow(int source_row, const QModelIndex &parent) const
{
99
100
101
102
103
104
    const QVariant fileName = sourceModel()->data(parent.child(source_row, 0));
#if defined(Q_OS_UNIX)
    if (sourceModel()->data(parent) == m_root && fileName == m_dotdot)
        return false;
#endif
    return fileName != m_dot;
105
106
}

107
// FolderNavigationModel: Shows path as tooltip.
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
class FolderNavigationModel : public QFileSystemModel
{
public:
    explicit FolderNavigationModel(QObject *parent = 0);
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
};

FolderNavigationModel::FolderNavigationModel(QObject *parent) :
    QFileSystemModel(parent)
{
}

QVariant FolderNavigationModel::data(const QModelIndex &index, int role) const
{
    if (role == Qt::ToolTipRole)
        return QDir::toNativeSeparators(QDir::cleanPath(filePath(index)));
    else
        return QFileSystemModel::data(index, role);
}

con's avatar
con committed
128
129
130
131
132
/*!
  /class FolderNavigationWidget

  Shows a file system folder
  */
hjk's avatar
hjk committed
133
134
FolderNavigationWidget::FolderNavigationWidget(QWidget *parent)
    : QWidget(parent),
135
      m_listView(new QListView(this)),
136
      m_fileSystemModel(new FolderNavigationModel(this)),
137
      m_filterModel(new DotRemovalFilter(this)),
hjk's avatar
hjk committed
138
      m_title(new QLabel(this)),
139
      m_autoSync(false)
con's avatar
con committed
140
{
141
    m_fileSystemModel->setResolveSymlinks(false);
142
    m_fileSystemModel->setIconProvider(Core::FileIconProvider::instance());
143
144
145
146
147
148
149
150
    QDir::Filters filters = QDir::AllDirs | QDir::Files | QDir::Drives
                            | QDir::Readable| QDir::Writable
                            | QDir::Executable | QDir::Hidden;
#ifdef Q_OS_WIN // Symlinked directories can cause file watcher warnings on Win32.
    filters |= QDir::NoSymLinks;
#endif
    m_fileSystemModel->setFilter(filters);
    m_filterModel->setSourceModel(m_fileSystemModel);
151
    m_listView->setIconSize(QSize(16,16));
152
153
154
155
    m_listView->setModel(m_filterModel);
    m_listView->setFrameStyle(QFrame::NoFrame);
    m_listView->setAttribute(Qt::WA_MacShowFocusRect, false);
    setFocusProxy(m_listView);
con's avatar
con committed
156
157
158

    QVBoxLayout *layout = new QVBoxLayout();
    layout->addWidget(m_title);
159
    layout->addWidget(m_listView);
con's avatar
con committed
160
161
162
163
164
165
    m_title->setMargin(5);
    layout->setSpacing(0);
    layout->setContentsMargins(0, 0, 0, 0);
    setLayout(layout);

    // connections
166
167
    connect(m_listView, SIGNAL(activated(const QModelIndex&)),
            this, SLOT(slotOpenItem(const QModelIndex&)));
con's avatar
con committed
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188

    setAutoSynchronization(true);
}

void FolderNavigationWidget::toggleAutoSynchronization()
{
    setAutoSynchronization(!m_autoSync);
}

bool FolderNavigationWidget::autoSynchronization() const
{
    return m_autoSync;
}

void FolderNavigationWidget::setAutoSynchronization(bool sync)
{
    if (sync == m_autoSync)
        return;

    m_autoSync = sync;

hjk's avatar
hjk committed
189
    Core::FileManager *fileManager = Core::ICore::instance()->fileManager();
con's avatar
con committed
190
    if (m_autoSync) {
hjk's avatar
hjk committed
191
192
        connect(fileManager, SIGNAL(currentFileChanged(QString)),
                this, SLOT(setCurrentFile(QString)));
con's avatar
con committed
193
194
        setCurrentFile(fileManager->currentFile());
    } else {
hjk's avatar
hjk committed
195
196
        disconnect(fileManager, SIGNAL(currentFileChanged(QString)),
                this, SLOT(setCurrentFile(QString)));
con's avatar
con committed
197
198
199
200
201
    }
}

void FolderNavigationWidget::setCurrentFile(const QString &filePath)
{
202
203
204
205
206
207
208
209
210
    // Try to find directory of current file
    bool pathOpened = false;
    if (!filePath.isEmpty())  {
        const QFileInfo fi(filePath);
        if (fi.exists())
            pathOpened = setCurrentDirectory(fi.absolutePath());
    }
    if (!pathOpened)  // Default to home.
        setCurrentDirectory(Utils::PathChooser::homePath());
con's avatar
con committed
211

212
213
214
    // Select the current file.
    if (pathOpened) {
        const QModelIndex fileIndex = m_fileSystemModel->index(filePath);
con's avatar
con committed
215
        if (fileIndex.isValid()) {
216
217
            QItemSelectionModel *selections = m_listView->selectionModel();
            const QModelIndex mainIndex = m_filterModel->mapFromSource(fileIndex);
con's avatar
con committed
218
219
            selections->setCurrentIndex(mainIndex, QItemSelectionModel::SelectCurrent
                                                 | QItemSelectionModel::Clear);
220
            m_listView->scrollTo(mainIndex);
con's avatar
con committed
221
        }
222
223
224
225
226
    }
}

bool FolderNavigationWidget::setCurrentDirectory(const QString &directory)
{
227
228
229
230
231
232
    const QString newDirectory = directory.isEmpty() ? QDir::rootPath() : directory;
    if (debug)
        qDebug() << "setcurdir" << directory << newDirectory;
    // Set the root path on the model instead of changing the top index
    // of the view to cause the model to clean out its file watchers.
    const QModelIndex index = m_fileSystemModel->setRootPath(newDirectory);
233
234
235
236
    if (!index.isValid()) {
        setCurrentTitle(QString(), QString());
        return false;
    }
237
    m_listView->setRootIndex(m_filterModel->mapFromSource(index));
238
    const QDir current(QDir::cleanPath(newDirectory));
239
240
    setCurrentTitle(current.dirName(),
                    QDir::toNativeSeparators(current.absolutePath()));
241
    return !directory.isEmpty();
con's avatar
con committed
242
243
}

244
QString FolderNavigationWidget::currentDirectory() const
con's avatar
con committed
245
{
246
    return m_fileSystemModel->rootPath();
247
248
249
250
251
252
253
254
255
256
257
258
259
}

void FolderNavigationWidget::slotOpenItem(const QModelIndex &viewIndex)
{
    if (viewIndex.isValid())
        openItem(m_filterModel->mapToSource(viewIndex));
}

void FolderNavigationWidget::openItem(const QModelIndex &srcIndex)
{
    const QString fileName = m_fileSystemModel->fileName(srcIndex);
    if (fileName == QLatin1String("."))
        return;
260
261
262
    if (fileName == QLatin1String("..")) {
        // cd up: Special behaviour: The fileInfo of ".." is that of the parent directory.
        const QString parentPath = m_fileSystemModel->fileInfo(srcIndex).absoluteFilePath();
263
        setCurrentDirectory(parentPath);
264
265
266
        return;
    }
    if (m_fileSystemModel->isDir(srcIndex)) { // Change to directory
267
268
269
        const QFileInfo fi = m_fileSystemModel->fileInfo(srcIndex);
        if (fi.isReadable() && fi.isExecutable())
            setCurrentDirectory(m_fileSystemModel->filePath(srcIndex));
270
        return;
con's avatar
con committed
271
    }
272
273
    // Open file.
    Core::EditorManager *editorManager = Core::EditorManager::instance();
274
    editorManager->openEditor(m_fileSystemModel->filePath(srcIndex), QString(), Core::EditorManager::ModeSwitch);
con's avatar
con committed
275
276
}

277
void FolderNavigationWidget::setCurrentTitle(QString dirName, const QString &fullPath)
con's avatar
con committed
278
{
279
280
    if (dirName.isEmpty())
        dirName = fullPath;
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
    m_title->setText(dirName);
    m_title->setToolTip(fullPath);
}

QModelIndex FolderNavigationWidget::currentItem() const
{
    const QModelIndex current = m_listView->currentIndex();
    if (current.isValid())
        return m_filterModel->mapToSource(current);
    return QModelIndex();
}

// Format the text for the "open" action of the context menu according
// to the selectect entry
static inline QString actionOpenText(const QFileSystemModel *model,
                                     const QModelIndex &index)
{
    if (!index.isValid())
        return FolderNavigationWidget::tr("Open");
    const QString fileName = model->fileName(index);
    if (fileName == QLatin1String(".."))
Leena Miettinen's avatar
Leena Miettinen committed
302
        return FolderNavigationWidget::tr("Open Parent Folder");
303
304
305
306
307
308
309
310
    return FolderNavigationWidget::tr("Open \"%1\"").arg(fileName);
}

void FolderNavigationWidget::contextMenuEvent(QContextMenuEvent *ev)
{
    QMenu menu;
    // Open current item
    const QModelIndex current = currentItem();
311
    const bool hasCurrentItem = current.isValid();
312
    QAction *actionOpen = menu.addAction(actionOpenText(m_fileSystemModel, current));
313
314
315
316
317
318
    actionOpen->setEnabled(hasCurrentItem);
    // Explorer & teminal
    QAction *actionExplorer = menu.addAction(msgGraphicalShellAction());
    actionExplorer->setEnabled(hasCurrentItem);
    QAction *actionTerminal = menu.addAction(msgTerminalAction());
    actionTerminal->setEnabled(hasCurrentItem);
319
320
321
322
323
324
325
326

    // open with...
    if (!m_fileSystemModel->isDir(current)) {
        QMenu *openWith = menu.addMenu(tr("Open with"));
        ProjectExplorerPlugin::populateOpenWithMenu(openWith,
                                                    m_fileSystemModel->filePath(current));
    }

327
    // Open file dialog to choose a path starting from current
Leena Miettinen's avatar
Leena Miettinen committed
328
    QAction *actionChooseFolder = menu.addAction(tr("Choose Folder..."));
329
330
331
332
333
334
335
336
337
338
339

    QAction *action = menu.exec(ev->globalPos());
    if (!action)
        return;

    ev->accept();
    if (action == actionOpen) { // Handle open file.
        openItem(current);
        return;
    }
    if (action == actionChooseFolder) { // Open file dialog
Leena Miettinen's avatar
Leena Miettinen committed
340
        const QString newPath = QFileDialog::getExistingDirectory(this, tr("Choose Folder"), currentDirectory());
341
342
        if (!newPath.isEmpty())
            setCurrentDirectory(newPath);
343
344
345
346
347
348
349
350
351
352
        return;
    }
    if (action == actionTerminal) {
        openTerminal(m_fileSystemModel->filePath(current));
        return;
    }
    if (action == actionExplorer) {
        showInGraphicalShell(this, m_fileSystemModel->filePath(current));
        return;
    }
353
354
    ProjectExplorerPlugin::openEditorFromAction(action,
                                                m_fileSystemModel->filePath(current));
355
356
357
358
359
360
361
362
363
}

QString FolderNavigationWidget::msgGraphicalShellAction()
{
#if defined(Q_OS_WIN)
    return tr("Show in Explorer...");
#elif defined(Q_OS_MAC)
    return tr("Show in Finder...");
#else
Leena Miettinen's avatar
Leena Miettinen committed
364
    return tr("Show Containing Folder...");
365
366
367
368
369
370
#endif
}

QString FolderNavigationWidget::msgTerminalAction()
{
#ifdef Q_OS_WIN
Leena Miettinen's avatar
Leena Miettinen committed
371
    return tr("Open Command Prompt Here...");
372
#else
Leena Miettinen's avatar
Leena Miettinen committed
373
    return tr("Open Terminal Here...");
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
#endif
}

// Show error with option to open settings.
static inline void showGraphicalShellError(QWidget *parent,
                                           const QString &app,
                                           const QString &error)
{
    const QString title = FolderNavigationWidget::tr("Launching a file browser failed");
    const QString msg = FolderNavigationWidget::tr("Unable to start the file manager:\n\n%1\n\n").arg(app);
    QMessageBox mbox(QMessageBox::Warning, title, msg, QMessageBox::Close, parent);
    if (!error.isEmpty())
        mbox.setDetailedText(FolderNavigationWidget::tr("'%1' returned the following error:\n\n%2").arg(app, error));
    QAbstractButton *settingsButton = mbox.addButton(FolderNavigationWidget::tr("Settings..."), QMessageBox::ActionRole);
    mbox.exec();
    if (mbox.clickedButton() == settingsButton)
        Core::ICore::instance()->showOptionsDialog(QLatin1String(Core::Constants::SETTINGS_CATEGORY_CORE),
                                                   QLatin1String(Core::Constants::SETTINGS_ID_ENVIRONMENT));
}

void FolderNavigationWidget::showInGraphicalShell(QWidget *parent, const QString &pathIn)
{
    // Mac, Windows support folder or file.
#if defined(Q_OS_WIN)
398
    const QString explorer = Utils::Environment::systemEnvironment().searchInPath(QLatin1String("explorer.exe"));
399
400
    if (explorer.isEmpty()) {
        QMessageBox::warning(parent,
Leena Miettinen's avatar
Leena Miettinen committed
401
                             tr("Launching Windows Explorer Failed"),
402
403
                             tr("Could not find explorer.exe in path to launch Windows Explorer."));
        return;
404
    }
405
406
407
408
409
    QString param;
    if (!QFileInfo(pathIn).isDir())
        param = QLatin1String("/select,");
    param += QDir::toNativeSeparators(pathIn);
    QProcess::startDetached(explorer, QStringList(param));
410
#elif defined(Q_OS_MAC)
411
    Q_UNUSED(parent)
412
413
414
415
416
417
418
419
420
421
422
423
    QStringList scriptArgs;
    scriptArgs << QLatin1String("-e")
               << QString::fromLatin1("tell application \"Finder\" to reveal POSIX file \"%1\"")
                                     .arg(pathIn);
    QProcess::execute(QLatin1String("/usr/bin/osascript"), scriptArgs);
    scriptArgs.clear();
    scriptArgs << QLatin1String("-e")
               << QLatin1String("tell application \"Finder\" to activate");
    QProcess::execute("/usr/bin/osascript", scriptArgs);
#else
    // we cannot select a file here, because no file browser really supports it...
    const QFileInfo fileInfo(pathIn);
424
    const QString folder = fileInfo.isDir() ? fileInfo.absoluteFilePath() : fileInfo.filePath();
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
    const QString app = Utils::UnixUtils::fileBrowser(Core::ICore::instance()->settings());
    QProcess browserProc;
    const QString browserArgs = Utils::UnixUtils::substituteFileBrowserParameters(app, folder);
    if (debug)
        qDebug() <<  browserArgs;
    bool success = browserProc.startDetached(browserArgs);
    const QString error = QString::fromLocal8Bit(browserProc.readAllStandardError());
    success = success && error.isEmpty();
    if (!success)
        showGraphicalShellError(parent, app, error);
#endif
}

void FolderNavigationWidget::openTerminal(const QString &path)
{
    // Get terminal application
#ifdef Q_OS_WIN
    const QString terminalEmulator = QString::fromLocal8Bit(qgetenv("COMSPEC"));
    const QStringList args; // none
444
445
446
447
#elif defined(Q_WS_MAC)
    const QString terminalEmulator = Core::ICore::instance()->resourcePath()
            + QLatin1String("/scripts/openTerminal.command");
    QStringList args;
448
#else
449
450
    QStringList args = Utils::QtcProcess::splitArgs(
        Utils::ConsoleProcess::terminalEmulator(Core::ICore::instance()->settings()));
451
452
453
454
455
456
457
458
459
460
    const QString terminalEmulator = args.takeFirst();
    const QString shell = QString::fromLocal8Bit(qgetenv("SHELL"));
    args.append(shell);
#endif
    // Launch terminal with working directory set.
    const QFileInfo fileInfo(path);
    const QString pwd = QDir::toNativeSeparators(fileInfo.isDir() ?
                                                 fileInfo.absoluteFilePath() :
                                                 fileInfo.absolutePath());
    QProcess::startDetached(terminalEmulator, args, pwd);
con's avatar
con committed
461
462
}

463
// --------------------FolderNavigationWidgetFactory
hjk's avatar
hjk committed
464
FolderNavigationWidgetFactory::FolderNavigationWidgetFactory()
con's avatar
con committed
465
466
467
468
469
470
471
{
}

FolderNavigationWidgetFactory::~FolderNavigationWidgetFactory()
{
}

472
QString FolderNavigationWidgetFactory::displayName() const
con's avatar
con committed
473
474
475
476
{
    return tr("File System");
}

477
478
479
480
481
int FolderNavigationWidgetFactory::priority() const
{
    return 400;
}

482
483
484
485
486
487
QString FolderNavigationWidgetFactory::id() const
{
    return QLatin1String("File System");
}

QKeySequence FolderNavigationWidgetFactory::activationSequence() const
con's avatar
con committed
488
489
490
491
492
493
494
{
    return QKeySequence(Qt::ALT + Qt::Key_Y);
}

Core::NavigationView FolderNavigationWidgetFactory::createWidget()
{
    Core::NavigationView n;
hjk's avatar
hjk committed
495
    FolderNavigationWidget *ptw = new FolderNavigationWidget;
con's avatar
con committed
496
497
    n.widget = ptw;
    QToolButton *toggleSync = new QToolButton;
498
    toggleSync->setIcon(QIcon(QLatin1String(Core::Constants::ICON_LINK)));
con's avatar
con committed
499
500
501
502
    toggleSync->setCheckable(true);
    toggleSync->setChecked(ptw->autoSynchronization());
    toggleSync->setToolTip(tr("Synchronize with Editor"));
    connect(toggleSync, SIGNAL(clicked(bool)), ptw, SLOT(toggleAutoSynchronization()));
Roberto Raggi's avatar
Roberto Raggi committed
503
    n.dockToolBarWidgets << toggleSync;
con's avatar
con committed
504
505
506
    return n;
}

507
508
509
} // namespace Internal
} // namespace ProjectExplorer

con's avatar
con committed
510
#include "foldernavigationwidget.moc"