Commit d6061a23 authored by con's avatar con

Refactor the externally modified file handling

Reviewed-by: dt
parent 5bc886c5
......@@ -29,9 +29,11 @@
#include "reloadpromptutils.h"
#include <QtGui/QMessageBox>
#include <QtCore/QCoreApplication>
#include <QtCore/QDir>
#include <QtGui/QMessageBox>
#include <QtGui/QPushButton>
#include <QtGui/QAbstractButton>
using namespace Utils;
......@@ -68,3 +70,27 @@ QTCREATOR_UTILS_EXPORT Utils::ReloadPromptAnswer
}
return ReloadNone;
}
QTCREATOR_UTILS_EXPORT Utils::FileDeletedPromptAnswer
Utils::fileDeletedPrompt(const QString &fileName, QWidget *parent)
{
const QString title = QCoreApplication::translate("Utils::fileDeletedPrompt", "File has been removed");
QString msg;
msg = QCoreApplication::translate("Utils::fileDeletedPrompt",
"The file %1 has been removed outside Qt Creator. Do you want to save it under a different name, or close the editor?").arg(QDir::toNativeSeparators(fileName));
QMessageBox box(QMessageBox::Question, title, msg, QMessageBox::NoButton, parent);
QPushButton *close = box.addButton(QCoreApplication::translate("Utils::fileDeletedPrompt", "Close"), QMessageBox::RejectRole);
QPushButton *saveas = box.addButton(QCoreApplication::translate("Utils::fileDeletedPrompt", "Save as..."), QMessageBox::ActionRole);
QPushButton *save = box.addButton(QCoreApplication::translate("Utils::fileDeletedPrompt", "Save"), QMessageBox::AcceptRole);
box.setDefaultButton(saveas);
box.exec();
QAbstractButton *clickedbutton = box.clickedButton();
if (clickedbutton == close) {
return FileDeletedClose;
} else if (clickedbutton == saveas) {
return FileDeletedSaveAs;
} else if (clickedbutton == save) {
return FileDeletedSave;
}
return FileDeletedClose;
}
......@@ -44,6 +44,10 @@ enum ReloadPromptAnswer { ReloadCurrent, ReloadAll, ReloadSkipCurrent, ReloadNon
QTCREATOR_UTILS_EXPORT ReloadPromptAnswer reloadPrompt(const QString &fileName, bool modified, QWidget *parent);
QTCREATOR_UTILS_EXPORT ReloadPromptAnswer reloadPrompt(const QString &title, const QString &prompt, QWidget *parent);
enum FileDeletedPromptAnswer { FileDeletedClose, FileDeletedSaveAs, FileDeletedSave };
QTCREATOR_UTILS_EXPORT FileDeletedPromptAnswer fileDeletedPrompt(const QString &fileName, QWidget *parent);
} // namespace Utils
#endif // RELOADPROMPTUTILS_H
......@@ -47,6 +47,7 @@
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/editormanager/ieditor.h>
#include <coreplugin/icore.h>
#include <coreplugin/ifile.h>
#include <coreplugin/mimedatabase.h>
#include <coreplugin/uniqueidmanager.h>
#include <extensionsystem/pluginmanager.h>
......@@ -251,41 +252,24 @@ public:
bool isSaveAsAllowed() const { return true; }
void modified(ReloadBehavior *behavior) {
const QString fileName = m_fileName;
ReloadBehavior reloadBehavior(ChangeTrigger state, ChangeType type) const {
if (type == TypePermissions)
return BehaviorSilent;
if (type == TypeContents) {
if (state == TriggerInternal && !isModified())
return BehaviorSilent;
return BehaviorAsk;
}
return BehaviorAsk;
}
switch (*behavior) {
case Core::IFile::ReloadNone:
return;
case Core::IFile::ReloadUnmodified:
if (!isModified()) {
open(fileName);
return;
}
break;
case Core::IFile::ReloadAll:
open(fileName);
void reload(ReloadFlag flag, ChangeType type) {
if (flag == FlagIgnore)
return;
case Core::IFile::ReloadPermissions:
if (type == TypePermissions) {
emit changed();
return;
case Core::IFile::AskForReload:
break;
}
switch (Utils::reloadPrompt(fileName, isModified(), Core::ICore::instance()->mainWindow())) {
case Utils::ReloadCurrent:
open(fileName);
break;
case Utils::ReloadAll:
open(fileName);
*behavior = Core::IFile::ReloadAll;
break;
case Utils::ReloadSkipCurrent:
break;
case Utils::ReloadNone:
*behavior = Core::IFile::ReloadNone;
break;
} else {
open(m_fileName);
}
}
......
......@@ -114,34 +114,25 @@ ImageViewerFile::ImageViewerFile(ImageViewer *parent)
}
void ImageViewerFile::modified(ReloadBehavior * behavior)
Core::IFile::ReloadBehavior ImageViewerFile::reloadBehavior(Core::IFile::ChangeTrigger state,
Core::IFile::ChangeType type) const
{
switch (*behavior) {
case Core::IFile::ReloadNone:
return;
case Core::IFile::ReloadUnmodified:
case Core::IFile::ReloadAll:
m_editor->open(m_fileName);
return;
case Core::IFile::ReloadPermissions:
return;
case Core::IFile::AskForReload:
break;
}
if (type == TypePermissions)
return BehaviorSilent;
if (type == TypeContents && state == TriggerInternal)
return BehaviorSilent;
return BehaviorAsk;
}
switch (Utils::reloadPrompt(m_fileName, isModified(), Core::ICore::instance()->mainWindow())) {
case Utils::ReloadCurrent:
m_editor->open(m_fileName);
break;
case Utils::ReloadAll:
void ImageViewerFile::reload(Core::IFile::ReloadFlag flag,
Core::IFile::ChangeType type)
{
if (flag == FlagIgnore)
return;
if (type == TypePermissions) {
emit changed();
} else {
m_editor->open(m_fileName);
*behavior = Core::IFile::ReloadAll;
break;
case Utils::ReloadSkipCurrent:
break;
case Utils::ReloadNone:
*behavior = Core::IFile::ReloadNone;
break;
}
}
......
......@@ -80,7 +80,8 @@ public:
bool isReadOnly() const { return true; }
bool isSaveAsAllowed() const { return false; }
void modified(ReloadBehavior *behavior);
ReloadBehavior reloadBehavior(ChangeTrigger state, ChangeType type) const;
void reload(ReloadFlag flag, ChangeType type);
void checkPermissions() {}
......
......@@ -598,9 +598,17 @@ bool CMakeFile::isSaveAsAllowed() const
return false;
}
void CMakeFile::modified(ReloadBehavior *behavior)
Core::IFile::ReloadBehavior CMakeFile::reloadBehavior(ChangeTrigger state, ChangeType type) const
{
Q_UNUSED(behavior)
Q_UNUSED(state)
Q_UNUSED(type)
return BehaviorSilent;
}
void CMakeFile::reload(ReloadFlag flag, ChangeType type)
{
Q_UNUSED(flag)
Q_UNUSED(type)
}
CMakeBuildSettingsWidget::CMakeBuildSettingsWidget(CMakeProject *project)
......
......@@ -193,7 +193,9 @@ public:
bool isReadOnly() const;
bool isSaveAsAllowed() const;
void modified(ReloadBehavior *behavior);
ReloadBehavior reloadBehavior(ChangeTrigger state, ChangeType type) const;
void reload(ReloadFlag flag, ChangeType type);
private:
CMakeProject *m_project;
QString m_fileName;
......
......@@ -214,7 +214,7 @@ struct EditorManagerPrivate {
OpenEditorsModel *m_editorModel;
QString m_externalEditor;
IFile::ReloadBehavior m_reloadBehavior;
IFile::ReloadSetting m_reloadSetting;
};
}
......@@ -235,7 +235,7 @@ EditorManagerPrivate::EditorManagerPrivate(ICore *core, QWidget *parent) :
m_openInExternalEditorAction(new QAction(EditorManager::tr("Open in External Editor"), parent)),
m_windowPopup(0),
m_coreListener(0),
m_reloadBehavior(IFile::AskForReload)
m_reloadSetting(IFile::AlwaysAsk)
{
m_editorModel = new OpenEditorsModel(parent);
}
......@@ -1716,7 +1716,7 @@ void EditorManager::saveSettings()
SettingsDatabase *settings = m_d->m_core->settingsDatabase();
settings->setValue(QLatin1String(documentStatesKey), m_d->m_editorStates);
settings->setValue(QLatin1String(externalEditorKey), m_d->m_externalEditor);
settings->setValue(QLatin1String(reloadBehaviorKey), m_d->m_reloadBehavior);
settings->setValue(QLatin1String(reloadBehaviorKey), m_d->m_reloadSetting);
}
void EditorManager::readSettings()
......@@ -1741,7 +1741,7 @@ void EditorManager::readSettings()
m_d->m_externalEditor = settings->value(QLatin1String(externalEditorKey)).toString();
if (settings->contains(QLatin1String(reloadBehaviorKey)))
m_d->m_reloadBehavior = (IFile::ReloadBehavior)settings->value(QLatin1String(reloadBehaviorKey)).toInt();
m_d->m_reloadSetting = (IFile::ReloadSetting)settings->value(QLatin1String(reloadBehaviorKey)).toInt();
}
......@@ -1765,8 +1765,7 @@ void EditorManager::revertToSaved()
return;
}
IFile::ReloadBehavior temp = IFile::ReloadAll;
currEditor->file()->modified(&temp);
currEditor->file()->reload(IFile::FlagReload, IFile::TypeContents);
}
void EditorManager::showEditorInfoBar(const QString &id,
......@@ -1899,14 +1898,14 @@ QString EditorManager::externalEditor() const
return m_d->m_externalEditor;
}
void EditorManager::setReloadBehavior(IFile::ReloadBehavior behavior)
void EditorManager::setReloadSetting(IFile::ReloadSetting behavior)
{
m_d->m_reloadBehavior = behavior;
m_d->m_reloadSetting = behavior;
}
IFile::ReloadBehavior EditorManager::reloadBehavior() const
IFile::ReloadSetting EditorManager::reloadSetting() const
{
return m_d->m_reloadBehavior;
return m_d->m_reloadSetting;
}
Core::IEditor *EditorManager::duplicateEditor(Core::IEditor *editor)
......
......@@ -185,8 +185,8 @@ public:
QString defaultExternalEditor() const;
QString externalEditorHelpText() const;
void setReloadBehavior(IFile::ReloadBehavior behavior);
IFile::ReloadBehavior reloadBehavior() const;
void setReloadSetting(IFile::ReloadSetting behavior);
IFile::ReloadSetting reloadSetting() const;
// Helper to display a message dialog when encountering a read-only
// file, prompting the user about how to make it writeable.
......
......@@ -40,6 +40,7 @@
#include <utils/qtcassert.h>
#include <utils/pathchooser.h>
#include <utils/reloadpromptutils.h>
#include <QtCore/QSettings>
#include <QtCore/QFileInfo>
......@@ -227,8 +228,9 @@ void FileManager::addFileInfo(IFile *file)
if (!d->m_states.contains(fixedname)) {
d->m_states.insert(fixedname, Internal::FileState());
if (!fixedname.isEmpty())
if (!fixedname.isEmpty()) {
d->m_fileWatcher->addPath(fixedname);
}
}
d->m_states[fixedname].lastUpdatedState.insert(file, item);
......@@ -278,8 +280,9 @@ void FileManager::removeFileInfo(const QString &fileName, IFile *file)
if (d->m_states.value(fixedName).lastUpdatedState.isEmpty()) {
d->m_states.remove(fixedName);
if (!fixedName.isEmpty())
if (!fixedName.isEmpty()) {
d->m_fileWatcher->removePath(fixedName);
}
}
}
......@@ -341,7 +344,9 @@ void FileManager::checkForNewFileName()
// check if the IFile is in the map
if (d->m_states.value(fileName).lastUpdatedState.contains(file)) {
// Should checkForNewFileName also call updateFileInfo if the name didn't change?
// the file might have been deleted and written again, so guard against that
d->m_fileWatcher->removePath(fileName);
d->m_fileWatcher->addPath(fileName);
updateFileInfo(file);
return;
}
......@@ -718,17 +723,25 @@ void FileManager::checkForReload()
d->m_blockActivated = true;
IFile::ReloadBehavior behavior = EditorManager::instance()->reloadBehavior();
IFile::ReloadSetting defaultBehavior = EditorManager::instance()->reloadSetting();
Utils::ReloadPromptAnswer previousAnswer = Utils::ReloadCurrent;
QStringList allFileNames;
foreach(const QString &fileName, d->m_changedFiles) {
allFileNames << fileName;
QList<IEditor*> editorsToClose;
QMap<IFile*, QString> filesToSave;
QStringList modifiedFileNames;
foreach (const QString &fileName, d->m_changedFiles) {
// Get the information from the filesystem
IFile::ChangeTrigger behavior = IFile::TriggerExternal;
IFile::ChangeType type = IFile::TypeContents;
QFileInfo fi(fileName);
bool expected = false;
if (fi.lastModified() == d->m_states.value(fileName).expected.modified
&& fi.permissions() == d->m_states.value(fileName).expected.permissions) {
expected = true;
if (!fi.exists()) {
type = IFile::TypeRemoved;
} else {
modifiedFileNames << fileName;
if (fi.lastModified() == d->m_states.value(fileName).expected.modified
&& fi.permissions() == d->m_states.value(fileName).expected.permissions) {
behavior = IFile::TriggerInternal;
}
}
const QMap<IFile *, Internal::FileStateItem> &lastUpdated =
......@@ -736,35 +749,109 @@ void FileManager::checkForReload()
QMap<IFile *, Internal::FileStateItem>::const_iterator it, end;
it = lastUpdated.constBegin();
end = lastUpdated.constEnd();
for ( ; it != end; ++it) {
IFile *file = it.key();
// Compare
if (it.value().modified == fi.lastModified()
&& it.value().permissions == fi.permissions()) {
// Already up to date
continue;
}
// we've got some modification
// check if it's contents or permissions:
if (it.value().modified == fi.lastModified()) {
// Only permission change
file->reload(IFile::FlagReload, IFile::TypePermissions);
// now we know it's a content change or file was removed
} else if (defaultBehavior == IFile::ReloadUnmodified
&& type == IFile::TypeContents && !file->isModified()) {
// content change, but unmodified (and settings say to reload in this case)
file->reload(IFile::FlagReload, type);
// file was removed or it's a content change and the default behavior for
// unmodified files didn't kick in
} else if (defaultBehavior == IFile::IgnoreAll) {
// content change or removed, but settings say ignore
file->reload(IFile::FlagIgnore, type);
// either the default behavior is to always ask,
// or the ReloadUnmodified default behavior didn't kick in,
// so do whatever the IFile wants us to do
} else {
// Update IFile
if (expected) {
IFile::ReloadBehavior tempBeh = IFile::ReloadUnmodified;
it.key()->modified(&tempBeh);
} else {
if (it.value().modified == fi.lastModified()) {
// Only permission change
IFile::ReloadBehavior tempBeh = IFile::ReloadPermissions;
it.key()->modified(&tempBeh);
// check if IFile wants us to ask
if (file->reloadBehavior(behavior, type) == IFile::BehaviorSilent) {
// content change or removed, IFile wants silent handling
file->reload(IFile::FlagReload, type);
// IFile wants us to ask
} else if (type == IFile::TypeContents) {
// content change, IFile wants to ask user
if (previousAnswer == Utils::ReloadNone) {
// answer already given, ignore
file->reload(IFile::FlagIgnore, IFile::TypeContents);
} else if (previousAnswer == Utils::ReloadAll) {
// answer already given, reload
file->reload(IFile::FlagReload, IFile::TypeContents);
} else {
it.key()->modified(&behavior);
// Ask about content change
previousAnswer = Utils::reloadPrompt(fileName, file->isModified(), QApplication::activeWindow());
switch (previousAnswer) {
case Utils::ReloadAll:
case Utils::ReloadCurrent:
file->reload(IFile::FlagReload, IFile::TypeContents);
break;
case Utils::ReloadSkipCurrent:
case Utils::ReloadNone:
file->reload(IFile::FlagIgnore, IFile::TypeContents);
break;
}
}
// IFile wants us to ask, and it's the TypeRemoved case
} else {
// Ask about removed file
bool unhandled = true;
while (unhandled) {
switch (Utils::fileDeletedPrompt(fileName, QApplication::activeWindow())) {
case Utils::FileDeletedSave:
filesToSave.insert(file, fileName);
unhandled = false;
break;
case Utils::FileDeletedSaveAs:
{
const QString &saveFileName = getSaveAsFileName(file);
if (!saveFileName.isEmpty()) {
filesToSave.insert(file, saveFileName);
unhandled = false;
}
break;
}
case Utils::FileDeletedClose:
editorsToClose << EditorManager::instance()->editorsForFile(file);
unhandled = false;
break;
}
}
}
updateFileInfo(it.key());
}
updateFileInfo(file);
}
}
if (!allFileNames.isEmpty()) {
d->m_fileWatcher->removePaths(allFileNames);
d->m_fileWatcher->addPaths(allFileNames);
// cleanup
if (!modifiedFileNames.isEmpty()) {
d->m_fileWatcher->removePaths(modifiedFileNames);
d->m_fileWatcher->addPaths(modifiedFileNames);
}
d->m_changedFiles.clear();
// handle deleted files
EditorManager::instance()->closeEditors(editorsToClose, false);
QMapIterator<IFile *, QString> it(filesToSave);
while (it.hasNext()) {
it.next();
blockFileChange(it.key());
it.key()->save(it.value());
unblockFileChange(it.key());
}
d->m_blockActivated = false;
}
......
......@@ -124,7 +124,7 @@ QWidget *GeneralSettings::createPage(QWidget *parent)
fillLanguageBox();
m_page->colorButton->setColor(StyleHelper::requestedBaseColor());
m_page->externalEditorEdit->setText(EditorManager::instance()->externalEditor());
m_page->reloadBehavior->setCurrentIndex(EditorManager::instance()->reloadBehavior());
m_page->reloadBehavior->setCurrentIndex(EditorManager::instance()->reloadSetting());
#ifdef Q_OS_UNIX
m_page->terminalEdit->setText(ConsoleProcess::terminalEmulator(settings));
#else
......@@ -182,7 +182,7 @@ void GeneralSettings::apply()
// Apply the new base color if accepted
StyleHelper::setBaseColor(m_page->colorButton->color());
EditorManager::instance()->setExternalEditor(m_page->externalEditorEdit->text());
EditorManager::instance()->setReloadBehavior(IFile::ReloadBehavior(m_page->reloadBehavior->currentIndex()));
EditorManager::instance()->setReloadSetting(IFile::ReloadSetting(m_page->reloadBehavior->currentIndex()));
#ifdef Q_OS_UNIX
ConsoleProcess::setTerminalEmulator(Core::ICore::instance()->settings(),
m_page->terminalEdit->text());
......
......@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>487</width>
<height>319</height>
<width>492</width>
<height>339</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
......@@ -234,7 +234,7 @@
</item>
<item>
<property name="text">
<string>Reload all modified files</string>
<string>Reload all unmodified files</string>
</property>
</item>
<item>
......
......@@ -44,12 +44,31 @@ class CORE_EXPORT IFile : public QObject
public:
// This enum must match the indexes of the reloadBehavior widget
// in generalsettings.ui
enum ReloadBehavior {
AskForReload = 0,
enum ReloadSetting {
AlwaysAsk = 0,
ReloadUnmodified = 1,
ReloadNone = 2,
ReloadAll,
ReloadPermissions
IgnoreAll = 2
};
enum ChangeTrigger {
TriggerInternal,
TriggerExternal
};
enum ChangeType {
TypeContents,
TypePermissions,
TypeRemoved
};
enum ReloadBehavior {
BehaviorAsk,
BehaviorSilent
};
enum ReloadFlag {
FlagReload,
FlagIgnore
};
IFile(QObject *parent = 0) : QObject(parent) {}
......@@ -66,7 +85,8 @@ public:
virtual bool isReadOnly() const = 0;
virtual bool isSaveAsAllowed() const = 0;
virtual void modified(ReloadBehavior *behavior) = 0;
virtual ReloadBehavior reloadBehavior(ChangeTrigger state, ChangeType type) const = 0;
virtual void reload(ReloadFlag flag, ChangeType type) = 0;
virtual void checkPermissions() {}
......
......@@ -111,43 +111,26 @@ bool FormWindowFile::isSaveAsAllowed() const
return true;
}
void FormWindowFile::modified(Core::IFile::ReloadBehavior *behavior)
Core::IFile::ReloadBehavior FormWindowFile::reloadBehavior(ChangeTrigger state, ChangeType type) const
{
if (Designer::Constants::Internal::debug)
qDebug() << Q_FUNC_INFO << m_fileName << *behavior;
if (type == TypePermissions)
return BehaviorSilent;
if (type == TypeContents) {
if (state == TriggerInternal && !isModified())
return BehaviorSilent;
return BehaviorAsk;
}
return BehaviorAsk;
}
switch (*behavior) {
case Core::IFile::ReloadNone:
return;
case Core::IFile::ReloadUnmodified:
if (!isModified()) {
reload(m_fileName);
return;
}
break;
case Core::IFile::ReloadAll:
emit reload(m_fileName);
void FormWindowFile::reload(ReloadFlag flag, ChangeType type)
{
if (flag == FlagIgnore)
return;
case Core::IFile::ReloadPermissions:
if (type == TypePermissions) {
emit changed();
return;
case Core::IFile::AskForReload:
break;
}
switch (Utils::reloadPrompt(m_fileName, isModified(), Core::ICore::instance()->mainWindow())) {
case Utils::ReloadCurrent:
emit reload(m_fileName);
break;
case Utils::ReloadAll:
} else {
emit reload(m_fileName);
*behavior = Core::IFile::ReloadAll;
break;
case Utils::ReloadSkipCurrent:
break;
case Utils::ReloadNone:
*behavior = Core::IFile::ReloadNone;
break;
}
}
......
......@@ -55,7 +55,8 @@ public:
virtual bool isModified() const;
virtual bool isReadOnly() const;
virtual bool isSaveAsAllowed() const;
virtual void modified(Core::IFile::ReloadBehavior *behavior);
ReloadBehavior reloadBehavior(ChangeTrigger state, ChangeType type) const;
void reload(ReloadFlag flag, ChangeType type);
virtual QString defaultPath() const;
virtual QString suggestedFileName() const;
virtual QString mimeType() const;
......
......@@ -579,6 +579,15 @@ bool GenericProjectFile::isSaveAsAllowed() const
return false;
}
void GenericProjectFile::modified(ReloadBehavior *)
Core::IFile::ReloadBehavior GenericProjectFile::reloadBehavior(ChangeTrigger state, ChangeType type) const
{
Q_UNUSED(state)
Q_UNUSED(type)