From 36e24807ca8101a50d097b15c8f2c3bed0876e88 Mon Sep 17 00:00:00 2001 From: Remisa Yousefvand Date: Mon, 2 Dec 2024 14:07:34 +0330 Subject: [PATCH] v0.0.59 --- .github/workflows/cmake-multi-platform.yml | 206 ++++++++++++--------- CHANGELOG.md | 20 ++ CMakeLists.txt.user | 22 +-- src/codeeditor.cpp | 99 +++++++++- src/codeeditor.h | 9 + src/document.cpp | 39 +++- src/document.h | 1 + src/helpers.cpp | 12 +- src/helpers.h | 1 + src/indentation/indentationdialog.cpp | 7 +- src/mainwindow.cpp | 83 +++++++-- src/mainwindow.h | 7 +- src/search/filesearchworker.cpp | 28 ++- src/systemfind/richtextdelegate.cpp | 1 + src/systemreplace/systemreplacedialog.cpp | 2 - src/systemsearchresultdialog.cpp | 37 ++-- src/systemtextdelegate.cpp | 6 +- 17 files changed, 426 insertions(+), 154 deletions(-) diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index ca60ee0..216f4fb 100755 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -1,4 +1,4 @@ -name: CI Build +name: Build and Release on: push: @@ -9,134 +9,158 @@ on: jobs: build: runs-on: ${{ matrix.os }} - strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest] + os: [ubuntu-latest, macos-latest, windows-latest] include: - - os: windows-latest - c_compiler: cl - cpp_compiler: cl - os: ubuntu-latest - c_compiler: gcc - cpp_compiler: g++ + artifact_extension: tar.gz - os: macos-latest - c_compiler: clang - cpp_compiler: clang++ + artifact_extension: zip + - os: windows-latest + artifact_extension: zip steps: - name: Checkout code uses: actions/checkout@v4 - ### Ubuntu Dependencies + # Ubuntu Section - name: Install dependencies (Ubuntu) if: matrix.os == 'ubuntu-latest' run: | sudo apt-get update sudo apt-get install -y ninja-build \ qt6-base-dev qt6-tools-dev qt6-tools-dev-tools \ - qt6-l10n-tools libgl1-mesa-dev libglu1-mesa-dev - echo "CMAKE_PREFIX_PATH=/usr/lib/x86_64-linux-gnu/cmake/Qt6" >> $GITHUB_ENV + qt6-l10n-tools libgl1-mesa-dev libglu1-mesa-dev rsync + - name: Configure CMake (Ubuntu) + if: matrix.os == 'ubuntu-latest' + run: | + cmake -B "${{ github.workspace }}/build" \ + -DCMAKE_BUILD_TYPE=Release \ + -G "Ninja" \ + -S "${{ github.workspace }}" + - name: Build Notepad-- (Ubuntu) + if: matrix.os == 'ubuntu-latest' + run: cmake --build "${{ github.workspace }}/build" --config Release + - name: Package Notepad-- (Ubuntu) + if: matrix.os == 'ubuntu-latest' + run: | + BUILD_DIR="${{ github.workspace }}/build" + OUTPUT_DIR="${{ github.workspace }}/artifacts" + mkdir -p $OUTPUT_DIR + tar -czvf $OUTPUT_DIR/Notepad--_ubuntu.tar.gz -C $BUILD_DIR . + - name: Upload Ubuntu Artifact + if: matrix.os == 'ubuntu-latest' + uses: actions/upload-artifact@v4 + with: + name: Notepad--_ubuntu + path: "${{ github.workspace }}/artifacts/Notepad--_ubuntu.tar.gz" - ### macOS Dependencies + # macOS Section - name: Install dependencies (macOS) if: matrix.os == 'macos-latest' run: | brew install ninja qt - echo "CMAKE_PREFIX_PATH=$(brew --prefix qt)" >> $GITHUB_ENV + - name: Configure CMake (macOS) + if: matrix.os == 'macos-latest' + run: | + cmake -B "${{ github.workspace }}/build" \ + -DCMAKE_BUILD_TYPE=Release \ + -G "Ninja" \ + -S "${{ github.workspace }}" + - name: Build Notepad-- (macOS) + if: matrix.os == 'macos-latest' + run: cmake --build "${{ github.workspace }}/build" --config Release + - name: Package Notepad-- (macOS) + if: matrix.os == 'macos-latest' + run: | + BUILD_DIR="${{ github.workspace }}/build" + OUTPUT_DIR="${{ github.workspace }}/artifacts" + mkdir -p $OUTPUT_DIR + zip -r $OUTPUT_DIR/Notepad--_macos.zip $BUILD_DIR + - name: Upload macOS Artifact + if: matrix.os == 'macos-latest' + uses: actions/upload-artifact@v4 + with: + name: Notepad--_macos + path: "${{ github.workspace }}/artifacts/Notepad--_macos.zip" - ### Windows Setup + # Windows Section - name: Install dependencies (Windows) if: matrix.os == 'windows-latest' - shell: powershell run: | - choco install visualstudio2022buildtools -y choco install ninja -y choco install cmake --installargs 'ADD_CMAKE_TO_PATH=System' -y - choco install python3 -y - choco install llvm -y - choco install gperf -y - pip install html5lib - - - name: Cache Qt Source (Windows) + choco install mingw -y + shell: cmd + - name: Install Qt (Windows) if: matrix.os == 'windows-latest' - uses: actions/cache@v3 + uses: jurplel/install-qt-action@v4 with: - path: D:\qt-source - key: qt-source-6.5.3 - restore-keys: qt-source- - - - name: Download Qt Source if Missing (Windows) + version: '6.5.3' + host: 'windows' + target: 'desktop' + arch: 'win64_mingw' + - name: Configure CMake (Windows) if: matrix.os == 'windows-latest' - shell: powershell run: | - $qtSourceDir = "D:\qt-source" - $qtZip = "$qtSourceDir\qt-everywhere-src-6.5.3.zip" - - if (-Not (Test-Path "$qtSourceDir\qt-everywhere-src-6.5.3\qtbase\configure.bat")) { - Write-Host "Qt source not found in cache. Downloading..." - New-Item -ItemType Directory -Path $qtSourceDir -Force - Invoke-WebRequest -Uri "https://download.qt.io/official_releases/qt/6.5/6.5.3/single/qt-everywhere-src-6.5.3.zip" -OutFile $qtZip - Expand-Archive -Path $qtZip -DestinationPath $qtSourceDir - } else { - Write-Host "Qt source found in cache." - } - - - name: Build and Install Qt from Source (Windows) + cmake -B "${{ github.workspace }}\\build" ^ + -DCMAKE_BUILD_TYPE=Release ^ + -G Ninja ^ + -DCMAKE_PREFIX_PATH=D:\\a\\Notepad--\\Qt\\6.5.3\\mingw_64 ^ + -S "${{ github.workspace }}" + shell: cmd + - name: Build Notepad-- (Windows) if: matrix.os == 'windows-latest' + run: cmake --build "${{ github.workspace }}\\build" --config Release shell: cmd + - name: Package Notepad-- (Windows) + if: matrix.os == 'windows-latest' run: | - REM Locate Visual Studio Build Tools - FOR /F "tokens=*" %%i IN ('"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" ^ - -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath') DO SET VS_PATH=%%i - IF NOT DEFINED VS_PATH ( - echo Visual Studio Build Tools not found! && exit /b 1 - ) - SET "VS_VARS_CMD=%VS_PATH%\VC\Auxiliary\Build\vcvars64.bat" - CALL "%VS_VARS_CMD%" || exit /b 1 + $BUILD_DIR="${{ github.workspace }}\\build" + $OUTPUT_DIR="${{ github.workspace }}\\artifacts" + mkdir $OUTPUT_DIR + Compress-Archive -Path "$BUILD_DIR\\*" -DestinationPath "$OUTPUT_DIR\\Notepad--_windows.zip" + shell: pwsh + - name: Upload Windows Artifact + if: matrix.os == 'windows-latest' + uses: actions/upload-artifact@v4 + with: + name: Notepad--_windows + path: "${{ github.workspace }}\\artifacts\\Notepad--_windows.zip" - REM Build Qt from source - SET QT_SOURCE=D:\qt-source\qt-everywhere-src-6.5.3 - IF NOT EXIST "%QT_SOURCE%\qtbase\configure.bat" ( - echo "configure.bat not found in %QT_SOURCE%\qtbase" && exit /b 1 - ) - cd "%QT_SOURCE%\qtbase" - call configure.bat -top-level -prefix C:\Qt -release -opensource -confirm-license -nomake examples -nomake tests -platform win32-msvc -cmake-generator Ninja ^ - -DFEATURE_clang=ON ^ - -DFEATURE_clangcpp=ON ^ - -DLLVM_INSTALL_DIR="C:/Program Files/LLVM" ^ - -Wno-dev || exit /b 1 + release: + runs-on: ubuntu-latest + needs: build + steps: + - name: Checkout code + uses: actions/checkout@v4 - REM Verify if build.ninja exists - IF NOT EXIST build.ninja ( - echo "Error: build.ninja not generated. Check configuration step for issues." && exit /b 1 - ) + - name: Download Artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts - REM Proceed with the build and install steps - ninja || exit /b 1 - ninja install || exit /b 1 + - name: Generate Tag Name + id: tag_name + run: echo "RELEASE_TAG=release-$(date +%Y%m%d%H%M%S)" >> $GITHUB_ENV - - name: Set Qt Environment Variables (Windows) - if: matrix.os == 'windows-latest' - shell: powershell - run: | - echo "Qt6_DIR=C:\Qt\lib\cmake\Qt6" >> $env:GITHUB_ENV - echo "QT_PLUGIN_PATH=C:\Qt\plugins" >> $env:GITHUB_ENV - echo "QML2_IMPORT_PATH=C:\Qt\qml" >> $env:GITHUB_ENV + - name: Create GitHub Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ env.RELEASE_TAG }} + release_name: Notepad-- Release + draft: false + prerelease: false - - name: Configure CMake + - name: Upload Artifacts to Release run: | - cmake -B "${{ github.workspace }}/build" \ - -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} \ - -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} \ - -DCMAKE_BUILD_TYPE=Release \ - -G "Ninja" \ - -S "${{ github.workspace }}" - - - name: Build - run: cmake --build "${{ github.workspace }}/build" --config Release - - - name: Test - working-directory: "${{ github.workspace }}/build" - run: ctest --output-on-failure --build-config Release + for file in $(find artifacts -type f); do + gh release upload ${{ env.RELEASE_TAG }} "$file" + done + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index da638b6..dbfa818 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Change Log +## 0.0.59 + +- Default Tab Fixed + +## 0.0.58 + +- View -> Show Symbol -> Show Tabs + +## 0.0.57 + +- Indentation Implemented + +## 0.0.56 + +- Search & Replace Menu -> Fixed double click on results + +## 0.0.55 + +- Search & Replace Menu -> Fixed casing in search + ## 0.0.54 - Implemented: diff --git a/CMakeLists.txt.user b/CMakeLists.txt.user index a2a3473..0e3b4c0 100755 --- a/CMakeLists.txt.user +++ b/CMakeLists.txt.user @@ -1,6 +1,6 @@ - + EnvironmentId @@ -103,13 +103,13 @@ false -DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} --DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} --DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} -DCMAKE_BUILD_TYPE:STRING=Debug --DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DCMAKE_GENERATOR:STRING=Ninja -DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake --DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} --DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} /data/Code/Qt/Notepad-- 0 /data/Code/Qt/Notepad--/build/Desktop-Debug @@ -161,13 +161,13 @@ false -DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} --DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} --DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} -DCMAKE_BUILD_TYPE:STRING=Release --DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DCMAKE_GENERATOR:STRING=Ninja -DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake --DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} --DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} /data/Code/Qt/Notepad-- /data/Code/Qt/Notepad--/build/Desktop-Release diff --git a/src/codeeditor.cpp b/src/codeeditor.cpp index 3c067ff..de48379 100755 --- a/src/codeeditor.cpp +++ b/src/codeeditor.cpp @@ -2,7 +2,8 @@ #include #include #include -#include "helpers.h" +#include +#include "settings.h" CodeEditor::CodeEditor(QWidget *parent) : QPlainTextEdit(parent), lineNumberArea(new LineNumberArea(this)) { @@ -26,6 +27,11 @@ CodeEditor::CodeEditor(QWidget *parent) updateLineNumberAreaWidth(0); highlightCurrentLine(); + + m_useTabs = Settings::instance()->loadSetting("Indentation", "Option", "Tabs") == "Tabs"; + m_indentationWidth = Settings::instance()->loadSetting("Indentation", "Size", "1").toInt(); + m_showTabs = Settings::instance()->loadSetting("View", "ShowTabs", "false") == "true"; + m_tabWidth = Settings::instance()->loadSetting("View", "TabWidth", "4").toInt(); } int CodeEditor::lineNumberAreaWidth() { @@ -154,8 +160,11 @@ void CodeEditor::lineNumberAreaPaintEvent(QPaintEvent *event) { } void CodeEditor::applyIndentation(bool useTabs, int indentationWidth) { + m_useTabs = useTabs; + m_indentationWidth = indentationWidth; QTextCursor cursor = textCursor(); - QString indentation = useTabs ? "\t" : QString(indentationWidth, ' '); + QString indentation = useTabs ? QString(indentationWidth, '\t') + : QString(indentationWidth, ' '); cursor.insertText(indentation); } @@ -213,3 +222,89 @@ void CodeEditor::gotoLineInEditor(int lineNumber) { } } +void CodeEditor::keyPressEvent(QKeyEvent *event) { + if (event->key() == Qt::Key_Tab) { + // User pressed Tab + QTextCursor cursor = textCursor(); + + // Determine the indentation string (tabs or spaces) + QString indentation = m_useTabs ? QString(m_indentationWidth, '\t') + : QString(m_indentationWidth, ' '); + + // Insert the indentation + cursor.insertText(indentation); + + // Prevent further handling of the Tab key + return; + } else if (event->key() == Qt::Key_Backtab) { + // User pressed Shift+Tab (outdent logic) + QTextCursor cursor = textCursor(); + cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor); + + QString blockText = cursor.selectedText(); + if (m_useTabs && blockText.startsWith(QString(m_indentationWidth, '\t'))) { + cursor.removeSelectedText(); + } else if (!m_useTabs && blockText.startsWith(QString(m_indentationWidth, ' '))) { + cursor.removeSelectedText(); + } + + // Prevent further handling of Shift+Tab + return; + } + + // Pass other keys to the default handler + QPlainTextEdit::keyPressEvent(event); +} + +void CodeEditor::setShowTabs(bool enabled) { + if (m_showTabs != enabled) { + m_showTabs = enabled; + viewport()->update(); // Redraw the editor to show or hide tab symbols + } +} + +bool CodeEditor::showTabs() const { + return m_showTabs; +} + +void CodeEditor::setTabWidth(int width = 4) { + m_tabWidth = width; + viewport()->update(); // Trigger a repaint to apply the new width +} + +void CodeEditor::paintEvent(QPaintEvent *event) { + QPlainTextEdit::paintEvent(event); + + if (!m_showTabs) return; + + QPainter painter(viewport()); + painter.setPen(Qt::gray); + + QTextBlock block = document()->firstBlock(); + QFontMetrics metrics(font()); + + while (block.isValid()) { + QString text = block.text(); + int blockStart = block.position(); + QTextCursor blockCursor(block); + + // Iterate over characters in the block + for (int i = 0; i < text.length(); ++i) { + if (text[i] == '\t') { + // Move the cursor to the tab character + blockCursor.setPosition(blockStart + i); + + // Get the rectangle of the cursor position + QRect rect = cursorRect(blockCursor); + + // Adjust the position for the tab symbol + //QPoint position(rect.left(), rect.top() + metrics.ascent()); + QPoint position(rect.left() + metrics.ascent(), rect.top() + metrics.ascent()); + + // Draw the tab symbol + painter.drawText(position, "→"); + } + } + block = block.next(); + } +} diff --git a/src/codeeditor.h b/src/codeeditor.h index 00bc0d2..a8a5360 100755 --- a/src/codeeditor.h +++ b/src/codeeditor.h @@ -24,9 +24,14 @@ class CodeEditor : public QPlainTextEdit { void highlightAllOccurrences(const QString& keyword); void goToLineInText(int lineNumber); void gotoLineInEditor(int lineNumber); + void setShowTabs(bool enabled); + bool showTabs() const; + void setTabWidth(int width); protected: void resizeEvent(QResizeEvent *event) override; + virtual void keyPressEvent(QKeyEvent *event) override; + void paintEvent(QPaintEvent *event) override; signals: void textChanged(); // FIXME: Remove this line. @@ -38,6 +43,10 @@ private slots: private: QWidget *lineNumberArea; QTabWidget* m_documentsTab; + bool m_useTabs; + int m_indentationWidth; + bool m_showTabs = false; + int m_tabWidth; }; class LineNumberArea : public QWidget { diff --git a/src/document.cpp b/src/document.cpp index 22bd00c..9a46d8e 100755 --- a/src/document.cpp +++ b/src/document.cpp @@ -14,6 +14,7 @@ #include #include #include +#include "helpers.h" #include "document.h" #include "fileloaderworker.h" #include "codeeditor.h" @@ -320,6 +321,7 @@ void Document::saveAcopyAs() { bool Document::closeDocument() { qDebug() << "Checking document close: isLoading=" << m_isLoading << ", isSaving=" << m_isSaving; + // FIXME: m_isLoading sometimes is true wrongly if (m_isLoading || m_isSaving) { qDebug() << "Document still loading or saving, cannot close."; return false; @@ -335,7 +337,28 @@ bool Document::closeDocument() { QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); if (reply == QMessageBox::Save) { - saveFile(); // Save the document + if (Helpers::isValidFilePath(m_filePath)) { + saveFile(); // Save the document + } else { + if (m_editor->document()) { + // Show the "Save As" dialog + QString filePath = QFileDialog::getSaveFileName( + this, QObject::tr("Save File As"), "", + QObject::tr("Text Files (*.txt);;All Files (*)")); + + if (!filePath.isEmpty()) { + // Save the file and update the tab name + saveFileAs(filePath); + QFileInfo fileInfo(filePath); + QString title = fileInfo.completeBaseName(); + setTitle(title); + } + } else { + // Show a warning if no document is available + QMessageBox::warning(this, QObject::tr("Error"), QObject::tr("No document to save.")); + } + } + return true; // After saving, allow closing the tab } else if (reply == QMessageBox::Discard) { return true; // Discard changes and close the document @@ -348,6 +371,20 @@ bool Document::closeDocument() { return true; } +void Document::setTitle(const QString &title) { + if (!title.isEmpty()) { + if (auto tabWidget = qobject_cast(parentWidget())) { + int tabIndex = tabWidget->indexOf(this); + if (tabIndex != -1) { + tabWidget->setTabText(tabIndex, title); + qDebug() << "Tab title updated to:" << title; + } + } + } else { + qDebug() << "Empty title provided. No changes made."; + } +} + void Document::goToLineNumberInEditor() { bool ok; int lineNumber = QInputDialog::getInt(this, tr("Go to Line"), diff --git a/src/document.h b/src/document.h index 340cfeb..63c11a7 100755 --- a/src/document.h +++ b/src/document.h @@ -50,6 +50,7 @@ class Document : public QWidget { int savedCursorPosition() const; void setSavedCursorPosition(int position); QThread* workerThread() const; + void setTitle(const QString &title); signals: void uiReady(); diff --git a/src/helpers.cpp b/src/helpers.cpp index 7de51ea..cf49646 100644 --- a/src/helpers.cpp +++ b/src/helpers.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -29,12 +30,6 @@ void Helpers::RemoveMe(QTabWidget* documentsTab) { } } -void Helpers::AddDefaultTab(QTabWidget* documentsTab) { - Document* defaultDoc = new Document("Untitled Document", documentsTab); - documentsTab->addTab(defaultDoc, "Untitled Document"); - documentsTab->setCurrentWidget(defaultDoc); -} - void Helpers::CloseTab(QTabWidget* documentsTab, int index) { QWidget* tab = documentsTab->widget(index); if (tab) { @@ -127,6 +122,11 @@ bool Helpers::isValidRegularExpression(const QString& pattern) { return regex.isValid(); } +bool Helpers::isValidFilePath(const QString& filePath) { + QFileInfo fileInfo(filePath); + return fileInfo.exists() && fileInfo.isFile(); +} + int Helpers::countKeywordsInLine(const QString& line, const SearchOptions& options) { QString keyword = options.keyword; if (keyword.isEmpty()) { diff --git a/src/helpers.h b/src/helpers.h index 07541b0..13c9dd4 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -24,6 +24,7 @@ class Helpers { static void showInformationMessage(const QString& message); static void notImplemented(QWidget* parent = nullptr); static bool isValidRegularExpression(const QString& pattern); + static bool isValidFilePath(const QString& filePath); static int countKeywordsInLine(const QString &line, const SearchOptions &searchOptions); static QString highlightKeywords(const QString& line, const SearchOptions& options); diff --git a/src/indentation/indentationdialog.cpp b/src/indentation/indentationdialog.cpp index c622922..cbf2d9c 100644 --- a/src/indentation/indentationdialog.cpp +++ b/src/indentation/indentationdialog.cpp @@ -71,15 +71,14 @@ void IndentationDialog::on_buttonBox_accepted() bool useTabs = ui->tabs->isChecked(); int indentationWidth = ui->number->value(); - auto* settings = Settings::instance(); - settings->saveSetting("Indentation", "Option", useTabs ? "Tabs" : "Spaces"); - settings->saveSetting("Indentation", "Size", indentationWidth); + Settings::instance()->saveSetting("Indentation", "Option", useTabs ? "Tabs" : "Spaces"); + Settings::instance()->saveSetting("Indentation", "Size", indentationWidth); Document* doc = qobject_cast(parent()); if (doc && doc->editor()) { doc->editor()->applyIndentation(useTabs, indentationWidth); } - Helpers::notImplemented(this); + QMessageBox::information(this, "Information", "Restart needed.", QMessageBox::Ok); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ede93a1..6e9ae35 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -53,7 +53,7 @@ MainWindow::MainWindow(QWidget* parent) formatting->setupActions(ui->actionWindows_Format, ui->action_Unix_OS_X_Format, ui->action_Old_Mac_Format); Helpers::RemoveMe(ui->documentsTab); - Helpers::AddDefaultTab(ui->documentsTab); + fileOperations->newDocument(); Helpers::zMenu(ui->menu_Language, this); qDebug() << "MainWindow initialized..."; @@ -67,6 +67,8 @@ MainWindow::~MainWindow() { delete ui; delete fileOperations; delete textOperations; + delete m_systemFindDialog; + delete m_systemReplaceDialog; } Ui::MainWindow* MainWindow::getUi() const { @@ -366,7 +368,6 @@ void MainWindow::on_actionReplace_P_revious_triggered() } void MainWindow::on_actionFind_System_triggered() { - qDebug() << "on_actionFind_System_triggered called"; if (!m_systemFindDialog) { qDebug() << "Creating new SystemFindDialog"; @@ -382,7 +383,7 @@ void MainWindow::on_actionFind_System_triggered() { }); // Ensure connections are established - setupSearchResultDialogConnections(); + setupSearchResultDialogConnectionsForFind(); m_systemFindDialog->show(); } else { @@ -392,7 +393,33 @@ void MainWindow::on_actionFind_System_triggered() { } } -void MainWindow::setupSearchResultDialogConnections() { +void MainWindow::on_actionReplace_S_ystem_triggered() +{ + if (!m_systemReplaceDialog) { + qDebug() << "Creating new SystemReplaceDialog"; + + m_systemReplaceDialog = new SystemReplaceDialog(this); + m_systemReplaceDialog->setWindowModality(Qt::NonModal); + m_systemReplaceDialog->setAttribute(Qt::WA_DeleteOnClose); + + // Reset pointer when dialog is destroyed + connect(m_systemReplaceDialog, &QObject::destroyed, this, [this]() { + qDebug() << "SystemReplaceDialog destroyed. Resetting pointer."; + m_systemReplaceDialog = nullptr; + }); + + // Ensure connections are established + setupSearchResultDialogConnectionsForReplace(); + + m_systemReplaceDialog->show(); + } else { + qDebug() << "SystemReplaceDialog already exists. Bringing it to the front."; + m_systemReplaceDialog->raise(); + m_systemReplaceDialog->activateWindow(); + } +} + +void MainWindow::setupSearchResultDialogConnectionsForFind() { QTimer::singleShot(0, this, [this]() { SystemSearchResultDialog* m_systemSearchResultDialog = m_systemFindDialog->findChild("SystemSearchResultDialog"); @@ -402,21 +429,34 @@ void MainWindow::setupSearchResultDialogConnections() { // Re-establish the signal-slot connection connect(m_systemSearchResultDialog, &SystemSearchResultDialog::openFileAtMatch, - this, &MainWindow::openSearchResult, Qt::UniqueConnection); + this, &MainWindow::openSearchResult); qDebug() << "Signal-Slot Connection for SystemSearchResultDialog re-established."; } else { qDebug() << "SystemSearchResultDialog not found. Retrying..."; - QTimer::singleShot(100, this, &MainWindow::setupSearchResultDialogConnections); + QTimer::singleShot(100, this, &MainWindow::setupSearchResultDialogConnectionsForFind); } }); } -void MainWindow::on_actionReplace_S_ystem_triggered() -{ - SystemReplaceDialog* systemReplaceDialog = new SystemReplaceDialog(this); - systemReplaceDialog->setWindowModality(Qt::NonModal); - systemReplaceDialog->show(); +void MainWindow::setupSearchResultDialogConnectionsForReplace() { + QTimer::singleShot(0, this, [this]() { + SystemSearchResultDialog* m_systemSearchResultDialog = + m_systemReplaceDialog->findChild("SystemSearchResultDialog"); + + if (m_systemSearchResultDialog) { + qDebug() << "SystemSearchResultDialog found:" << m_systemSearchResultDialog; + + // Re-establish the signal-slot connection + connect(m_systemSearchResultDialog, &SystemSearchResultDialog::openFileAtMatch, + this, &MainWindow::openSearchResult); + + qDebug() << "Signal-Slot Connection for SystemSearchResultDialog re-established."; + } else { + qDebug() << "SystemSearchResultDialog not found. Retrying..."; + QTimer::singleShot(100, this, &MainWindow::setupSearchResultDialogConnectionsForReplace); + } + }); } void MainWindow::on_actionGo_to_Line_in_Text_triggered() @@ -452,15 +492,24 @@ void MainWindow::on_action_Find_triggered() { } } +/* View Menu */ +void MainWindow::on_action_Show_Tabs_triggered(bool checked) +{ + qDebug() << "Show Tabs is: " << checked; + Settings::instance()->saveSetting("View", "ShowTabs", checked ? "true" : "false"); + for (int i = 0; i < ui->documentsTab->count(); ++i) { + Document *doc = qobject_cast(ui->documentsTab->widget(i)); + if (doc) { + doc->editor()->setShowTabs(checked); + } + } +} -/* View Menu */ - - @@ -473,9 +522,6 @@ void MainWindow::on_action_Find_triggered() { /* Helper Functions */ - - - // FIXME: Wrong line selected void MainWindow::openSearchResult(const QString &filePath, int lineNumber) { qInfo() << "openSearchResult called. File Path:" << filePath << ", Line Number:" << lineNumber; @@ -605,6 +651,3 @@ void MainWindow::setActiveDocumentEditorInReplaceDialog() { - - - diff --git a/src/mainwindow.h b/src/mainwindow.h index e210862..be9e7f0 100755 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -13,6 +13,7 @@ #include "mainwindow/textoperations.h" #include "indentation/indentationmanager.h" #include "systemfind/systemfinddialog.h" +#include "systemreplace/systemreplacedialog.h" #include "systemsearchresultdialog.h" QT_BEGIN_NAMESPACE @@ -144,6 +145,8 @@ private slots: void on_actionGo_to_Line_in_Editor_triggered(); + void on_action_Show_Tabs_triggered(bool checked); + private: Ui::MainWindow* ui; FileOperations* fileOperations; @@ -156,7 +159,8 @@ private slots: void applyColorCoding(Document* doc, bool isModified); void setActiveDocumentEditorInFindDialog(); void setActiveDocumentEditorInReplaceDialog(); - void setupSearchResultDialogConnections(); + void setupSearchResultDialogConnectionsForFind(); + void setupSearchResultDialogConnectionsForReplace(); void setupIndentationMenu(); QAction* action_Custom; @@ -166,6 +170,7 @@ private slots: void loadIndentationSetting(); // Load and apply the saved setting IndentationManager* indentationManager; SystemFindDialog* m_systemFindDialog = nullptr; + SystemReplaceDialog* m_systemReplaceDialog = nullptr; SystemSearchResultDialog* m_systemSearchResultDialog; FindDialog* findDialog; ReplaceDialog* replaceDialog; diff --git a/src/search/filesearchworker.cpp b/src/search/filesearchworker.cpp index 930940e..31e1f39 100644 --- a/src/search/filesearchworker.cpp +++ b/src/search/filesearchworker.cpp @@ -39,6 +39,7 @@ FileSearchResults FileSearchWorker::searchInFile() { bool hasMatch = false; QString highlightedLine = line; // For highlighting + qInfo() << "Highlight line from threadpool worker: " << highlightedLine; while (it.hasNext()) { QRegularExpressionMatch match = it.next(); @@ -65,6 +66,29 @@ FileSearchResults FileSearchWorker::searchInFile() { } QString FileSearchWorker::highlightMatches(const QString& line, const QRegularExpression& pattern) { - QString highlighted = line; - return highlighted.replace(pattern, "" + m_options.keyword + ""); + qInfo() << "highlightMatches input line: " << line; + QString highlighted; + int lastIndex = 0; // Track the last processed index in the line + + // Find all matches in the line + QRegularExpressionMatchIterator it = pattern.globalMatch(line); + while (it.hasNext()) { + QRegularExpressionMatch match = it.next(); + + // Append text from the last index to the current match start + highlighted.append(line.mid(lastIndex, match.capturedStart(0) - lastIndex)); + + // Wrap the matched text with tags + highlighted.append(QStringLiteral("%1").arg(match.captured(0))); + + // Update the last processed index + lastIndex = match.capturedEnd(0); + } + + // Append any remaining text after the last match + highlighted.append(line.mid(lastIndex)); + qInfo() << "highlightMatches output line: " << line; + + return highlighted; } + diff --git a/src/systemfind/richtextdelegate.cpp b/src/systemfind/richtextdelegate.cpp index 544d02c..b9744f1 100644 --- a/src/systemfind/richtextdelegate.cpp +++ b/src/systemfind/richtextdelegate.cpp @@ -53,6 +53,7 @@ void RichTextDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti } QSize RichTextDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { + Q_UNUSED(option); QTextDocument doc; QString text = index.data().toString(); diff --git a/src/systemreplace/systemreplacedialog.cpp b/src/systemreplace/systemreplacedialog.cpp index 8721b78..2710068 100644 --- a/src/systemreplace/systemreplacedialog.cpp +++ b/src/systemreplace/systemreplacedialog.cpp @@ -46,8 +46,6 @@ SystemReplaceDialog::SystemReplaceDialog(QWidget *parent) SystemReplaceDialog::~SystemReplaceDialog() { delete ui; - delete m_replace; - delete m_searchOptions; delete m_systemSearchResultDialog; } diff --git a/src/systemsearchresultdialog.cpp b/src/systemsearchresultdialog.cpp index 8f518e7..bab89b4 100644 --- a/src/systemsearchresultdialog.cpp +++ b/src/systemsearchresultdialog.cpp @@ -138,20 +138,33 @@ void SystemSearchResultDialog::addSearchResult(const FileSearchResults &result) // Iterate through the matches to populate the result model for (const auto &[lineNumber, lineContent] : result.matches) { - // Highlight keywords with cyan + qInfo() << "addSearchResult lineContent input: " << lineContent; + QString highlightedLine = lineContent; - QString pattern = QStringLiteral("%1"); - highlightedLine.replace( - QRegularExpression(QString("\\b%1\\b").arg(QRegularExpression::escape(m_searchOptions.keyword)), - m_searchOptions.matchCase - ? QRegularExpression::NoPatternOption - : QRegularExpression::CaseInsensitiveOption), - pattern.arg(m_searchOptions.keyword)); + QRegularExpression pattern( + QString("\\b%1\\b").arg(QRegularExpression::escape(m_searchOptions.keyword)), + m_searchOptions.matchCase + ? QRegularExpression::NoPatternOption + : QRegularExpression::CaseInsensitiveOption); + + QString processedLine; + int lastPos = 0; + + // Highlight the matches manually + QRegularExpressionMatchIterator it = pattern.globalMatch(highlightedLine); + while (it.hasNext()) { + QRegularExpressionMatch match = it.next(); + processedLine += highlightedLine.mid(lastPos, match.capturedStart() - lastPos); + processedLine += QStringLiteral("%1") + .arg(match.captured(0)); + lastPos = match.capturedEnd(); + } + processedLine += highlightedLine.mid(lastPos); // Append the remaining part QStandardItem *lineItem = new QStandardItem(); lineItem->setFlags(lineItem->flags() | Qt::ItemIsSelectable); - lineItem->setData(highlightedLine, Qt::DisplayRole); // Highlighted line content - lineItem->setData(lineNumber + 1, Qt::UserRole); // Line number (1-based) + lineItem->setData(processedLine, Qt::DisplayRole); // Highlighted line content + lineItem->setData(lineNumber + 1, Qt::UserRole); // Line number (1-based) QStandardItem *matchesItem = new QStandardItem(QString::number(1)); matchesItem->setFlags(matchesItem->flags() | Qt::ItemIsSelectable); @@ -171,11 +184,9 @@ void SystemSearchResultDialog::addSearchResult(const FileSearchResults &result) << "Reported matches:" << result.matchCount << "First keyword line number:" << firstKeywordLineNumber; - // If needed, handle the first keyword line highlighting + // Highlight the first match in the editor if (firstKeywordLineNumber > 0) { qDebug() << "First keyword found at line:" << firstKeywordLineNumber << "in file:" << result.filePath; - // Call the highlighting function here, if necessary - // Example: highlightLineInView(result.filePath, firstKeywordLineNumber); } } diff --git a/src/systemtextdelegate.cpp b/src/systemtextdelegate.cpp index a98385d..07b3cac 100644 --- a/src/systemtextdelegate.cpp +++ b/src/systemtextdelegate.cpp @@ -1,7 +1,11 @@ -#include "systemtextdelegate.h" #include #include #include +#include +#include +#include +#include +#include "systemtextdelegate.h" SystemTextDelegate::SystemTextDelegate(QObject *parent) : QStyledItemDelegate(parent) {}