Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 69 additions & 8 deletions src/FileManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ FileManager::~FileManager() {}

void FileManager::initialize(CodeEditor *editor, MainWindow *mainWindow)
{
m_editor = editor;
m_editor = editor;
m_mainWindow = mainWindow;
}

Expand All @@ -37,7 +37,68 @@ void FileManager::setCurrentFileName(const QString fileName)

void FileManager::newFile()
{
// Logic to create a new file
QString m_currentFileName = getCurrentFileName();
bool isFileSaved = !m_currentFileName.isEmpty();
bool isTextEditorEmpty = this->m_editor->toPlainText().isEmpty();

// File has not been saved and the text editor is not empty
if (!isFileSaved && !isTextEditorEmpty)
{
// Create box to prompt user to save changes to file
QMessageBox promptBox;
promptBox.setWindowTitle("Save Current File");
promptBox.setText("Would you like to save the file?");
promptBox.setStandardButtons(QMessageBox::Save | QMessageBox::Cancel);
promptBox.setDefaultButton(QMessageBox::Save);

int option = promptBox.exec();
// return if the user hit Cancel button
if (option == QMessageBox::Cancel)
{
return;
}

saveFile();
}
// File has been previously saved
else if (isFileSaved)
{
// Read from saved file and compare to current file
QFile file(m_currentFileName);

if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
return;
QTextStream in(&file);

QString savedFileContents = in.readAll();
if (savedFileContents != this->m_editor->toPlainText().trimmed())
{
// Create box to prompt user to save changes to file
QMessageBox promptBox;
promptBox.setWindowTitle("Changes Detected");
promptBox.setText("Would you like to save the current changes to the file?");
promptBox.setStandardButtons(QMessageBox::Save | QMessageBox::Cancel);
promptBox.setDefaultButton(QMessageBox::Save);
int option = promptBox.exec();
// return if the user hit Cancel button
if (option == QMessageBox::Cancel)
{
return;
}

saveFile();
}
}
else
{
MainWindow *newWindow = new MainWindow();
newWindow->setWindowTitle("Code Astra ~ untitled");
newWindow->show();
}

// // New window will be created with the untitled name

// newWindow->~MainWindow();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will again create a new file in a new window, and not in the current one.
You should not create a new window object as an object is already created at the initialization of the software (the m_mainWindow, the m_ is how you know it is available as a member here).
Plus, you also want to reset the editor (with member editor object m_editor).

Finally, you also want this to be outside of the condition (outside the if/else statement). Otherwise, it will not be applied properly once you fix what I said above.

Here is a possible implementation:

    // Clear the editor and reset the state for a new file
    setCurrentFileName("");
    m_editor->clear();
    if (m_mainWindow)
    {
        m_mainWindow->setWindowTitle("CodeAstra ~ untitled");
    }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have made the fixes so that the editor is only reset. The reason I have created an else statement for cleaning the editor is that if the user exits during the save process, then the user still has the current state of the editor intact. The editor will only be reset to an untitled file if there are no changes to the current saved file or if the file has not been saved. Please let me know if you would like me to keep my logic in there for detecting changes to the current file.

Copy link
Member

@chrisdedman chrisdedman Apr 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see @franciscomartinez45 . Yeah. The problem is that when you save the changes, it does not create a new file (I tested it right now). You may want to wrap this inside an if statement instead of an else statement, such as:

    if( !m_currentFileName.isEmpty())
    {
        setCurrentFileName("");
        m_editor->clear();
        m_mainWindow->setWindowTitle("Code Astra ~ untitled");
    }

Here, if the user is changing their mind, the code will check if the file name is empty; if not empty, it will go through the process and clear the editor. If it is empty, that would mean that the file has not been saved, so it won't clear it.

}

void FileManager::saveFile()
Expand Down Expand Up @@ -75,7 +136,7 @@ void FileManager::saveFile()
void FileManager::saveFileAs()
{
QString fileExtension = getFileExtension();
QString filter = "All Files (*);;C++ Files (*.cpp *.h);;Text Files (*.txt)";
QString filter = "All Files (*);;C++ Files (*.cpp *.h);;Text Files (*.txt)";
if (!fileExtension.isEmpty())
{
filter = QString("%1 Files (*.%2);;%3").arg(fileExtension.toUpper(), fileExtension, filter);
Expand Down Expand Up @@ -235,14 +296,14 @@ OperationResult FileManager::deletePath(const QFileInfo &pathInfo)
}

std::filesystem::path pathToDelete = pathInfo.absoluteFilePath().toStdString();

QString qPathToDelete = QString::fromStdString(pathToDelete.string());
// Validate the input path
if (!isValidPath(pathToDelete))
{
return {false, "ERROR: invalid file path." + pathToDelete.filename().string()};
}

if (!QFile::moveToTrash(pathToDelete))
if (!QFile::moveToTrash(qPathToDelete))
{
return {false, "ERROR: failed to delete: " + pathToDelete.string()};
}
Expand Down Expand Up @@ -324,11 +385,11 @@ OperationResult FileManager::duplicatePath(const QFileInfo &pathInfo)
// Validate the input path
if (!isValidPath(filePath))
{
return {false , "Invalid path."};
return {false, "Invalid path."};
}

std::string fileName = filePath.stem().string();
std::filesystem::path dupPath = filePath.parent_path() / (fileName + "_copy" + filePath.extension().c_str());
std::string fileName = filePath.stem().string();
std::filesystem::path dupPath = filePath.parent_path() / (fileName + "_copy" + filePath.extension().c_str());

int counter = 1;
while (QFileInfo(dupPath).exists())
Expand Down
55 changes: 28 additions & 27 deletions tests/test_mainwindow.cpp
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason for changing QCOMPARE_EQ to QCOMPARE_H?

The change will introduce a serious issue. From my research, QCOMPARE_H is not a valid test assertion macro and has no effect in testing. The original implementation with QCOMPARE_EQ is correct as it checks the equality of the menu bar’s action count and provides meaningful test output in case of failure.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment left on the previous conversation on this file. Thanks

Original file line number Diff line number Diff line change
Expand Up @@ -51,55 +51,56 @@ void TestMainWindow::testMenuBar()
{
QMenuBar *menuBar = mainWindow->menuBar();
QVERIFY2(menuBar != nullptr, "MainWindow must have a QMenuBar.");
QCOMPARE_EQ(menuBar->actions().size(), 3); // File, Help, CodeAstra
QCOMPARE_H(menuBar->actions().size(), 3); // File, Help, CodeAstra

QMenu *fileMenu = menuBar->findChild<QMenu *>("File");
QVERIFY2(fileMenu != nullptr, "QMenuBar must contain a 'File' menu.");
QCOMPARE_EQ(fileMenu->title(), "File");
QCOMPARE_H(fileMenu->title(), "File");

QMenu *helpMenu = menuBar->findChild<QMenu *>("Help");
QVERIFY2(helpMenu != nullptr, "QMenuBar must contain a 'Help' menu.");
QCOMPARE_EQ(helpMenu->title(), "Help");
QCOMPARE_H(helpMenu->title(), "Help");

QMenu *appMenu = menuBar->findChild<QMenu *>("CodeAstra");
QVERIFY2(appMenu != nullptr, "QMenuBar must contain a 'CodeAstra' menu.");
QCOMPARE_EQ(appMenu->title(), "CodeAstra");
QCOMPARE_H(appMenu->title(), "CodeAstra");
}

void TestMainWindow::testInitTree()
{
QSplitter *splitter = dynamic_cast<QSplitter *>(mainWindow->centralWidget());
QVERIFY2(splitter != nullptr, "Central widget should be a QSplitter.");

QCOMPARE_EQ(splitter->handleWidth(), 5);
QCOMPARE_EQ(splitter->childrenCollapsible(), false);
QCOMPARE_EQ(splitter->opaqueResize(), true);
QCOMPARE_H(splitter->handleWidth(), 5);
QCOMPARE_H(splitter->childrenCollapsible(), false);
QCOMPARE_H(splitter->opaqueResize(), true);

QList<int> sizes = splitter->sizes();
QCOMPARE_EQ(sizes.size(), 2);
QCOMPARE_H(sizes.size(), 2);
}

void TestMainWindow::testCreateAction()
{
// Mock parameters for createAction
QIcon icon;
QString text = "Test Action";
QKeySequence shortcut = QKeySequence(Qt::CTRL | Qt::Key_T);
QString statusTip = "This is a test action";
bool slotCalled = false;

auto slot = [&slotCalled]() { slotCalled = true; };

QAction *action = mainWindow->createAction(icon, text, shortcut, statusTip, slot);

QVERIFY2(action != nullptr, "Action should be successfully created.");
QCOMPARE_EQ(action->text(), text);
QCOMPARE_EQ(action->shortcuts().first(), shortcut);
QCOMPARE_EQ(action->statusTip(), statusTip);

// Simulate triggering the action
action->trigger();
QCOMPARE_EQ(slotCalled, true);
// Mock parameters for createAction
QIcon icon;
QString text = "Test Action";
QKeySequence shortcut = QKeySequence(Qt::CTRL | Qt::Key_T);
QString statusTip = "This is a test action";
bool slotCalled = false;

auto slot = [&slotCalled]()
{ slotCalled = true; };

QAction *action = mainWindow->createAction(icon, text, shortcut, statusTip, slot);

QVERIFY2(action != nullptr, "Action should be successfully created.");
QCOMPARE_H(action->text(), text);
QCOMPARE_H(action->shortcuts().first(), shortcut);
QCOMPARE_H(action->statusTip(), statusTip);

// Simulate triggering the action
action->trigger();
QCOMPARE_H(slotCalled, true);
}

QTEST_MAIN(TestMainWindow)
Expand Down
30 changes: 15 additions & 15 deletions tests/test_syntax.cpp
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question as the other test file. I am not sure to understand your logic in changing QCOMPARE_EQ to QCOMPARE_H. I will wait for your answer, but I would prefer to change back to the original implementation.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for all the feed back Chris! I would like to note that I am working on WSL. When I was compiling the code I kept getting issue with the QCOMPARE_EQ function. Instead, it advised me to use QCOMPARE_H. No real logic behind using it besides the fact it makes my code work on my machine. I would also like to not that QCOMPARE() also works. Not sure if it is a machine independent issue.
Screenshot 2025-04-23 154920

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi @franciscomartinez45 . Ok, so I did some research, and your Qt6 version on WSL is probably not updated. QCOMPARE_EQ() is a better and safer feature for testing than QCOMPARE() and was introduced in Qt 6.6.0 (see here https://doc.qt.io/qt-6/qtest.html#QCOMPARE_EQ).

Could you check your Qt version by adding a debugging line (maybe at the start of the program in the main function) and show me what you get?

qDebug() << QT_VERSION_STR;

If your version is greater than 6.6.0, then it would be best to update with QCOMPARE() for now, as QCOMPARE_H() isn't intended for this purpose

Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ YAML::Node createTestConfig()

YAML::Node category1;
YAML::Node rule1;
rule1["regex"] = "\\bint\\b";
rule1["color"] = "#ff0000";
rule1["bold"] = true;
rule1["regex"] = "\\bint\\b";
rule1["color"] = "#ff0000";
rule1["bold"] = true;
rule1["italic"] = false;
category1.push_back(rule1);

YAML::Node rule2;
rule2["regex"] = "\\bfloat\\b";
rule2["color"] = "#00ff00";
rule2["bold"] = false;
rule2["regex"] = "\\bfloat\\b";
rule2["color"] = "#00ff00";
rule2["bold"] = false;
rule2["italic"] = true;
category1.push_back(rule2);

Expand Down Expand Up @@ -51,7 +51,7 @@ void TestSyntax::initTestCase()
{
qDebug() << "Initializing TestSyntax tests...";
document = new QTextDocument();
syntax = new Syntax(document, YAML::Node());
syntax = new Syntax(document, YAML::Node());
}

void TestSyntax::cleanupTestCase()
Expand All @@ -78,17 +78,17 @@ void TestSyntax::testLoadValidSyntaxRules()

// Check the first rule
const auto &rule1 = syntax->m_syntaxRules[0];
QCOMPARE_EQ(rule1.m_pattern.pattern(), "\\bint\\b");
QCOMPARE_EQ(rule1.m_format.foreground().color(), QColor("#ff0000"));
QCOMPARE_EQ(rule1.m_format.fontWeight(), QFont::Bold);
QCOMPARE_NE(rule1.m_format.fontItalic(), true);
QCOMPARE_H(rule1.m_pattern.pattern(), "\\bint\\b");
QCOMPARE_H(rule1.m_format.foreground().color(), QColor("#ff0000"));
QCOMPARE_H(rule1.m_format.fontWeight(), QFont::Bold);
QCOMPARE_H(rule1.m_format.fontItalic(), true);

// Check the second rule
const auto &rule2 = syntax->m_syntaxRules[1];
QCOMPARE_EQ(rule2.m_pattern.pattern(), "\\bfloat\\b");
QCOMPARE_EQ(rule2.m_format.foreground().color(), QColor("#00ff00"));
QCOMPARE_EQ(rule2.m_format.fontWeight(), QFont::Normal);
QCOMPARE_EQ(rule2.m_format.fontItalic(), true);
QCOMPARE_H(rule2.m_pattern.pattern(), "\\bfloat\\b");
QCOMPARE_H(rule2.m_format.foreground().color(), QColor("#00ff00"));
QCOMPARE_H(rule2.m_format.fontWeight(), QFont::Normal);
QCOMPARE_H(rule2.m_format.fontItalic(), true);
}

void TestSyntax::testLoadMissingKeywords()
Expand Down
Loading