1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
10 ** GNU Lesser General Public License Usage
12 ** This file may be used under the terms of the GNU Lesser General Public
13 ** License version 2.1 as published by the Free Software Foundation and
14 ** appearing in the file LICENSE.LGPL included in the packaging of this file.
15 ** Please review the following information to ensure the GNU Lesser General
16 ** Public License version 2.1 requirements will be met:
17 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
19 ** In addition, as a special exception, Nokia gives you certain additional
20 ** rights. These rights are described in the Nokia Qt LGPL Exception
21 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
25 ** Alternatively, this file may be used in accordance with the terms and
26 ** conditions contained in a signed written agreement between you and Nokia.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
31 **************************************************************************/
33 #include "foldernavigationwidget.h"
34 #include "projectexplorer.h"
35 #include "projectexplorerconstants.h"
37 #include <coreplugin/icore.h>
38 #include <coreplugin/fileiconprovider.h>
39 #include <coreplugin/filemanager.h>
40 #include <coreplugin/editormanager/editormanager.h>
41 #include <coreplugin/coreconstants.h>
43 #include <utils/environment.h>
44 #include <utils/pathchooser.h>
45 #include <utils/qtcassert.h>
46 #include <utils/qtcprocess.h>
47 #include <utils/unixutils.h>
48 #include <utils/consoleprocess.h>
50 #include <QtCore/QDebug>
51 #include <QtCore/QProcess>
52 #include <QtCore/QSize>
53 #include <QtGui/QFileSystemModel>
54 #include <QtGui/QVBoxLayout>
55 #include <QtGui/QToolButton>
56 #include <QtGui/QPushButton>
57 #include <QtGui/QLabel>
58 #include <QtGui/QListView>
59 #include <QtGui/QSortFilterProxyModel>
60 #include <QtGui/QAction>
61 #include <QtGui/QMenu>
62 #include <QtGui/QFileDialog>
63 #include <QtGui/QContextMenuEvent>
64 #include <QtGui/QMessageBox>
68 namespace ProjectExplorer {
71 // Hide the '.' entry.
72 class DotRemovalFilter : public QSortFilterProxyModel
76 explicit DotRemovalFilter(QObject *parent = 0);
78 virtual bool filterAcceptsRow(int source_row, const QModelIndex &parent) const;
80 #if defined(Q_OS_UNIX)
81 const QVariant m_root;
82 const QVariant m_dotdot;
87 DotRemovalFilter::DotRemovalFilter(QObject *parent) :
88 QSortFilterProxyModel(parent),
89 #if defined(Q_OS_UNIX)
90 m_root(QString(QLatin1Char('/'))),
91 m_dotdot(QLatin1String("..")),
93 m_dot(QString(QLatin1Char('.')))
97 bool DotRemovalFilter::filterAcceptsRow(int source_row, const QModelIndex &parent) const
99 const QVariant fileName = sourceModel()->data(parent.child(source_row, 0));
100 #if defined(Q_OS_UNIX)
101 if (sourceModel()->data(parent) == m_root && fileName == m_dotdot)
104 return fileName != m_dot;
107 // FolderNavigationModel: Shows path as tooltip.
108 class FolderNavigationModel : public QFileSystemModel
111 explicit FolderNavigationModel(QObject *parent = 0);
112 QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
115 FolderNavigationModel::FolderNavigationModel(QObject *parent) :
116 QFileSystemModel(parent)
120 QVariant FolderNavigationModel::data(const QModelIndex &index, int role) const
122 if (role == Qt::ToolTipRole)
123 return QDir::toNativeSeparators(QDir::cleanPath(filePath(index)));
125 return QFileSystemModel::data(index, role);
129 /class FolderNavigationWidget
131 Shows a file system folder
133 FolderNavigationWidget::FolderNavigationWidget(QWidget *parent)
135 m_listView(new QListView(this)),
136 m_fileSystemModel(new FolderNavigationModel(this)),
137 m_filterModel(new DotRemovalFilter(this)),
138 m_title(new QLabel(this)),
141 m_fileSystemModel->setResolveSymlinks(false);
142 m_fileSystemModel->setIconProvider(Core::FileIconProvider::instance());
143 QDir::Filters filters = QDir::AllDirs | QDir::Files | QDir::Drives
144 | QDir::Readable| QDir::Writable
145 | QDir::Executable | QDir::Hidden;
146 #ifdef Q_OS_WIN // Symlinked directories can cause file watcher warnings on Win32.
147 filters |= QDir::NoSymLinks;
149 m_fileSystemModel->setFilter(filters);
150 m_filterModel->setSourceModel(m_fileSystemModel);
151 m_listView->setIconSize(QSize(16,16));
152 m_listView->setModel(m_filterModel);
153 m_listView->setFrameStyle(QFrame::NoFrame);
154 m_listView->setAttribute(Qt::WA_MacShowFocusRect, false);
155 setFocusProxy(m_listView);
157 QVBoxLayout *layout = new QVBoxLayout();
158 layout->addWidget(m_title);
159 layout->addWidget(m_listView);
160 m_title->setMargin(5);
161 layout->setSpacing(0);
162 layout->setContentsMargins(0, 0, 0, 0);
166 connect(m_listView, SIGNAL(activated(const QModelIndex&)),
167 this, SLOT(slotOpenItem(const QModelIndex&)));
169 setAutoSynchronization(true);
172 void FolderNavigationWidget::toggleAutoSynchronization()
174 setAutoSynchronization(!m_autoSync);
177 bool FolderNavigationWidget::autoSynchronization() const
182 void FolderNavigationWidget::setAutoSynchronization(bool sync)
184 if (sync == m_autoSync)
189 Core::FileManager *fileManager = Core::ICore::instance()->fileManager();
191 connect(fileManager, SIGNAL(currentFileChanged(QString)),
192 this, SLOT(setCurrentFile(QString)));
193 setCurrentFile(fileManager->currentFile());
195 disconnect(fileManager, SIGNAL(currentFileChanged(QString)),
196 this, SLOT(setCurrentFile(QString)));
200 void FolderNavigationWidget::setCurrentFile(const QString &filePath)
202 // Try to find directory of current file
203 bool pathOpened = false;
204 if (!filePath.isEmpty()) {
205 const QFileInfo fi(filePath);
207 pathOpened = setCurrentDirectory(fi.absolutePath());
209 if (!pathOpened) // Default to home.
210 setCurrentDirectory(Utils::PathChooser::homePath());
212 // Select the current file.
214 const QModelIndex fileIndex = m_fileSystemModel->index(filePath);
215 if (fileIndex.isValid()) {
216 QItemSelectionModel *selections = m_listView->selectionModel();
217 const QModelIndex mainIndex = m_filterModel->mapFromSource(fileIndex);
218 selections->setCurrentIndex(mainIndex, QItemSelectionModel::SelectCurrent
219 | QItemSelectionModel::Clear);
220 m_listView->scrollTo(mainIndex);
225 bool FolderNavigationWidget::setCurrentDirectory(const QString &directory)
227 const QString newDirectory = directory.isEmpty() ? QDir::rootPath() : directory;
229 qDebug() << "setcurdir" << directory << newDirectory;
230 // Set the root path on the model instead of changing the top index
231 // of the view to cause the model to clean out its file watchers.
232 const QModelIndex index = m_fileSystemModel->setRootPath(newDirectory);
233 if (!index.isValid()) {
234 setCurrentTitle(QString(), QString());
237 m_listView->setRootIndex(m_filterModel->mapFromSource(index));
238 const QDir current(QDir::cleanPath(newDirectory));
239 setCurrentTitle(current.dirName(),
240 QDir::toNativeSeparators(current.absolutePath()));
241 return !directory.isEmpty();
244 QString FolderNavigationWidget::currentDirectory() const
246 return m_fileSystemModel->rootPath();
249 void FolderNavigationWidget::slotOpenItem(const QModelIndex &viewIndex)
251 if (viewIndex.isValid())
252 openItem(m_filterModel->mapToSource(viewIndex));
255 void FolderNavigationWidget::openItem(const QModelIndex &srcIndex)
257 const QString fileName = m_fileSystemModel->fileName(srcIndex);
258 if (fileName == QLatin1String("."))
260 if (fileName == QLatin1String("..")) {
261 // cd up: Special behaviour: The fileInfo of ".." is that of the parent directory.
262 const QString parentPath = m_fileSystemModel->fileInfo(srcIndex).absoluteFilePath();
263 setCurrentDirectory(parentPath);
266 if (m_fileSystemModel->isDir(srcIndex)) { // Change to directory
267 const QFileInfo fi = m_fileSystemModel->fileInfo(srcIndex);
268 if (fi.isReadable() && fi.isExecutable())
269 setCurrentDirectory(m_fileSystemModel->filePath(srcIndex));
273 Core::EditorManager *editorManager = Core::EditorManager::instance();
274 editorManager->openEditor(m_fileSystemModel->filePath(srcIndex), QString(), Core::EditorManager::ModeSwitch);
277 void FolderNavigationWidget::setCurrentTitle(QString dirName, const QString &fullPath)
279 if (dirName.isEmpty())
281 m_title->setText(dirName);
282 m_title->setToolTip(fullPath);
285 QModelIndex FolderNavigationWidget::currentItem() const
287 const QModelIndex current = m_listView->currentIndex();
288 if (current.isValid())
289 return m_filterModel->mapToSource(current);
290 return QModelIndex();
293 // Format the text for the "open" action of the context menu according
294 // to the selectect entry
295 static inline QString actionOpenText(const QFileSystemModel *model,
296 const QModelIndex &index)
298 if (!index.isValid())
299 return FolderNavigationWidget::tr("Open");
300 const QString fileName = model->fileName(index);
301 if (fileName == QLatin1String(".."))
302 return FolderNavigationWidget::tr("Open Parent Folder");
303 return FolderNavigationWidget::tr("Open \"%1\"").arg(fileName);
306 void FolderNavigationWidget::contextMenuEvent(QContextMenuEvent *ev)
310 const QModelIndex current = currentItem();
311 const bool hasCurrentItem = current.isValid();
312 QAction *actionOpen = menu.addAction(actionOpenText(m_fileSystemModel, current));
313 actionOpen->setEnabled(hasCurrentItem);
314 // Explorer & teminal
315 QAction *actionExplorer = menu.addAction(msgGraphicalShellAction());
316 actionExplorer->setEnabled(hasCurrentItem);
317 QAction *actionTerminal = menu.addAction(msgTerminalAction());
318 actionTerminal->setEnabled(hasCurrentItem);
321 if (!m_fileSystemModel->isDir(current)) {
322 QMenu *openWith = menu.addMenu(tr("Open with"));
323 ProjectExplorerPlugin::populateOpenWithMenu(openWith,
324 m_fileSystemModel->filePath(current));
327 // Open file dialog to choose a path starting from current
328 QAction *actionChooseFolder = menu.addAction(tr("Choose Folder..."));
330 QAction *action = menu.exec(ev->globalPos());
335 if (action == actionOpen) { // Handle open file.
339 if (action == actionChooseFolder) { // Open file dialog
340 const QString newPath = QFileDialog::getExistingDirectory(this, tr("Choose Folder"), currentDirectory());
341 if (!newPath.isEmpty())
342 setCurrentDirectory(newPath);
345 if (action == actionTerminal) {
346 openTerminal(m_fileSystemModel->filePath(current));
349 if (action == actionExplorer) {
350 showInGraphicalShell(this, m_fileSystemModel->filePath(current));
353 ProjectExplorerPlugin::openEditorFromAction(action,
354 m_fileSystemModel->filePath(current));
357 QString FolderNavigationWidget::msgGraphicalShellAction()
359 #if defined(Q_OS_WIN)
360 return tr("Show in Explorer...");
361 #elif defined(Q_OS_MAC)
362 return tr("Show in Finder...");
364 return tr("Show Containing Folder...");
368 QString FolderNavigationWidget::msgTerminalAction()
371 return tr("Open Command Prompt Here...");
373 return tr("Open Terminal Here...");
377 // Show error with option to open settings.
378 static inline void showGraphicalShellError(QWidget *parent,
380 const QString &error)
382 const QString title = FolderNavigationWidget::tr("Launching a file browser failed");
383 const QString msg = FolderNavigationWidget::tr("Unable to start the file manager:\n\n%1\n\n").arg(app);
384 QMessageBox mbox(QMessageBox::Warning, title, msg, QMessageBox::Close, parent);
385 if (!error.isEmpty())
386 mbox.setDetailedText(FolderNavigationWidget::tr("'%1' returned the following error:\n\n%2").arg(app, error));
387 QAbstractButton *settingsButton = mbox.addButton(FolderNavigationWidget::tr("Settings..."), QMessageBox::ActionRole);
389 if (mbox.clickedButton() == settingsButton)
390 Core::ICore::instance()->showOptionsDialog(QLatin1String(Core::Constants::SETTINGS_CATEGORY_CORE),
391 QLatin1String(Core::Constants::SETTINGS_ID_ENVIRONMENT));
394 void FolderNavigationWidget::showInGraphicalShell(QWidget *parent, const QString &pathIn)
396 // Mac, Windows support folder or file.
397 #if defined(Q_OS_WIN)
398 const QString explorer = Utils::Environment::systemEnvironment().searchInPath(QLatin1String("explorer.exe"));
399 if (explorer.isEmpty()) {
400 QMessageBox::warning(parent,
401 tr("Launching Windows Explorer Failed"),
402 tr("Could not find explorer.exe in path to launch Windows Explorer."));
406 if (!QFileInfo(pathIn).isDir())
407 param = QLatin1String("/select,");
408 param += QDir::toNativeSeparators(pathIn);
409 QProcess::startDetached(explorer, QStringList(param));
410 #elif defined(Q_OS_MAC)
412 QStringList scriptArgs;
413 scriptArgs << QLatin1String("-e")
414 << QString::fromLatin1("tell application \"Finder\" to reveal POSIX file \"%1\"")
416 QProcess::execute(QLatin1String("/usr/bin/osascript"), scriptArgs);
418 scriptArgs << QLatin1String("-e")
419 << QLatin1String("tell application \"Finder\" to activate");
420 QProcess::execute("/usr/bin/osascript", scriptArgs);
422 // we cannot select a file here, because no file browser really supports it...
423 const QFileInfo fileInfo(pathIn);
424 const QString folder = fileInfo.absoluteFilePath();
425 const QString app = Utils::UnixUtils::fileBrowser(Core::ICore::instance()->settings());
426 QProcess browserProc;
427 const QString browserArgs = Utils::UnixUtils::substituteFileBrowserParameters(app, folder);
429 qDebug() << browserArgs;
430 bool success = browserProc.startDetached(browserArgs);
431 const QString error = QString::fromLocal8Bit(browserProc.readAllStandardError());
432 success = success && error.isEmpty();
434 showGraphicalShellError(parent, app, error);
438 void FolderNavigationWidget::openTerminal(const QString &path)
440 // Get terminal application
442 const QString terminalEmulator = QString::fromLocal8Bit(qgetenv("COMSPEC"));
443 const QStringList args; // none
445 QStringList args = Utils::QtcProcess::splitArgs(
446 Utils::ConsoleProcess::terminalEmulator(Core::ICore::instance()->settings()));
447 const QString terminalEmulator = args.takeFirst();
448 const QString shell = QString::fromLocal8Bit(qgetenv("SHELL"));
451 // Launch terminal with working directory set.
452 const QFileInfo fileInfo(path);
453 const QString pwd = QDir::toNativeSeparators(fileInfo.isDir() ?
454 fileInfo.absoluteFilePath() :
455 fileInfo.absolutePath());
456 QProcess::startDetached(terminalEmulator, args, pwd);
459 // --------------------FolderNavigationWidgetFactory
460 FolderNavigationWidgetFactory::FolderNavigationWidgetFactory()
464 FolderNavigationWidgetFactory::~FolderNavigationWidgetFactory()
468 QString FolderNavigationWidgetFactory::displayName() const
470 return tr("File System");
473 int FolderNavigationWidgetFactory::priority() const
478 QString FolderNavigationWidgetFactory::id() const
480 return QLatin1String("File System");
483 QKeySequence FolderNavigationWidgetFactory::activationSequence() const
485 return QKeySequence(Qt::ALT + Qt::Key_Y);
488 Core::NavigationView FolderNavigationWidgetFactory::createWidget()
490 Core::NavigationView n;
491 FolderNavigationWidget *ptw = new FolderNavigationWidget;
493 QToolButton *toggleSync = new QToolButton;
494 toggleSync->setIcon(QIcon(QLatin1String(Core::Constants::ICON_LINK)));
495 toggleSync->setCheckable(true);
496 toggleSync->setChecked(ptw->autoSynchronization());
497 toggleSync->setToolTip(tr("Synchronize with Editor"));
498 connect(toggleSync, SIGNAL(clicked(bool)), ptw, SLOT(toggleAutoSynchronization()));
499 n.dockToolBarWidgets << toggleSync;
503 } // namespace Internal
504 } // namespace ProjectExplorer
506 #include "foldernavigationwidget.moc"