summaryrefslogtreecommitdiff
path: root/src/plugins/coreplugin/documentmanager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/coreplugin/documentmanager.cpp')
-rw-r--r--src/plugins/coreplugin/documentmanager.cpp1389
1 files changed, 1389 insertions, 0 deletions
diff --git a/src/plugins/coreplugin/documentmanager.cpp b/src/plugins/coreplugin/documentmanager.cpp
new file mode 100644
index 0000000000..9ab5362645
--- /dev/null
+++ b/src/plugins/coreplugin/documentmanager.cpp
@@ -0,0 +1,1389 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** 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.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** 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.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**************************************************************************/
+
+#include "documentmanager.h"
+
+#include "editormanager.h"
+#include "icore.h"
+#include "ieditor.h"
+#include "ieditorfactory.h"
+#include "iexternaleditor.h"
+#include "idocument.h"
+#include "iversioncontrol.h"
+#include "mimedatabase.h"
+#include "saveitemsdialog.h"
+#include "vcsmanager.h"
+#include "coreconstants.h"
+
+#include <utils/qtcassert.h>
+#include <utils/pathchooser.h>
+#include <utils/reloadpromptutils.h>
+
+#include <QDateTime>
+#include <QDir>
+#include <QFile>
+#include <QFileInfo>
+#include <QFileSystemWatcher>
+#include <QPair>
+#include <QSettings>
+#include <QTimer>
+#include <QAction>
+#include <QFileDialog>
+#include <QMainWindow>
+#include <QMenu>
+#include <QMessageBox>
+#include <QPushButton>
+
+/*!
+ \class Core::DocumentManager
+ \mainclass
+ \inheaderfile documentmanager.h
+ \brief Manages a set of IDocument objects.
+
+ The DocumentManager service monitors a set of IDocument's. Plugins should register
+ files they work with at the service. The files the IDocument's point to will be
+ monitored at filesystem level. If a file changes, the status of the IDocument's
+ will be adjusted accordingly. Furthermore, on application exit the user will
+ be asked to save all modified files.
+
+ Different IDocument objects in the set can point to the same file in the
+ filesystem. The monitoring for a IDocument can be blocked by blockFileChange(), and
+ enabled again by unblockFileChange().
+
+ The functions expectFileChange() and unexpectFileChange() mark a file change
+ as expected. On expected file changes all IDocument objects are notified to reload
+ themselves.
+
+ The DocumentManager service also provides two convenience methods for saving
+ files: saveModifiedFiles() and saveModifiedFilesSilently(). Both take a list
+ of FileInterfaces as an argument, and return the list of files which were
+ _not_ saved.
+
+ The service also manages the list of recent files to be shown to the user
+ (see addToRecentFiles() and recentFiles()).
+ */
+
+static const char settingsGroupC[] = "RecentFiles";
+static const char filesKeyC[] = "Files";
+static const char editorsKeyC[] = "EditorIds";
+
+static const char directoryGroupC[] = "Directories";
+static const char projectDirectoryKeyC[] = "Projects";
+static const char useProjectDirectoryKeyC[] = "UseProjectsDirectory";
+
+
+namespace Core {
+
+static void readSettings();
+
+static QList<IDocument *> saveModifiedFilesHelper(const QList<IDocument *> &documents,
+ bool *cancelled, bool silently,
+ const QString &message,
+ const QString &alwaysSaveMessage = QString(),
+ bool *alwaysSave = 0);
+
+namespace Internal {
+
+struct OpenWithEntry
+{
+ OpenWithEntry() : editorFactory(0), externalEditor(0) {}
+ IEditorFactory *editorFactory;
+ IExternalEditor *externalEditor;
+ QString fileName;
+};
+
+struct FileStateItem
+{
+ QDateTime modified;
+ QFile::Permissions permissions;
+};
+
+struct FileState
+{
+ QMap<IDocument *, FileStateItem> lastUpdatedState;
+ FileStateItem expected;
+};
+
+
+struct DocumentManagerPrivate
+{
+ explicit DocumentManagerPrivate(QMainWindow *mw);
+ QFileSystemWatcher *fileWatcher();
+ QFileSystemWatcher *linkWatcher();
+
+ QMap<QString, FileState> m_states;
+ QSet<QString> m_changedFiles;
+ QList<IDocument *> m_documentsWithoutWatch;
+ QMap<IDocument *, QStringList> m_documentsWithWatch;
+ QSet<QString> m_expectedFileNames;
+
+ QList<DocumentManager::RecentFile> m_recentFiles;
+ static const int m_maxRecentFiles = 7;
+
+ QString m_currentFile;
+
+ QMainWindow *m_mainWindow;
+ QFileSystemWatcher *m_fileWatcher; // Delayed creation.
+ QFileSystemWatcher *m_linkWatcher; // Delayed creation (only UNIX/if a link is seen).
+ bool m_blockActivated;
+ QString m_lastVisitedDirectory;
+ QString m_projectsDirectory;
+ bool m_useProjectsDirectory;
+ // When we are callling into a IDocument
+ // we don't want to receive a changed()
+ // signal
+ // That makes the code easier
+ IDocument *m_blockedIDocument;
+};
+
+static DocumentManager *m_instance;
+static Internal::DocumentManagerPrivate *d;
+
+QFileSystemWatcher *DocumentManagerPrivate::fileWatcher()
+{
+ if (!m_fileWatcher) {
+ m_fileWatcher= new QFileSystemWatcher(m_instance);
+ QObject::connect(m_fileWatcher, SIGNAL(fileChanged(QString)),
+ m_instance, SLOT(changedFile(QString)));
+ }
+ return m_fileWatcher;
+}
+
+QFileSystemWatcher *DocumentManagerPrivate::linkWatcher()
+{
+#ifdef Q_OS_UNIX
+ if (!m_linkWatcher) {
+ m_linkWatcher = new QFileSystemWatcher(m_instance);
+ m_linkWatcher->setObjectName(QLatin1String("_qt_autotest_force_engine_poller"));
+ QObject::connect(m_linkWatcher, SIGNAL(fileChanged(QString)),
+ m_instance, SLOT(changedFile(QString)));
+ }
+ return m_linkWatcher;
+#else
+ return fileWatcher();
+#endif
+}
+
+DocumentManagerPrivate::DocumentManagerPrivate(QMainWindow *mw) :
+ m_mainWindow(mw),
+ m_fileWatcher(0),
+ m_linkWatcher(0),
+ m_blockActivated(false),
+ m_lastVisitedDirectory(QDir::currentPath()),
+#ifdef Q_OS_MAC // Creator is in bizarre places when launched via finder.
+ m_useProjectsDirectory(true),
+#else
+ m_useProjectsDirectory(false),
+#endif
+ m_blockedIDocument(0)
+{
+}
+
+} // namespace Internal
+} // namespace Core
+
+Q_DECLARE_METATYPE(Core::Internal::OpenWithEntry)
+
+namespace Core {
+
+using namespace Internal;
+
+DocumentManager::DocumentManager(QMainWindow *mw)
+ : QObject(mw)
+{
+ d = new DocumentManagerPrivate(mw);
+ m_instance = this;
+ connect(d->m_mainWindow, SIGNAL(windowActivated()),
+ this, SLOT(mainWindowActivated()));
+ connect(ICore::instance(), SIGNAL(contextChanged(Core::IContext*,Core::Context)),
+ this, SLOT(syncWithEditor(Core::IContext*)));
+
+ readSettings();
+}
+
+DocumentManager::~DocumentManager()
+{
+ delete d;
+}
+
+DocumentManager *DocumentManager::instance()
+{
+ return m_instance;
+}
+
+/* only called from addFileInfo(IDocument *) */
+static void addFileInfo(const QString &fileName, IDocument *document, bool isLink)
+{
+ FileStateItem state;
+ if (!fileName.isEmpty()) {
+ const QFileInfo fi(fileName);
+ state.modified = fi.lastModified();
+ state.permissions = fi.permissions();
+ // Add watcher if we don't have that already
+ if (!d->m_states.contains(fileName)) {
+ d->m_states.insert(fileName, FileState());
+
+ }
+ QFileSystemWatcher *watcher = 0;
+ if (isLink)
+ watcher = d->linkWatcher();
+ else
+ watcher = d->fileWatcher();
+ if (!watcher->files().contains(fileName))
+ watcher->addPath(fileName);
+
+ d->m_states[fileName].lastUpdatedState.insert(document, state);
+ }
+ d->m_documentsWithWatch[document].append(fileName); // inserts a new QStringList if not already there
+}
+
+/* Adds the IDocument's file and possibly it's final link target to both m_states
+ (if it's file name is not empty), and the m_filesWithWatch list,
+ and adds a file watcher for each if not already done.
+ (The added file names are guaranteed to be absolute and cleaned.) */
+static void addFileInfo(IDocument *document)
+{
+ const QString fixedName = DocumentManager::fixFileName(document->fileName(), DocumentManager::KeepLinks);
+ const QString fixedResolvedName = DocumentManager::fixFileName(document->fileName(), DocumentManager::ResolveLinks);
+ addFileInfo(fixedResolvedName, document, false);
+ if (fixedName != fixedResolvedName)
+ addFileInfo(fixedName, document, true);
+}
+
+/*!
+ \fn bool DocumentManager::addFiles(const QList<IDocument *> &documents, bool addWatcher)
+
+ Adds a list of IDocument's to the collection. If \a addWatcher is true (the default),
+ the files are added to a file system watcher that notifies the file manager
+ about file changes.
+*/
+void DocumentManager::addDocuments(const QList<IDocument *> &documents, bool addWatcher)
+{
+ if (!addWatcher) {
+ // We keep those in a separate list
+
+ foreach (IDocument *document, documents) {
+ if (document && !d->m_documentsWithoutWatch.contains(document)) {
+ connect(document, SIGNAL(destroyed(QObject *)), m_instance, SLOT(documentDestroyed(QObject *)));
+ d->m_documentsWithoutWatch.append(document);
+ }
+ }
+ return;
+ }
+
+ foreach (IDocument *document, documents) {
+ if (document && !d->m_documentsWithWatch.contains(document)) {
+ connect(document, SIGNAL(changed()), m_instance, SLOT(checkForNewFileName()));
+ connect(document, SIGNAL(destroyed(QObject *)), m_instance, SLOT(documentDestroyed(QObject *)));
+ addFileInfo(document);
+ }
+ }
+}
+
+
+/* Removes all occurrences of the IDocument from m_filesWithWatch and m_states.
+ If that results in a file no longer being referenced by any IDocument, this
+ also removes the file watcher.
+*/
+static void removeFileInfo(IDocument *document)
+{
+ if (!d->m_documentsWithWatch.contains(document))
+ return;
+ foreach (const QString &fileName, d->m_documentsWithWatch.value(document)) {
+ if (!d->m_states.contains(fileName))
+ continue;
+ d->m_states[fileName].lastUpdatedState.remove(document);
+ if (d->m_states.value(fileName).lastUpdatedState.isEmpty()) {
+ if (d->m_fileWatcher && d->m_fileWatcher->files().contains(fileName))
+ d->m_fileWatcher->removePath(fileName);
+ if (d->m_linkWatcher && d->m_linkWatcher->files().contains(fileName))
+ d->m_linkWatcher->removePath(fileName);
+ d->m_states.remove(fileName);
+ }
+ }
+ d->m_documentsWithWatch.remove(document);
+}
+
+/// Dumps the state of the file manager's map
+/// For debugging purposes
+/*
+static void dump()
+{
+ qDebug() << "======== dumping state map";
+ QMap<QString, FileState>::const_iterator it, end;
+ it = d->m_states.constBegin();
+ end = d->m_states.constEnd();
+ for (; it != end; ++it) {
+ qDebug() << it.key();
+ qDebug() << " expected:" << it.value().expected.modified;
+
+ QMap<IDocument *, FileStateItem>::const_iterator jt, jend;
+ jt = it.value().lastUpdatedState.constBegin();
+ jend = it.value().lastUpdatedState.constEnd();
+ for (; jt != jend; ++jt) {
+ qDebug() << " " << jt.key()->fileName() << jt.value().modified;
+ }
+ }
+ qDebug() << "------- dumping files with watch list";
+ foreach (IDocument *key, d->m_filesWithWatch.keys()) {
+ qDebug() << key->fileName() << d->m_filesWithWatch.value(key);
+ }
+ qDebug() << "------- dumping watch list";
+ if (d->m_fileWatcher)
+ qDebug() << d->m_fileWatcher->files();
+ qDebug() << "------- dumping link watch list";
+ if (d->m_linkWatcher)
+ qDebug() << d->m_linkWatcher->files();
+}
+*/
+
+/*!
+ \fn void DocumentManager::renamedFile(const QString &from, const QString &to)
+ \brief Tells the file manager that a file has been renamed on disk from within Qt Creator.
+
+ Needs to be called right after the actual renaming on disk (i.e. before the file system
+ watcher can report the event during the next event loop run). \a from needs to be an absolute file path.
+ This will notify all IDocument objects pointing to that file of the rename
+ by calling IDocument::rename, and update the cached time and permission
+ information to avoid annoying the user with "file has been removed"
+ popups.
+*/
+void DocumentManager::renamedFile(const QString &from, const QString &to)
+{
+ const QString &fixedFrom = fixFileName(from, KeepLinks);
+
+ // gather the list of IDocuments
+ QList<IDocument *> documentsToRename;
+ QMapIterator<IDocument *, QStringList> it(d->m_documentsWithWatch);
+ while (it.hasNext()) {
+ it.next();
+ if (it.value().contains(fixedFrom))
+ documentsToRename.append(it.key());
+ }
+
+ // rename the IDocuments
+ foreach (IDocument *document, documentsToRename) {
+ d->m_blockedIDocument = document;
+ removeFileInfo(document);
+ document->rename(to);
+ addFileInfo(document);
+ d->m_blockedIDocument = 0;
+ }
+}
+/*!
+ \fn bool DocumentManager::addFile(IDocument *document, bool addWatcher)
+
+ Adds a IDocument object to the collection. If \a addWatcher is true (the default),
+ the file is added to a file system watcher that notifies the file manager
+ about file changes.
+*/
+void DocumentManager::addDocument(IDocument *document, bool addWatcher)
+{
+ addDocuments(QList<IDocument *>() << document, addWatcher);
+}
+
+void DocumentManager::documentDestroyed(QObject *obj)
+{
+ IDocument *document = static_cast<IDocument*>(obj);
+ // Check the special unwatched first:
+ if (!d->m_documentsWithoutWatch.removeOne(document))
+ removeFileInfo(document);
+}
+
+/*!
+ \fn bool DocumentManager::removeFile(IDocument *document)
+
+ Removes a IDocument object from the collection.
+
+ Returns true if the file specified by \a document had the addWatcher argument to addDocument() set.
+*/
+bool DocumentManager::removeDocument(IDocument *document)
+{
+ QTC_ASSERT(document, return false);
+
+ bool addWatcher = false;
+ // Special casing unwatched files
+ if (!d->m_documentsWithoutWatch.removeOne(document)) {
+ addWatcher = true;
+ removeFileInfo(document);
+ disconnect(document, SIGNAL(changed()), m_instance, SLOT(checkForNewFileName()));
+ }
+ disconnect(document, SIGNAL(destroyed(QObject *)), m_instance, SLOT(documentDestroyed(QObject *)));
+ return addWatcher;
+}
+
+/* Slot reacting on IDocument::changed. We need to check if the signal was sent
+ because the file was saved under different name. */
+void DocumentManager::checkForNewFileName()
+{
+ IDocument *document = qobject_cast<IDocument *>(sender());
+ // We modified the IDocument
+ // Trust the other code to also update the m_states map
+ if (document == d->m_blockedIDocument)
+ return;
+ QTC_ASSERT(document, return);
+ QTC_ASSERT(d->m_documentsWithWatch.contains(document), return);
+
+ // Maybe the name has changed or file has been deleted and created again ...
+ // This also updates the state to the on disk state
+ removeFileInfo(document);
+ addFileInfo(document);
+}
+
+/*!
+ \fn QString DocumentManager::fixFileName(const QString &fileName, FixMode fixmode)
+ Returns a guaranteed cleaned path in native form. If the file exists,
+ it will either be a cleaned absolute file path (fixmode == KeepLinks), or
+ a cleaned canonical file path (fixmode == ResolveLinks).
+*/
+QString DocumentManager::fixFileName(const QString &fileName, FixMode fixmode)
+{
+ QString s = fileName;
+ QFileInfo fi(s);
+ if (fi.exists()) {
+ if (fixmode == ResolveLinks)
+ s = fi.canonicalFilePath();
+ else
+ s = QDir::cleanPath(fi.absoluteFilePath());
+ } else {
+ s = QDir::cleanPath(s);
+ }
+ s = QDir::toNativeSeparators(s);
+#ifdef Q_OS_WIN
+ s = s.toLower();
+#endif
+ return s;
+}
+
+/*!
+ \fn QList<IDocument*> DocumentManager::modifiedFiles() const
+
+ Returns the list of IDocument's that have been modified.
+*/
+QList<IDocument *> DocumentManager::modifiedDocuments()
+{
+ QList<IDocument *> modified;
+
+ foreach (IDocument *document, d->m_documentsWithWatch.keys()) {
+ if (document->isModified())
+ modified << document;
+ }
+
+ foreach (IDocument *document, d->m_documentsWithoutWatch) {
+ if (document->isModified())
+ modified << document;
+ }
+
+ return modified;
+}
+
+/*!
+ \fn void DocumentManager::expectFileChange(const QString &fileName)
+
+ Any subsequent change to \a fileName is treated as a expected file change.
+
+ \see DocumentManager::unexpectFileChange(const QString &fileName)
+*/
+void DocumentManager::expectFileChange(const QString &fileName)
+{
+ if (fileName.isEmpty())
+ return;
+ d->m_expectedFileNames.insert(fileName);
+}
+
+/* only called from unblock and unexpect file change methods */
+static void updateExpectedState(const QString &fileName)
+{
+ if (fileName.isEmpty())
+ return;
+ if (d->m_states.contains(fileName)) {
+ QFileInfo fi(fileName);
+ d->m_states[fileName].expected.modified = fi.lastModified();
+ d->m_states[fileName].expected.permissions = fi.permissions();
+ }
+}
+
+/*!
+ \fn void DocumentManager::unexpectFileChange(const QString &fileName)
+
+ Any change to \a fileName are unexpected again.
+
+ \see DocumentManager::expectFileChange(const QString &fileName)
+*/
+void DocumentManager::unexpectFileChange(const QString &fileName)
+{
+ // We are updating the expected time of the file
+ // And in changedFile we'll check if the modification time
+ // is the same as the saved one here
+ // If so then it's a expected change
+
+ if (fileName.isEmpty())
+ return;
+ d->m_expectedFileNames.remove(fileName);
+ const QString fixedName = fixFileName(fileName, KeepLinks);
+ updateExpectedState(fixedName);
+ const QString fixedResolvedName = fixFileName(fileName, ResolveLinks);
+ if (fixedName != fixedResolvedName)
+ updateExpectedState(fixedResolvedName);
+}
+
+/*!
+ \fn QList<IDocument*> DocumentManager::saveModifiedFilesSilently(const QList<IDocument*> &documents)
+
+ Tries to save the files listed in \a documents. The \a cancelled argument is set to true
+ if the user cancelled the dialog. Returns the files that could not be saved.
+*/
+QList<IDocument *> DocumentManager::saveModifiedDocumentsSilently(const QList<IDocument *> &documents, bool *cancelled)
+{
+ return saveModifiedFilesHelper(documents, cancelled, true, QString());
+}
+
+/*!
+ \fn QList<IDocument*> DocumentManager::saveModifiedFiles(const QList<IDocument *> &documents, bool *cancelled, const QString &message, const QString &alwaysSaveMessage, bool *alwaysSave)
+
+ Asks the user whether to save the files listed in \a documents .
+ Opens a dialog with the given \a message, and a additional
+ text that should be used to ask if the user wants to enabled automatic save
+ of modified files (in this context).
+ The \a cancelled argument is set to true if the user cancelled the dialog,
+ \a alwaysSave is set to match the selection of the user, if files should
+ always automatically be saved.
+ Returns the files that have not been saved.
+*/
+QList<IDocument *> DocumentManager::saveModifiedDocuments(const QList<IDocument *> &documents,
+ bool *cancelled, const QString &message,
+ const QString &alwaysSaveMessage,
+ bool *alwaysSave)
+{
+ return saveModifiedFilesHelper(documents, cancelled, false, message, alwaysSaveMessage, alwaysSave);
+}
+
+static QList<IDocument *> saveModifiedFilesHelper(const QList<IDocument *> &documents,
+ bool *cancelled,
+ bool silently,
+ const QString &message,
+ const QString &alwaysSaveMessage,
+ bool *alwaysSave)
+{
+ if (cancelled)
+ (*cancelled) = false;
+
+ QList<IDocument *> notSaved;
+ QMap<IDocument *, QString> modifiedDocumentsMap;
+ QList<IDocument *> modifiedDocuments;
+
+ foreach (IDocument *document, documents) {
+ if (document->isModified()) {
+ QString name = document->fileName();
+ if (name.isEmpty())
+ name = document->suggestedFileName();
+
+ // There can be several IDocuments pointing to the same file
+ // Prefer one that is not readonly
+ // (even though it *should* not happen that the IDocuments are inconsistent with readonly)
+ if (!modifiedDocumentsMap.key(name, 0) || !document->isFileReadOnly())
+ modifiedDocumentsMap.insert(document, name);
+ }
+ }
+ modifiedDocuments = modifiedDocumentsMap.keys();
+ if (!modifiedDocuments.isEmpty()) {
+ QList<IDocument *> documentsToSave;
+ if (silently) {
+ documentsToSave = modifiedDocuments;
+ } else {
+ SaveItemsDialog dia(d->m_mainWindow, modifiedDocuments);
+ if (!message.isEmpty())
+ dia.setMessage(message);
+ if (!alwaysSaveMessage.isNull())
+ dia.setAlwaysSaveMessage(alwaysSaveMessage);
+ if (dia.exec() != QDialog::Accepted) {
+ if (cancelled)
+ (*cancelled) = true;
+ if (alwaysSave)
+ *alwaysSave = dia.alwaysSaveChecked();
+ notSaved = modifiedDocuments;
+ return notSaved;
+ }
+ if (alwaysSave)
+ *alwaysSave = dia.alwaysSaveChecked();
+ documentsToSave = dia.itemsToSave();
+ }
+
+ foreach (IDocument *document, documentsToSave) {
+ if (!EditorManager::instance()->saveDocument(document)) {
+ if (cancelled)
+ *cancelled = true;
+ notSaved.append(document);
+ }
+ }
+ }
+ return notSaved;
+}
+
+bool DocumentManager::saveDocument(IDocument *document, const QString &fileName, bool *isReadOnly)
+{
+ bool ret = true;
+ QString effName = fileName.isEmpty() ? document->fileName() : fileName;
+ expectFileChange(effName); // This only matters to other IDocuments which refer to this file
+ bool addWatcher = removeDocument(document); // So that our own IDocument gets no notification at all
+
+ QString errorString;
+ if (!document->save(&errorString, fileName, false)) {
+ if (isReadOnly) {
+ QFile ofi(effName);
+ // Check whether the existing file is writable
+ if (!ofi.open(QIODevice::ReadWrite) && ofi.open(QIODevice::ReadOnly)) {
+ *isReadOnly = true;
+ goto out;
+ }
+ *isReadOnly = false;
+ }
+ QMessageBox::critical(d->m_mainWindow, tr("File Error"), errorString);
+ out:
+ ret = false;
+ }
+
+ addDocument(document, addWatcher);
+ unexpectFileChange(effName);
+ return ret;
+}
+
+QString DocumentManager::getSaveFileName(const QString &title, const QString &pathIn,
+ const QString &filter, QString *selectedFilter)
+{
+ const QString &path = pathIn.isEmpty() ? fileDialogInitialDirectory() : pathIn;
+ QString fileName;
+ bool repeat;
+ do {
+ repeat = false;
+ fileName = QFileDialog::getSaveFileName(
+ d->m_mainWindow, title, path, filter, selectedFilter, QFileDialog::DontConfirmOverwrite);
+ if (!fileName.isEmpty()) {
+ // If the selected filter is All Files (*) we leave the name exactly as the user
+ // specified. Otherwise the suffix must be one available in the selected filter. If
+ // the name already ends with such suffix nothing needs to be done. But if not, the
+ // first one from the filter is appended.
+ if (selectedFilter && *selectedFilter != QCoreApplication::translate(
+ "Core", Constants::ALL_FILES_FILTER)) {
+ // Mime database creates filter strings like this: Anything here (*.foo *.bar)
+ QRegExp regExp(QLatin1String(".*\\s+\\((.*)\\)$"));
+ const int index = regExp.lastIndexIn(*selectedFilter);
+ bool suffixOk = false;
+ if (index != -1) {
+ const QStringList &suffixes = regExp.cap(1).remove(QLatin1Char('*')).split(QLatin1Char(' '));
+ foreach (const QString &suffix, suffixes)
+ if (fileName.endsWith(suffix)) {
+ suffixOk = true;
+ break;
+ }
+ if (!suffixOk && !suffixes.isEmpty())
+ fileName.append(suffixes.at(0));
+ }
+ }
+ if (QFile::exists(fileName)) {
+ if (QMessageBox::warning(d->m_mainWindow, tr("Overwrite?"),
+ tr("An item named '%1' already exists at this location. "
+ "Do you want to overwrite it?").arg(fileName),
+ QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) {
+ repeat = true;
+ }
+ }
+ }
+ } while (repeat);
+ if (!fileName.isEmpty())
+ setFileDialogLastVisitedDirectory(QFileInfo(fileName).absolutePath());
+ return fileName;
+}
+
+QString DocumentManager::getSaveFileNameWithExtension(const QString &title, const QString &pathIn,
+ const QString &filter)
+{
+ QString selected = filter;
+ return getSaveFileName(title, pathIn, filter, &selected);
+}
+
+/*!
+ \fn QString DocumentManager::getSaveAsFileName(IDocument *document, const QString &filter, QString *selectedFilter)
+
+ Asks the user for a new file name (Save File As) for /arg document.
+*/
+QString DocumentManager::getSaveAsFileName(IDocument *document, const QString &filter, QString *selectedFilter)
+{
+ if (!document)
+ return QLatin1String("");
+ QString absoluteFilePath = document->fileName();
+ const QFileInfo fi(absoluteFilePath);
+ QString fileName = fi.fileName();
+ QString path = fi.absolutePath();
+ if (absoluteFilePath.isEmpty()) {
+ fileName = document->suggestedFileName();
+ const QString defaultPath = document->defaultPath();
+ if (!defaultPath.isEmpty())
+ path = defaultPath;
+ }
+
+ QString filterString;
+ if (filter.isEmpty()) {
+ if (const MimeType &mt = Core::ICore::mimeDatabase()->findByFile(fi))
+ filterString = mt.filterString();
+ selectedFilter = &filterString;
+ } else {
+ filterString = filter;
+ }
+
+ absoluteFilePath = getSaveFileName(tr("Save File As"),
+ path + QDir::separator() + fileName,
+ filterString,
+ selectedFilter);
+ return absoluteFilePath;
+}
+
+/*!
+ \fn QStringList DocumentManager::getOpenFileNames(const QString &filters,
+ const QString pathIn,
+ QString *selectedFilter)
+
+ Asks the user for a set of file names to be opened. The \a filters
+ and \a selectedFilter parameters is interpreted like in
+ QFileDialog::getOpenFileNames(), \a pathIn specifies a path to open the dialog
+ in, if that is not overridden by the users policy.
+*/
+
+QStringList DocumentManager::getOpenFileNames(const QString &filters,
+ const QString pathIn,
+ QString *selectedFilter)
+{
+ QString path = pathIn;
+ if (path.isEmpty()) {
+ if (!d->m_currentFile.isEmpty())
+ path = QFileInfo(d->m_currentFile).absoluteFilePath();
+ if (path.isEmpty() && useProjectsDirectory())
+ path = projectsDirectory();
+ }
+ const QStringList files = QFileDialog::getOpenFileNames(d->m_mainWindow,
+ tr("Open File"),
+ path, filters,
+ selectedFilter);
+ if (!files.isEmpty())
+ setFileDialogLastVisitedDirectory(QFileInfo(files.front()).absolutePath());
+ return files;
+}
+
+DocumentManager::ReadOnlyAction
+ DocumentManager::promptReadOnlyFile(const QString &fileName,
+ const IVersionControl *versionControl,
+ QWidget *parent,
+ bool displaySaveAsButton)
+{
+ // Version Control: If automatic open is desired, open right away.
+ bool promptVCS = false;
+ if (versionControl && versionControl->supportsOperation(IVersionControl::OpenOperation)) {
+ if (versionControl->settingsFlags() & IVersionControl::AutoOpen)
+ return RO_OpenVCS;
+ promptVCS = true;
+ }
+
+ // Create message box.
+ QMessageBox msgBox(QMessageBox::Question, tr("File Is Read Only"),
+ tr("The file <i>%1</i> is read only.").arg(QDir::toNativeSeparators(fileName)),
+ QMessageBox::Cancel, parent);
+
+ QPushButton *vcsButton = 0;
+ if (promptVCS)
+ vcsButton = msgBox.addButton(tr("Open with VCS (%1)").arg(versionControl->displayName()), QMessageBox::AcceptRole);
+
+ QPushButton *makeWritableButton = msgBox.addButton(tr("Make Writable"), QMessageBox::AcceptRole);
+
+ QPushButton *saveAsButton = 0;
+ if (displaySaveAsButton)
+ saveAsButton = msgBox.addButton(tr("Save As..."), QMessageBox::ActionRole);
+
+ msgBox.setDefaultButton(vcsButton ? vcsButton : makeWritableButton);
+ msgBox.exec();
+
+ QAbstractButton *clickedButton = msgBox.clickedButton();
+ if (clickedButton == vcsButton)
+ return RO_OpenVCS;
+ if (clickedButton == makeWritableButton)
+ return RO_MakeWriteable;
+ if (displaySaveAsButton && clickedButton == saveAsButton)
+ return RO_SaveAs;
+ return RO_Cancel;
+}
+
+void DocumentManager::changedFile(const QString &fileName)
+{
+ const bool wasempty = d->m_changedFiles.isEmpty();
+
+ if (d->m_states.contains(fileName))
+ d->m_changedFiles.insert(fileName);
+
+ if (wasempty && !d->m_changedFiles.isEmpty()) {
+ QTimer::singleShot(200, this, SLOT(checkForReload()));
+ }
+}
+
+void DocumentManager::mainWindowActivated()
+{
+ //we need to do this asynchronously because
+ //opening a dialog ("Reload?") in a windowactivated event
+ //freezes on Mac
+ QTimer::singleShot(0, this, SLOT(checkForReload()));
+}
+
+void DocumentManager::checkForReload()
+{
+ if (d->m_changedFiles.isEmpty())
+ return;
+ if (QApplication::activeWindow() != d->m_mainWindow)
+ return;
+
+ if (d->m_blockActivated)
+ return;
+
+ d->m_blockActivated = true;
+
+ IDocument::ReloadSetting defaultBehavior = EditorManager::instance()->reloadSetting();
+ Utils::ReloadPromptAnswer previousAnswer = Utils::ReloadCurrent;
+
+ QList<IEditor*> editorsToClose;
+ QMap<IDocument*, QString> documentsToSave;
+
+ // collect file information
+ QMap<QString, FileStateItem> currentStates;
+ QMap<QString, IDocument::ChangeType> changeTypes;
+ QSet<IDocument *> changedIDocuments;
+ foreach (const QString &fileName, d->m_changedFiles) {
+ IDocument::ChangeType type = IDocument::TypeContents;
+ FileStateItem state;
+ QFileInfo fi(fileName);
+ if (!fi.exists()) {
+ type = IDocument::TypeRemoved;
+ } else {
+ state.modified = fi.lastModified();
+ state.permissions = fi.permissions();
+ }
+ currentStates.insert(fileName, state);
+ changeTypes.insert(fileName, type);
+ foreach (IDocument *document, d->m_states.value(fileName).lastUpdatedState.keys())
+ changedIDocuments.insert(document);
+ }
+
+ // clean up. do this before we may enter the main loop, otherwise we would
+ // lose consecutive notifications.
+ d->m_changedFiles.clear();
+
+ // collect information about "expected" file names
+ // we can't do the "resolving" already in expectFileChange, because
+ // if the resolved names are different when unexpectFileChange is called
+ // we would end up with never-unexpected file names
+ QSet<QString> expectedFileNames;
+ foreach (const QString &fileName, d->m_expectedFileNames) {
+ const QString fixedName = fixFileName(fileName, KeepLinks);
+ expectedFileNames.insert(fixedName);
+ const QString fixedResolvedName = fixFileName(fileName, ResolveLinks);
+ if (fixedName != fixedResolvedName)
+ expectedFileNames.insert(fixedResolvedName);
+ }
+
+ // handle the IDocuments
+ QStringList errorStrings;
+ foreach (IDocument *document, changedIDocuments) {
+ IDocument::ChangeTrigger trigger = IDocument::TriggerInternal;
+ IDocument::ChangeType type = IDocument::TypePermissions;
+ bool changed = false;
+ // find out the type & behavior from the two possible files
+ // behavior is internal if all changes are expected (and none removed)
+ // type is "max" of both types (remove > contents > permissions)
+ foreach (const QString & fileName, d->m_documentsWithWatch.value(document)) {
+ // was the file reported?
+ if (!currentStates.contains(fileName))
+ continue;
+
+ FileStateItem currentState = currentStates.value(fileName);
+ FileStateItem expectedState = d->m_states.value(fileName).expected;
+ FileStateItem lastState = d->m_states.value(fileName).lastUpdatedState.value(document);
+
+ // did the file actually change?
+ if (lastState.modified == currentState.modified && lastState.permissions == currentState.permissions)
+ continue;
+ changed = true;
+
+ // was it only a permission change?
+ if (lastState.modified == currentState.modified)
+ continue;
+
+ // was the change unexpected?
+ if ((currentState.modified != expectedState.modified || currentState.permissions != expectedState.permissions)
+ && !expectedFileNames.contains(fileName)) {
+ trigger = IDocument::TriggerExternal;
+ }
+
+ // find out the type
+ IDocument::ChangeType fileChange = changeTypes.value(fileName);
+ if (fileChange == IDocument::TypeRemoved) {
+ type = IDocument::TypeRemoved;
+ } else if (fileChange == IDocument::TypeContents && type == IDocument::TypePermissions) {
+ type = IDocument::TypeContents;
+ }
+ }
+
+ if (!changed) // probably because the change was blocked with (un)blockFileChange
+ continue;
+
+ // handle it!
+ d->m_blockedIDocument = document;
+
+ bool success = true;
+ QString errorString;
+ // we've got some modification
+ // check if it's contents or permissions:
+ if (type == IDocument::TypePermissions) {
+ // Only permission change
+ success = document->reload(&errorString, IDocument::FlagReload, IDocument::TypePermissions);
+ // now we know it's a content change or file was removed
+ } else if (defaultBehavior == IDocument::ReloadUnmodified
+ && type == IDocument::TypeContents && !document->isModified()) {
+ // content change, but unmodified (and settings say to reload in this case)
+ success = document->reload(&errorString, IDocument::FlagReload, type);
+ // file was removed or it's a content change and the default behavior for
+ // unmodified files didn't kick in
+ } else if (defaultBehavior == IDocument::ReloadUnmodified
+ && type == IDocument::TypeRemoved && !document->isModified()) {
+ // file removed, but unmodified files should be reloaded
+ // so we close the file
+ editorsToClose << EditorManager::instance()->editorsForDocument(document);
+ } else if (defaultBehavior == IDocument::IgnoreAll) {
+ // content change or removed, but settings say ignore
+ success = document->reload(&errorString, IDocument::FlagIgnore, type);
+ // either the default behavior is to always ask,
+ // or the ReloadUnmodified default behavior didn't kick in,
+ // so do whatever the IDocument wants us to do
+ } else {
+ // check if IDocument wants us to ask
+ if (document->reloadBehavior(trigger, type) == IDocument::BehaviorSilent) {
+ // content change or removed, IDocument wants silent handling
+ success = document->reload(&errorString, IDocument::FlagReload, type);
+ // IDocument wants us to ask
+ } else if (type == IDocument::TypeContents) {
+ // content change, IDocument wants to ask user
+ if (previousAnswer == Utils::ReloadNone) {
+ // answer already given, ignore
+ success = document->reload(&errorString, IDocument::FlagIgnore, IDocument::TypeContents);
+ } else if (previousAnswer == Utils::ReloadAll) {
+ // answer already given, reload
+ success = document->reload(&errorString, IDocument::FlagReload, IDocument::TypeContents);
+ } else {
+ // Ask about content change
+ previousAnswer = Utils::reloadPrompt(document->fileName(), document->isModified(), QApplication::activeWindow());
+ switch (previousAnswer) {
+ case Utils::ReloadAll:
+ case Utils::ReloadCurrent:
+ success = document->reload(&errorString, IDocument::FlagReload, IDocument::TypeContents);
+ break;
+ case Utils::ReloadSkipCurrent:
+ case Utils::ReloadNone:
+ success = document->reload(&errorString, IDocument::FlagIgnore, IDocument::TypeContents);
+ break;
+ }
+ }
+ // IDocument wants us to ask, and it's the TypeRemoved case
+ } else {
+ // Ask about removed file
+ bool unhandled = true;
+ while (unhandled) {
+ switch (Utils::fileDeletedPrompt(document->fileName(), trigger == IDocument::TriggerExternal, QApplication::activeWindow())) {
+ case Utils::FileDeletedSave:
+ documentsToSave.insert(document, document->fileName());
+ unhandled = false;
+ break;
+ case Utils::FileDeletedSaveAs:
+ {
+ const QString &saveFileName = getSaveAsFileName(document);
+ if (!saveFileName.isEmpty()) {
+ documentsToSave.insert(document, saveFileName);
+ unhandled = false;
+ }
+ break;
+ }
+ case Utils::FileDeletedClose:
+ editorsToClose << EditorManager::instance()->editorsForDocument(document);
+ unhandled = false;
+ break;
+ }
+ }
+ }
+ }
+ if (!success) {
+ if (errorString.isEmpty())
+ errorStrings << tr("Cannot reload %1").arg(QDir::toNativeSeparators(document->fileName()));
+ else
+ errorStrings << errorString;
+ }
+
+ // update file info, also handling if e.g. link target has changed
+ removeFileInfo(document);
+ addFileInfo(document);
+ d->m_blockedIDocument = 0;
+ }
+ if (!errorStrings.isEmpty())
+ QMessageBox::critical(d->m_mainWindow, tr("File Error"),
+ errorStrings.join(QLatin1String("\n")));
+
+ // handle deleted files
+ EditorManager::instance()->closeEditors(editorsToClose, false);
+ QMapIterator<IDocument *, QString> it(documentsToSave);
+ while (it.hasNext()) {
+ it.next();
+ saveDocument(it.key(), it.value());
+ it.key()->checkPermissions();
+ }
+
+ d->m_blockActivated = false;
+
+// dump();
+}
+
+void DocumentManager::syncWithEditor(Core::IContext *context)
+{
+ if (!context)
+ return;
+
+ Core::IEditor *editor = Core::EditorManager::instance()->currentEditor();
+ if (editor && (editor->widget() == context->widget()) &&
+ !editor->isTemporary())
+ setCurrentFile(editor->document()->fileName());
+}
+
+/*!
+ \fn void DocumentManager::addToRecentFiles(const QString &fileName, const QString &editorId)
+
+ Adds the \a fileName to the list of recent files. Associates the file to
+ be reopened with an editor of the given \a editorId, if possible.
+ \a editorId defaults to the empty id, which means to let the system figure out
+ the best editor itself.
+*/
+void DocumentManager::addToRecentFiles(const QString &fileName, const Id &editorId)
+{
+ if (fileName.isEmpty())
+ return;
+ QString unifiedForm(fixFileName(fileName, KeepLinks));
+ QMutableListIterator<RecentFile > it(d->m_recentFiles);
+ while (it.hasNext()) {
+ RecentFile file = it.next();
+ QString recentUnifiedForm(fixFileName(file.first, DocumentManager::KeepLinks));
+ if (unifiedForm == recentUnifiedForm)
+ it.remove();
+ }
+ if (d->m_recentFiles.count() > d->m_maxRecentFiles)
+ d->m_recentFiles.removeLast();
+ d->m_recentFiles.prepend(RecentFile(fileName, editorId));
+}
+
+/*!
+ \fn void DocumentManager::clearRecentFiles()
+
+ Clears the list of recent files. Should only be called by
+ the core plugin when the user chooses to clear it.
+*/
+void DocumentManager::clearRecentFiles()
+{
+ d->m_recentFiles.clear();
+}
+
+/*!
+ \fn QStringList DocumentManager::recentFiles() const
+
+ Returns the list of recent files.
+*/
+QList<DocumentManager::RecentFile> DocumentManager::recentFiles()
+{
+ return d->m_recentFiles;
+}
+
+void DocumentManager::saveSettings()
+{
+ QStringList recentFiles;
+ QStringList recentEditorIds;
+ foreach (const RecentFile &file, d->m_recentFiles) {
+ recentFiles.append(file.first);
+ recentEditorIds.append(file.second.toString());
+ }
+
+ QSettings *s = Core::ICore::settings();
+ s->beginGroup(QLatin1String(settingsGroupC));
+ s->setValue(QLatin1String(filesKeyC), recentFiles);
+ s->setValue(QLatin1String(editorsKeyC), recentEditorIds);
+ s->endGroup();
+ s->beginGroup(QLatin1String(directoryGroupC));
+ s->setValue(QLatin1String(projectDirectoryKeyC), d->m_projectsDirectory);
+ s->setValue(QLatin1String(useProjectDirectoryKeyC), d->m_useProjectsDirectory);
+ s->endGroup();
+}
+
+void readSettings()
+{
+ QSettings *s = Core::ICore::settings();
+ d->m_recentFiles.clear();
+ s->beginGroup(QLatin1String(settingsGroupC));
+ QStringList recentFiles = s->value(QLatin1String(filesKeyC)).toStringList();
+ QStringList recentEditorIds = s->value(QLatin1String(editorsKeyC)).toStringList();
+ s->endGroup();
+ // clean non-existing files
+ QStringListIterator ids(recentEditorIds);
+ foreach (const QString &fileName, recentFiles) {
+ QString editorId;
+ if (ids.hasNext()) // guard against old or weird settings
+ editorId = ids.next();
+ if (QFileInfo(fileName).isFile())
+ d->m_recentFiles.append(DocumentManager::RecentFile(QDir::fromNativeSeparators(fileName), // from native to guard against old settings
+ Id(editorId)));
+ }
+
+ s->beginGroup(QLatin1String(directoryGroupC));
+ const QString settingsProjectDir = s->value(QLatin1String(projectDirectoryKeyC),
+ QString()).toString();
+ if (!settingsProjectDir.isEmpty() && QFileInfo(settingsProjectDir).isDir()) {
+ d->m_projectsDirectory = settingsProjectDir;
+ } else {
+ d->m_projectsDirectory = Utils::PathChooser::homePath();
+ }
+ d->m_useProjectsDirectory = s->value(QLatin1String(useProjectDirectoryKeyC),
+ d->m_useProjectsDirectory).toBool();
+ s->endGroup();
+}
+
+/*!
+
+ The current file is e.g. the file currently opened when an editor is active,
+ or the selected file in case a Project Explorer is active ...
+
+ \sa currentFile
+ */
+void DocumentManager::setCurrentFile(const QString &filePath)
+{
+ if (d->m_currentFile == filePath)
+ return;
+ d->m_currentFile = filePath;
+ emit m_instance->currentFileChanged(d->m_currentFile);
+}
+
+/*!
+ Returns the absolute path of the current file
+
+ The current file is e.g. the file currently opened when an editor is active,
+ or the selected file in case a Project Explorer is active ...
+
+ \sa setCurrentFile
+ */
+QString DocumentManager::currentFile()
+{
+ return d->m_currentFile;
+}
+
+/*!
+
+ Returns the initial directory for a new file dialog. If there is
+ a current file, use that, else use last visited directory.
+
+ \sa setFileDialogLastVisitedDirectory
+*/
+
+QString DocumentManager::fileDialogInitialDirectory()
+{
+ if (!d->m_currentFile.isEmpty())
+ return QFileInfo(d->m_currentFile).absolutePath();
+ return d->m_lastVisitedDirectory;
+}
+
+/*!
+
+ Returns the directory for projects. Defaults to HOME.
+
+ \sa setProjectsDirectory, setUseProjectsDirectory
+*/
+
+QString DocumentManager::projectsDirectory()
+{
+ return d->m_projectsDirectory;
+}
+
+/*!
+
+ Set the directory for projects.
+
+ \sa projectsDirectory, useProjectsDirectory
+*/
+
+void DocumentManager::setProjectsDirectory(const QString &dir)
+{
+ d->m_projectsDirectory = dir;
+}
+
+/*!
+
+ Returns whether the directory for projects is to be
+ used or the user wants the current directory.
+
+ \sa setProjectsDirectory, setUseProjectsDirectory
+*/
+
+bool DocumentManager::useProjectsDirectory()
+{
+ return d->m_useProjectsDirectory;
+}
+
+/*!
+
+ Sets whether the directory for projects is to be used.
+
+ \sa projectsDirectory, useProjectsDirectory
+*/
+
+void DocumentManager::setUseProjectsDirectory(bool useProjectsDirectory)
+{
+ d->m_useProjectsDirectory = useProjectsDirectory;
+}
+
+/*!
+
+ Returns last visited directory of a file dialog.
+
+ \sa setFileDialogLastVisitedDirectory, fileDialogInitialDirectory
+
+*/
+
+QString DocumentManager::fileDialogLastVisitedDirectory()
+{
+ return d->m_lastVisitedDirectory;
+}
+
+/*!
+
+ Set the last visited directory of a file dialog that will be remembered
+ for the next one.
+
+ \sa fileDialogLastVisitedDirectory, fileDialogInitialDirectory
+
+ */
+
+void DocumentManager::setFileDialogLastVisitedDirectory(const QString &directory)
+{
+ d->m_lastVisitedDirectory = directory;
+}
+
+void DocumentManager::notifyFilesChangedInternally(const QStringList &files)
+{
+ emit m_instance->filesChangedInternally(files);
+}
+
+void DocumentManager::populateOpenWithMenu(QMenu *menu, const QString &fileName)
+{
+ typedef QList<IEditorFactory*> EditorFactoryList;
+ typedef QList<IExternalEditor*> ExternalEditorList;
+
+ menu->clear();
+
+ bool anyMatches = false;
+
+ if (const MimeType mt = ICore::mimeDatabase()->findByFile(QFileInfo(fileName))) {
+ const EditorFactoryList factories = ICore::editorManager()->editorFactories(mt, false);
+ const ExternalEditorList externalEditors = ICore::editorManager()->externalEditors(mt, false);
+ anyMatches = !factories.empty() || !externalEditors.empty();
+ if (anyMatches) {
+ // Add all suitable editors
+ foreach (IEditorFactory *editorFactory, factories) {
+ // Add action to open with this very editor factory
+ QString const actionTitle = editorFactory->displayName();
+ QAction * const action = menu->addAction(actionTitle);
+ OpenWithEntry entry;
+ entry.editorFactory = editorFactory;
+ entry.fileName = fileName;
+ action->setData(qVariantFromValue(entry));
+ }
+ // Add all suitable external editors
+ foreach (IExternalEditor *externalEditor, externalEditors) {
+ QAction * const action = menu->addAction(externalEditor->displayName());
+ OpenWithEntry entry;
+ entry.externalEditor = externalEditor;
+ entry.fileName = fileName;
+ action->setData(qVariantFromValue(entry));
+ }
+ }
+ }
+ menu->setEnabled(anyMatches);
+}
+
+void DocumentManager::executeOpenWithMenuAction(QAction *action)
+{
+ QTC_ASSERT(action, return);
+ EditorManager *em = EditorManager::instance();
+ const QVariant data = action->data();
+ OpenWithEntry entry = qVariantValue<OpenWithEntry>(data);
+ if (entry.editorFactory) {
+ // close any open editors that have this file open, but have a different type.
+ QList<IEditor *> editorsOpenForFile = em->editorsForFileName(entry.fileName);
+ if (!editorsOpenForFile.isEmpty()) {
+ foreach (IEditor *openEditor, editorsOpenForFile) {
+ if (entry.editorFactory->id() == openEditor->id())
+ editorsOpenForFile.removeAll(openEditor);
+ }
+ if (!em->closeEditors(editorsOpenForFile)) // don't open if cancel was pressed
+ return;
+ }
+
+ em->openEditor(entry.fileName, entry.editorFactory->id(), EditorManager::ModeSwitch);
+ return;
+ }
+ if (entry.externalEditor)
+ em->openExternalEditor(entry.fileName, entry.externalEditor->id());
+}
+
+void DocumentManager::slotExecuteOpenWithMenuAction(QAction *action)
+{
+ executeOpenWithMenuAction(action);
+}
+
+// -------------- FileChangeBlocker
+
+FileChangeBlocker::FileChangeBlocker(const QString &fileName)
+ : m_fileName(fileName)
+{
+ DocumentManager::expectFileChange(fileName);
+}
+
+FileChangeBlocker::~FileChangeBlocker()
+{
+ DocumentManager::unexpectFileChange(m_fileName);
+}
+
+} // namespace Core