documentmanager.cpp 50.6 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
hjk's avatar
hjk committed
3 4
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
con's avatar
con committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
con's avatar
con committed
7
**
hjk's avatar
hjk committed
8 9 10 11 12 13 14
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17 18 19 20 21 22 23 24 25
** Alternatively, 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, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
26 27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
hjk's avatar
hjk committed
29

30
#include "documentmanager.h"
hjk's avatar
hjk committed
31

32 33
#include "editormanager.h"
#include "icore.h"
34 35 36
#include "ieditor.h"
#include "ieditorfactory.h"
#include "iexternaleditor.h"
37
#include "idocument.h"
38 39
#include "iversioncontrol.h"
#include "mimedatabase.h"
con's avatar
con committed
40 41
#include "saveitemsdialog.h"
#include "vcsmanager.h"
42
#include "coreconstants.h"
con's avatar
con committed
43

hjk's avatar
hjk committed
44
#include <utils/qtcassert.h>
45
#include <utils/pathchooser.h>
46
#include <utils/reloadpromptutils.h>
hjk's avatar
hjk committed
47

48 49 50 51 52 53 54 55 56 57 58 59 60 61
#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>
con's avatar
con committed
62 63

/*!
64
  \class Core::DocumentManager
con's avatar
con committed
65
  \mainclass
66 67
  \inheaderfile documentmanager.h
  \brief Manages a set of IDocument objects.
con's avatar
con committed
68

69 70 71
  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
con's avatar
con committed
72 73 74
  will be adjusted accordingly. Furthermore, on application exit the user will
  be asked to save all modified files.

75 76
  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
con's avatar
con committed
77 78
  enabled again by unblockFileChange().

79
  The functions expectFileChange() and unexpectFileChange() mark a file change
80
  as expected. On expected file changes all IDocument objects are notified to reload
81 82
  themselves.

83
  The DocumentManager service also provides two convenience methods for saving
con's avatar
con committed
84 85 86 87 88 89 90 91
  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()).
 */

92 93 94
static const char settingsGroupC[] = "RecentFiles";
static const char filesKeyC[] = "Files";
static const char editorsKeyC[] = "EditorIds";
95

96 97 98
static const char directoryGroupC[] = "Directories";
static const char projectDirectoryKeyC[] = "Projects";
static const char useProjectDirectoryKeyC[] = "UseProjectsDirectory";
con's avatar
con committed
99

100

101
namespace Core {
102 103 104

static void readSettings();

105
static QList<IDocument *> saveModifiedFilesHelper(const QList<IDocument *> &documents,
106 107 108 109 110
                               bool *cancelled, bool silently,
                               const QString &message,
                               const QString &alwaysSaveMessage = QString(),
                               bool *alwaysSave = 0);

111 112
namespace Internal {

113 114 115 116 117 118 119 120
struct OpenWithEntry
{
    OpenWithEntry() : editorFactory(0), externalEditor(0) {}
    IEditorFactory *editorFactory;
    IExternalEditor *externalEditor;
    QString fileName;
};

121
struct FileStateItem
122 123 124 125 126
{
    QDateTime modified;
    QFile::Permissions permissions;
};

127 128
struct FileState
{
129
    QMap<IDocument *, FileStateItem> lastUpdatedState;
130 131 132
    FileStateItem expected;
};

dt's avatar
dt committed
133

134
struct DocumentManagerPrivate
135
{
136
    explicit DocumentManagerPrivate(QMainWindow *mw);
137 138
    QFileSystemWatcher *fileWatcher();
    QFileSystemWatcher *linkWatcher();
139

dt's avatar
dt committed
140
    QMap<QString, FileState> m_states;
141
    QSet<QString> m_changedFiles;
142 143
    QList<IDocument *> m_documentsWithoutWatch;
    QMap<IDocument *, QStringList> m_documentsWithWatch;
144
    QSet<QString> m_expectedFileNames;
145

146
    QList<DocumentManager::RecentFile> m_recentFiles;
147 148 149 150 151
    static const int m_maxRecentFiles = 7;

    QString m_currentFile;

    QMainWindow *m_mainWindow;
152
    QFileSystemWatcher *m_fileWatcher; // Delayed creation.
153
    QFileSystemWatcher *m_linkWatcher; // Delayed creation (only UNIX/if a link is seen).
154
    bool m_blockActivated;
155 156 157
    QString m_lastVisitedDirectory;
    QString m_projectsDirectory;
    bool m_useProjectsDirectory;
158
    // When we are callling into a IDocument
dt's avatar
dt committed
159 160 161
    // we don't want to receive a changed()
    // signal
    // That makes the code easier
162
    IDocument *m_blockedIDocument;
163 164
};

165 166
static DocumentManager *m_instance;
static Internal::DocumentManagerPrivate *d;
167

168
QFileSystemWatcher *DocumentManagerPrivate::fileWatcher()
169 170
{
    if (!m_fileWatcher) {
171
        m_fileWatcher= new QFileSystemWatcher(m_instance);
172 173 174
        QObject::connect(m_fileWatcher, SIGNAL(fileChanged(QString)),
                         m_instance, SLOT(changedFile(QString)));
    }
175
    return m_fileWatcher;
176 177
}

178
QFileSystemWatcher *DocumentManagerPrivate::linkWatcher()
179 180 181 182 183 184 185 186
{
#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)));
    }
187
    return m_linkWatcher;
188
#else
189
    return fileWatcher();
190 191 192
#endif
}

193
DocumentManagerPrivate::DocumentManagerPrivate(QMainWindow *mw) :
con's avatar
con committed
194
    m_mainWindow(mw),
195 196
    m_fileWatcher(0),
    m_linkWatcher(0),
197 198 199
    m_blockActivated(false),
    m_lastVisitedDirectory(QDir::currentPath()),
#ifdef Q_OS_MAC  // Creator is in bizarre places when launched via finder.
dt's avatar
dt committed
200
    m_useProjectsDirectory(true),
201
#else
dt's avatar
dt committed
202
    m_useProjectsDirectory(false),
203
#endif
204
    m_blockedIDocument(0)
con's avatar
con committed
205
{
206 207 208
}

} // namespace Internal
209 210 211 212 213
} // namespace Core

Q_DECLARE_METATYPE(Core::Internal::OpenWithEntry)

namespace Core {
214

215 216
using namespace Internal;

217
DocumentManager::DocumentManager(QMainWindow *mw)
218
  : QObject(mw)
219
{
220
    d = new DocumentManagerPrivate(mw);
221
    m_instance = this;
222
    connect(d->m_mainWindow, SIGNAL(windowActivated()),
con's avatar
con committed
223
        this, SLOT(mainWindowActivated()));
hjk's avatar
hjk committed
224
    connect(ICore::instance(), SIGNAL(contextChanged(Core::IContext*,Core::Context)),
con's avatar
con committed
225 226
        this, SLOT(syncWithEditor(Core::IContext*)));

227
    readSettings();
con's avatar
con committed
228 229
}

230
DocumentManager::~DocumentManager()
231 232 233 234
{
    delete d;
}

235
DocumentManager *DocumentManager::instance()
236
{
237 238 239
    return m_instance;
}

240 241
/* only called from addFileInfo(IDocument *) */
static void addFileInfo(const QString &fileName, IDocument *document, bool isLink)
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
{
    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);

261
        d->m_states[fileName].lastUpdatedState.insert(document, state);
262
    }
263
    d->m_documentsWithWatch[document].append(fileName); // inserts a new QStringList if not already there
264 265
}

266
/* Adds the IDocument's file and possibly it's final link target to both m_states
267 268 269
   (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.) */
270
static void addFileInfo(IDocument *document)
271
{
272 273 274
    const QString fixedName = DocumentManager::fixFileName(document->fileName(), DocumentManager::KeepLinks);
    const QString fixedResolvedName = DocumentManager::fixFileName(document->fileName(), DocumentManager::ResolveLinks);
    addFileInfo(fixedResolvedName, document, false);
275
    if (fixedName != fixedResolvedName)
276
        addFileInfo(fixedName, document, true);
277 278
}

con's avatar
con committed
279
/*!
280
    \fn bool DocumentManager::addFiles(const QList<IDocument *> &documents, bool addWatcher)
con's avatar
con committed
281

282
    Adds a list of IDocument's to the collection. If \a addWatcher is true (the default),
con's avatar
con committed
283 284
    the files are added to a file system watcher that notifies the file manager
    about file changes.
con's avatar
con committed
285
*/
286
void DocumentManager::addDocuments(const QList<IDocument *> &documents, bool addWatcher)
con's avatar
con committed
287
{
dt's avatar
dt committed
288 289 290
    if (!addWatcher) {
        // We keep those in a separate list

291 292
        foreach (IDocument *document, documents) {
            if (document && !d->m_documentsWithoutWatch.contains(document)) {
Robert Loehning's avatar
Robert Loehning committed
293
                connect(document, SIGNAL(destroyed(QObject*)), m_instance, SLOT(documentDestroyed(QObject*)));
Robert Loehning's avatar
Robert Loehning committed
294
                connect(document, SIGNAL(fileNameChanged(QString,QString)), m_instance, SLOT(fileNameChanged(QString,QString)));
295
                d->m_documentsWithoutWatch.append(document);
296 297
            }
        }
con's avatar
con committed
298
        return;
dt's avatar
dt committed
299 300
    }

301 302 303
    foreach (IDocument *document, documents) {
        if (document && !d->m_documentsWithWatch.contains(document)) {
            connect(document, SIGNAL(changed()), m_instance, SLOT(checkForNewFileName()));
Robert Loehning's avatar
Robert Loehning committed
304
            connect(document, SIGNAL(destroyed(QObject*)), m_instance, SLOT(documentDestroyed(QObject*)));
Robert Loehning's avatar
Robert Loehning committed
305
            connect(document, SIGNAL(fileNameChanged(QString,QString)), m_instance, SLOT(fileNameChanged(QString,QString)));
306
            addFileInfo(document);
307
        }
con's avatar
con committed
308 309 310
    }
}

311

312 313
/* 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
314 315
   also removes the file watcher.
*/
316
static void removeFileInfo(IDocument *document)
317
{
318
    if (!d->m_documentsWithWatch.contains(document))
319
        return;
320
    foreach (const QString &fileName, d->m_documentsWithWatch.value(document)) {
321 322
        if (!d->m_states.contains(fileName))
            continue;
323
        d->m_states[fileName].lastUpdatedState.remove(document);
324 325 326 327 328 329
        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);
330
        }
331
    }
332
    d->m_documentsWithWatch.remove(document);
333 334
}

dt's avatar
dt committed
335 336
/// Dumps the state of the file manager's map
/// For debugging purposes
337 338
/*
static void dump()
dt's avatar
dt committed
339
{
340
    qDebug() << "======== dumping state map";
341
    QMap<QString, FileState>::const_iterator it, end;
dt's avatar
dt committed
342 343 344 345
    it = d->m_states.constBegin();
    end = d->m_states.constEnd();
    for (; it != end; ++it) {
        qDebug() << it.key();
346
        qDebug() << "   expected:" << it.value().expected.modified;
dt's avatar
dt committed
347

348
        QMap<IDocument *, FileStateItem>::const_iterator jt, jend;
dt's avatar
dt committed
349 350 351
        jt = it.value().lastUpdatedState.constBegin();
        jend = it.value().lastUpdatedState.constEnd();
        for (; jt != jend; ++jt) {
352
            qDebug() << "  " << jt.key()->fileName() << jt.value().modified;
dt's avatar
dt committed
353 354
        }
    }
355
    qDebug() << "------- dumping files with watch list";
356
    foreach (IDocument *key, d->m_filesWithWatch.keys()) {
357 358 359
        qDebug() << key->fileName() << d->m_filesWithWatch.value(key);
    }
    qDebug() << "------- dumping watch list";
360 361
    if (d->m_fileWatcher)
        qDebug() << d->m_fileWatcher->files();
362
    qDebug() << "------- dumping link watch list";
363 364
    if (d->m_linkWatcher)
        qDebug() << d->m_linkWatcher->files();
dt's avatar
dt committed
365
}
366
*/
dt's avatar
dt committed
367

368
/*!
369
    \fn void DocumentManager::renamedFile(const QString &from, const QString &to)
370 371 372 373
    \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.
374 375
    This will notify all IDocument objects pointing to that file of the rename
    by calling IDocument::rename, and update the cached time and permission
376 377 378
    information to avoid annoying the user with "file has been removed"
    popups.
*/
379
void DocumentManager::renamedFile(const QString &from, const QString &to)
dt's avatar
dt committed
380
{
381
    const QString &fixedFrom = fixFileName(from, KeepLinks);
dt's avatar
dt committed
382

383 384 385
    // gather the list of IDocuments
    QList<IDocument *> documentsToRename;
    QMapIterator<IDocument *, QStringList> it(d->m_documentsWithWatch);
386 387 388
    while (it.hasNext()) {
        it.next();
        if (it.value().contains(fixedFrom))
389
            documentsToRename.append(it.key());
390 391
    }

392 393 394 395 396 397 398
    // rename the IDocuments
    foreach (IDocument *document, documentsToRename) {
        d->m_blockedIDocument = document;
        removeFileInfo(document);
        document->rename(to);
        addFileInfo(document);
        d->m_blockedIDocument = 0;
399
    }
400
    emit m_instance->allDocumentsRenamed(from, to);
401
}
402 403 404 405 406 407 408 409 410 411

void DocumentManager::fileNameChanged(const QString &oldName, const QString &newName)
{
    IDocument *doc = qobject_cast<IDocument *>(sender());
    QTC_ASSERT(doc, return);
    if (doc == d->m_blockedIDocument)
        return;
    emit m_instance->documentRenamed(doc, oldName, newName);
}

con's avatar
con committed
412
/*!
413
    \fn bool DocumentManager::addFile(IDocument *document, bool addWatcher)
con's avatar
con committed
414

415
    Adds a IDocument object to the collection. If \a addWatcher is true (the default),
con's avatar
con committed
416 417
    the file is added to a file system watcher that notifies the file manager
    about file changes.
con's avatar
con committed
418
*/
419
void DocumentManager::addDocument(IDocument *document, bool addWatcher)
con's avatar
con committed
420
{
421
    addDocuments(QList<IDocument *>() << document, addWatcher);
con's avatar
con committed
422 423
}

424
void DocumentManager::documentDestroyed(QObject *obj)
con's avatar
con committed
425
{
426
    IDocument *document = static_cast<IDocument*>(obj);
dt's avatar
dt committed
427
    // Check the special unwatched first:
428 429
    if (!d->m_documentsWithoutWatch.removeOne(document))
        removeFileInfo(document);
con's avatar
con committed
430 431 432
}

/*!
433
    \fn bool DocumentManager::removeFile(IDocument *document)
con's avatar
con committed
434

435
    Removes a IDocument object from the collection.
con's avatar
con committed
436

437
    Returns true if the file specified by \a document had the addWatcher argument to addDocument() set.
con's avatar
con committed
438
*/
439
bool DocumentManager::removeDocument(IDocument *document)
con's avatar
con committed
440
{
441
    QTC_ASSERT(document, return false);
con's avatar
con committed
442

443
    bool addWatcher = false;
dt's avatar
dt committed
444
    // Special casing unwatched files
445
    if (!d->m_documentsWithoutWatch.removeOne(document)) {
446
        addWatcher = true;
447 448
        removeFileInfo(document);
        disconnect(document, SIGNAL(changed()), m_instance, SLOT(checkForNewFileName()));
dt's avatar
dt committed
449
    }
Robert Loehning's avatar
Robert Loehning committed
450
    disconnect(document, SIGNAL(destroyed(QObject*)), m_instance, SLOT(documentDestroyed(QObject*)));
451
    return addWatcher;
con's avatar
con committed
452 453
}

454
/* Slot reacting on IDocument::changed. We need to check if the signal was sent
455
   because the file was saved under different name. */
456
void DocumentManager::checkForNewFileName()
con's avatar
con committed
457
{
458 459
    IDocument *document = qobject_cast<IDocument *>(sender());
    // We modified the IDocument
dt's avatar
dt committed
460
    // Trust the other code to also update the m_states map
461
    if (document == d->m_blockedIDocument)
dt's avatar
dt committed
462
        return;
463 464
    QTC_ASSERT(document, return);
    QTC_ASSERT(d->m_documentsWithWatch.contains(document), return);
465

466
    // Maybe the name has changed or file has been deleted and created again ...
467
    // This also updates the state to the on disk state
468 469
    removeFileInfo(document);
    addFileInfo(document);
con's avatar
con committed
470 471
}

472
/*!
473
    \fn QString DocumentManager::fixFileName(const QString &fileName, FixMode fixmode)
474 475 476 477
    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).
*/
478
QString DocumentManager::fixFileName(const QString &fileName, FixMode fixmode)
con's avatar
con committed
479 480
{
    QString s = fileName;
481
    QFileInfo fi(s);
482 483 484 485 486 487 488 489 490
    if (fi.exists()) {
        if (fixmode == ResolveLinks)
            s = fi.canonicalFilePath();
        else
            s = QDir::cleanPath(fi.absoluteFilePath());
    } else {
        s = QDir::cleanPath(s);
    }
    s = QDir::toNativeSeparators(s);
con's avatar
con committed
491 492 493
#ifdef Q_OS_WIN
    s = s.toLower();
#endif
494
    return s;
con's avatar
con committed
495 496 497
}

/*!
498
    \fn QList<IDocument*> DocumentManager::modifiedFiles() const
con's avatar
con committed
499

500
    Returns the list of IDocument's that have been modified.
con's avatar
con committed
501
*/
502
QList<IDocument *> DocumentManager::modifiedDocuments()
con's avatar
con committed
503
{
504
    QList<IDocument *> modified;
con's avatar
con committed
505

506 507 508
    foreach (IDocument *document, d->m_documentsWithWatch.keys()) {
        if (document->isModified())
            modified << document;
con's avatar
con committed
509
    }
510

511 512 513
    foreach (IDocument *document, d->m_documentsWithoutWatch) {
        if (document->isModified())
            modified << document;
dt's avatar
dt committed
514 515
    }

516
    return modified;
con's avatar
con committed
517 518
}

519
/*!
520
    \fn void DocumentManager::expectFileChange(const QString &fileName)
521 522 523

    Any subsequent change to \a fileName is treated as a expected file change.

524
    \see DocumentManager::unexpectFileChange(const QString &fileName)
525
*/
526
void DocumentManager::expectFileChange(const QString &fileName)
con's avatar
con committed
527
{
528 529 530
    if (fileName.isEmpty())
        return;
    d->m_expectedFileNames.insert(fileName);
531 532
}

533 534 535 536 537 538 539 540 541 542 543 544
/* 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();
    }
}

545
/*!
546
    \fn void DocumentManager::unexpectFileChange(const QString &fileName)
547 548 549

    Any change to \a fileName are unexpected again.

550
    \see DocumentManager::expectFileChange(const QString &fileName)
551
*/
552
void DocumentManager::unexpectFileChange(const QString &fileName)
553 554 555 556 557 558
{
    // 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

559 560
    if (fileName.isEmpty())
        return;
561
    d->m_expectedFileNames.remove(fileName);
562 563 564 565 566
    const QString fixedName = fixFileName(fileName, KeepLinks);
    updateExpectedState(fixedName);
    const QString fixedResolvedName = fixFileName(fileName, ResolveLinks);
    if (fixedName != fixedResolvedName)
        updateExpectedState(fixedResolvedName);
567 568
}

con's avatar
con committed
569
/*!
570
    \fn QList<IDocument*> DocumentManager::saveModifiedFilesSilently(const QList<IDocument*> &documents)
con's avatar
con committed
571

572
    Tries to save the files listed in \a documents. The \a cancelled argument is set to true
573
    if the user cancelled the dialog. Returns the files that could not be saved.
con's avatar
con committed
574
*/
575
QList<IDocument *> DocumentManager::saveModifiedDocumentsSilently(const QList<IDocument *> &documents, bool *cancelled)
con's avatar
con committed
576
{
577
    return saveModifiedFilesHelper(documents, cancelled, true, QString());
con's avatar
con committed
578 579 580
}

/*!
581
    \fn QList<IDocument*> DocumentManager::saveModifiedFiles(const QList<IDocument *> &documents, bool *cancelled, const QString &message, const QString &alwaysSaveMessage, bool *alwaysSave)
con's avatar
con committed
582

583
    Asks the user whether to save the files listed in \a documents .
con's avatar
con committed
584 585 586 587 588 589 590
    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.
con's avatar
con committed
591
*/
592
QList<IDocument *> DocumentManager::saveModifiedDocuments(const QList<IDocument *> &documents,
593 594 595
                                              bool *cancelled, const QString &message,
                                              const QString &alwaysSaveMessage,
                                              bool *alwaysSave)
con's avatar
con committed
596
{
597
    return saveModifiedFilesHelper(documents, cancelled, false, message, alwaysSaveMessage, alwaysSave);
con's avatar
con committed
598 599
}

600
static QList<IDocument *> saveModifiedFilesHelper(const QList<IDocument *> &documents,
601 602 603 604 605
                                              bool *cancelled,
                                              bool silently,
                                              const QString &message,
                                              const QString &alwaysSaveMessage,
                                              bool *alwaysSave)
con's avatar
con committed
606 607 608 609
{
    if (cancelled)
        (*cancelled) = false;

610 611 612
    QList<IDocument *> notSaved;
    QMap<IDocument *, QString> modifiedDocumentsMap;
    QList<IDocument *> modifiedDocuments;
con's avatar
con committed
613

614 615 616
    foreach (IDocument *document, documents) {
        if (document->isModified()) {
            QString name = document->fileName();
con's avatar
con committed
617
            if (name.isEmpty())
618
                name = document->suggestedFileName();
con's avatar
con committed
619

620
            // There can be several IDocuments pointing to the same file
621
            // Prefer one that is not readonly
622 623 624
            // (even though it *should* not happen that the IDocuments are inconsistent with readonly)
            if (!modifiedDocumentsMap.key(name, 0) || !document->isFileReadOnly())
                modifiedDocumentsMap.insert(document, name);
con's avatar
con committed
625 626
        }
    }
627 628 629
    modifiedDocuments = modifiedDocumentsMap.keys();
    if (!modifiedDocuments.isEmpty()) {
        QList<IDocument *> documentsToSave;
con's avatar
con committed
630
        if (silently) {
631
            documentsToSave = modifiedDocuments;
con's avatar
con committed
632
        } else {
633
            SaveItemsDialog dia(d->m_mainWindow, modifiedDocuments);
con's avatar
con committed
634 635
            if (!message.isEmpty())
                dia.setMessage(message);
636 637
            if (!alwaysSaveMessage.isNull())
                dia.setAlwaysSaveMessage(alwaysSaveMessage);
con's avatar
con committed
638 639 640
            if (dia.exec() != QDialog::Accepted) {
                if (cancelled)
                    (*cancelled) = true;
641 642
                if (alwaysSave)
                    *alwaysSave = dia.alwaysSaveChecked();
643
                notSaved = modifiedDocuments;
con's avatar
con committed
644 645
                return notSaved;
            }
646 647
            if (alwaysSave)
                *alwaysSave = dia.alwaysSaveChecked();
648
            documentsToSave = dia.itemsToSave();
con's avatar
con committed
649 650
        }

651 652
        foreach (IDocument *document, documentsToSave) {
            if (!EditorManager::instance()->saveDocument(document)) {
653 654
                if (cancelled)
                    *cancelled = true;
655
                notSaved.append(document);
con's avatar
con committed
656 657 658 659 660 661
            }
        }
    }
    return notSaved;
}

662
bool DocumentManager::saveDocument(IDocument *document, const QString &fileName, bool *isReadOnly)
663
{
664
    bool ret = true;
665 666 667
    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
668

669
    QString errorString;
670
    if (!document->save(&errorString, fileName, false)) {
671 672 673
        if (isReadOnly) {
            QFile ofi(effName);
            // Check whether the existing file is writable
674
            if (!ofi.open(QIODevice::ReadWrite) && ofi.open(QIODevice::ReadOnly)) {
675
                *isReadOnly = true;
676
                goto out;
677 678 679
            }
            *isReadOnly = false;
        }
680 681
        QMessageBox::critical(d->m_mainWindow, tr("File Error"),
                              tr("Error while saving file: %1").arg(errorString));
682 683
      out:
        ret = false;
684
    }
685

686
    addDocument(document, addWatcher);
687 688
    unexpectFileChange(effName);
    return ret;
689 690
}

691
QString DocumentManager::getSaveFileName(const QString &title, const QString &pathIn,
692
                                     const QString &filter, QString *selectedFilter)
con's avatar
con committed
693
{
694
    const QString &path = pathIn.isEmpty() ? fileDialogInitialDirectory() : pathIn;
con's avatar
con committed
695 696 697 698
    QString fileName;
    bool repeat;
    do {
        repeat = false;
699 700 701 702 703 704 705 706 707 708
        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)
709
                QRegExp regExp(QLatin1String(".*\\s+\\((.*)\\)$"));
710 711 712
                const int index = regExp.lastIndexIn(*selectedFilter);
                bool suffixOk = false;
                if (index != -1) {
713
                    const QStringList &suffixes = regExp.cap(1).remove(QLatin1Char('*')).split(QLatin1Char(' '));
714 715 716 717 718 719 720 721 722
                    foreach (const QString &suffix, suffixes)
                        if (fileName.endsWith(suffix)) {
                            suffixOk = true;
                            break;
                        }
                    if (!suffixOk && !suffixes.isEmpty())
                        fileName.append(suffixes.at(0));
                }
            }
con's avatar
con committed
723
            if (QFile::exists(fileName)) {
724
                if (QMessageBox::warning(d->m_mainWindow, tr("Overwrite?"),
725 726 727
                    tr("An item named '%1' already exists at this location. "
                       "Do you want to overwrite it?").arg(fileName),
                    QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) {
con's avatar
con committed
728
                    repeat = true;
729
                }
con's avatar
con committed
730 731 732
            }
        }
    } while (repeat);
733 734
    if (!fileName.isEmpty())
        setFileDialogLastVisitedDirectory(QFileInfo(fileName).absolutePath());
con's avatar
con committed
735 736 737
    return fileName;
}

738
QString DocumentManager::getSaveFileNameWithExtension(const QString &title, const QString &pathIn,
739 740 741 742 743 744
                                                  const QString &filter)
{
    QString selected = filter;
    return getSaveFileName(title, pathIn, filter, &selected);
}

con's avatar
con committed
745
/*!
746
    \fn QString DocumentManager::getSaveAsFileName(IDocument *document, const QString &filter, QString *selectedFilter)
con's avatar
con committed
747

748
    Asks the user for a new file name (Save File As) for /arg document.
con's avatar
con committed
749
*/
750
QString DocumentManager::getSaveAsFileName(IDocument *document, const QString &filter, QString *selectedFilter)
con's avatar
con committed
751
{
752
    if (!document)
con's avatar
con committed
753
        return QLatin1String("");
754
    QString absoluteFilePath = document->fileName();
con's avatar
con committed
755 756 757 758
    const QFileInfo fi(absoluteFilePath);
    QString fileName = fi.fileName();
    QString path = fi.absolutePath();
    if (absoluteFilePath.isEmpty()) {
759 760
        fileName = document->suggestedFileName();
        const QString defaultPath = document->defaultPath();
con's avatar
con committed
761 762 763
        if (!defaultPath.isEmpty())
            path = defaultPath;
    }
764

con's avatar
con committed
765
    QString filterString;
766
    if (filter.isEmpty()) {
hjk's avatar
hjk committed
767
        if (const MimeType &mt = Core::ICore::mimeDatabase()->findByFile(fi))
768 769 770 771
            filterString = mt.filterString();
        selectedFilter = &filterString;
    } else {
        filterString = filter;
con's avatar
con committed
772 773
    }

774
    absoluteFilePath = getSaveFileName(tr("Save File As"),
con's avatar
con committed
775 776
        path + QDir::separator() + fileName,
        filterString,
777
        selectedFilter);
con's avatar
con committed
778 779 780
    return absoluteFilePath;
}

781
/*!
782
    \fn QStringList DocumentManager::getOpenFileNames(const QString &filters,
783 784
                                                  const QString pathIn,
                                                  QString *selectedFilter)
785

con's avatar
con committed
786 787 788 789
    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.
790 791
*/

792
QStringList DocumentManager::getOpenFileNames(const QString &filters,
793 794