OSDN Git Service

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