← Back to list

QT Project Folder Structure, writing clean code

.txt

Daniel Gakwayya · 2026-03-13 02:32 · 0 claps · 6.5 min read
#qt #cpp11 #windows
Open on Medium ↗
Wiki topics: 💻 · Programming

QT Project Folder Structure, writing clean code

.txt

cmake_minimum_required(VERSION 3.16)

project(NotepadPro LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON)

Enable Qt MOC/UIC/RCC

set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON)

find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Gui Widgets ) qt_standard_project_setup()

qt_add_executable(NotepadPro main.cpp

src/ui/MainWindow.h src/ui/MainWindow.cpp src/ui/MainWindow.ui src/ui/EditorWidget.h src/ui/EditorWidget.cpp

src/core/Document.h src/core/Document.cpp src/core/FileManager.h src/core/FileManager.cpp src/core/SettingsManager.h src/core/SettingsManager.cpp )

target_include_directories(NotepadPro PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src )

set_target_properties(${PROJECT_NAME} PROPERTIES WIN32_EXECUTABLE TRUE )

target_link_libraries(${PROJECT_NAME} PUBLIC Qt::Core Qt::Gui Qt::Widgets )

— — -

pragma once

include <QString>

class Document { public: Document() = default;

void setFilePath(const QString& path); QString filePath() const;

QString displayName() const;

bool isModified() const; void setModified(bool modified);

bool isUntitled() const;

private: QString m_filePath; bool m_modified = false; bool m_isUntitled = true; };

— — — — — — — — — — -

include “Document.h”

include <QFileInfo>

void Document::setFilePath(const QString& path) { m_filePath = path; m_isUntitled = false; }

QString Document::filePath() const { return m_filePath; }

QString Document::displayName() const { if (m_isUntitled) return “Untitled”;

return QFileInfo(m_filePath).fileName(); }

bool Document::isModified() const { return m_modified; }

void Document::setModified(bool modified) { m_modified = modified; }

bool Document::isUntitled() const { return m_isUntitled; }

— —

include “FileManager.h”

include <QFile>

include <QTextStream>

std::optional<QString> FileManager::readFile(const QString& path) { QFile file(path);

if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return std::nullopt;

QTextStream in(&file); in.setEncoding(QStringConverter::Utf8);

return in.readAll(); }

bool FileManager::writeFile(const QString& path, const QString& content) { QFile file(path);

if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) return false;

QTextStream out(&file); out.setEncoding(QStringConverter::Utf8); out << content;

return true; }

— — -—

pragma once

include <QString>

include <optional>

class FileManager { public: static std::optional<QString> readFile(const QString& path); static bool writeFile(const QString& path, const QString& content); };

— — — — — — — —

include “EditorWidget.h”

include “../core/Document.h”

include <QPlainTextEdit>

include <QVBoxLayout>

include <QTextDocument>

EditorWidget::EditorWidget(QWidget* parent) : QWidget(parent), m_document(std::make_unique<Document>()) { m_editor = new QPlainTextEdit(this);

auto* layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(m_editor);

connect(m_editor->document(), &QTextDocument::modificationChanged, this, [this](bool modified) { m_document->setModified(modified); emit modificationChanged(modified); }); }

Document* EditorWidget::document() const { return m_document.get(); }

QString EditorWidget::text() const { return m_editor->toPlainText(); }

void EditorWidget::setText(const QString& text) { m_editor->setPlainText(text); m_document->setModified(false); }

— —

pragma once

include <QWidget>

include <memory>

class QPlainTextEdit; class Document;

class EditorWidget : public QWidget { Q_OBJECT

public: explicit EditorWidget(QWidget* parent = nullptr); ~EditorWidget() override = default;

Document* document() const; QString text() const; void setText(const QString& text);

signals: void modificationChanged(bool modified);

private: QPlainTextEdit* m_editor = nullptr; std::unique_ptr<Document> m_document; };

— — -

include <QTabWidget>

include <QStatusBar>

include <QLabel>

include <QMenuBar>

include <QFileDialog>

include <QMessageBox>

include <QFileInfo>

include <QAction>

include <QCloseEvent>

include “MainWindow.h”

include “../core/FileManager.h”

include “../core/Document.h”

MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) { setupUi(); }

void MainWindow::setupUi() { // Tab Widget (central) m_tabWidget = new QTabWidget(this); m_tabWidget->setTabsClosable(true); m_tabWidget->setMovable(true); m_tabWidget->setDocumentMode(true);

setCentralWidget(m_tabWidget);

// Status Bar m_statusLabel = new QLabel(“Ready”, this); statusBar()->addPermanentWidget(m_statusLabel);

resize(1000, 700); connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, [this](int index) { auto editor = qobject_cast<EditorWidget>(m_tabWidget->widget(index)); if (!editor) return;

if (!maybeSave(editor)) return;

m_tabWidget->removeTab(index); editor->deleteLater(); }); setupMenus(); }

void MainWindow::setupMenus() { m_fileMenu = menuBar()->addMenu(“&File”);

m_actionNew = m_fileMenu->addAction(“&New”); m_actionOpen = m_fileMenu->addAction(“&Open…”); m_actionSave = m_fileMenu->addAction(“&Save”); m_actionSaveAs = m_fileMenu->addAction(“Save &As…”); m_fileMenu->addSeparator(); m_actionExit = m_fileMenu->addAction(“E&xit”);

connect(m_actionNew, &QAction::triggered, this, &MainWindow::createNewTab); connect(m_actionOpen, &QAction::triggered, this, &MainWindow::openFile); connect(m_actionSave, &QAction::triggered, this, &MainWindow::saveFile); connect(m_actionSaveAs, &QAction::triggered, this, &MainWindow::saveFileAs); connect(m_actionExit, &QAction::triggered, this, &QWidget::close); }

void MainWindow::createNewTab() { auto* editor = new EditorWidget(this);

QString title = QString(“Untitled %1”).arg(m_untitledCounter++); addEditorTab(editor, title); }

EditorWidget MainWindow::currentEditor() const { return qobject_cast<EditorWidget>(m_tabWidget->currentWidget()); }

EditorWidget* MainWindow::findOpenEditor(const QString& path) { QString normalized = QFileInfo(path).canonicalFilePath();

for (int i = 0; i < m_tabWidget->count(); ++i) { auto editor = qobject_cast<EditorWidget>(m_tabWidget->widget(i)); if (!editor) continue;

QString openPath = QFileInfo(editor->document()->filePath()).canonicalFilePath();

if (!openPath.isEmpty() && openPath == normalized) return editor; }

return nullptr; }

void MainWindow::openFile() { QString path = QFileDialog::getOpenFileName(this, “Open File”);

if (path.isEmpty()) return;

if (auto* existing = findOpenEditor(path)) { m_tabWidget->setCurrentWidget(existing); return; }

auto content = FileManager::readFile(path); if (!content) { QMessageBox::warning(this, “Error”, “Could not open file.”); return; }

auto editor = new EditorWidget(this); editor->setText(content); editor->document()->setFilePath(path);

addEditorTab(editor, QFileInfo(path).fileName()); }

void MainWindow::saveFile() { auto* editor = currentEditor(); if (!editor) return;

if (editor->document()->isUntitled()) { saveFileAs(); return; }

QString path = editor->document()->filePath();

if (!FileManager::writeFile(path, editor->text())) { QMessageBox::warning(this, “Error”, “Could not save file.”); return; }

editor->document()->setModified(false); }

void MainWindow::saveFileAs() { auto* editor = currentEditor(); if (!editor) return;

QString path = QFileDialog::getSaveFileName(this, “Save File As”);

if (path.isEmpty()) return;

if (!FileManager::writeFile(path, editor->text())) { QMessageBox::warning(this, “Error”, “Could not save file.”); return; }

editor->document()->setFilePath(path); editor->document()->setModified(false);

int index = m_tabWidget->indexOf(editor); m_tabWidget->setTabText(index, QFileInfo(path).fileName()); }

void MainWindow::addEditorTab(EditorWidget* editor, const QString& title) { m_tabWidget->addTab(editor, title); m_tabWidget->setCurrentWidget(editor);

connect(editor, &EditorWidget::modificationChanged, this, [this, editor](bool modified) { int index = m_tabWidget->indexOf(editor); if (index == -1) return;

QString tabTitle = editor->document()->displayName(); if (modified) tabTitle += “*”;

m_tabWidget->setTabText(index, tabTitle); }); }

bool MainWindow::maybeSave(EditorWidget* editor) { if (!editor->document()->isModified()) return true;

QString name = editor->document()->displayName();

auto ret = QMessageBox::warning( this, “Unsaved Changes”, QString(“‘%1’ has unsaved changes.\nDo you want to save?”).arg(name), QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel );

if (ret == QMessageBox::Save) { m_tabWidget->setCurrentWidget(editor); saveFile(); return !editor->document()->isModified(); } else if (ret == QMessageBox::Cancel) { return false; }

return true; // Discard }

void MainWindow::closeEvent(QCloseEvent event) { for (int i = 0; i < m_tabWidget->count(); ++i) { auto editor = qobject_cast<EditorWidget*>(m_tabWidget->widget(i)); if (!editor) continue;

if (!maybeSave(editor)) { event->ignore(); return; } }

event->accept(); }

— —

pragma once

include <QMainWindow>

include “EditorWidget.h”

class QTabWidget; class QLabel; class QAction; class QMenu;

class MainWindow : public QMainWindow { Q_OBJECT

public: explicit MainWindow(QWidget* parent = nullptr); ~MainWindow() override = default;

private: void setupMenus(); void createNewTab(); void openFile(); void saveFile(); void saveFileAs(); EditorWidget currentEditor() const; EditorWidget findOpenEditor(const QString& path); void addEditorTab(EditorWidget editor, const QString& title); bool maybeSave(EditorWidget editor);

private: QMenu* m_fileMenu = nullptr;

QAction m_actionNew = nullptr; QAction m_actionOpen = nullptr; QAction m_actionSave = nullptr; QAction m_actionSaveAs = nullptr; QAction* m_actionExit = nullptr;

int m_untitledCounter = 1;

private: void setupUi();

private: QTabWidget m_tabWidget = nullptr; QLabel m_statusLabel = nullptr;

protected: void closeEvent(QCloseEvent* event) override; };

— —

<?xml version=”1.0" encoding=”UTF-8"?> <ui version=”4.0"> <class>MainWindow</class> <widget class=”QMainWindow” name=”MainWindow”> <property name=”geometry”> <rect> <x>0</x> <y>0</y> <width>1000</width> <height>700</height> </rect> </property> <property name=”windowTitle”> <string>Notepad Clone</string> </property>

<! — CENTRAL WIDGET → <widget class=”QWidget” name=”centralwidget”> <layout class=”QVBoxLayout” name=”verticalLayout”> <property name=”spacing”> <number>0</number> </property> <property name=”leftMargin”> <number>0</number> </property> <property name=”topMargin”> <number>0</number> </property> <property name=”rightMargin”> <number>0</number> </property> <property name=”bottomMargin”> <number>0</number> </property>

<item> <widget class=”QTabWidget” name=”tabWidget”> <property name=”tabsClosable”> <bool>true</bool> </property> <property name=”movable”> <bool>true</bool> </property> <property name=”documentMode”> <bool>true</bool> </property> <property name=”tabPosition”> <enum>QTabWidget::North</enum> </property> </widget> </item>

</layout> </widget>

<! — MENU BAR → <widget class=”QMenuBar” name=”menubar”>

<widget class=”QMenu” name=”menuFile”> <property name=”title”> <string>&amp;File</string> </property> <addaction name=”actionNew”/> <addaction name=”actionOpen”/> <addaction name=”actionSave”/> <addaction name=”actionSaveAs”/> <addaction name=”separator”/> <addaction name=”actionExit”/> </widget>

<widget class=”QMenu” name=”menuEdit”> <property name=”title”> <string>&amp;Edit</string> </property> </widget>

<widget class=”QMenu” name=”menuFormat”> <property name=”title”> <string>F&amp;ormat</string> </property> </widget>

<widget class=”QMenu” name=”menuView”> <property name=”title”> <string>&amp;View</string> </property> </widget>

<widget class=”QMenu” name=”menuHelp”> <property name=”title”> <string>&amp;Help</string> </property> </widget>

<addaction name=”menuFile”/> <addaction name=”menuEdit”/> <addaction name=”menuFormat”/> <addaction name=”menuView”/> <addaction name=”menuHelp”/>

</widget>

<! — STATUS BAR → <widget class=”QStatusBar” name=”statusbar”/>

<! — ACTIONS →

<action name=”actionNew”> <property name=”text”> <string>&amp;New</string> </property> <property name=”shortcut”> <string>Ctrl+N</string> </property> </action>

<action name=”actionOpen”> <property name=”text”> <string>&amp;Open…</string> </property> <property name=”shortcut”> <string>Ctrl+O</string> </property> </action>

<action name=”actionSave”> <property name=”text”> <string>&amp;Save</string> </property> <property name=”shortcut”> <string>Ctrl+S</string> </property> </action>

<action name=”actionSaveAs”> <property name=”text”> <string>Save &amp;As…</string> </property> <property name=”shortcut”> <string>Ctrl+Shift+S</string> </property> </action>

<action name=”actionExit”> <property name=”text”> <string>E&amp;xit</string> </property> <property name=”shortcut”> <string>Alt+F4</string> </property> </action>

</widget>

<resources/> <connections/> </ui>

— —

include “MainWindow.h”

include “ui_MainWindow.h”

include “EditorWidget.h”

include “../core/FileManager.h”

include <QTabWidget>

include <QFileDialog>

include <QMessageBox>

include <QFileInfo>

include <QLabel>

include <QCloseEvent>

MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this);

// Status bar label m_statusLabel = new QLabel(“Ready”, this); statusBar()->addPermanentWidget(m_statusLabel);

// Configure tab widget ui->tabWidget->setTabsClosable(true); ui->tabWidget->setMovable(true); ui->tabWidget->setDocumentMode(true);

// Close tab handling connect(ui->tabWidget, &QTabWidget::tabCloseRequested, this, [this](int index) { auto editor = qobject_cast<EditorWidget>(ui->tabWidget->widget(index));

if (!editor) return;

if (!maybeSave(editor)) return;

ui->tabWidget->removeTab(index); editor->deleteLater(); }); }

MainWindow::~MainWindow() { delete ui; }

EditorWidget MainWindow::currentEditor() const { return qobject_cast<EditorWidget>(ui->tabWidget->currentWidget()); }

void MainWindow::addEditorTab(EditorWidget* editor, const QString& title) { ui->tabWidget->addTab(editor, title); ui->tabWidget->setCurrentWidget(editor);

connect(editor, &EditorWidget::modificationChanged, this, [this, editor](bool modified) { int index = ui->tabWidget->indexOf(editor);

if (index == -1) return;

QString title = editor->document()->displayName();

if (modified) title += “*”;

ui->tabWidget->setTabText(index, title); }); }

EditorWidget* MainWindow::findOpenEditor(const QString& path) { QString normalized = QFileInfo(path).canonicalFilePath();

for (int i = 0; i < ui->tabWidget->count(); ++i) { auto editor = qobject_cast<EditorWidget>(ui->tabWidget->widget(i));

if (!editor) continue;

QString openPath = QFileInfo(editor->document()->filePath()).canonicalFilePath();

if (!openPath.isEmpty() && openPath == normalized) return editor; }

return nullptr; }

void MainWindow::on_actionNew_triggered() { auto* editor = new EditorWidget(this);

QString title = QString(“Untitled %1”).arg(m_untitledCounter++);

addEditorTab(editor, title); }

void MainWindow::on_actionOpen_triggered() { QString path = QFileDialog::getOpenFileName(this, “Open File”);

if (path.isEmpty()) return;

// Prevent duplicate open if (auto* existing = findOpenEditor(path)) { ui->tabWidget->setCurrentWidget(existing); return; }

auto content = FileManager::readFile(path);

if (!content) { QMessageBox::warning(this, “Error”, “Could not open file.”); return; }

auto* editor = new EditorWidget(this);

editor->setText(*content); editor->document()->setFilePath(path);

addEditorTab(editor, QFileInfo(path).fileName()); }

void MainWindow::on_actionSave_triggered() { auto* editor = currentEditor();

if (!editor) return;

if (editor->document()->isUntitled()) { on_actionSaveAs_triggered(); return; }

QString path = editor->document()->filePath();

if (!FileManager::writeFile(path, editor->text())) { QMessageBox::warning(this, “Error”, “Could not save file.”); return; }

editor->document()->setModified(false); }

void MainWindow::on_actionSaveAs_triggered() { auto* editor = currentEditor();

if (!editor) return;

QString path = QFileDialog::getSaveFileName(this, “Save File As”);

if (path.isEmpty()) return;

if (!FileManager::writeFile(path, editor->text())) { QMessageBox::warning(this, “Error”, “Could not save file.”); return; }

editor->document()->setFilePath(path); editor->document()->setModified(false);

int index = ui->tabWidget->indexOf(editor);

ui->tabWidget->setTabText(index, QFileInfo(path).fileName()); }

void MainWindow::on_actionExit_triggered() { close(); }

bool MainWindow::maybeSave(EditorWidget* editor) { if (!editor->document()->isModified()) return true;

QString name = editor->document()->displayName();

auto ret = QMessageBox::warning( this, “Unsaved Changes”, QString(“‘%1’ has unsaved changes.\nDo you want to save?”) .arg(name), QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel );

if (ret == QMessageBox::Save) { ui->tabWidget->setCurrentWidget(editor); on_actionSave_triggered();

return !editor->document()->isModified(); } else if (ret == QMessageBox::Cancel) { return false; }

return true; }

void MainWindow::closeEvent(QCloseEvent event) { for (int i = 0; i < ui->tabWidget->count(); ++i) { auto editor = qobject_cast<EditorWidget*>(ui->tabWidget->widget(i));

if (!editor) continue;

if (!maybeSave(editor)) { event->ignore(); return; } }

event->accept(); }


메타데이터
post_id
5fc52abb64d2
slug
qt-project-folder-structure-writing-clean-code-5fc52abb64d2
url
https://medium.com/@danielgakwayya/qt-project-folder-structure-writing-clean-code-5fc52abb64d2
canonical_url
https://medium.com/@danielgakwayya/qt-project-folder-structure-writing-clean-code-5fc52abb64d2
author_url
https://medium.com/@danielgakwayya
status
ok
fetched_at
2026-06-23 03:48:11