OSDN Git Service

It's 2011 now.
[qt-creator-jp/qt-creator-jp.git] / src / plugins / projectexplorer / foldernavigationwidget.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 "foldernavigationwidget.h"
35 #include "projectexplorer.h"
36 #include "projectexplorerconstants.h"
37
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>
43
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>
50
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>
66
67 enum { debug = 0 };
68
69 namespace ProjectExplorer {
70 namespace Internal {
71
72 // Hide the '.' entry.
73 class DotRemovalFilter : public QSortFilterProxyModel
74 {
75     Q_OBJECT
76 public:
77     explicit DotRemovalFilter(QObject *parent = 0);
78 protected:
79     virtual bool filterAcceptsRow(int source_row, const QModelIndex &parent) const;
80 private:
81 #if defined(Q_OS_UNIX)
82     const QVariant m_root;
83     const QVariant m_dotdot;
84 #endif
85     const QVariant m_dot;
86 };
87
88 DotRemovalFilter::DotRemovalFilter(QObject *parent) :
89     QSortFilterProxyModel(parent),
90 #if defined(Q_OS_UNIX)
91     m_root(QString(QLatin1Char('/'))),
92     m_dotdot(QLatin1String("..")),
93 #endif
94     m_dot(QString(QLatin1Char('.')))
95 {
96 }
97
98 bool DotRemovalFilter::filterAcceptsRow(int source_row, const QModelIndex &parent) const
99 {
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)
103         return false;
104 #endif
105     return fileName != m_dot;
106 }
107
108 // FolderNavigationModel: Shows path as tooltip.
109 class FolderNavigationModel : public QFileSystemModel
110 {
111 public:
112     explicit FolderNavigationModel(QObject *parent = 0);
113     QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
114 };
115
116 FolderNavigationModel::FolderNavigationModel(QObject *parent) :
117     QFileSystemModel(parent)
118 {
119 }
120
121 QVariant FolderNavigationModel::data(const QModelIndex &index, int role) const
122 {
123     if (role == Qt::ToolTipRole)
124         return QDir::toNativeSeparators(QDir::cleanPath(filePath(index)));
125     else
126         return QFileSystemModel::data(index, role);
127 }
128
129 /*!
130   /class FolderNavigationWidget
131
132   Shows a file system folder
133   */
134 FolderNavigationWidget::FolderNavigationWidget(QWidget *parent)
135     : 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)),
140       m_autoSync(false)
141 {
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;
149 #endif
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);
157
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);
164     setLayout(layout);
165
166     // connections
167     connect(m_listView, SIGNAL(activated(const QModelIndex&)),
168             this, SLOT(slotOpenItem(const QModelIndex&)));
169
170     setAutoSynchronization(true);
171 }
172
173 void FolderNavigationWidget::toggleAutoSynchronization()
174 {
175     setAutoSynchronization(!m_autoSync);
176 }
177
178 bool FolderNavigationWidget::autoSynchronization() const
179 {
180     return m_autoSync;
181 }
182
183 void FolderNavigationWidget::setAutoSynchronization(bool sync)
184 {
185     if (sync == m_autoSync)
186         return;
187
188     m_autoSync = sync;
189
190     Core::FileManager *fileManager = Core::ICore::instance()->fileManager();
191     if (m_autoSync) {
192         connect(fileManager, SIGNAL(currentFileChanged(QString)),
193                 this, SLOT(setCurrentFile(QString)));
194         setCurrentFile(fileManager->currentFile());
195     } else {
196         disconnect(fileManager, SIGNAL(currentFileChanged(QString)),
197                 this, SLOT(setCurrentFile(QString)));
198     }
199 }
200
201 void FolderNavigationWidget::setCurrentFile(const QString &filePath)
202 {
203     // Try to find directory of current file
204     bool pathOpened = false;
205     if (!filePath.isEmpty())  {
206         const QFileInfo fi(filePath);
207         if (fi.exists())
208             pathOpened = setCurrentDirectory(fi.absolutePath());
209     }
210     if (!pathOpened)  // Default to home.
211         setCurrentDirectory(Utils::PathChooser::homePath());
212
213     // Select the current file.
214     if (pathOpened) {
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);
222         }
223     }
224 }
225
226 bool FolderNavigationWidget::setCurrentDirectory(const QString &directory)
227 {
228     const QString newDirectory = directory.isEmpty() ? QDir::rootPath() : directory;
229     if (debug)
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());
236         return false;
237     }
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();
243 }
244
245 QString FolderNavigationWidget::currentDirectory() const
246 {
247     return m_fileSystemModel->rootPath();
248 }
249
250 void FolderNavigationWidget::slotOpenItem(const QModelIndex &viewIndex)
251 {
252     if (viewIndex.isValid())
253         openItem(m_filterModel->mapToSource(viewIndex));
254 }
255
256 void FolderNavigationWidget::openItem(const QModelIndex &srcIndex)
257 {
258     const QString fileName = m_fileSystemModel->fileName(srcIndex);
259     if (fileName == QLatin1String("."))
260         return;
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);
265         return;
266     }
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));
271         return;
272     }
273     // Open file.
274     Core::EditorManager *editorManager = Core::EditorManager::instance();
275     editorManager->openEditor(m_fileSystemModel->filePath(srcIndex), QString(), Core::EditorManager::ModeSwitch);
276 }
277
278 void FolderNavigationWidget::setCurrentTitle(QString dirName, const QString &fullPath)
279 {
280     if (dirName.isEmpty())
281         dirName = fullPath;
282     m_title->setText(dirName);
283     m_title->setToolTip(fullPath);
284 }
285
286 QModelIndex FolderNavigationWidget::currentItem() const
287 {
288     const QModelIndex current = m_listView->currentIndex();
289     if (current.isValid())
290         return m_filterModel->mapToSource(current);
291     return QModelIndex();
292 }
293
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)
298 {
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);
305 }
306
307 void FolderNavigationWidget::contextMenuEvent(QContextMenuEvent *ev)
308 {
309     QMenu menu;
310     // Open current item
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);
320
321     // open with...
322     if (!m_fileSystemModel->isDir(current)) {
323         QMenu *openWith = menu.addMenu(tr("Open with"));
324         ProjectExplorerPlugin::populateOpenWithMenu(openWith,
325                                                     m_fileSystemModel->filePath(current));
326     }
327
328     // Open file dialog to choose a path starting from current
329     QAction *actionChooseFolder = menu.addAction(tr("Choose Folder..."));
330
331     QAction *action = menu.exec(ev->globalPos());
332     if (!action)
333         return;
334
335     ev->accept();
336     if (action == actionOpen) { // Handle open file.
337         openItem(current);
338         return;
339     }
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);
344         return;
345     }
346     if (action == actionTerminal) {
347         openTerminal(m_fileSystemModel->filePath(current));
348         return;
349     }
350     if (action == actionExplorer) {
351         showInGraphicalShell(this, m_fileSystemModel->filePath(current));
352         return;
353     }
354     ProjectExplorerPlugin::openEditorFromAction(action,
355                                                 m_fileSystemModel->filePath(current));
356 }
357
358 QString FolderNavigationWidget::msgGraphicalShellAction()
359 {
360 #if defined(Q_OS_WIN)
361     return tr("Show in Explorer...");
362 #elif defined(Q_OS_MAC)
363     return tr("Show in Finder...");
364 #else
365     return tr("Show Containing Folder...");
366 #endif
367 }
368
369 QString FolderNavigationWidget::msgTerminalAction()
370 {
371 #ifdef Q_OS_WIN
372     return tr("Open Command Prompt Here...");
373 #else
374     return tr("Open Terminal Here...");
375 #endif
376 }
377
378 // Show error with option to open settings.
379 static inline void showGraphicalShellError(QWidget *parent,
380                                            const QString &app,
381                                            const QString &error)
382 {
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);
389     mbox.exec();
390     if (mbox.clickedButton() == settingsButton)
391         Core::ICore::instance()->showOptionsDialog(QLatin1String(Core::Constants::SETTINGS_CATEGORY_CORE),
392                                                    QLatin1String(Core::Constants::SETTINGS_ID_ENVIRONMENT));
393 }
394
395 void FolderNavigationWidget::showInGraphicalShell(QWidget *parent, const QString &pathIn)
396 {
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."));
404         return;
405     }
406     QString param;
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)
412     Q_UNUSED(parent)
413     QStringList scriptArgs;
414     scriptArgs << QLatin1String("-e")
415                << QString::fromLatin1("tell application \"Finder\" to reveal POSIX file \"%1\"")
416                                      .arg(pathIn);
417     QProcess::execute(QLatin1String("/usr/bin/osascript"), scriptArgs);
418     scriptArgs.clear();
419     scriptArgs << QLatin1String("-e")
420                << QLatin1String("tell application \"Finder\" to activate");
421     QProcess::execute("/usr/bin/osascript", scriptArgs);
422 #else
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);
429     if (debug)
430         qDebug() <<  browserArgs;
431     bool success = browserProc.startDetached(browserArgs);
432     const QString error = QString::fromLocal8Bit(browserProc.readAllStandardError());
433     success = success && error.isEmpty();
434     if (!success)
435         showGraphicalShellError(parent, app, error);
436 #endif
437 }
438
439 void FolderNavigationWidget::openTerminal(const QString &path)
440 {
441     // Get terminal application
442 #ifdef Q_OS_WIN
443     const QString terminalEmulator = QString::fromLocal8Bit(qgetenv("COMSPEC"));
444     const QStringList args; // none
445 #else
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"));
450     args.append(shell);
451 #endif
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);
458 }
459
460 // --------------------FolderNavigationWidgetFactory
461 FolderNavigationWidgetFactory::FolderNavigationWidgetFactory()
462 {
463 }
464
465 FolderNavigationWidgetFactory::~FolderNavigationWidgetFactory()
466 {
467 }
468
469 QString FolderNavigationWidgetFactory::displayName() const
470 {
471     return tr("File System");
472 }
473
474 int FolderNavigationWidgetFactory::priority() const
475 {
476     return 400;
477 }
478
479 QString FolderNavigationWidgetFactory::id() const
480 {
481     return QLatin1String("File System");
482 }
483
484 QKeySequence FolderNavigationWidgetFactory::activationSequence() const
485 {
486     return QKeySequence(Qt::ALT + Qt::Key_Y);
487 }
488
489 Core::NavigationView FolderNavigationWidgetFactory::createWidget()
490 {
491     Core::NavigationView n;
492     FolderNavigationWidget *ptw = new FolderNavigationWidget;
493     n.widget = ptw;
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;
501     return n;
502 }
503
504 } // namespace Internal
505 } // namespace ProjectExplorer
506
507 #include "foldernavigationwidget.moc"