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 "foldernavigationwidget.h"
35 #include "projectexplorer.h"
36 #include "projectexplorerconstants.h"
38 #include <coreplugin/icore.h>
39 #include <coreplugin/fileiconprovider.h>
40 #include <coreplugin/filemanager.h>
41 #include <coreplugin/editormanager/editormanager.h>
42 #include <coreplugin/coreconstants.h>
44 #include <utils/environment.h>
45 #include <utils/pathchooser.h>
46 #include <utils/qtcassert.h>
47 #include <utils/qtcprocess.h>
48 #include <utils/unixutils.h>
49 #include <utils/consoleprocess.h>
51 #include <QtCore/QDebug>
52 #include <QtCore/QProcess>
53 #include <QtCore/QSize>
54 #include <QtGui/QFileSystemModel>
55 #include <QtGui/QVBoxLayout>
56 #include <QtGui/QToolButton>
57 #include <QtGui/QPushButton>
58 #include <QtGui/QLabel>
59 #include <QtGui/QListView>
60 #include <QtGui/QSortFilterProxyModel>
61 #include <QtGui/QAction>
62 #include <QtGui/QMenu>
63 #include <QtGui/QFileDialog>
64 #include <QtGui/QContextMenuEvent>
65 #include <QtGui/QMessageBox>
69 namespace ProjectExplorer {
72 // Hide the '.' entry.
73 class DotRemovalFilter : public QSortFilterProxyModel
77 explicit DotRemovalFilter(QObject *parent = 0);
79 virtual bool filterAcceptsRow(int source_row, const QModelIndex &parent) const;
81 #if defined(Q_OS_UNIX)
82 const QVariant m_root;
83 const QVariant m_dotdot;
88 DotRemovalFilter::DotRemovalFilter(QObject *parent) :
89 QSortFilterProxyModel(parent),
90 #if defined(Q_OS_UNIX)
91 m_root(QString(QLatin1Char('/'))),
92 m_dotdot(QLatin1String("..")),
94 m_dot(QString(QLatin1Char('.')))
98 bool DotRemovalFilter::filterAcceptsRow(int source_row, const QModelIndex &parent) const
100 const QVariant fileName = sourceModel()->data(parent.child(source_row, 0));
101 #if defined(Q_OS_UNIX)
102 if (sourceModel()->data(parent) == m_root && fileName == m_dotdot)
105 return fileName != m_dot;
108 // FolderNavigationModel: Shows path as tooltip.
109 class FolderNavigationModel : public QFileSystemModel
112 explicit FolderNavigationModel(QObject *parent = 0);
113 QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
116 FolderNavigationModel::FolderNavigationModel(QObject *parent) :
117 QFileSystemModel(parent)
121 QVariant FolderNavigationModel::data(const QModelIndex &index, int role) const
123 if (role == Qt::ToolTipRole)
124 return QDir::toNativeSeparators(QDir::cleanPath(filePath(index)));
126 return QFileSystemModel::data(index, role);
130 /class FolderNavigationWidget
132 Shows a file system folder
134 FolderNavigationWidget::FolderNavigationWidget(QWidget *parent)
136 m_listView(new QListView(this)),
137 m_fileSystemModel(new FolderNavigationModel(this)),
138 m_filterModel(new DotRemovalFilter(this)),
139 m_title(new QLabel(this)),
142 m_fileSystemModel->setResolveSymlinks(false);
143 m_fileSystemModel->setIconProvider(Core::FileIconProvider::instance());
144 QDir::Filters filters = QDir::AllDirs | QDir::Files | QDir::Drives
145 | QDir::Readable| QDir::Writable
146 | QDir::Executable | QDir::Hidden;
147 #ifdef Q_OS_WIN // Symlinked directories can cause file watcher warnings on Win32.
148 filters |= QDir::NoSymLinks;
150 m_fileSystemModel->setFilter(filters);
151 m_filterModel->setSourceModel(m_fileSystemModel);
152 m_listView->setIconSize(QSize(16,16));
153 m_listView->setModel(m_filterModel);
154 m_listView->setFrameStyle(QFrame::NoFrame);
155 m_listView->setAttribute(Qt::WA_MacShowFocusRect, false);
156 setFocusProxy(m_listView);
158 QVBoxLayout *layout = new QVBoxLayout();
159 layout->addWidget(m_title);
160 layout->addWidget(m_listView);
161 m_title->setMargin(5);
162 layout->setSpacing(0);
163 layout->setContentsMargins(0, 0, 0, 0);
167 connect(m_listView, SIGNAL(activated(const QModelIndex&)),
168 this, SLOT(slotOpenItem(const QModelIndex&)));
170 setAutoSynchronization(true);
173 void FolderNavigationWidget::toggleAutoSynchronization()
175 setAutoSynchronization(!m_autoSync);
178 bool FolderNavigationWidget::autoSynchronization() const
183 void FolderNavigationWidget::setAutoSynchronization(bool sync)
185 if (sync == m_autoSync)
190 Core::FileManager *fileManager = Core::ICore::instance()->fileManager();
192 connect(fileManager, SIGNAL(currentFileChanged(QString)),
193 this, SLOT(setCurrentFile(QString)));
194 setCurrentFile(fileManager->currentFile());
196 disconnect(fileManager, SIGNAL(currentFileChanged(QString)),
197 this, SLOT(setCurrentFile(QString)));
201 void FolderNavigationWidget::setCurrentFile(const QString &filePath)
203 // Try to find directory of current file
204 bool pathOpened = false;
205 if (!filePath.isEmpty()) {
206 const QFileInfo fi(filePath);
208 pathOpened = setCurrentDirectory(fi.absolutePath());
210 if (!pathOpened) // Default to home.
211 setCurrentDirectory(Utils::PathChooser::homePath());
213 // Select the current file.
215 const QModelIndex fileIndex = m_fileSystemModel->index(filePath);
216 if (fileIndex.isValid()) {
217 QItemSelectionModel *selections = m_listView->selectionModel();
218 const QModelIndex mainIndex = m_filterModel->mapFromSource(fileIndex);
219 selections->setCurrentIndex(mainIndex, QItemSelectionModel::SelectCurrent
220 | QItemSelectionModel::Clear);
221 m_listView->scrollTo(mainIndex);
226 bool FolderNavigationWidget::setCurrentDirectory(const QString &directory)
228 const QString newDirectory = directory.isEmpty() ? QDir::rootPath() : directory;
230 qDebug() << "setcurdir" << directory << newDirectory;
231 // Set the root path on the model instead of changing the top index
232 // of the view to cause the model to clean out its file watchers.
233 const QModelIndex index = m_fileSystemModel->setRootPath(newDirectory);
234 if (!index.isValid()) {
235 setCurrentTitle(QString(), QString());
238 m_listView->setRootIndex(m_filterModel->mapFromSource(index));
239 const QDir current(QDir::cleanPath(newDirectory));
240 setCurrentTitle(current.dirName(),
241 QDir::toNativeSeparators(current.absolutePath()));
242 return !directory.isEmpty();
245 QString FolderNavigationWidget::currentDirectory() const
247 return m_fileSystemModel->rootPath();
250 void FolderNavigationWidget::slotOpenItem(const QModelIndex &viewIndex)
252 if (viewIndex.isValid())
253 openItem(m_filterModel->mapToSource(viewIndex));
256 void FolderNavigationWidget::openItem(const QModelIndex &srcIndex)
258 const QString fileName = m_fileSystemModel->fileName(srcIndex);
259 if (fileName == QLatin1String("."))
261 if (fileName == QLatin1String("..")) {
262 // cd up: Special behaviour: The fileInfo of ".." is that of the parent directory.
263 const QString parentPath = m_fileSystemModel->fileInfo(srcIndex).absoluteFilePath();
264 setCurrentDirectory(parentPath);
267 if (m_fileSystemModel->isDir(srcIndex)) { // Change to directory
268 const QFileInfo fi = m_fileSystemModel->fileInfo(srcIndex);
269 if (fi.isReadable() && fi.isExecutable())
270 setCurrentDirectory(m_fileSystemModel->filePath(srcIndex));
274 Core::EditorManager *editorManager = Core::EditorManager::instance();
275 editorManager->openEditor(m_fileSystemModel->filePath(srcIndex), QString(), Core::EditorManager::ModeSwitch);
278 void FolderNavigationWidget::setCurrentTitle(QString dirName, const QString &fullPath)
280 if (dirName.isEmpty())
282 m_title->setText(dirName);
283 m_title->setToolTip(fullPath);
286 QModelIndex FolderNavigationWidget::currentItem() const
288 const QModelIndex current = m_listView->currentIndex();
289 if (current.isValid())
290 return m_filterModel->mapToSource(current);
291 return QModelIndex();
294 // Format the text for the "open" action of the context menu according
295 // to the selectect entry
296 static inline QString actionOpenText(const QFileSystemModel *model,
297 const QModelIndex &index)
299 if (!index.isValid())
300 return FolderNavigationWidget::tr("Open");
301 const QString fileName = model->fileName(index);
302 if (fileName == QLatin1String(".."))
303 return FolderNavigationWidget::tr("Open Parent Folder");
304 return FolderNavigationWidget::tr("Open \"%1\"").arg(fileName);
307 void FolderNavigationWidget::contextMenuEvent(QContextMenuEvent *ev)
311 const QModelIndex current = currentItem();
312 const bool hasCurrentItem = current.isValid();
313 QAction *actionOpen = menu.addAction(actionOpenText(m_fileSystemModel, current));
314 actionOpen->setEnabled(hasCurrentItem);
315 // Explorer & teminal
316 QAction *actionExplorer = menu.addAction(msgGraphicalShellAction());
317 actionExplorer->setEnabled(hasCurrentItem);
318 QAction *actionTerminal = menu.addAction(msgTerminalAction());
319 actionTerminal->setEnabled(hasCurrentItem);
322 if (!m_fileSystemModel->isDir(current)) {
323 QMenu *openWith = menu.addMenu(tr("Open with"));
324 ProjectExplorerPlugin::populateOpenWithMenu(openWith,
325 m_fileSystemModel->filePath(current));
328 // Open file dialog to choose a path starting from current
329 QAction *actionChooseFolder = menu.addAction(tr("Choose Folder..."));
331 QAction *action = menu.exec(ev->globalPos());
336 if (action == actionOpen) { // Handle open file.
340 if (action == actionChooseFolder) { // Open file dialog
341 const QString newPath = QFileDialog::getExistingDirectory(this, tr("Choose Folder"), currentDirectory());
342 if (!newPath.isEmpty())
343 setCurrentDirectory(newPath);
346 if (action == actionTerminal) {
347 openTerminal(m_fileSystemModel->filePath(current));
350 if (action == actionExplorer) {
351 showInGraphicalShell(this, m_fileSystemModel->filePath(current));
354 ProjectExplorerPlugin::openEditorFromAction(action,
355 m_fileSystemModel->filePath(current));
358 QString FolderNavigationWidget::msgGraphicalShellAction()
360 #if defined(Q_OS_WIN)
361 return tr("Show in Explorer...");
362 #elif defined(Q_OS_MAC)
363 return tr("Show in Finder...");
365 return tr("Show Containing Folder...");
369 QString FolderNavigationWidget::msgTerminalAction()
372 return tr("Open Command Prompt Here...");
374 return tr("Open Terminal Here...");
378 // Show error with option to open settings.
379 static inline void showGraphicalShellError(QWidget *parent,
381 const QString &error)
383 const QString title = FolderNavigationWidget::tr("Launching a file browser failed");
384 const QString msg = FolderNavigationWidget::tr("Unable to start the file manager:\n\n%1\n\n").arg(app);
385 QMessageBox mbox(QMessageBox::Warning, title, msg, QMessageBox::Close, parent);
386 if (!error.isEmpty())
387 mbox.setDetailedText(FolderNavigationWidget::tr("'%1' returned the following error:\n\n%2").arg(app, error));
388 QAbstractButton *settingsButton = mbox.addButton(FolderNavigationWidget::tr("Settings..."), QMessageBox::ActionRole);
390 if (mbox.clickedButton() == settingsButton)
391 Core::ICore::instance()->showOptionsDialog(QLatin1String(Core::Constants::SETTINGS_CATEGORY_CORE),
392 QLatin1String(Core::Constants::SETTINGS_ID_ENVIRONMENT));
395 void FolderNavigationWidget::showInGraphicalShell(QWidget *parent, const QString &pathIn)
397 // Mac, Windows support folder or file.
398 #if defined(Q_OS_WIN)
399 const QString explorer = Utils::Environment::systemEnvironment().searchInPath(QLatin1String("explorer.exe"));
400 if (explorer.isEmpty()) {
401 QMessageBox::warning(parent,
402 tr("Launching Windows Explorer Failed"),
403 tr("Could not find explorer.exe in path to launch Windows Explorer."));
407 if (!QFileInfo(pathIn).isDir())
408 param = QLatin1String("/select,");
409 param += QDir::toNativeSeparators(pathIn);
410 QProcess::startDetached(explorer, QStringList(param));
411 #elif defined(Q_OS_MAC)
413 QStringList scriptArgs;
414 scriptArgs << QLatin1String("-e")
415 << QString::fromLatin1("tell application \"Finder\" to reveal POSIX file \"%1\"")
417 QProcess::execute(QLatin1String("/usr/bin/osascript"), scriptArgs);
419 scriptArgs << QLatin1String("-e")
420 << QLatin1String("tell application \"Finder\" to activate");
421 QProcess::execute("/usr/bin/osascript", scriptArgs);
423 // we cannot select a file here, because no file browser really supports it...
424 const QFileInfo fileInfo(pathIn);
425 const QString folder = fileInfo.absoluteFilePath();
426 const QString app = Utils::UnixUtils::fileBrowser(Core::ICore::instance()->settings());
427 QProcess browserProc;
428 const QString browserArgs = Utils::UnixUtils::substituteFileBrowserParameters(app, folder);
430 qDebug() << browserArgs;
431 bool success = browserProc.startDetached(browserArgs);
432 const QString error = QString::fromLocal8Bit(browserProc.readAllStandardError());
433 success = success && error.isEmpty();
435 showGraphicalShellError(parent, app, error);
439 void FolderNavigationWidget::openTerminal(const QString &path)
441 // Get terminal application
443 const QString terminalEmulator = QString::fromLocal8Bit(qgetenv("COMSPEC"));
444 const QStringList args; // none
446 QStringList args = Utils::QtcProcess::splitArgs(
447 Utils::ConsoleProcess::terminalEmulator(Core::ICore::instance()->settings()));
448 const QString terminalEmulator = args.takeFirst();
449 const QString shell = QString::fromLocal8Bit(qgetenv("SHELL"));
452 // Launch terminal with working directory set.
453 const QFileInfo fileInfo(path);
454 const QString pwd = QDir::toNativeSeparators(fileInfo.isDir() ?
455 fileInfo.absoluteFilePath() :
456 fileInfo.absolutePath());
457 QProcess::startDetached(terminalEmulator, args, pwd);
460 // --------------------FolderNavigationWidgetFactory
461 FolderNavigationWidgetFactory::FolderNavigationWidgetFactory()
465 FolderNavigationWidgetFactory::~FolderNavigationWidgetFactory()
469 QString FolderNavigationWidgetFactory::displayName() const
471 return tr("File System");
474 int FolderNavigationWidgetFactory::priority() const
479 QString FolderNavigationWidgetFactory::id() const
481 return QLatin1String("File System");
484 QKeySequence FolderNavigationWidgetFactory::activationSequence() const
486 return QKeySequence(Qt::ALT + Qt::Key_Y);
489 Core::NavigationView FolderNavigationWidgetFactory::createWidget()
491 Core::NavigationView n;
492 FolderNavigationWidget *ptw = new FolderNavigationWidget;
494 QToolButton *toggleSync = new QToolButton;
495 toggleSync->setIcon(QIcon(QLatin1String(Core::Constants::ICON_LINK)));
496 toggleSync->setCheckable(true);
497 toggleSync->setChecked(ptw->autoSynchronization());
498 toggleSync->setToolTip(tr("Synchronize with Editor"));
499 connect(toggleSync, SIGNAL(clicked(bool)), ptw, SLOT(toggleAutoSynchronization()));
500 n.dockToolBarWidgets << toggleSync;
504 } // namespace Internal
505 } // namespace ProjectExplorer
507 #include "foldernavigationwidget.moc"