1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
10 ** GNU Lesser General Public License Usage
12 ** This file may be used under the terms of the GNU Lesser General Public
13 ** License version 2.1 as published by the Free Software Foundation and
14 ** appearing in the file LICENSE.LGPL included in the packaging of this file.
15 ** Please review the following information to ensure the GNU Lesser General
16 ** Public License version 2.1 requirements will be met:
17 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
19 ** In addition, as a special exception, Nokia gives you certain additional
20 ** rights. These rights are described in the Nokia Qt LGPL Exception
21 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
25 ** Alternatively, this file may be used in accordance with the terms and
26 ** conditions contained in a signed written agreement between you and Nokia.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
31 **************************************************************************/
33 #include "filemanager.h"
35 #include "editormanager.h"
39 #include "iversioncontrol.h"
40 #include "mimedatabase.h"
41 #include "saveitemsdialog.h"
42 #include "vcsmanager.h"
43 #include "coreconstants.h"
45 #include <utils/qtcassert.h>
46 #include <utils/pathchooser.h>
47 #include <utils/reloadpromptutils.h>
49 #include <QtCore/QSettings>
50 #include <QtCore/QFileInfo>
51 #include <QtCore/QFile>
52 #include <QtCore/QDir>
53 #include <QtCore/QTimer>
54 #include <QtCore/QFileSystemWatcher>
55 #include <QtCore/QDateTime>
56 #include <QtGui/QFileDialog>
57 #include <QtGui/QMessageBox>
58 #include <QtGui/QMainWindow>
59 #include <QtGui/QPushButton>
62 \class Core::FileManager
64 \inheaderfile filemanager.h
65 \brief Manages a set of IFile objects.
67 The FileManager service monitors a set of IFile's. Plugins should register
68 files they work with at the service. The files the IFile's point to will be
69 monitored at filesystem level. If a file changes, the status of the IFile's
70 will be adjusted accordingly. Furthermore, on application exit the user will
71 be asked to save all modified files.
73 Different IFile objects in the set can point to the same file in the
74 filesystem. The monitoring for a IFile can be blocked by blockFileChange(), and
75 enabled again by unblockFileChange().
77 The functions expectFileChange() and unexpectFileChange() mark a file change
78 as expected. On expected file changes all IFile objects are notified to reload
81 The FileManager service also provides two convenience methods for saving
82 files: saveModifiedFiles() and saveModifiedFilesSilently(). Both take a list
83 of FileInterfaces as an argument, and return the list of files which were
86 The service also manages the list of recent files to be shown to the user
87 (see addToRecentFiles() and recentFiles()).
90 static const char * const settingsGroupC = "RecentFiles";
91 static const char * const filesKeyC = "Files";
92 static const char * const editorsKeyC = "EditorIds";
94 static const char * const directoryGroupC = "Directories";
95 static const char * const projectDirectoryKeyC = "Projects";
96 static const char * const useProjectDirectoryKeyC = "UseProjectsDirectory";
104 QFile::Permissions permissions;
109 QMap<IFile *, FileStateItem> lastUpdatedState;
110 FileStateItem expected;
114 struct FileManagerPrivate {
115 explicit FileManagerPrivate(FileManager *q, QMainWindow *mw);
117 static FileManager *m_instance;
118 QMap<QString, FileState> m_states;
119 QStringList m_changedFiles;
120 QList<IFile *> m_filesWithoutWatch;
121 QMap<IFile *, QStringList> m_filesWithWatch;
122 QSet<QString> m_expectedFileNames;
124 QList<FileManager::RecentFile> m_recentFiles;
125 static const int m_maxRecentFiles = 7;
127 QString m_currentFile;
129 QMainWindow *m_mainWindow;
130 QFileSystemWatcher *m_fileWatcher;
131 QFileSystemWatcher *m_linkWatcher;
132 bool m_blockActivated;
133 QString m_lastVisitedDirectory;
134 QString m_projectsDirectory;
135 bool m_useProjectsDirectory;
136 // When we are callling into a IFile
137 // we don't want to receive a changed()
139 // That makes the code easier
140 IFile *m_blockedIFile;
143 FileManager *FileManagerPrivate::m_instance = 0;
145 FileManagerPrivate::FileManagerPrivate(FileManager *q, QMainWindow *mw) :
147 m_fileWatcher(new QFileSystemWatcher(q)),
148 m_blockActivated(false),
149 m_lastVisitedDirectory(QDir::currentPath()),
150 #ifdef Q_OS_MAC // Creator is in bizarre places when launched via finder.
151 m_useProjectsDirectory(true),
153 m_useProjectsDirectory(false),
158 q->connect(m_fileWatcher, SIGNAL(fileChanged(QString)),
159 q, SLOT(changedFile(QString)));
161 m_linkWatcher = new QFileSystemWatcher(q);
162 m_linkWatcher->setObjectName(QLatin1String("_qt_autotest_force_engine_poller"));
163 q->connect(m_linkWatcher, SIGNAL(fileChanged(QString)),
164 q, SLOT(changedFile(QString)));
166 m_linkWatcher = m_fileWatcher;
170 } // namespace Internal
172 FileManager::FileManager(QMainWindow *mw)
174 d(new Internal::FileManagerPrivate(this, mw))
176 Core::ICore *core = Core::ICore::instance();
177 connect(d->m_mainWindow, SIGNAL(windowActivated()),
178 this, SLOT(mainWindowActivated()));
179 connect(core, SIGNAL(contextChanged(Core::IContext*,Core::Context)),
180 this, SLOT(syncWithEditor(Core::IContext*)));
185 FileManager::~FileManager()
190 FileManager *FileManager::instance()
192 return Internal::FileManagerPrivate::m_instance;
196 \fn bool FileManager::addFiles(const QList<IFile *> &files, bool addWatcher)
198 Adds a list of IFile's to the collection. If \a addWatcher is true (the default),
199 the files are added to a file system watcher that notifies the file manager
202 void FileManager::addFiles(const QList<IFile *> &files, bool addWatcher)
205 // We keep those in a separate list
207 foreach (IFile *file, files) {
208 if (file && !d->m_filesWithoutWatch.contains(file)) {
209 connect(file, SIGNAL(destroyed(QObject *)), this, SLOT(fileDestroyed(QObject *)));
210 d->m_filesWithoutWatch.append(file);
216 foreach (IFile *file, files) {
217 if (file && !d->m_filesWithWatch.contains(file)) {
218 connect(file, SIGNAL(changed()), this, SLOT(checkForNewFileName()));
219 connect(file, SIGNAL(destroyed(QObject *)), this, SLOT(fileDestroyed(QObject *)));
225 /* Adds the IFile's file and possibly it's final link target to both m_states
226 (if it's file name is not empty), and the m_filesWithWatch list,
227 and adds a file watcher for each if not already done.
228 (The added file names are guaranteed to be absolute and cleaned.) */
229 void FileManager::addFileInfo(IFile *file)
231 const QString fixedName = fixFileName(file->fileName(), KeepLinks);
232 const QString fixedResolvedName = fixFileName(file->fileName(), ResolveLinks);
233 addFileInfo(fixedResolvedName, file, false);
234 if (fixedName != fixedResolvedName)
235 addFileInfo(fixedName, file, true);
238 /* only called from addFileInfo(IFile *) */
239 void FileManager::addFileInfo(const QString &fileName, IFile *file, bool isLink)
241 Internal::FileStateItem state;
242 if (!fileName.isEmpty()) {
243 const QFileInfo fi(fileName);
244 state.modified = fi.lastModified();
245 state.permissions = fi.permissions();
246 // Add watcher if we don't have that already
247 if (!d->m_states.contains(fileName)) {
248 d->m_states.insert(fileName, Internal::FileState());
251 d->m_linkWatcher->addPath(fileName);
253 d->m_fileWatcher->addPath(fileName);
255 d->m_states[fileName].lastUpdatedState.insert(file, state);
257 d->m_filesWithWatch[file].append(fileName); // inserts a new QStringList if not already there
260 /* Updates the time stamp and permission information of the files
261 registered for this IFile (in m_filesWithWatch; can be the IFile's file + final link target) */
262 void FileManager::updateFileInfo(IFile *file)
264 foreach (const QString &fileName, d->m_filesWithWatch.value(file)) {
265 // If the filename is empty there's nothing to do
266 if (fileName.isEmpty())
268 const QFileInfo fi(fileName);
269 Internal::FileStateItem item;
270 item.modified = fi.lastModified();
271 item.permissions = fi.permissions();
272 QTC_ASSERT(d->m_states.contains(fileName), continue);
273 QTC_ASSERT(d->m_states.value(fileName).lastUpdatedState.contains(file), continue);
274 d->m_states[fileName].lastUpdatedState.insert(file, item);
278 /// Dumps the state of the file manager's map
279 /// For debugging purposes
280 void FileManager::dump()
282 qDebug() << "======== dumping state map";
283 QMap<QString, Internal::FileState>::const_iterator it, end;
284 it = d->m_states.constBegin();
285 end = d->m_states.constEnd();
286 for (; it != end; ++it) {
287 qDebug() << it.key();
288 qDebug() << " expected:" << it.value().expected.modified;
290 QMap<IFile *, Internal::FileStateItem>::const_iterator jt, jend;
291 jt = it.value().lastUpdatedState.constBegin();
292 jend = it.value().lastUpdatedState.constEnd();
293 for (; jt != jend; ++jt) {
294 qDebug() << " " << jt.key()->fileName() << jt.value().modified;
297 qDebug() << "------- dumping files with watch list";
298 foreach (IFile *key, d->m_filesWithWatch.keys()) {
299 qDebug() << key->fileName() << d->m_filesWithWatch.value(key);
301 qDebug() << "------- dumping watch list";
302 qDebug() << d->m_fileWatcher->files();
303 qDebug() << "------- dumping link watch list";
304 qDebug() << d->m_linkWatcher->files();
308 \fn void FileManager::renamedFile(const QString &from, QString &to)
309 \brief Tells the file manager that a file has been renamed on disk from within Qt Creator.
311 Needs to be called right after the actual renaming on disk (i.e. before the file system
312 watcher can report the event during the next event loop run). \a from needs to be an absolute file path.
313 This will notify all IFile objects pointing to that file of the rename
314 by calling IFile::rename, and update the cached time and permission
315 information to avoid annoying the user with "file has been removed"
318 void FileManager::renamedFile(const QString &from, const QString &to)
320 const QString &fixedFrom = fixFileName(from, KeepLinks);
322 // gather the list of IFiles
323 QList<IFile *> filesToRename;
324 QMapIterator<IFile *, QStringList> it(d->m_filesWithWatch);
325 while (it.hasNext()) {
327 if (it.value().contains(fixedFrom))
328 filesToRename.append(it.key());
332 foreach (IFile *file, filesToRename) {
333 d->m_blockedIFile = file;
334 removeFileInfo(file);
337 d->m_blockedIFile = 0;
341 /* Removes all occurrences of the IFile from m_filesWithWatch and m_states.
342 If that results in a file no longer being referenced by any IFile, this
343 also removes the file watcher.
345 void FileManager::removeFileInfo(IFile *file)
347 if (!d->m_filesWithWatch.contains(file))
349 foreach (const QString &fileName, d->m_filesWithWatch.value(file)) {
350 if (!d->m_states.contains(fileName))
352 d->m_states[fileName].lastUpdatedState.remove(file);
353 if (d->m_states.value(fileName).lastUpdatedState.isEmpty()) {
354 if (d->m_fileWatcher->files().contains(fileName))
355 d->m_fileWatcher->removePath(fileName);
356 if (d->m_linkWatcher->files().contains(fileName))
357 d->m_linkWatcher->removePath(fileName);
358 d->m_states.remove(fileName);
361 d->m_filesWithWatch.remove(file);
365 \fn bool FileManager::addFile(IFile *files, bool addWatcher)
367 Adds a IFile object to the collection. If \a addWatcher is true (the default),
368 the file is added to a file system watcher that notifies the file manager
371 void FileManager::addFile(IFile *file, bool addWatcher)
373 addFiles(QList<IFile *>() << file, addWatcher);
376 void FileManager::fileDestroyed(QObject *obj)
378 IFile *file = static_cast<IFile*>(obj);
379 // Check the special unwatched first:
380 if (d->m_filesWithoutWatch.contains(file)) {
381 d->m_filesWithoutWatch.removeOne(file);
384 removeFileInfo(file);
388 \fn bool FileManager::removeFile(IFile *file)
390 Removes a IFile object from the collection.
392 Returns true if the file specified by \a file has been part of the file list.
394 void FileManager::removeFile(IFile *file)
396 QTC_ASSERT(file, return);
398 // Special casing unwatched files
399 if (d->m_filesWithoutWatch.contains(file)) {
400 disconnect(file, SIGNAL(destroyed(QObject *)), this, SLOT(fileDestroyed(QObject *)));
401 d->m_filesWithoutWatch.removeOne(file);
405 removeFileInfo(file);
406 disconnect(file, SIGNAL(changed()), this, SLOT(checkForNewFileName()));
407 disconnect(file, SIGNAL(destroyed(QObject *)), this, SLOT(fileDestroyed(QObject *)));
410 /* Slot reacting on IFile::changed. We need to check if the signal was sent
411 because the file was saved under different name. */
412 void FileManager::checkForNewFileName()
414 IFile *file = qobject_cast<IFile *>(sender());
415 // We modified the IFile
416 // Trust the other code to also update the m_states map
417 if (file == d->m_blockedIFile)
419 QTC_ASSERT(file, return);
420 QTC_ASSERT(d->m_filesWithWatch.contains(file), return);
422 // Maybe the name has changed or file has been deleted and created again ...
423 // This also updates the state to the on disk state
424 removeFileInfo(file);
429 \fn QString FileManager::fixFileName(const QString &fileName, FixMode fixmode)
430 Returns a guaranteed cleaned path in native form. If the file exists,
431 it will either be a cleaned absolute file path (fixmode == KeepLinks), or
432 a cleaned canonical file path (fixmode == ResolveLinks).
434 QString FileManager::fixFileName(const QString &fileName, FixMode fixmode)
436 QString s = fileName;
439 if (fixmode == ResolveLinks)
440 s = fi.canonicalFilePath();
442 s = QDir::cleanPath(fi.absoluteFilePath());
444 s = QDir::cleanPath(s);
446 s = QDir::toNativeSeparators(s);
454 \fn QList<IFile*> FileManager::modifiedFiles() const
456 Returns the list of IFile's that have been modified.
458 QList<IFile *> FileManager::modifiedFiles() const
460 QList<IFile *> modifiedFiles;
462 foreach (IFile *file, d->m_filesWithWatch.keys()) {
463 if (file->isModified())
464 modifiedFiles << file;
467 foreach(IFile *file, d->m_filesWithoutWatch) {
468 if (file->isModified())
469 modifiedFiles << file;
472 return modifiedFiles;
476 \fn void FileManager::blockFileChange(IFile *file)
478 Blocks the monitoring of the file the \a file argument points to.
480 void FileManager::blockFileChange(IFile *file)
487 \fn void FileManager::unblockFileChange(IFile *file)
489 Enables the monitoring of the file the \a file argument points to, and update the status of the corresponding IFile's.
491 void FileManager::unblockFileChange(IFile *file)
493 // We are updating the lastUpdated time to the current modification time
494 // in changedFile we'll compare the modification time with the last updated
495 // time, and if they are the same, then we don't deliver that notification
496 // to corresponding IFile
498 // Also we are updating the expected time of the file
499 // in changedFile we'll check if the modification time
500 // is the same as the saved one here
501 // If so then it's a expected change
503 updateFileInfo(file);
504 foreach (const QString &fileName, d->m_filesWithWatch.value(file))
505 updateExpectedState(fileName);
509 \fn void FileManager::expectFileChange(const QString &fileName)
511 Any subsequent change to \a fileName is treated as a expected file change.
513 \see FileManager::unexpectFileChange(const QString &fileName)
515 void FileManager::expectFileChange(const QString &fileName)
517 if (fileName.isEmpty())
519 d->m_expectedFileNames.insert(fileName);
523 \fn void FileManager::unexpectFileChange(const QString &fileName)
525 Any change to \a fileName are unexpected again.
527 \see FileManager::expectFileChange(const QString &fileName)
529 void FileManager::unexpectFileChange(const QString &fileName)
531 // We are updating the expected time of the file
532 // And in changedFile we'll check if the modification time
533 // is the same as the saved one here
534 // If so then it's a expected change
536 if (fileName.isEmpty())
538 d->m_expectedFileNames.remove(fileName);
539 const QString fixedName = fixFileName(fileName, KeepLinks);
540 updateExpectedState(fixedName);
541 const QString fixedResolvedName = fixFileName(fileName, ResolveLinks);
542 if (fixedName != fixedResolvedName)
543 updateExpectedState(fixedResolvedName);
546 /* only called from unblock and unexpect file change methods */
547 void FileManager::updateExpectedState(const QString &fileName)
549 if (fileName.isEmpty())
551 if (d->m_states.contains(fileName)) {
552 QFileInfo fi(fileName);
553 d->m_states[fileName].expected.modified = fi.lastModified();
554 d->m_states[fileName].expected.permissions = fi.permissions();
559 \fn QList<IFile*> FileManager::saveModifiedFilesSilently(const QList<IFile*> &files)
561 Tries to save the files listed in \a files . Returns the files that could not be saved.
563 QList<IFile *> FileManager::saveModifiedFilesSilently(const QList<IFile *> &files)
565 return saveModifiedFiles(files, 0, true, QString());
569 \fn QList<IFile*> FileManager::saveModifiedFiles(const QList<IFile *> &files, bool *cancelled, const QString &message, const QString &alwaysSaveMessage, bool *alwaysSave)
571 Asks the user whether to save the files listed in \a files .
572 Opens a dialog with the given \a message, and a additional
573 text that should be used to ask if the user wants to enabled automatic save
574 of modified files (in this context).
575 The \a cancelled argument is set to true if the user cancelled the dialog,
576 \a alwaysSave is set to match the selection of the user, if files should
577 always automatically be saved.
578 Returns the files that have not been saved.
580 QList<IFile *> FileManager::saveModifiedFiles(const QList<IFile *> &files,
581 bool *cancelled, const QString &message,
582 const QString &alwaysSaveMessage,
585 return saveModifiedFiles(files, cancelled, false, message, alwaysSaveMessage, alwaysSave);
588 QList<IFile *> FileManager::saveModifiedFiles(const QList<IFile *> &files,
591 const QString &message,
592 const QString &alwaysSaveMessage,
596 (*cancelled) = false;
598 QList<IFile *> notSaved;
599 QMap<IFile *, QString> modifiedFilesMap;
600 QList<IFile *> modifiedFiles;
602 foreach (IFile *file, files) {
603 if (file->isModified()) {
604 QString name = file->fileName();
606 name = file->suggestedFileName();
608 // There can be several FileInterfaces pointing to the same file
609 // Select one that is not readonly.
610 if (!(modifiedFilesMap.key(name, 0)
611 && file->isReadOnly()))
612 modifiedFilesMap.insert(file, name);
615 modifiedFiles = modifiedFilesMap.keys();
616 if (!modifiedFiles.isEmpty()) {
617 QList<IFile *> filesToSave;
619 filesToSave = modifiedFiles;
621 Internal::SaveItemsDialog dia(d->m_mainWindow, modifiedFiles);
622 if (!message.isEmpty())
623 dia.setMessage(message);
624 if (!alwaysSaveMessage.isNull())
625 dia.setAlwaysSaveMessage(alwaysSaveMessage);
626 if (dia.exec() != QDialog::Accepted) {
630 *alwaysSave = dia.alwaysSaveChecked();
631 notSaved = modifiedFiles;
635 *alwaysSave = dia.alwaysSaveChecked();
636 filesToSave = dia.itemsToSave();
639 foreach (IFile *file, filesToSave) {
640 if (!EditorManager::instance()->saveFile(file)) {
643 notSaved.append(file);
650 QString FileManager::getSaveFileName(const QString &title, const QString &pathIn,
651 const QString &filter, QString *selectedFilter)
653 const QString &path = pathIn.isEmpty() ? fileDialogInitialDirectory() : pathIn;
658 fileName = QFileDialog::getSaveFileName(
659 d->m_mainWindow, title, path, filter, selectedFilter, QFileDialog::DontConfirmOverwrite);
660 if (!fileName.isEmpty()) {
661 // If the selected filter is All Files (*) we leave the name exactly as the user
662 // specified. Otherwise the suffix must be one available in the selected filter. If
663 // the name already ends with such suffix nothing needs to be done. But if not, the
664 // first one from the filter is appended.
665 if (selectedFilter && *selectedFilter != QCoreApplication::translate(
666 "Core", Constants::ALL_FILES_FILTER)) {
667 // Mime database creates filter strings like this: Anything here (*.foo *.bar)
668 QRegExp regExp(".*\\s+\\((.*)\\)$");
669 const int index = regExp.lastIndexIn(*selectedFilter);
670 bool suffixOk = false;
672 const QStringList &suffixes = regExp.cap(1).remove('*').split(' ');
673 foreach (const QString &suffix, suffixes)
674 if (fileName.endsWith(suffix)) {
678 if (!suffixOk && !suffixes.isEmpty())
679 fileName.append(suffixes.at(0));
682 if (QFile::exists(fileName)) {
683 if (QMessageBox::warning(d->m_mainWindow, tr("Overwrite?"),
684 tr("An item named '%1' already exists at this location. "
685 "Do you want to overwrite it?").arg(fileName),
686 QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) {
692 if (!fileName.isEmpty())
693 setFileDialogLastVisitedDirectory(QFileInfo(fileName).absolutePath());
697 QString FileManager::getSaveFileNameWithExtension(const QString &title, const QString &pathIn,
698 const QString &filter)
700 QString selected = filter;
701 return getSaveFileName(title, pathIn, filter, &selected);
705 \fn QString FileManager::getSaveAsFileName(IFile *file)
707 Asks the user for a new file name (Save File As) for /arg file.
709 QString FileManager::getSaveAsFileName(IFile *file, const QString &filter, QString *selectedFilter)
712 return QLatin1String("");
713 QString absoluteFilePath = file->fileName();
714 const QFileInfo fi(absoluteFilePath);
715 QString fileName = fi.fileName();
716 QString path = fi.absolutePath();
717 if (absoluteFilePath.isEmpty()) {
718 fileName = file->suggestedFileName();
719 const QString defaultPath = file->defaultPath();
720 if (!defaultPath.isEmpty())
724 QString filterString;
725 if (filter.isEmpty()) {
726 if (const MimeType &mt = Core::ICore::instance()->mimeDatabase()->findByFile(fi))
727 filterString = mt.filterString();
728 selectedFilter = &filterString;
730 filterString = filter;
733 absoluteFilePath = getSaveFileName(tr("Save File As"),
734 path + QDir::separator() + fileName,
737 return absoluteFilePath;
741 \fn QString FileManager::getOpenFileNames(const QString &filters, const QString &pathIn, QString *selectedFilter) const
743 Asks the user for a set of file names to be opened. The \a filters
744 and \a selectedFilter parameters is interpreted like in
745 QFileDialog::getOpenFileNames(), \a pathIn specifies a path to open the dialog
746 in, if that is not overridden by the users policy.
749 QStringList FileManager::getOpenFileNames(const QString &filters,
750 const QString pathIn,
751 QString *selectedFilter)
753 QString path = pathIn;
754 if (path.isEmpty()) {
755 if (!d->m_currentFile.isEmpty())
756 path = QFileInfo(d->m_currentFile).absoluteFilePath();
757 if (path.isEmpty() && useProjectsDirectory())
758 path = projectsDirectory();
760 const QStringList files = QFileDialog::getOpenFileNames(d->m_mainWindow,
764 if (!files.isEmpty())
765 setFileDialogLastVisitedDirectory(QFileInfo(files.front()).absolutePath());
769 FileManager::ReadOnlyAction
770 FileManager::promptReadOnlyFile(const QString &fileName,
771 const IVersionControl *versionControl,
773 bool displaySaveAsButton)
775 // Version Control: If automatic open is desired, open right away.
776 bool promptVCS = false;
777 if (versionControl && versionControl->supportsOperation(IVersionControl::OpenOperation)) {
778 if (versionControl->settingsFlags() & IVersionControl::AutoOpen)
783 // Create message box.
784 QMessageBox msgBox(QMessageBox::Question, tr("File is Read Only"),
785 tr("The file <i>%1</i> is read only.").arg(QDir::toNativeSeparators(fileName)),
786 QMessageBox::Cancel, parent);
788 QPushButton *vcsButton = 0;
790 vcsButton = msgBox.addButton(tr("Open with VCS (%1)").arg(versionControl->displayName()), QMessageBox::AcceptRole);
792 QPushButton *makeWritableButton = msgBox.addButton(tr("Make writable"), QMessageBox::AcceptRole);
794 QPushButton *saveAsButton = 0;
795 if (displaySaveAsButton)
796 saveAsButton = msgBox.addButton(tr("Save as ..."), QMessageBox::ActionRole);
798 msgBox.setDefaultButton(vcsButton ? vcsButton : makeWritableButton);
801 QAbstractButton *clickedButton = msgBox.clickedButton();
802 if (clickedButton == vcsButton)
804 if (clickedButton == makeWritableButton)
805 return RO_MakeWriteable;
806 if (displaySaveAsButton && clickedButton == saveAsButton)
811 void FileManager::changedFile(const QString &fileName)
813 const bool wasempty = d->m_changedFiles.isEmpty();
815 if (!d->m_changedFiles.contains(fileName) && d->m_states.contains(fileName))
816 d->m_changedFiles.append(fileName);
818 if (wasempty && !d->m_changedFiles.isEmpty()) {
819 QTimer::singleShot(200, this, SLOT(checkForReload()));
823 void FileManager::mainWindowActivated()
825 //we need to do this asynchronously because
826 //opening a dialog ("Reload?") in a windowactivated event
828 QTimer::singleShot(0, this, SLOT(checkForReload()));
831 void FileManager::checkForReload()
833 if (d->m_changedFiles.isEmpty())
835 if (QApplication::activeWindow() != d->m_mainWindow)
838 if (d->m_blockActivated)
841 d->m_blockActivated = true;
843 IFile::ReloadSetting defaultBehavior = EditorManager::instance()->reloadSetting();
844 Utils::ReloadPromptAnswer previousAnswer = Utils::ReloadCurrent;
846 QList<IEditor*> editorsToClose;
847 QMap<IFile*, QString> filesToSave;
849 // collect file information
850 QMap<QString, Internal::FileStateItem> currentStates;
851 QMap<QString, IFile::ChangeType> changeTypes;
852 QSet<IFile *> changedIFiles;
853 foreach (const QString &fileName, d->m_changedFiles) {
854 IFile::ChangeType type = IFile::TypeContents;
855 Internal::FileStateItem state;
856 QFileInfo fi(fileName);
858 type = IFile::TypeRemoved;
860 state.modified = fi.lastModified();
861 state.permissions = fi.permissions();
863 currentStates.insert(fileName, state);
864 changeTypes.insert(fileName, type);
865 foreach (IFile *file, d->m_states.value(fileName).lastUpdatedState.keys())
866 changedIFiles.insert(file);
869 // collect information about "expected" file names
870 // we can't do the "resolving" already in expectFileChange, because
871 // if the resolved names are different when unexpectFileChange is called
872 // we would end up with never-unexpected file names
873 QSet<QString> expectedFileNames;
874 foreach (const QString &fileName, d->m_expectedFileNames) {
875 const QString fixedName = fixFileName(fileName, KeepLinks);
876 expectedFileNames.insert(fixedName);
877 const QString fixedResolvedName = fixFileName(fileName, ResolveLinks);
878 if (fixedName != fixedResolvedName)
879 expectedFileNames.insert(fixedResolvedName);
883 foreach (IFile *file, changedIFiles) {
884 IFile::ChangeTrigger behavior = IFile::TriggerInternal;
885 IFile::ChangeType type = IFile::TypePermissions;
886 bool changed = false;
887 // find out the type & behavior from the two possible files
888 // behavior is internal if all changes are expected (and none removed)
889 // type is "max" of both types (remove > contents > permissions)
890 foreach (const QString & fileName, d->m_filesWithWatch.value(file)) {
891 // was the file reported?
892 if (!currentStates.contains(fileName))
895 Internal::FileStateItem currentState = currentStates.value(fileName);
896 Internal::FileStateItem expectedState = d->m_states.value(fileName).expected;
897 Internal::FileStateItem lastState = d->m_states.value(fileName).lastUpdatedState.value(file);
898 // did the file actually change?
899 if (lastState.modified == currentState.modified && lastState.permissions == currentState.permissions)
903 // was it only a permission change?
904 if (lastState.modified == currentState.modified)
907 // was the change unexpected?
908 if ((currentState.modified != expectedState.modified || currentState.permissions != expectedState.permissions)
909 && !expectedFileNames.contains(fileName)) {
910 behavior = IFile::TriggerExternal;
914 IFile::ChangeType fileChange = changeTypes.value(fileName);
915 if (fileChange == IFile::TypeRemoved) {
916 type = IFile::TypeRemoved;
917 behavior = IFile::TriggerExternal; // removed files always trigger externally
918 } else if (fileChange == IFile::TypeContents && type == IFile::TypePermissions) {
919 type = IFile::TypeContents;
923 if (!changed) // probably because the change was blocked with (un)blockFileChange
927 d->m_blockedIFile = file;
929 // we've got some modification
930 // check if it's contents or permissions:
931 if (type == IFile::TypePermissions) {
932 // Only permission change
933 file->reload(IFile::FlagReload, IFile::TypePermissions);
934 // now we know it's a content change or file was removed
935 } else if (defaultBehavior == IFile::ReloadUnmodified
936 && type == IFile::TypeContents && !file->isModified()) {
937 // content change, but unmodified (and settings say to reload in this case)
938 file->reload(IFile::FlagReload, type);
939 // file was removed or it's a content change and the default behavior for
940 // unmodified files didn't kick in
941 } else if (defaultBehavior == IFile::IgnoreAll) {
942 // content change or removed, but settings say ignore
943 file->reload(IFile::FlagIgnore, type);
944 // either the default behavior is to always ask,
945 // or the ReloadUnmodified default behavior didn't kick in,
946 // so do whatever the IFile wants us to do
948 // check if IFile wants us to ask
949 if (file->reloadBehavior(behavior, type) == IFile::BehaviorSilent) {
950 // content change or removed, IFile wants silent handling
951 file->reload(IFile::FlagReload, type);
952 // IFile wants us to ask
953 } else if (type == IFile::TypeContents) {
954 // content change, IFile wants to ask user
955 if (previousAnswer == Utils::ReloadNone) {
956 // answer already given, ignore
957 file->reload(IFile::FlagIgnore, IFile::TypeContents);
958 } else if (previousAnswer == Utils::ReloadAll) {
959 // answer already given, reload
960 file->reload(IFile::FlagReload, IFile::TypeContents);
962 // Ask about content change
963 previousAnswer = Utils::reloadPrompt(file->fileName(), file->isModified(), QApplication::activeWindow());
964 switch (previousAnswer) {
965 case Utils::ReloadAll:
966 case Utils::ReloadCurrent:
967 file->reload(IFile::FlagReload, IFile::TypeContents);
969 case Utils::ReloadSkipCurrent:
970 case Utils::ReloadNone:
971 file->reload(IFile::FlagIgnore, IFile::TypeContents);
975 // IFile wants us to ask, and it's the TypeRemoved case
977 // Ask about removed file
978 bool unhandled = true;
980 switch (Utils::fileDeletedPrompt(file->fileName(), QApplication::activeWindow())) {
981 case Utils::FileDeletedSave:
982 filesToSave.insert(file, file->fileName());
985 case Utils::FileDeletedSaveAs:
987 const QString &saveFileName = getSaveAsFileName(file);
988 if (!saveFileName.isEmpty()) {
989 filesToSave.insert(file, saveFileName);
994 case Utils::FileDeletedClose:
995 editorsToClose << EditorManager::instance()->editorsForFile(file);
1003 // update file info, also handling if e.g. link target has changed
1004 removeFileInfo(file);
1006 d->m_blockedIFile = 0;
1010 d->m_changedFiles.clear();
1012 // handle deleted files
1013 EditorManager::instance()->closeEditors(editorsToClose, false);
1014 QMapIterator<IFile *, QString> it(filesToSave);
1015 while (it.hasNext()) {
1017 blockFileChange(it.key());
1018 it.key()->save(it.value());
1019 unblockFileChange(it.key());
1020 it.key()->checkPermissions();
1023 d->m_blockActivated = false;
1028 void FileManager::syncWithEditor(Core::IContext *context)
1033 Core::IEditor *editor = Core::EditorManager::instance()->currentEditor();
1034 if (editor && (editor->widget() == context->widget()) &&
1035 !editor->isTemporary())
1036 setCurrentFile(editor->file()->fileName());
1040 \fn void FileManager::addToRecentFiles(const QString &fileName, , const QString &editorId)
1042 Adds the \a fileName to the list of recent files. Associates the file to
1043 be reopened with an editor of the given \a editorId, if possible.
1044 \a editorId defaults to the empty id, which means to let the system figure out
1045 the best editor itself.
1047 void FileManager::addToRecentFiles(const QString &fileName, const QString &editorId)
1049 if (fileName.isEmpty())
1051 QString unifiedForm(fixFileName(fileName, KeepLinks));
1052 QMutableListIterator<RecentFile > it(d->m_recentFiles);
1053 while (it.hasNext()) {
1054 RecentFile file = it.next();
1055 QString recentUnifiedForm(fixFileName(file.first, KeepLinks));
1056 if (unifiedForm == recentUnifiedForm)
1059 if (d->m_recentFiles.count() > d->m_maxRecentFiles)
1060 d->m_recentFiles.removeLast();
1061 d->m_recentFiles.prepend(RecentFile(fileName, editorId));
1065 \fn QStringList FileManager::recentFiles() const
1067 Returns the list of recent files.
1069 QList<FileManager::RecentFile> FileManager::recentFiles() const
1071 return d->m_recentFiles;
1074 void FileManager::saveSettings()
1076 QStringList recentFiles;
1077 QStringList recentEditorIds;
1078 foreach (const RecentFile &file, d->m_recentFiles) {
1079 recentFiles.append(file.first);
1080 recentEditorIds.append(file.second);
1083 QSettings *s = Core::ICore::instance()->settings();
1084 s->beginGroup(QLatin1String(settingsGroupC));
1085 s->setValue(QLatin1String(filesKeyC), recentFiles);
1086 s->setValue(QLatin1String(editorsKeyC), recentEditorIds);
1088 s->beginGroup(QLatin1String(directoryGroupC));
1089 s->setValue(QLatin1String(projectDirectoryKeyC), d->m_projectsDirectory);
1090 s->setValue(QLatin1String(useProjectDirectoryKeyC), d->m_useProjectsDirectory);
1094 void FileManager::readSettings()
1096 QSettings *s = Core::ICore::instance()->settings();
1097 d->m_recentFiles.clear();
1098 s->beginGroup(QLatin1String(settingsGroupC));
1099 QStringList recentFiles = s->value(QLatin1String(filesKeyC)).toStringList();
1100 QStringList recentEditorIds = s->value(QLatin1String(editorsKeyC)).toStringList();
1102 // clean non-existing files
1103 QStringListIterator ids(recentEditorIds);
1104 foreach (const QString &fileName, recentFiles) {
1106 if (ids.hasNext()) // guard against old or weird settings
1107 editorId = ids.next();
1108 if (QFileInfo(fileName).isFile())
1109 d->m_recentFiles.append(RecentFile(QDir::fromNativeSeparators(fileName), // from native to guard against old settings
1113 s->beginGroup(QLatin1String(directoryGroupC));
1114 const QString settingsProjectDir = s->value(QLatin1String(projectDirectoryKeyC),
1115 QString()).toString();
1116 if (!settingsProjectDir.isEmpty() && QFileInfo(settingsProjectDir).isDir()) {
1117 d->m_projectsDirectory = settingsProjectDir;
1119 d->m_projectsDirectory = Utils::PathChooser::homePath();
1121 d->m_useProjectsDirectory = s->value(QLatin1String(useProjectDirectoryKeyC),
1122 d->m_useProjectsDirectory).toBool();
1128 The current file is e.g. the file currently opened when an editor is active,
1129 or the selected file in case a Project Explorer is active ...
1133 void FileManager::setCurrentFile(const QString &filePath)
1135 if (d->m_currentFile == filePath)
1137 d->m_currentFile = filePath;
1138 emit currentFileChanged(d->m_currentFile);
1142 Returns the absolute path of the current file
1144 The current file is e.g. the file currently opened when an editor is active,
1145 or the selected file in case a Project Explorer is active ...
1149 QString FileManager::currentFile() const
1151 return d->m_currentFile;
1156 Returns the initial directory for a new file dialog. If there is
1157 a current file, use that, else use last visited directory.
1159 \sa setFileDialogLastVisitedDirectory
1162 QString FileManager::fileDialogInitialDirectory() const
1164 if (!d->m_currentFile.isEmpty())
1165 return QFileInfo(d->m_currentFile).absolutePath();
1166 return d->m_lastVisitedDirectory;
1171 Returns the directory for projects. Defaults to HOME.
1173 \sa setProjectsDirectory, setUseProjectsDirectory
1176 QString FileManager::projectsDirectory() const
1178 return d->m_projectsDirectory;
1183 Set the directory for projects.
1185 \sa projectsDirectory, useProjectsDirectory
1188 void FileManager::setProjectsDirectory(const QString &dir)
1190 d->m_projectsDirectory = dir;
1195 Returns whether the directory for projects is to be
1196 used or the user wants the current directory.
1198 \sa setProjectsDirectory, setUseProjectsDirectory
1201 bool FileManager::useProjectsDirectory() const
1203 return d->m_useProjectsDirectory;
1208 Sets whether the directory for projects is to be used.
1210 \sa projectsDirectory, useProjectsDirectory
1213 void FileManager::setUseProjectsDirectory(bool useProjectsDirectory)
1215 d->m_useProjectsDirectory = useProjectsDirectory;
1220 Returns last visited directory of a file dialog.
1222 \sa setFileDialogLastVisitedDirectory, fileDialogInitialDirectory
1226 QString FileManager::fileDialogLastVisitedDirectory() const
1228 return d->m_lastVisitedDirectory;
1233 Set the last visited directory of a file dialog that will be remembered
1236 \sa fileDialogLastVisitedDirectory, fileDialogInitialDirectory
1240 void FileManager::setFileDialogLastVisitedDirectory(const QString &directory)
1242 d->m_lastVisitedDirectory = directory;
1245 void FileManager::notifyFilesChangedInternally(const QStringList &files)
1247 emit filesChangedInternally(files);
1250 // -------------- FileChangeBlocker
1252 FileChangeBlocker::FileChangeBlocker(const QString &fileName)
1253 : m_fileName(fileName)
1255 Core::FileManager *fm = Core::ICore::instance()->fileManager();
1256 fm->expectFileChange(fileName);
1259 FileChangeBlocker::~FileChangeBlocker()
1261 Core::FileManager *fm = Core::ICore::instance()->fileManager();
1262 fm->unexpectFileChange(m_fileName);