OSDN Git Service

Update license.
[qt-creator-jp/qt-creator-jp.git] / src / plugins / coreplugin / filemanager.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 "filemanager.h"
34
35 #include "editormanager.h"
36 #include "ieditor.h"
37 #include "icore.h"
38 #include "ifile.h"
39 #include "iversioncontrol.h"
40 #include "mimedatabase.h"
41 #include "saveitemsdialog.h"
42 #include "vcsmanager.h"
43 #include "coreconstants.h"
44
45 #include <utils/qtcassert.h>
46 #include <utils/pathchooser.h>
47 #include <utils/reloadpromptutils.h>
48
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>
60
61 /*!
62   \class Core::FileManager
63   \mainclass
64   \inheaderfile filemanager.h
65   \brief Manages a set of IFile objects.
66
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.
72
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().
76
77   The functions expectFileChange() and unexpectFileChange() mark a file change
78   as expected. On expected file changes all IFile objects are notified to reload
79   themselves.
80
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
84   _not_ saved.
85
86   The service also manages the list of recent files to be shown to the user
87   (see addToRecentFiles() and recentFiles()).
88  */
89
90 static const char * const settingsGroupC = "RecentFiles";
91 static const char * const filesKeyC = "Files";
92 static const char * const editorsKeyC = "EditorIds";
93
94 static const char * const directoryGroupC = "Directories";
95 static const char * const projectDirectoryKeyC = "Projects";
96 static const char * const useProjectDirectoryKeyC = "UseProjectsDirectory";
97
98 namespace Core {
99 namespace Internal {
100
101 struct FileStateItem
102 {
103     QDateTime modified;
104     QFile::Permissions permissions;
105 };
106
107 struct FileState
108 {
109     QMap<IFile *, FileStateItem> lastUpdatedState;
110     FileStateItem expected;
111 };
112
113
114 struct FileManagerPrivate {
115     explicit FileManagerPrivate(FileManager *q, QMainWindow *mw);
116
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;
123
124     QList<FileManager::RecentFile> m_recentFiles;
125     static const int m_maxRecentFiles = 7;
126
127     QString m_currentFile;
128
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()
138     // signal
139     // That makes the code easier
140     IFile *m_blockedIFile;
141 };
142
143 FileManager *FileManagerPrivate::m_instance = 0;
144
145 FileManagerPrivate::FileManagerPrivate(FileManager *q, QMainWindow *mw) :
146     m_mainWindow(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),
152 #else
153     m_useProjectsDirectory(false),
154 #endif
155     m_blockedIFile(0)
156 {
157     m_instance = q;
158     q->connect(m_fileWatcher, SIGNAL(fileChanged(QString)),
159         q, SLOT(changedFile(QString)));
160 #ifdef Q_OS_UNIX
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)));
165 #else
166     m_linkWatcher = m_fileWatcher;
167 #endif
168 }
169
170 } // namespace Internal
171
172 FileManager::FileManager(QMainWindow *mw)
173   : QObject(mw),
174     d(new Internal::FileManagerPrivate(this, mw))
175 {
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*)));
181
182     readSettings();
183 }
184
185 FileManager::~FileManager()
186 {
187     delete d;
188 }
189
190 FileManager *FileManager::instance()
191 {
192     return Internal::FileManagerPrivate::m_instance;
193 }
194
195 /*!
196     \fn bool FileManager::addFiles(const QList<IFile *> &files, bool addWatcher)
197
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
200     about file changes.
201 */
202 void FileManager::addFiles(const QList<IFile *> &files, bool addWatcher)
203 {
204     if (!addWatcher) {
205         // We keep those in a separate list
206
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);
211             }
212         }
213         return;
214     }
215
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 *)));
220             addFileInfo(file);
221         }
222     }
223 }
224
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)
230 {
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);
236 }
237
238 /* only called from addFileInfo(IFile *) */
239 void FileManager::addFileInfo(const QString &fileName, IFile *file, bool isLink)
240 {
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());
249
250             if (isLink)
251                 d->m_linkWatcher->addPath(fileName);
252             else
253                 d->m_fileWatcher->addPath(fileName);
254         }
255         d->m_states[fileName].lastUpdatedState.insert(file, state);
256     }
257     d->m_filesWithWatch[file].append(fileName); // inserts a new QStringList if not already there
258 }
259
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)
263 {
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())
267             continue;
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);
275     }
276 }
277
278 /// Dumps the state of the file manager's map
279 /// For debugging purposes
280 void FileManager::dump()
281 {
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;
289
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;
295         }
296     }
297     qDebug() << "------- dumping files with watch list";
298     foreach (IFile *key, d->m_filesWithWatch.keys()) {
299         qDebug() << key->fileName() << d->m_filesWithWatch.value(key);
300     }
301     qDebug() << "------- dumping watch list";
302     qDebug() << d->m_fileWatcher->files();
303     qDebug() << "------- dumping link watch list";
304     qDebug() << d->m_linkWatcher->files();
305 }
306
307 /*!
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.
310
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"
316     popups.
317 */
318 void FileManager::renamedFile(const QString &from, const QString &to)
319 {
320     const QString &fixedFrom = fixFileName(from, KeepLinks);
321
322     // gather the list of IFiles
323     QList<IFile *> filesToRename;
324     QMapIterator<IFile *, QStringList> it(d->m_filesWithWatch);
325     while (it.hasNext()) {
326         it.next();
327         if (it.value().contains(fixedFrom))
328             filesToRename.append(it.key());
329     }
330
331     // rename the IFiles
332     foreach (IFile *file, filesToRename) {
333         d->m_blockedIFile = file;
334         removeFileInfo(file);
335         file->rename(to);
336         addFileInfo(file);
337         d->m_blockedIFile = 0;
338     }
339 }
340
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.
344 */
345 void FileManager::removeFileInfo(IFile *file)
346 {
347     if (!d->m_filesWithWatch.contains(file))
348         return;
349     foreach (const QString &fileName, d->m_filesWithWatch.value(file)) {
350         if (!d->m_states.contains(fileName))
351             continue;
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);
359         }
360     }
361     d->m_filesWithWatch.remove(file);
362 }
363
364 /*!
365     \fn bool FileManager::addFile(IFile *files, bool addWatcher)
366
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
369     about file changes.
370 */
371 void FileManager::addFile(IFile *file, bool addWatcher)
372 {
373     addFiles(QList<IFile *>() << file, addWatcher);
374 }
375
376 void FileManager::fileDestroyed(QObject *obj)
377 {
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);
382         return;
383     }
384     removeFileInfo(file);
385 }
386
387 /*!
388     \fn bool FileManager::removeFile(IFile *file)
389
390     Removes a IFile object from the collection.
391
392     Returns true if the file specified by \a file has been part of the file list.
393 */
394 void FileManager::removeFile(IFile *file)
395 {
396     QTC_ASSERT(file, return);
397
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);
402         return;
403     }
404
405     removeFileInfo(file);
406     disconnect(file, SIGNAL(changed()), this, SLOT(checkForNewFileName()));
407     disconnect(file, SIGNAL(destroyed(QObject *)), this, SLOT(fileDestroyed(QObject *)));
408 }
409
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()
413 {
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)
418         return;
419     QTC_ASSERT(file, return);
420     QTC_ASSERT(d->m_filesWithWatch.contains(file), return);
421
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);
425     addFileInfo(file);
426 }
427
428 /*!
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).
433 */
434 QString FileManager::fixFileName(const QString &fileName, FixMode fixmode)
435 {
436     QString s = fileName;
437     QFileInfo fi(s);
438     if (fi.exists()) {
439         if (fixmode == ResolveLinks)
440             s = fi.canonicalFilePath();
441         else
442             s = QDir::cleanPath(fi.absoluteFilePath());
443     } else {
444         s = QDir::cleanPath(s);
445     }
446     s = QDir::toNativeSeparators(s);
447 #ifdef Q_OS_WIN
448     s = s.toLower();
449 #endif
450     return s;
451 }
452
453 /*!
454     \fn QList<IFile*> FileManager::modifiedFiles() const
455
456     Returns the list of IFile's that have been modified.
457 */
458 QList<IFile *> FileManager::modifiedFiles() const
459 {
460     QList<IFile *> modifiedFiles;
461
462     foreach (IFile *file, d->m_filesWithWatch.keys()) {
463         if (file->isModified())
464             modifiedFiles << file;
465     }
466
467     foreach(IFile *file, d->m_filesWithoutWatch) {
468         if (file->isModified())
469             modifiedFiles << file;
470     }
471
472     return modifiedFiles;
473 }
474
475 /*!
476     \fn void FileManager::blockFileChange(IFile *file)
477
478     Blocks the monitoring of the file the \a file argument points to.
479 */
480 void FileManager::blockFileChange(IFile *file)
481 {
482     // Nothing to do
483     Q_UNUSED(file);
484 }
485
486 /*!
487     \fn void FileManager::unblockFileChange(IFile *file)
488
489     Enables the monitoring of the file the \a file argument points to, and update the status of the corresponding IFile's.
490 */
491 void FileManager::unblockFileChange(IFile *file)
492 {
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
497     //
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
502
503     updateFileInfo(file);
504     foreach (const QString &fileName, d->m_filesWithWatch.value(file))
505         updateExpectedState(fileName);
506 }
507
508 /*!
509     \fn void FileManager::expectFileChange(const QString &fileName)
510
511     Any subsequent change to \a fileName is treated as a expected file change.
512
513     \see FileManager::unexpectFileChange(const QString &fileName)
514 */
515 void FileManager::expectFileChange(const QString &fileName)
516 {
517     if (fileName.isEmpty())
518         return;
519     d->m_expectedFileNames.insert(fileName);
520 }
521
522 /*!
523     \fn void FileManager::unexpectFileChange(const QString &fileName)
524
525     Any change to \a fileName are unexpected again.
526
527     \see FileManager::expectFileChange(const QString &fileName)
528 */
529 void FileManager::unexpectFileChange(const QString &fileName)
530 {
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
535
536     if (fileName.isEmpty())
537         return;
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);
544 }
545
546 /* only called from unblock and unexpect file change methods */
547 void FileManager::updateExpectedState(const QString &fileName)
548 {
549     if (fileName.isEmpty())
550         return;
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();
555     }
556 }
557
558 /*!
559     \fn QList<IFile*> FileManager::saveModifiedFilesSilently(const QList<IFile*> &files)
560
561     Tries to save the files listed in \a files . Returns the files that could not be saved.
562 */
563 QList<IFile *> FileManager::saveModifiedFilesSilently(const QList<IFile *> &files)
564 {
565     return saveModifiedFiles(files, 0, true, QString());
566 }
567
568 /*!
569     \fn QList<IFile*> FileManager::saveModifiedFiles(const QList<IFile *> &files, bool *cancelled, const QString &message, const QString &alwaysSaveMessage, bool *alwaysSave)
570
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.
579 */
580 QList<IFile *> FileManager::saveModifiedFiles(const QList<IFile *> &files,
581                                               bool *cancelled, const QString &message,
582                                               const QString &alwaysSaveMessage,
583                                               bool *alwaysSave)
584 {
585     return saveModifiedFiles(files, cancelled, false, message, alwaysSaveMessage, alwaysSave);
586 }
587
588 QList<IFile *> FileManager::saveModifiedFiles(const QList<IFile *> &files,
589                                               bool *cancelled,
590                                               bool silently,
591                                               const QString &message,
592                                               const QString &alwaysSaveMessage,
593                                               bool *alwaysSave)
594 {
595     if (cancelled)
596         (*cancelled) = false;
597
598     QList<IFile *> notSaved;
599     QMap<IFile *, QString> modifiedFilesMap;
600     QList<IFile *> modifiedFiles;
601
602     foreach (IFile *file, files) {
603         if (file->isModified()) {
604             QString name = file->fileName();
605             if (name.isEmpty())
606                 name = file->suggestedFileName();
607
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);
613         }
614     }
615     modifiedFiles = modifiedFilesMap.keys();
616     if (!modifiedFiles.isEmpty()) {
617         QList<IFile *> filesToSave;
618         if (silently) {
619             filesToSave = modifiedFiles;
620         } else {
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) {
627                 if (cancelled)
628                     (*cancelled) = true;
629                 if (alwaysSave)
630                     *alwaysSave = dia.alwaysSaveChecked();
631                 notSaved = modifiedFiles;
632                 return notSaved;
633             }
634             if (alwaysSave)
635                 *alwaysSave = dia.alwaysSaveChecked();
636             filesToSave = dia.itemsToSave();
637         }
638
639         foreach (IFile *file, filesToSave) {
640             if (!EditorManager::instance()->saveFile(file)) {
641                 if (cancelled)
642                     *cancelled = true;
643                 notSaved.append(file);
644             }
645         }
646     }
647     return notSaved;
648 }
649
650 QString FileManager::getSaveFileName(const QString &title, const QString &pathIn,
651                                      const QString &filter, QString *selectedFilter)
652 {
653     const QString &path = pathIn.isEmpty() ? fileDialogInitialDirectory() : pathIn;
654     QString fileName;
655     bool repeat;
656     do {
657         repeat = false;
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;
671                 if (index != -1) {
672                     const QStringList &suffixes = regExp.cap(1).remove('*').split(' ');
673                     foreach (const QString &suffix, suffixes)
674                         if (fileName.endsWith(suffix)) {
675                             suffixOk = true;
676                             break;
677                         }
678                     if (!suffixOk && !suffixes.isEmpty())
679                         fileName.append(suffixes.at(0));
680                 }
681             }
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) {
687                     repeat = true;
688                 }
689             }
690         }
691     } while (repeat);
692     if (!fileName.isEmpty())
693         setFileDialogLastVisitedDirectory(QFileInfo(fileName).absolutePath());
694     return fileName;
695 }
696
697 QString FileManager::getSaveFileNameWithExtension(const QString &title, const QString &pathIn,
698                                                   const QString &filter)
699 {
700     QString selected = filter;
701     return getSaveFileName(title, pathIn, filter, &selected);
702 }
703
704 /*!
705     \fn QString FileManager::getSaveAsFileName(IFile *file)
706
707     Asks the user for a new file name (Save File As) for /arg file.
708 */
709 QString FileManager::getSaveAsFileName(IFile *file, const QString &filter, QString *selectedFilter)
710 {
711     if (!file)
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())
721             path = defaultPath;
722     }
723
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;
729     } else {
730         filterString = filter;
731     }
732
733     absoluteFilePath = getSaveFileName(tr("Save File As"),
734         path + QDir::separator() + fileName,
735         filterString,
736         selectedFilter);
737     return absoluteFilePath;
738 }
739
740 /*!
741     \fn QString FileManager::getOpenFileNames(const QString &filters, const QString &pathIn, QString *selectedFilter) const
742
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.
747 */
748
749 QStringList FileManager::getOpenFileNames(const QString &filters,
750                                           const QString pathIn,
751                                           QString *selectedFilter)
752 {
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();
759     }
760     const QStringList files = QFileDialog::getOpenFileNames(d->m_mainWindow,
761                                                       tr("Open File"),
762                                                       path, filters,
763                                                       selectedFilter);
764     if (!files.isEmpty())
765         setFileDialogLastVisitedDirectory(QFileInfo(files.front()).absolutePath());
766     return files;
767 }
768
769 FileManager::ReadOnlyAction
770     FileManager::promptReadOnlyFile(const QString &fileName,
771                                       const IVersionControl *versionControl,
772                                       QWidget *parent,
773                                       bool displaySaveAsButton)
774 {
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)
779             return RO_OpenVCS;
780         promptVCS = true;
781     }
782
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);
787
788     QPushButton *vcsButton = 0;
789     if (promptVCS)
790         vcsButton = msgBox.addButton(tr("Open with VCS (%1)").arg(versionControl->displayName()), QMessageBox::AcceptRole);
791
792     QPushButton *makeWritableButton =  msgBox.addButton(tr("Make writable"), QMessageBox::AcceptRole);
793
794     QPushButton *saveAsButton = 0;
795     if (displaySaveAsButton)
796         saveAsButton = msgBox.addButton(tr("Save as ..."), QMessageBox::ActionRole);
797
798     msgBox.setDefaultButton(vcsButton ? vcsButton : makeWritableButton);
799     msgBox.exec();
800
801     QAbstractButton *clickedButton = msgBox.clickedButton();
802     if (clickedButton == vcsButton)
803         return RO_OpenVCS;
804     if (clickedButton == makeWritableButton)
805         return RO_MakeWriteable;
806     if (displaySaveAsButton && clickedButton == saveAsButton)
807         return RO_SaveAs;
808     return  RO_Cancel;
809 }
810
811 void FileManager::changedFile(const QString &fileName)
812 {
813     const bool wasempty = d->m_changedFiles.isEmpty();
814
815     if (!d->m_changedFiles.contains(fileName) && d->m_states.contains(fileName))
816         d->m_changedFiles.append(fileName);
817
818     if (wasempty && !d->m_changedFiles.isEmpty()) {
819         QTimer::singleShot(200, this, SLOT(checkForReload()));
820     }
821 }
822
823 void FileManager::mainWindowActivated()
824 {
825     //we need to do this asynchronously because
826     //opening a dialog ("Reload?") in a windowactivated event
827     //freezes on Mac
828     QTimer::singleShot(0, this, SLOT(checkForReload()));
829 }
830
831 void FileManager::checkForReload()
832 {
833     if (d->m_changedFiles.isEmpty())
834         return;
835     if (QApplication::activeWindow() != d->m_mainWindow)
836         return;
837
838     if (d->m_blockActivated)
839         return;
840
841     d->m_blockActivated = true;
842
843     IFile::ReloadSetting defaultBehavior = EditorManager::instance()->reloadSetting();
844     Utils::ReloadPromptAnswer previousAnswer = Utils::ReloadCurrent;
845
846     QList<IEditor*> editorsToClose;
847     QMap<IFile*, QString> filesToSave;
848
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);
857         if (!fi.exists()) {
858             type = IFile::TypeRemoved;
859         } else {
860             state.modified = fi.lastModified();
861             state.permissions = fi.permissions();
862         }
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);
867     }
868
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);
880     }
881
882     // handle the IFiles
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))
893                 continue;
894
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)
900                 continue;
901             changed = true;
902
903             // was it only a permission change?
904             if (lastState.modified == currentState.modified)
905                 continue;
906
907             // was the change unexpected?
908             if ((currentState.modified != expectedState.modified || currentState.permissions != expectedState.permissions)
909                     && !expectedFileNames.contains(fileName)) {
910                 behavior = IFile::TriggerExternal;
911             }
912
913             // find out the type
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;
920             }
921         }
922
923         if (!changed) // probably because the change was blocked with (un)blockFileChange
924             continue;
925
926         // handle it!
927         d->m_blockedIFile = file;
928
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
947         } else {
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);
961                 } else {
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);
968                         break;
969                     case Utils::ReloadSkipCurrent:
970                     case Utils::ReloadNone:
971                         file->reload(IFile::FlagIgnore, IFile::TypeContents);
972                         break;
973                     }
974                 }
975             // IFile wants us to ask, and it's the TypeRemoved case
976             } else {
977                 // Ask about removed file
978                 bool unhandled = true;
979                 while (unhandled) {
980                     switch (Utils::fileDeletedPrompt(file->fileName(), QApplication::activeWindow())) {
981                     case Utils::FileDeletedSave:
982                         filesToSave.insert(file, file->fileName());
983                         unhandled = false;
984                         break;
985                     case Utils::FileDeletedSaveAs:
986                     {
987                         const QString &saveFileName = getSaveAsFileName(file);
988                         if (!saveFileName.isEmpty()) {
989                             filesToSave.insert(file, saveFileName);
990                             unhandled = false;
991                         }
992                         break;
993                     }
994                     case Utils::FileDeletedClose:
995                         editorsToClose << EditorManager::instance()->editorsForFile(file);
996                         unhandled = false;
997                         break;
998                     }
999                 }
1000             }
1001         }
1002
1003         // update file info, also handling if e.g. link target has changed
1004         removeFileInfo(file);
1005         addFileInfo(file);
1006         d->m_blockedIFile = 0;
1007     }
1008
1009     // clean up
1010     d->m_changedFiles.clear();
1011
1012     // handle deleted files
1013     EditorManager::instance()->closeEditors(editorsToClose, false);
1014     QMapIterator<IFile *, QString> it(filesToSave);
1015     while (it.hasNext()) {
1016         it.next();
1017         blockFileChange(it.key());
1018         it.key()->save(it.value());
1019         unblockFileChange(it.key());
1020         it.key()->checkPermissions();
1021     }
1022
1023     d->m_blockActivated = false;
1024
1025 //    dump();
1026 }
1027
1028 void FileManager::syncWithEditor(Core::IContext *context)
1029 {
1030     if (!context)
1031         return;
1032
1033     Core::IEditor *editor = Core::EditorManager::instance()->currentEditor();
1034     if (editor && (editor->widget() == context->widget()) &&
1035         !editor->isTemporary())
1036         setCurrentFile(editor->file()->fileName());
1037 }
1038
1039 /*!
1040     \fn void FileManager::addToRecentFiles(const QString &fileName, , const QString &editorId)
1041
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.
1046 */
1047 void FileManager::addToRecentFiles(const QString &fileName, const QString &editorId)
1048 {
1049     if (fileName.isEmpty())
1050         return;
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)
1057             it.remove();
1058     }
1059     if (d->m_recentFiles.count() > d->m_maxRecentFiles)
1060         d->m_recentFiles.removeLast();
1061     d->m_recentFiles.prepend(RecentFile(fileName, editorId));
1062 }
1063
1064 /*!
1065     \fn QStringList FileManager::recentFiles() const
1066
1067     Returns the list of recent files.
1068 */
1069 QList<FileManager::RecentFile> FileManager::recentFiles() const
1070 {
1071     return d->m_recentFiles;
1072 }
1073
1074 void FileManager::saveSettings()
1075 {
1076     QStringList recentFiles;
1077     QStringList recentEditorIds;
1078     foreach (const RecentFile &file, d->m_recentFiles) {
1079         recentFiles.append(file.first);
1080         recentEditorIds.append(file.second);
1081     }
1082
1083     QSettings *s = Core::ICore::instance()->settings();
1084     s->beginGroup(QLatin1String(settingsGroupC));
1085     s->setValue(QLatin1String(filesKeyC), recentFiles);
1086     s->setValue(QLatin1String(editorsKeyC), recentEditorIds);
1087     s->endGroup();
1088     s->beginGroup(QLatin1String(directoryGroupC));
1089     s->setValue(QLatin1String(projectDirectoryKeyC), d->m_projectsDirectory);
1090     s->setValue(QLatin1String(useProjectDirectoryKeyC), d->m_useProjectsDirectory);
1091     s->endGroup();
1092 }
1093
1094 void FileManager::readSettings()
1095 {
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();
1101     s->endGroup();
1102     // clean non-existing files
1103     QStringListIterator ids(recentEditorIds);
1104     foreach (const QString &fileName, recentFiles) {
1105         QString editorId;
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
1110                                                editorId));
1111     }
1112
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;
1118     } else {
1119         d->m_projectsDirectory = Utils::PathChooser::homePath();
1120     }
1121     d->m_useProjectsDirectory = s->value(QLatin1String(useProjectDirectoryKeyC),
1122                                          d->m_useProjectsDirectory).toBool();
1123     s->endGroup();
1124 }
1125
1126 /*!
1127
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 ...
1130
1131   \sa currentFile
1132   */
1133 void FileManager::setCurrentFile(const QString &filePath)
1134 {
1135     if (d->m_currentFile == filePath)
1136         return;
1137     d->m_currentFile = filePath;
1138     emit currentFileChanged(d->m_currentFile);
1139 }
1140
1141 /*!
1142   Returns the absolute path of the current file
1143
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 ...
1146
1147   \sa setCurrentFile
1148   */
1149 QString FileManager::currentFile() const
1150 {
1151     return d->m_currentFile;
1152 }
1153
1154 /*!
1155
1156   Returns the initial directory for a new file dialog. If there is
1157   a current file, use that, else use last visited directory.
1158
1159   \sa setFileDialogLastVisitedDirectory
1160 */
1161
1162 QString FileManager::fileDialogInitialDirectory() const
1163 {
1164     if (!d->m_currentFile.isEmpty())
1165         return QFileInfo(d->m_currentFile).absolutePath();
1166     return d->m_lastVisitedDirectory;
1167 }
1168
1169 /*!
1170
1171   Returns the directory for projects. Defaults to HOME.
1172
1173   \sa setProjectsDirectory, setUseProjectsDirectory
1174 */
1175
1176 QString FileManager::projectsDirectory() const
1177 {
1178     return d->m_projectsDirectory;
1179 }
1180
1181 /*!
1182
1183   Set the directory for projects.
1184
1185   \sa projectsDirectory, useProjectsDirectory
1186 */
1187
1188 void FileManager::setProjectsDirectory(const QString &dir)
1189 {
1190     d->m_projectsDirectory = dir;
1191 }
1192
1193 /*!
1194
1195   Returns whether the directory for projects is to be
1196   used or the user wants the current directory.
1197
1198   \sa setProjectsDirectory, setUseProjectsDirectory
1199 */
1200
1201 bool FileManager::useProjectsDirectory() const
1202 {
1203     return d->m_useProjectsDirectory;
1204 }
1205
1206 /*!
1207
1208   Sets whether the directory for projects is to be used.
1209
1210   \sa projectsDirectory, useProjectsDirectory
1211 */
1212
1213 void FileManager::setUseProjectsDirectory(bool useProjectsDirectory)
1214 {
1215     d->m_useProjectsDirectory = useProjectsDirectory;
1216 }
1217
1218 /*!
1219
1220   Returns last visited directory of a file dialog.
1221
1222   \sa setFileDialogLastVisitedDirectory, fileDialogInitialDirectory
1223
1224 */
1225
1226 QString FileManager::fileDialogLastVisitedDirectory() const
1227 {
1228     return d->m_lastVisitedDirectory;
1229 }
1230
1231 /*!
1232
1233   Set the last visited directory of a file dialog that will be remembered
1234   for the next one.
1235
1236   \sa fileDialogLastVisitedDirectory, fileDialogInitialDirectory
1237
1238   */
1239
1240 void FileManager::setFileDialogLastVisitedDirectory(const QString &directory)
1241 {
1242     d->m_lastVisitedDirectory = directory;
1243 }
1244
1245 void FileManager::notifyFilesChangedInternally(const QStringList &files)
1246 {
1247     emit filesChangedInternally(files);
1248 }
1249
1250 // -------------- FileChangeBlocker
1251
1252 FileChangeBlocker::FileChangeBlocker(const QString &fileName)
1253     : m_fileName(fileName)
1254 {
1255     Core::FileManager *fm = Core::ICore::instance()->fileManager();
1256     fm->expectFileChange(fileName);
1257 }
1258
1259 FileChangeBlocker::~FileChangeBlocker()
1260 {
1261     Core::FileManager *fm = Core::ICore::instance()->fileManager();
1262     fm->unexpectFileChange(m_fileName);
1263 }
1264
1265 } // namespace Core