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

add auto-saving of modified editors

Task-number: QTCREATORBUG-2847
parent a14955d0
...@@ -203,8 +203,9 @@ public: ...@@ -203,8 +203,9 @@ public:
return QLatin1String(Constants::C_BINEDITOR_MIMETYPE); 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 const QString fileNameToUse
= fileName.isEmpty() ? m_fileName : fileName; = fileName.isEmpty() ? m_fileName : fileName;
if (m_editor->save(errorString, m_fileName, fileNameToUse)) { if (m_editor->save(errorString, m_fileName, fileNameToUse)) {
...@@ -363,7 +364,8 @@ public: ...@@ -363,7 +364,8 @@ public:
m_file->setFilename(QString()); m_file->setFilename(QString());
return true; 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); return m_file->open(errorString, fileName);
} }
Core::IFile *file() { return m_file; } Core::IFile *file() { return m_file; }
......
...@@ -738,12 +738,13 @@ CMakeFile::CMakeFile(CMakeProject *parent, QString fileName) ...@@ -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 // Once we have an texteditor open for this file, we probably do
// need to implement this, don't we. // need to implement this, don't we.
Q_UNUSED(errorString) Q_UNUSED(errorString)
Q_UNUSED(fileName) Q_UNUSED(fileName)
Q_UNUSED(autoSave)
return false; return false;
} }
......
...@@ -199,7 +199,7 @@ class CMakeFile : public Core::IFile ...@@ -199,7 +199,7 @@ class CMakeFile : public Core::IFile
public: public:
CMakeFile(CMakeProject *parent, QString fileName); 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 fileName() const;
QString defaultPath() const; QString defaultPath() const;
......
...@@ -64,6 +64,7 @@ ...@@ -64,6 +64,7 @@
#include <utils/consoleprocess.h> #include <utils/consoleprocess.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <QtCore/QDateTime>
#include <QtCore/QDebug> #include <QtCore/QDebug>
#include <QtCore/QFileInfo> #include <QtCore/QFileInfo>
#include <QtCore/QMap> #include <QtCore/QMap>
...@@ -71,6 +72,7 @@ ...@@ -71,6 +72,7 @@
#include <QtCore/QSet> #include <QtCore/QSet>
#include <QtCore/QSettings> #include <QtCore/QSettings>
#include <QtCore/QTextCodec> #include <QtCore/QTextCodec>
#include <QtCore/QTimer>
#include <QtGui/QAction> #include <QtGui/QAction>
#include <QtGui/QShortcut> #include <QtGui/QShortcut>
...@@ -190,6 +192,7 @@ struct EditorManagerPrivate { ...@@ -190,6 +192,7 @@ struct EditorManagerPrivate {
Internal::SplitterOrView *m_splitter; Internal::SplitterOrView *m_splitter;
QPointer<IEditor> m_currentEditor; QPointer<IEditor> m_currentEditor;
QPointer<SplitterOrView> m_currentView; QPointer<SplitterOrView> m_currentView;
QTimer *m_autoSaveTimer;
ICore *m_core; ICore *m_core;
...@@ -222,12 +225,16 @@ struct EditorManagerPrivate { ...@@ -222,12 +225,16 @@ struct EditorManagerPrivate {
IFile::ReloadSetting m_reloadSetting; IFile::ReloadSetting m_reloadSetting;
QString m_titleAddition; QString m_titleAddition;
bool m_autoSaveEnabled;
int m_autoSaveInterval;
}; };
} }
EditorManagerPrivate::EditorManagerPrivate(ICore *core, QWidget *parent) : EditorManagerPrivate::EditorManagerPrivate(ICore *core, QWidget *parent) :
m_view(0), m_view(0),
m_splitter(0), m_splitter(0),
m_autoSaveTimer(0),
m_core(core), m_core(core),
m_revertToSavedAction(new QAction(EditorManager::tr("Revert to Saved"), parent)), m_revertToSavedAction(new QAction(EditorManager::tr("Revert to Saved"), parent)),
m_saveAction(new QAction(parent)), m_saveAction(new QAction(parent)),
...@@ -241,7 +248,9 @@ EditorManagerPrivate::EditorManagerPrivate(ICore *core, QWidget *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_goForwardAction(new QAction(QIcon(QLatin1String(Constants::ICON_NEXT)), EditorManager::tr("Go Forward"), parent)),
m_windowPopup(0), m_windowPopup(0),
m_coreListener(0), m_coreListener(0),
m_reloadSetting(IFile::AlwaysAsk) m_reloadSetting(IFile::AlwaysAsk),
m_autoSaveEnabled(true),
m_autoSaveInterval(5)
{ {
m_editorModel = new OpenEditorsModel(parent); m_editorModel = new OpenEditorsModel(parent);
} }
...@@ -448,6 +457,10 @@ EditorManager::EditorManager(ICore *core, QWidget *parent) : ...@@ -448,6 +457,10 @@ EditorManager::EditorManager(ICore *core, QWidget *parent) :
updateActions(); updateActions();
m_d->m_windowPopup = new OpenEditorsWindow(this); 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() EditorManager::~EditorManager()
...@@ -486,6 +499,13 @@ void EditorManager::init() ...@@ -486,6 +499,13 @@ void EditorManager::init()
this, SLOT(updateVariable(QString))); 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) EditorToolBar *EditorManager::createToolBar(QWidget *parent)
{ {
...@@ -1187,6 +1207,11 @@ int extractLineNumber(QString *fileName) ...@@ -1187,6 +1207,11 @@ int extractLineNumber(QString *fileName)
return -1; return -1;
} }
static QString autoSaveName(const QString &fileName)
{
return fileName + QLatin1String(".autosave");
}
IEditor *EditorManager::openEditor(Core::Internal::EditorView *view, const QString &fileName, IEditor *EditorManager::openEditor(Core::Internal::EditorView *view, const QString &fileName,
const QString &editorId, OpenEditorFlags flags, bool *newEditor) const QString &editorId, OpenEditorFlags flags, bool *newEditor)
{ {
...@@ -1212,6 +1237,14 @@ IEditor *EditorManager::openEditor(Core::Internal::EditorView *view, const QStri ...@@ -1212,6 +1237,14 @@ IEditor *EditorManager::openEditor(Core::Internal::EditorView *view, const QStri
return activateEditor(view, editor, flags); 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); IEditor *editor = createEditor(editorId, fn);
// If we could not open the file in the requested editor, fall // If we could not open the file in the requested editor, fall
// back to the default editor: // back to the default editor:
...@@ -1222,12 +1255,14 @@ IEditor *EditorManager::openEditor(Core::Internal::EditorView *view, const QStri ...@@ -1222,12 +1255,14 @@ IEditor *EditorManager::openEditor(Core::Internal::EditorView *view, const QStri
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
QString errorString; QString errorString;
if (!editor->open(&errorString, fn)) { if (!editor->open(&errorString, fn, realFn)) {
QApplication::restoreOverrideCursor(); QApplication::restoreOverrideCursor();
QMessageBox::critical(m_d->m_core->mainWindow(), tr("File Error"), errorString); QMessageBox::critical(m_d->m_core->mainWindow(), tr("File Error"), errorString);
delete editor; delete editor;
return 0; return 0;
} }
if (realFn != fn)
editor->file()->setRestoredFrom(realFn);
addEditor(editor); addEditor(editor);
if (newEditor) if (newEditor)
...@@ -1402,6 +1437,25 @@ bool EditorManager::saveFile(IFile *fileParam) ...@@ -1402,6 +1437,25 @@ bool EditorManager::saveFile(IFile *fileParam)
return success; 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 MakeWritableResult
EditorManager::makeFileWritable(IFile *file) EditorManager::makeFileWritable(IFile *file)
{ {
...@@ -1549,8 +1603,11 @@ void EditorManager::updateWindowTitle() ...@@ -1549,8 +1603,11 @@ void EditorManager::updateWindowTitle()
void EditorManager::handleEditorStateChange() void EditorManager::handleEditorStateChange()
{ {
updateActions(); updateActions();
IEditor *theEditor = qobject_cast<IEditor *>(sender());
if (!theEditor->file()->isModified())
theEditor->file()->removeAutoSaveFile();
IEditor *currEditor = currentEditor(); IEditor *currEditor = currentEditor();
if (qobject_cast<IEditor *>(sender()) == currEditor) { if (theEditor == currEditor) {
updateWindowTitle(); updateWindowTitle();
emit currentEditorStateChanged(currEditor); emit currentEditorStateChanged(currEditor);
} }
...@@ -1771,8 +1828,15 @@ bool EditorManager::restoreState(const QByteArray &state) ...@@ -1771,8 +1828,15 @@ bool EditorManager::restoreState(const QByteArray &state)
QByteArray id; QByteArray id;
stream >> id; stream >> id;
if (!fileName.isEmpty() && !displayName.isEmpty()) if (!fileName.isEmpty() && !displayName.isEmpty()) {
m_d->m_editorModel->addRestoredEditor(fileName, displayName, QString::fromUtf8(id)); 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; QByteArray splitterstates;
...@@ -1796,12 +1860,16 @@ bool EditorManager::restoreState(const QByteArray &state) ...@@ -1796,12 +1860,16 @@ bool EditorManager::restoreState(const QByteArray &state)
static const char documentStatesKey[] = "EditorManager/DocumentStates"; static const char documentStatesKey[] = "EditorManager/DocumentStates";
static const char reloadBehaviorKey[] = "EditorManager/ReloadBehavior"; static const char reloadBehaviorKey[] = "EditorManager/ReloadBehavior";
static const char autoSaveEnabledKey[] = "EditorManager/AutoSaveEnabled";
static const char autoSaveIntervalKey[] = "EditorManager/AutoSaveInterval";
void EditorManager::saveSettings() void EditorManager::saveSettings()
{ {
SettingsDatabase *settings = m_d->m_core->settingsDatabase(); SettingsDatabase *settings = m_d->m_core->settingsDatabase();
settings->setValue(QLatin1String(documentStatesKey), m_d->m_editorStates); settings->setValue(QLatin1String(documentStatesKey), m_d->m_editorStates);
settings->setValue(QLatin1String(reloadBehaviorKey), m_d->m_reloadSetting); 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() void EditorManager::readSettings()
...@@ -1821,6 +1889,12 @@ void EditorManager::readSettings() ...@@ -1821,6 +1889,12 @@ void EditorManager::readSettings()
if (settings->contains(QLatin1String(reloadBehaviorKey))) if (settings->contains(QLatin1String(reloadBehaviorKey)))
m_d->m_reloadSetting = (IFile::ReloadSetting)settings->value(QLatin1String(reloadBehaviorKey)).toInt(); 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 ...@@ -1873,6 +1947,28 @@ IFile::ReloadSetting EditorManager::reloadSetting() const
return m_d->m_reloadSetting; 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 QTextCodec *EditorManager::defaultTextCodec() const
{ {
QSettings *settings = Core::ICore::instance()->settings(); QSettings *settings = Core::ICore::instance()->settings();
......
...@@ -178,6 +178,11 @@ public: ...@@ -178,6 +178,11 @@ public:
void setReloadSetting(IFile::ReloadSetting behavior); void setReloadSetting(IFile::ReloadSetting behavior);
IFile::ReloadSetting reloadSetting() const; IFile::ReloadSetting reloadSetting() const;
void setAutoSaveEnabled(bool enabled);
bool autoSaveEnabled() const;
void setAutoSaveInterval(int interval);
int autoSaveInterval() const;
QTextCodec *defaultTextCodec() const; QTextCodec *defaultTextCodec() const;
static qint64 maxTextFileSize(); static qint64 maxTextFileSize();
...@@ -211,6 +216,7 @@ private slots: ...@@ -211,6 +216,7 @@ private slots:
void updateWindowTitle(); void updateWindowTitle();
void handleEditorStateChange(); void handleEditorStateChange();
void updateVariable(const QString &variable); void updateVariable(const QString &variable);
void autoSave();
public slots: public slots:
void goBackInNavigationHistory(); void goBackInNavigationHistory();
...@@ -251,6 +257,7 @@ private: ...@@ -251,6 +257,7 @@ private:
IEditor *pickUnusedEditor() const; IEditor *pickUnusedEditor() const;
void addFileToRecentFiles(IFile *file); void addFileToRecentFiles(IFile *file);
void switchToPreferedMode(); void switchToPreferedMode();
void updateAutoSave();
static EditorManager *m_instance; static EditorManager *m_instance;
EditorManagerPrivate *m_d; EditorManagerPrivate *m_d;
......
...@@ -50,7 +50,7 @@ public: ...@@ -50,7 +50,7 @@ public:
virtual ~IEditor() {} virtual ~IEditor() {}
virtual bool createNew(const QString &contents = QString()) = 0; 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 IFile *file() = 0;
virtual QString id() const = 0; virtual QString id() const = 0;
virtual QString displayName() const = 0; virtual QString displayName() const = 0;
......
...@@ -624,7 +624,7 @@ bool FileManager::saveFile(IFile *file, const QString &fileName, bool *isReadOnl ...@@ -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 bool addWatcher = removeFile(file); // So that our own IFile gets no notification at all
QString errorString; QString errorString;
if (!file->save(&errorString, fileName)) { if (!file->save(&errorString, fileName, false)) {
if (isReadOnly) { if (isReadOnly) {
QFile ofi(effName); QFile ofi(effName);
// Check whether the existing file is writable // Check whether the existing file is writable
......
...@@ -151,6 +151,9 @@ QWidget *GeneralSettings::createPage(QWidget *parent) ...@@ -151,6 +151,9 @@ QWidget *GeneralSettings::createPage(QWidget *parent)
m_page->helpExternalFileBrowserButton->hide(); m_page->helpExternalFileBrowserButton->hide();
#endif #endif
m_page->autoSaveCheckBox->setChecked(EditorManager::instance()->autoSaveEnabled());
m_page->autoSaveInterval->setValue(EditorManager::instance()->autoSaveInterval());
connect(m_page->resetButton, SIGNAL(clicked()), connect(m_page->resetButton, SIGNAL(clicked()),
this, SLOT(resetInterfaceColor())); this, SLOT(resetInterfaceColor()));
#ifdef Q_OS_UNIX #ifdef Q_OS_UNIX
...@@ -199,6 +202,8 @@ void GeneralSettings::apply() ...@@ -199,6 +202,8 @@ void GeneralSettings::apply()
Utils::UnixUtils::setFileBrowser(Core::ICore::instance()->settings(), m_page->externalFileBrowserEdit->text()); Utils::UnixUtils::setFileBrowser(Core::ICore::instance()->settings(), m_page->externalFileBrowserEdit->text());
#endif #endif
#endif #endif
EditorManager::instance()->setAutoSaveEnabled(m_page->autoSaveCheckBox->isChecked());
EditorManager::instance()->setAutoSaveInterval(m_page->autoSaveInterval->value());
} }
void GeneralSettings::finish() void GeneralSettings::finish()
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>527</width> <width>527</width>
<height>295</height> <height>306</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
...@@ -213,6 +213,69 @@ ...@@ -213,6 +213,69 @@
</property> </property>
</widget> </widget>
</item> </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> </layout>
</widget> </widget>