documentmanager.cpp 50 KB
Newer Older
1
/**************************************************************************
con's avatar
con committed
2 3 4
**
** This file is part of Qt Creator
**
hjk's avatar
hjk committed
5
** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
con's avatar
con committed
6
**
Eike Ziller's avatar
Eike Ziller committed
7
** Contact: http://www.qt-project.org/
con's avatar
con committed
8
**
9
**
10
** GNU Lesser General Public License Usage
11
**
hjk's avatar
hjk committed
12 13 14 15 16 17
** 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.
18
**
con's avatar
con committed
19
** In addition, as a special exception, Nokia gives you certain additional
hjk's avatar
hjk committed
20
** rights. These rights are described in the Nokia Qt LGPL Exception
con's avatar
con committed
21 22
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
23 24 25 26 27
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
con's avatar
con committed
28
**
29
**************************************************************************/
hjk's avatar
hjk committed
30

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

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

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

49 50 51 52 53 54 55 56 57 58 59 60 61 62
#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
63 64

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

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

76 77
  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
78 79
  enabled again by unblockFileChange().

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

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

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

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

101

102
namespace Core {
103 104 105

static void readSettings();

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

112 113
namespace Internal {

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

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

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

dt's avatar
dt committed
134

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

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

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

    QString m_currentFile;

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

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

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

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

194
DocumentManagerPrivate::DocumentManagerPrivate(QMainWindow *mw) :
con's avatar
con committed
195
    m_mainWindow(mw),
196 197
    m_fileWatcher(0),
    m_linkWatcher(0),
198 199 200
    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
201
    m_useProjectsDirectory(true),
202
#else
dt's avatar
dt committed
203
    m_useProjectsDirectory(false),
204
#endif
205
    m_blockedIDocument(0)
con's avatar
con committed
206
{
207 208 209
}

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

Q_DECLARE_METATYPE(Core::Internal::OpenWithEntry)

namespace Core {
215

216 217
using namespace Internal;

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

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

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

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

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

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

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

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

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

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

302 303 304
    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
305
            connect(document, SIGNAL(destroyed(QObject*)), m_instance, SLOT(documentDestroyed(QObject*)));
Robert Loehning's avatar
Robert Loehning committed
306
            connect(document, SIGNAL(fileNameChanged(QString,QString)), m_instance, SLOT(fileNameChanged(QString,QString)));
307
            addFileInfo(document);
308
        }
con's avatar
con committed
309 310 311
    }
}

312

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

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

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

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

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

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

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
413
/*!
414
    \fn bool DocumentManager::addFile(IDocument *document, bool addWatcher)
con's avatar
con committed
415

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Any change to \a fileName are unexpected again.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

con's avatar
con committed
787 788 789