Commit 9ac137fb authored by Oswald Buddenhagen's avatar Oswald Buddenhagen

add auto-saving of modified editors

Task-number: QTCREATORBUG-2847
parent a14955d0
......@@ -203,8 +203,9 @@ public:
return QLatin1String(Constants::C_BINEDITOR_MIMETYPE);
}
bool save(QString *errorString, const QString &fileName = QString())
bool save(QString *errorString, const QString &fileName, bool autoSave)
{
QTC_ASSERT(!autoSave, return true); // bineditor does not support autosave - it would be a bit expensive
const QString fileNameToUse
= fileName.isEmpty() ? m_fileName : fileName;
if (m_editor->save(errorString, m_fileName, fileNameToUse)) {
......@@ -363,7 +364,8 @@ public:
m_file->setFilename(QString());
return true;
}
bool open(QString *errorString, const QString &fileName = QString()) {
bool open(QString *errorString, const QString &fileName, const QString &realFileName) {
QTC_ASSERT(fileName == realFileName, return false); // The bineditor can do no autosaving
return m_file->open(errorString, fileName);
}
Core::IFile *file() { return m_file; }
......
......@@ -738,12 +738,13 @@ CMakeFile::CMakeFile(CMakeProject *parent, QString fileName)
}
bool CMakeFile::save(QString *errorString, const QString &fileName)
bool CMakeFile::save(QString *errorString, const QString &fileName, bool autoSave)
{
// Once we have an texteditor open for this file, we probably do
// need to implement this, don't we.
Q_UNUSED(errorString)
Q_UNUSED(fileName)
Q_UNUSED(autoSave)
return false;
}
......
......@@ -199,7 +199,7 @@ class CMakeFile : public Core::IFile
public:
CMakeFile(CMakeProject *parent, QString fileName);
bool save(QString *errorString, const QString &fileName = QString());
bool save(QString *errorString, const QString &fileName, bool autoSave);
QString fileName() const;
QString defaultPath() const;
......
......@@ -64,6 +64,7 @@
#include <utils/consoleprocess.h>
#include <utils/qtcassert.h>
#include <QtCore/QDateTime>
#include <QtCore/QDebug>
#include <QtCore/QFileInfo>
#include <QtCore/QMap>
......@@ -71,6 +72,7 @@
#include <QtCore/QSet>
#include <QtCore/QSettings>
#include <QtCore/QTextCodec>
#include <QtCore/QTimer>
#include <QtGui/QAction>
#include <QtGui/QShortcut>
......@@ -190,6 +192,7 @@ struct EditorManagerPrivate {
Internal::SplitterOrView *m_splitter;
QPointer<IEditor> m_currentEditor;
QPointer<SplitterOrView> m_currentView;
QTimer *m_autoSaveTimer;
ICore *m_core;
......@@ -222,12 +225,16 @@ struct EditorManagerPrivate {
IFile::ReloadSetting m_reloadSetting;
QString m_titleAddition;
bool m_autoSaveEnabled;
int m_autoSaveInterval;
};
}
EditorManagerPrivate::EditorManagerPrivate(ICore *core, QWidget *parent) :
m_view(0),
m_splitter(0),
m_autoSaveTimer(0),
m_core(core),
m_revertToSavedAction(new QAction(EditorManager::tr("Revert to Saved"), parent)),
m_saveAction(new QAction(parent)),
......@@ -241,7 +248,9 @@ EditorManagerPrivate::EditorManagerPrivate(ICore *core, QWidget *parent) :
m_goForwardAction(new QAction(QIcon(QLatin1String(Constants::ICON_NEXT)), EditorManager::tr("Go Forward"), parent)),
m_windowPopup(0),
m_coreListener(0),
m_reloadSetting(IFile::AlwaysAsk)
m_reloadSetting(IFile::AlwaysAsk),
m_autoSaveEnabled(true),
m_autoSaveInterval(5)
{
m_editorModel = new OpenEditorsModel(parent);
}
......@@ -448,6 +457,10 @@ EditorManager::EditorManager(ICore *core, QWidget *parent) :
updateActions();
m_d->m_windowPopup = new OpenEditorsWindow(this);
m_d->m_autoSaveTimer = new QTimer(this);
connect(m_d->m_autoSaveTimer, SIGNAL(timeout()), SLOT(autoSave()));
updateAutoSave();
}
EditorManager::~EditorManager()
......@@ -486,6 +499,13 @@ void EditorManager::init()
this, SLOT(updateVariable(QString)));
}
void EditorManager::updateAutoSave()
{
if (m_d->m_autoSaveEnabled)
m_d->m_autoSaveTimer->start(m_d->m_autoSaveInterval * (60 * 1000));
else
m_d->m_autoSaveTimer->stop();
}
EditorToolBar *EditorManager::createToolBar(QWidget *parent)
{
......@@ -1187,6 +1207,11 @@ int extractLineNumber(QString *fileName)
return -1;
}
static QString autoSaveName(const QString &fileName)
{
return fileName + QLatin1String(".autosave");
}
IEditor *EditorManager::openEditor(Core::Internal::EditorView *view, const QString &fileName,
const QString &editorId, OpenEditorFlags flags, bool *newEditor)
{
......@@ -1212,6 +1237,14 @@ IEditor *EditorManager::openEditor(Core::Internal::EditorView *view, const QStri
return activateEditor(view, editor, flags);
}
QString realFn = autoSaveName(fn);
QFileInfo fi(fn);
QFileInfo rfi(realFn);
if (!fi.exists() || !rfi.exists() || fi.lastModified() >= rfi.lastModified()) {
QFile::remove(realFn);
realFn = fn;
}
IEditor *editor = createEditor(editorId, fn);
// If we could not open the file in the requested editor, fall
// back to the default editor:
......@@ -1222,12 +1255,14 @@ IEditor *EditorManager::openEditor(Core::Internal::EditorView *view, const QStri
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
QString errorString;
if (!editor->open(&errorString, fn)) {
if (!editor->open(&errorString, fn, realFn)) {
QApplication::restoreOverrideCursor();
QMessageBox::critical(m_d->m_core->mainWindow(), tr("File Error"), errorString);
delete editor;
return 0;
}
if (realFn != fn)
editor->file()->setRestoredFrom(realFn);
addEditor(editor);
if (newEditor)
......@@ -1402,6 +1437,25 @@ bool EditorManager::saveFile(IFile *fileParam)
return success;
}
void EditorManager::autoSave()
{
QStringList errors;
// FIXME: the saving should be staggered
foreach (IEditor *editor, openedEditors()) {
IFile *file = editor->file();
if (!file->isModified() || !file->shouldAutoSave())
continue;
if (file->fileName().isEmpty()) // FIXME: save them to a dedicated directory
continue;
QString errorString;
if (!file->autoSave(&errorString, autoSaveName(file->fileName())))
errors << errorString;
}
if (!errors.isEmpty())
QMessageBox::critical(m_d->m_core->mainWindow(), tr("File Error"),
errors.join(QLatin1String("\n")));
}
MakeWritableResult
EditorManager::makeFileWritable(IFile *file)
{
......@@ -1549,8 +1603,11 @@ void EditorManager::updateWindowTitle()
void EditorManager::handleEditorStateChange()
{
updateActions();
IEditor *theEditor = qobject_cast<IEditor *>(sender());
if (!theEditor->file()->isModified())
theEditor->file()->removeAutoSaveFile();
IEditor *currEditor = currentEditor();
if (qobject_cast<IEditor *>(sender()) == currEditor) {
if (theEditor == currEditor) {
updateWindowTitle();
emit currentEditorStateChanged(currEditor);
}
......@@ -1771,8 +1828,15 @@ bool EditorManager::restoreState(const QByteArray &state)
QByteArray id;
stream >> id;
if (!fileName.isEmpty() && !displayName.isEmpty())
m_d->m_editorModel->addRestoredEditor(fileName, displayName, QString::fromUtf8(id));
if (!fileName.isEmpty() && !displayName.isEmpty()) {
QFileInfo fi(fileName);
QFileInfo rfi(autoSaveName(fileName));
if (fi.exists() && rfi.exists() && fi.lastModified() < rfi.lastModified()) {
openEditor(fileName, QString::fromUtf8(id));
} else {
m_d->m_editorModel->addRestoredEditor(fileName, displayName, QString::fromUtf8(id));
}
}
}
QByteArray splitterstates;
......@@ -1796,12 +1860,16 @@ bool EditorManager::restoreState(const QByteArray &state)
static const char documentStatesKey[] = "EditorManager/DocumentStates";
static const char reloadBehaviorKey[] = "EditorManager/ReloadBehavior";
static const char autoSaveEnabledKey[] = "EditorManager/AutoSaveEnabled";
static const char autoSaveIntervalKey[] = "EditorManager/AutoSaveInterval";
void EditorManager::saveSettings()
{
SettingsDatabase *settings = m_d->m_core->settingsDatabase();
settings->setValue(QLatin1String(documentStatesKey), m_d->m_editorStates);
settings->setValue(QLatin1String(reloadBehaviorKey), m_d->m_reloadSetting);
settings->setValue(QLatin1String(autoSaveEnabledKey), m_d->m_autoSaveEnabled);
settings->setValue(QLatin1String(autoSaveIntervalKey), m_d->m_autoSaveInterval);
}
void EditorManager::readSettings()
......@@ -1821,6 +1889,12 @@ void EditorManager::readSettings()
if (settings->contains(QLatin1String(reloadBehaviorKey)))
m_d->m_reloadSetting = (IFile::ReloadSetting)settings->value(QLatin1String(reloadBehaviorKey)).toInt();
if (settings->contains(QLatin1String(autoSaveEnabledKey))) {
m_d->m_autoSaveEnabled = settings->value(QLatin1String(autoSaveEnabledKey)).toBool();
m_d->m_autoSaveInterval = settings->value(QLatin1String(autoSaveIntervalKey)).toInt();
}
updateAutoSave();
}
......@@ -1873,6 +1947,28 @@ IFile::ReloadSetting EditorManager::reloadSetting() const
return m_d->m_reloadSetting;
}
void EditorManager::setAutoSaveEnabled(bool enabled)
{
m_d->m_autoSaveEnabled = enabled;
updateAutoSave();
}
bool EditorManager::autoSaveEnabled() const
{
return m_d->m_autoSaveEnabled;
}
void EditorManager::setAutoSaveInterval(int interval)
{
m_d->m_autoSaveInterval = interval;
updateAutoSave();
}
int EditorManager::autoSaveInterval() const
{
return m_d->m_autoSaveInterval;
}
QTextCodec *EditorManager::defaultTextCodec() const
{
QSettings *settings = Core::ICore::instance()->settings();
......
......@@ -178,6 +178,11 @@ public:
void setReloadSetting(IFile::ReloadSetting behavior);
IFile::ReloadSetting reloadSetting() const;
void setAutoSaveEnabled(bool enabled);
bool autoSaveEnabled() const;
void setAutoSaveInterval(int interval);
int autoSaveInterval() const;
QTextCodec *defaultTextCodec() const;
static qint64 maxTextFileSize();
......@@ -211,6 +216,7 @@ private slots:
void updateWindowTitle();
void handleEditorStateChange();
void updateVariable(const QString &variable);
void autoSave();
public slots:
void goBackInNavigationHistory();
......@@ -251,6 +257,7 @@ private:
IEditor *pickUnusedEditor() const;
void addFileToRecentFiles(IFile *file);
void switchToPreferedMode();
void updateAutoSave();
static EditorManager *m_instance;
EditorManagerPrivate *m_d;
......
......@@ -50,7 +50,7 @@ public:
virtual ~IEditor() {}
virtual bool createNew(const QString &contents = QString()) = 0;
virtual bool open(QString *errorString, const QString &fileName = QString()) = 0;
virtual bool open(QString *errorString, const QString &fileName, const QString &realFileName) = 0;
virtual IFile *file() = 0;
virtual QString id() const = 0;
virtual QString displayName() const = 0;
......
......@@ -624,7 +624,7 @@ bool FileManager::saveFile(IFile *file, const QString &fileName, bool *isReadOnl
bool addWatcher = removeFile(file); // So that our own IFile gets no notification at all
QString errorString;
if (!file->save(&errorString, fileName)) {
if (!file->save(&errorString, fileName, false)) {
if (isReadOnly) {
QFile ofi(effName);
// Check whether the existing file is writable
......
......@@ -151,6 +151,9 @@ QWidget *GeneralSettings::createPage(QWidget *parent)
m_page->helpExternalFileBrowserButton->hide();
#endif
m_page->autoSaveCheckBox->setChecked(EditorManager::instance()->autoSaveEnabled());
m_page->autoSaveInterval->setValue(EditorManager::instance()->autoSaveInterval());
connect(m_page->resetButton, SIGNAL(clicked()),
this, SLOT(resetInterfaceColor()));
#ifdef Q_OS_UNIX
......@@ -199,6 +202,8 @@ void GeneralSettings::apply()
Utils::UnixUtils::setFileBrowser(Core::ICore::instance()->settings(), m_page->externalFileBrowserEdit->text());
#endif
#endif
EditorManager::instance()->setAutoSaveEnabled(m_page->autoSaveCheckBox->isChecked());
EditorManager::instance()->setAutoSaveInterval(m_page->autoSaveInterval->value());
}
void GeneralSettings::finish()
......
......@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>527</width>
<height>295</height>
<height>306</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
......@@ -213,6 +213,69 @@
</property>
</widget>
</item>
<item row="3" column="0" colspan="4">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QCheckBox" name="autoSaveCheckBox">
<property name="toolTip">
<string>If checked, temporary copies of modified files will be created automatically. If Qt Creator is restarted after a crash or power failure, it will ask whether the auto-saved content should be recovered.</string>
</property>
<property name="text">
<string>Auto-save modified files</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="autoSaveIntervalLabel">
<property name="text">
<string>Interval:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="autoSaveInterval">
<property name="suffix">
<string extracomment="unit for minutes">min</string>
</property>
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</item>
......@@ -252,5 +315,22 @@
<resources>
<include location="core.qrc"/>
</resources>
<connections/>
<connections>
<connection>
<sender>autoSaveCheckBox</sender>
<signal>toggled(bool)</signal>
<receiver>autoSaveInterval</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>161</x>
<y>262</y>
</hint>
<hint type="destinationlabel">
<x>342</x>
<y>263</y>
</hint>
</hints>
</connection>
</connections>
</ui>
......@@ -34,14 +34,17 @@
#include "infobar.h"
#include <QtCore/QFile>
namespace Core {
IFile::IFile(QObject *parent) : QObject(parent), m_infoBar(0), m_hasWriteWarning(false)
IFile::IFile(QObject *parent) : QObject(parent), m_infoBar(0), m_hasWriteWarning(false), m_restored(false)
{
}
IFile::~IFile()
{
removeAutoSaveFile();
delete m_infoBar;
}
......@@ -58,6 +61,43 @@ void IFile::checkPermissions()
{
}
bool IFile::shouldAutoSave() const
{
return false;
}
bool IFile::autoSave(QString *errorString, const QString &fileName)
{
if (!save(errorString, fileName, true))
return false;
m_autoSaveName = fileName;
return true;
}
static const char kRestoredAutoSave[] = "RestoredAutoSave";
void IFile::setRestoredFrom(const QString &name)
{
m_autoSaveName = name;
m_restored = true;
InfoBarEntry info(QLatin1String(kRestoredAutoSave),
tr("File was restored from auto-saved copy. "
"Use <i>Save</i> to confirm, or <i>Revert to Saved</i> to discard changes."));
infoBar()->addInfo(info);
}
void IFile::removeAutoSaveFile()
{
if (!m_autoSaveName.isEmpty()) {
QFile::remove(m_autoSaveName);
m_autoSaveName.clear();
if (m_restored) {
m_restored = false;
infoBar()->removeInfo(QLatin1String(kRestoredAutoSave));
}
}
}
InfoBar *IFile::infoBar()
{
if (!m_infoBar)
......
......@@ -84,13 +84,14 @@ public:
IFile(QObject *parent = 0);
virtual ~IFile();
virtual bool save(QString *errorString, const QString &fileName = QString()) = 0;
virtual bool save(QString *errorString, const QString &fileName = QString(), bool autoSave = false) = 0;
virtual QString fileName() const = 0;
virtual QString defaultPath() const = 0;
virtual QString suggestedFileName() const = 0;
virtual QString mimeType() const = 0;
virtual bool shouldAutoSave() const;
virtual bool isModified() const = 0;
virtual bool isReadOnly() const = 0;
virtual bool isSaveAsAllowed() const = 0;
......@@ -101,6 +102,10 @@ public:
virtual void checkPermissions();
bool autoSave(QString *errorString, const QString &fileName);
void setRestoredFrom(const QString &name);
void removeAutoSaveFile();
bool hasWriteWarning() const { return m_hasWriteWarning; }
void setWriteWarning(bool has) { m_hasWriteWarning = has; }
......@@ -113,8 +118,10 @@ signals:
void reloaded();
private:
QString m_autoSaveName;
InfoBar *m_infoBar;
bool m_hasWriteWarning;
bool m_restored;
};
} // namespace Core
......
......@@ -1746,9 +1746,9 @@ QString CPPEditor::id() const
return QLatin1String(CppEditor::Constants::CPPEDITOR_ID);
}
bool CPPEditor::open(QString *errorString, const QString & fileName)
bool CPPEditor::open(QString *errorString, const QString &fileName, const QString &realFileName)
{
bool b = TextEditor::BaseTextEditor::open(errorString, fileName);
bool b = TextEditor::BaseTextEditor::open(errorString, fileName, realFileName);
editorWidget()->setMimeType(Core::ICore::instance()->mimeDatabase()->findByFile(QFileInfo(fileName)).type());
return b;
}
......
......@@ -152,7 +152,7 @@ public:
QString id() const;
bool isTemporary() const { return false; }
virtual bool open(QString *errorString, const QString & fileName);
virtual bool open(QString *errorString, const QString &fileName, const QString &realFileName);
};
class CPPEditorWidget : public TextEditor::BaseTextEditorWidget
......
......@@ -124,15 +124,16 @@ bool FormWindowEditor::createNew(const QString &contents)
syncXmlEditor(contents);
d->m_file.setFileName(QString());
d->m_file.setShouldAutoSave(false);
return true;
}
void FormWindowEditor::slotOpen(QString *errorString, const QString &fileName)
{
open(errorString, fileName);
open(errorString, fileName, fileName);
}
bool FormWindowEditor::open(QString *errorString, const QString &fileName)
bool FormWindowEditor::open(QString *errorString, const QString &fileName, const QString &realFileName)
{
if (Designer::Constants::Internal::debug)
qDebug() << "FormWindowEditor::open" << fileName;
......@@ -149,7 +150,7 @@ bool FormWindowEditor::open(QString *errorString, const QString &fileName)
const QString absfileName = fi.absoluteFilePath();
Utils::FileReader reader;
if (!reader.fetch(absfileName, QIODevice::Text, errorString))
if (!reader.fetch(realFileName, QIODevice::Text, errorString))
return false;
form->setFileName(absfileName);
......@@ -158,11 +159,12 @@ bool FormWindowEditor::open(QString *errorString, const QString &fileName)
form->setContents(contents);
if (!form->mainContainer())
return false;
form->setDirty(false);
form->setDirty(fileName != realFileName);
syncXmlEditor(contents);
setDisplayName(fi.fileName());
d->m_file.setFileName(absfileName);
d->m_file.setShouldAutoSave(false);
if (Internal::ResourceHandler *rh = qFindChild<Designer::Internal::ResourceHandler*>(form))
rh->updateResources();
......
......@@ -71,7 +71,7 @@ public:
// IEditor
virtual bool createNew(const QString &contents = QString());
virtual bool open(QString *errorString, const QString &fileName = QString());
virtual bool open(QString *errorString, const QString &fileName, const QString &realFileName);
virtual Core::IFile *file();
virtual QString id() const;
virtual QString displayName() const;
......
......@@ -45,6 +45,7 @@
#include <QtGui/QMessageBox>
#include <QtGui/QMainWindow>
#include <QtGui/QUndoStack>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
......@@ -57,13 +58,16 @@ namespace Internal {
FormWindowFile::FormWindowFile(QDesignerFormWindowInterface *form, QObject *parent)
: Core::IFile(parent),
m_mimeType(QLatin1String(Designer::Constants::FORM_MIMETYPE)),
m_shouldAutoSave(false),
m_formWindow(form)
{
connect(m_formWindow->core()->formWindowManager(), SIGNAL(formWindowRemoved(QDesignerFormWindowInterface*)),
this, SLOT(slotFormWindowRemoved(QDesignerFormWindowInterface*)));
connect(m_formWindow->commandHistory(), SIGNAL(indexChanged(int)),
this, SLOT(setShouldAutoSave()));
}
bool FormWindowFile::save(QString *errorString, const QString &name /* = QString() */)
bool FormWindowFile::save(QString *errorString, const QString &name, bool autoSave)
{
const QString actualName = name.isEmpty() ? fileName() : name;
......@@ -77,12 +81,16 @@ bool FormWindowFile::save(QString *errorString, const QString &name /* = QString
const QFileInfo fi(actualName);
const QString oldFormName = m_formWindow->fileName();
const QString formName = fi.absoluteFilePath();