Skip to content

Commit 1d4d292

Browse files
authored
Merge pull request #37 from sandbox-science/feature/change-detection
Added File Change Detection
2 parents 73287c1 + 9a513bd commit 1d4d292

File tree

4 files changed

+123
-55
lines changed

4 files changed

+123
-55
lines changed

include/FileManager.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,18 @@ class FileManager : public QObject
5252
static OperationResult duplicatePath(const QFileInfo &pathInfo);
5353
static OperationResult deletePath(const QFileInfo &pathInfo);
5454

55+
int buildUnsavedChangesMessage() const;
56+
bool hasUnsavedChanges();
57+
5558
public slots:
5659
void newFile();
5760
void saveFile();
5861
void saveFileAs();
5962
void openFile();
6063
void loadFileInEditor(const QString &filePath);
6164

65+
bool promptUnsavedChanges();
66+
6267
QString getDirectoryPath() const;
6368

6469
private:
@@ -69,4 +74,5 @@ public slots:
6974
MainWindow *m_mainWindow;
7075
QSyntaxHighlighter *m_currentHighlighter = nullptr;
7176
QString m_currentFileName;
77+
bool m_isDirty = false;
7278
};

src/FileManager.cpp

Lines changed: 105 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <iostream>
1212
#include <fstream>
1313

14+
1415
FileManager::FileManager(CodeEditor *editor, MainWindow *mainWindow)
1516
: m_editor(editor), m_mainWindow(mainWindow)
1617
{
@@ -23,6 +24,8 @@ void FileManager::initialize(CodeEditor *editor, MainWindow *mainWindow)
2324
{
2425
m_editor = editor;
2526
m_mainWindow = mainWindow;
27+
28+
connect(m_editor, &QPlainTextEdit::textChanged, this, [this](){m_isDirty = true;});
2629
}
2730

2831
QString FileManager::getCurrentFileName() const
@@ -35,65 +38,102 @@ void FileManager::setCurrentFileName(const QString fileName)
3538
m_currentFileName = fileName;
3639
}
3740

38-
void FileManager::newFile()
41+
QString getLastSaved(const QFileInfo& file)
3942
{
40-
QString currentFileName = getCurrentFileName();
41-
bool isFileSaved = !currentFileName.isEmpty();
42-
bool isTextEditorEmpty = this->m_editor->toPlainText().isEmpty();
43-
// File has not been saved and the text editor is not empty
44-
if (!isFileSaved && !isTextEditorEmpty)
45-
{
46-
// Create box to prompt user to save changes to file
47-
QMessageBox promptBox;
48-
promptBox.setWindowTitle("Save Current File");
49-
promptBox.setText("Would you like to save the file?");
50-
promptBox.setStandardButtons(QMessageBox::Save | QMessageBox::Cancel);
51-
promptBox.setDefaultButton(QMessageBox::Save);
52-
53-
int option = promptBox.exec();
54-
// return if the user hit Cancel button
55-
if (option == QMessageBox::Cancel)
56-
{
57-
return;
58-
}
43+
const auto lastSaved = file.lastModified();
44+
const auto now = QDateTime::currentDateTime();
45+
const auto seconds = lastSaved.secsTo(now);
46+
47+
const int days = seconds / (60 * 60 * 24);
48+
if (days == 0)
49+
return "today";
50+
if (days == 1)
51+
return "yesterday";
52+
53+
return QString::number(days) + " days ago";
54+
}
5955

60-
saveFile();
56+
bool FileManager::hasUnsavedChanges()
57+
{
58+
if(!m_isDirty)
59+
{
60+
return false;
6161
}
62-
// File has been previously saved
63-
else if (isFileSaved)
62+
63+
// Additional safeguard if content still matches file on disk
64+
if (!m_currentFileName.isEmpty())
6465
{
65-
// Read from saved file and compare to current file
66-
QFile file(currentFileName);
67-
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
68-
return;
69-
QTextStream in(&file);
70-
QString savedFileContents = in.readAll();
71-
file.close();
72-
if (savedFileContents != this->m_editor->toPlainText().trimmed())
66+
QFile file(m_currentFileName);
67+
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
7368
{
74-
// Create box to prompt user to save changes to file
75-
QMessageBox promptBox;
76-
promptBox.setWindowTitle("Changes Detected");
77-
promptBox.setText("Would you like to save the current changes to the file?");
78-
promptBox.setStandardButtons(QMessageBox::Save | QMessageBox::Cancel);
79-
promptBox.setDefaultButton(QMessageBox::Save);
80-
int option = promptBox.exec();
81-
// return if the user hit Cancel button
82-
if (option == QMessageBox::Cancel)
69+
QTextStream in(&file);
70+
QString diskContents = in.readAll();
71+
if (diskContents == m_editor->toPlainText())
8372
{
84-
return;
73+
m_isDirty = false;
74+
return false;
8575
}
86-
saveFile();
8776
}
8877
}
8978

79+
return true; // nothing changed
80+
}
81+
82+
int FileManager::buildUnsavedChangesMessage() const
83+
{
84+
QString infoText = "Your changes will be lost if you don't save.";
9085
if (!m_currentFileName.isEmpty())
9186
{
92-
setCurrentFileName("");
93-
m_editor->clear();
94-
m_mainWindow->setWindowTitle("Code Astra ~ untitled");
87+
QFileInfo file(m_currentFileName);
88+
if (file.exists())
89+
{
90+
QString timeSinceSave = getLastSaved(file);
91+
infoText = "The document has been modified. It was last edited " + timeSinceSave + ".";
92+
}
9593
}
96-
94+
95+
QMessageBox promptBox;
96+
promptBox.setWindowTitle("Unsaved changes");
97+
promptBox.setText("Would you like to save your changes?");
98+
promptBox.setInformativeText(infoText);
99+
promptBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
100+
promptBox.setDefaultButton(QMessageBox::Save);
101+
102+
return promptBox.exec();
103+
}
104+
105+
bool FileManager::promptUnsavedChanges()
106+
{
107+
if (!hasUnsavedChanges())
108+
{
109+
return true;
110+
}
111+
112+
int option = buildUnsavedChangesMessage();
113+
if (option == QMessageBox::Save)
114+
{
115+
saveFile();
116+
}
117+
else if (option == QMessageBox::Cancel)
118+
{
119+
return false;
120+
}
121+
122+
// if discard selected, continue without saving.
123+
return true;
124+
}
125+
126+
void FileManager::newFile()
127+
{
128+
if (!promptUnsavedChanges())
129+
{
130+
return;
131+
}
132+
133+
m_currentFileName = "";
134+
m_editor->clear();
135+
m_mainWindow->setWindowTitle("Untitle ~ Code Astra");
136+
m_isDirty = false;
97137
}
98138

99139
void FileManager::saveFile()
@@ -104,7 +144,7 @@ void FileManager::saveFile()
104144
return;
105145
}
106146

107-
qDebug() << "Saving file:" << m_currentFileName;
147+
// qDebug() << "Saving file:" << m_currentFileName;
108148

109149
QFile file(m_currentFileName);
110150
if (!file.open(QFile::WriteOnly | QFile::Text))
@@ -125,6 +165,16 @@ void FileManager::saveFile()
125165
}
126166
file.close();
127167

168+
if (m_mainWindow)
169+
{
170+
m_mainWindow->setWindowTitle("CodeAstra ~ " + QFileInfo(m_currentFileName).fileName());
171+
}
172+
else
173+
{
174+
qWarning() << "MainWindow is not initialized in FileManager.";
175+
}
176+
177+
m_isDirty = false;
128178
emit m_editor->statusMessageChanged("File saved successfully.");
129179
}
130180

@@ -152,7 +202,7 @@ void FileManager::openFile()
152202
"All Files (*);;C++ Files (*.cpp *.h);;Text Files (*.txt)");
153203
if (!fileName.isEmpty())
154204
{
155-
qDebug() << "Opening file: " << fileName;
205+
// qDebug() << "Opening file: " << fileName;
156206
m_currentFileName = fileName;
157207
loadFileInEditor(fileName);
158208
}
@@ -164,7 +214,8 @@ void FileManager::openFile()
164214

165215
void FileManager::loadFileInEditor(const QString &filePath)
166216
{
167-
qDebug() << "Loading file:" << filePath;
217+
// qDebug() << "Loading file:" << filePath;
218+
168219
QFile file(filePath);
169220
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
170221
{
@@ -175,7 +226,9 @@ void FileManager::loadFileInEditor(const QString &filePath)
175226
QTextStream in(&file);
176227
if (m_editor)
177228
{
229+
m_editor->blockSignals(true);
178230
m_editor->setPlainText(in.readAll());
231+
m_editor->blockSignals(false);
179232

180233
delete m_currentHighlighter;
181234

@@ -197,6 +250,8 @@ void FileManager::loadFileInEditor(const QString &filePath)
197250
{
198251
qWarning() << "MainWindow is not initialized in FileManager.";
199252
}
253+
254+
m_isDirty = false;
200255
}
201256

202257
QString FileManager::getFileExtension() const
@@ -296,6 +351,7 @@ OperationResult FileManager::deletePath(const QFileInfo &pathInfo)
296351
{
297352
return {false, "ERROR: invalid file path." + pathToDelete.filename().string()};
298353
}
354+
299355
QString qPathToDelete = QString::fromStdString(pathToDelete.string());
300356
if (!QFile::moveToTrash(qPathToDelete))
301357
{
@@ -330,7 +386,6 @@ OperationResult FileManager::newFile(const QFileInfo &pathInfo, QString newFileP
330386
{
331387
file.close();
332388
}
333-
qDebug() << "New file created.";
334389

335390
FileManager::getInstance().setCurrentFileName(QString::fromStdString(filePath.string()));
336391
return {true, filePath.filename().string() + " created successfully."};

src/Syntax.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,6 @@ void Syntax::loadSyntaxRules(const YAML::Node &config)
5959
continue;
6060
}
6161

62-
qDebug() << "regex: " << regex;
63-
6462
QColor color;
6563
try
6664
{
@@ -76,7 +74,7 @@ void Syntax::loadSyntaxRules(const YAML::Node &config)
7674
//checks if the color is a valid color
7775
if(!color.isValid())
7876
{
79-
qWarning() << "Invalid COlor : Skipping...";
77+
qWarning() << "Invalid Color : Skipping...";
8078
continue;
8179
}
8280

src/Tree.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,17 @@ void Tree::openFile(const QModelIndex &index)
6767
return;
6868
}
6969

70-
FileManager::getInstance().setCurrentFileName(filePath);
71-
FileManager::getInstance().loadFileInEditor(filePath);
70+
FileManager &fm = FileManager::getInstance();
71+
if (fm.getCurrentFileName() != filePath)
72+
{
73+
if (!fm.promptUnsavedChanges())
74+
{
75+
return; // if user has cancelled
76+
}
77+
78+
fm.setCurrentFileName(filePath);
79+
fm.loadFileInEditor(filePath);
80+
}
7281
}
7382

7483
QFileSystemModel *Tree::getModel() const

0 commit comments

Comments
 (0)