← index
ACMX2/interface/editor.cpp
Source: ACMX2/interface/editor.cpp
#include "editor.hpp"
#include <QPlainTextEdit>
#include <QVBoxLayout>
#include <QSyntaxHighlighter>
#include <QTextCharFormat>
#include <QRegularExpression>
#include <QColor>
#include <QFont>
#include <QMenuBar>
#include <QAction>
#include <QFile>
#include <QFileDialog>
#include <QMessageBox>
#include <QInputDialog>
#include <QTextCursor>
#include <QClipboard>
#include <QApplication>
#include <QLabel>
#include <QStatusBar>
#include<QFile>
#include<QTextStream>

TextEditor::TextEditor(QWidget *parent)
    : QDialog(parent), m_modified(false), m_textEdit(nullptr), m_highlighter(nullptr), 
      m_statusBar(nullptr), m_lineColLabel(nullptr), vec(nullptr), index(-1), m_fontSize(24)
{
    init();
}

void TextEditor::setText(const QString &text) {
    m_textEdit->setPlainText(text);
    m_modified = false;
}

void TextEditor::setFileName(const QString &filen) {
    filename = filen;
    updateWindowTitle();
}

void TextEditor::updateWindowTitle() {
    QString title = "ACMX2";
    if (!filename.isEmpty()) {
        title += " - " + QFileInfo(filename).fileName();
    }
    if (m_modified) {
        title += " *";
    }
    setWindowTitle(title);
}

void TextEditor::init() {
    m_modified = false;
    m_fontSize = 24;
    
    QVBoxLayout* layout = new QVBoxLayout(this);
    
    QMenuBar *menuBar = new QMenuBar(this);
    
    QMenu *fileMenu = menuBar->addMenu("&File");
    
    QAction *saveAction = fileMenu->addAction("&Save");
    saveAction->setShortcut(QKeySequence::Save);
    
    QAction *saveAsAction = fileMenu->addAction("Save &As...");
    saveAsAction->setShortcut(QKeySequence::SaveAs);
    
    fileMenu->addSeparator();
    
    QAction *closeAction = fileMenu->addAction("&Close");
    closeAction->setShortcut(QKeySequence::Close);
    
    QMenu *editMenu = menuBar->addMenu("&Edit");
    
    QAction *undoAction = editMenu->addAction("&Undo");
    undoAction->setShortcut(QKeySequence::Undo);
    
    QAction *redoAction = editMenu->addAction("&Redo");
    redoAction->setShortcut(QKeySequence::Redo);
    
    editMenu->addSeparator();
    
    QAction *cutAction = editMenu->addAction("Cu&t");
    cutAction->setShortcut(QKeySequence::Cut);
    
    QAction *copyAction = editMenu->addAction("&Copy");
    copyAction->setShortcut(QKeySequence::Copy);
    
    QAction *pasteAction = editMenu->addAction("&Paste");
    pasteAction->setShortcut(QKeySequence::Paste);
    
    editMenu->addSeparator();
    
    QAction *selectAllAction = editMenu->addAction("Select &All");
    selectAllAction->setShortcut(QKeySequence::SelectAll);
    
    editMenu->addSeparator();
    
    QAction *findAction = editMenu->addAction("&Find...");
    findAction->setShortcut(QKeySequence::Find);
    
    QAction *findNextAction = editMenu->addAction("Find &Next");
    findNextAction->setShortcut(QKeySequence::FindNext);
    
    QAction *replaceAction = editMenu->addAction("&Replace...");
    replaceAction->setShortcut(QKeySequence::Replace);
    
    editMenu->addSeparator();
    
    QAction *gotoLineAction = editMenu->addAction("&Go to Line...");
    gotoLineAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_G));
    
    QMenu *viewMenu = menuBar->addMenu("&View");
    
    QAction *increaseFontAction = viewMenu->addAction("Increase Font Size");
    increaseFontAction->setShortcut(QKeySequence::ZoomIn);
    
    QAction *decreaseFontAction = viewMenu->addAction("Decrease Font Size");
    decreaseFontAction->setShortcut(QKeySequence::ZoomOut);
    
    QAction *resetFontAction = viewMenu->addAction("Reset Font Size");
    resetFontAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_0));
    
    viewMenu->addSeparator();
    
    QAction *toggleWordWrapAction = viewMenu->addAction("Word Wrap");
    toggleWordWrapAction->setCheckable(true);
    toggleWordWrapAction->setChecked(false);
    
    layout->setMenuBar(menuBar);
    
    m_textEdit = new CustomTextEdit(this);
    m_textEdit->setTabStopDistance(4 * m_textEdit->fontMetrics().horizontalAdvance(' '));
    updateFontSize();
    
    layout->addWidget(m_textEdit);
    
    m_statusBar = new QStatusBar(this);
    m_lineColLabel = new QLabel("Line: 1, Col: 1", this);
    m_statusBar->addPermanentWidget(m_lineColLabel);
    layout->addWidget(m_statusBar);
    
    m_highlighter = new GlslSyntaxHighlighter(m_textEdit->document());
    
    setLayout(layout);
    setGeometry(300, 300, 1024, 768);
    
    connect(saveAction, &QAction::triggered, this, &TextEditor::saveContents);
    connect(saveAsAction, &QAction::triggered, this, &TextEditor::saveAs);
    connect(closeAction, &QAction::triggered, this, &TextEditor::close);
    
    connect(undoAction, &QAction::triggered, m_textEdit, &QPlainTextEdit::undo);
    connect(redoAction, &QAction::triggered, m_textEdit, &QPlainTextEdit::redo);
    connect(cutAction, &QAction::triggered, m_textEdit, &QPlainTextEdit::cut);
    connect(copyAction, &QAction::triggered, m_textEdit, &QPlainTextEdit::copy);
    connect(pasteAction, &QAction::triggered, m_textEdit, &QPlainTextEdit::paste);
    connect(selectAllAction, &QAction::triggered, m_textEdit, &QPlainTextEdit::selectAll);
    
    connect(findAction, &QAction::triggered, this, &TextEditor::findText);
    connect(findNextAction, &QAction::triggered, this, &TextEditor::findNext);
    connect(replaceAction, &QAction::triggered, this, &TextEditor::replaceText);
    connect(gotoLineAction, &QAction::triggered, this, &TextEditor::gotoLine);
    
    connect(increaseFontAction, &QAction::triggered, this, &TextEditor::increaseFontSize);
    connect(decreaseFontAction, &QAction::triggered, this, &TextEditor::decreaseFontSize);
    connect(resetFontAction, &QAction::triggered, this, &TextEditor::resetFontSize);
    
    connect(toggleWordWrapAction, &QAction::triggered, this, [this](bool checked) {
        m_textEdit->setLineWrapMode(checked ? QPlainTextEdit::WidgetWidth : QPlainTextEdit::NoWrap);
    });
    
    connect(m_textEdit, &QPlainTextEdit::textChanged, this, [this]() {
        m_modified = true;
        updateWindowTitle();
    });
    
    connect(m_textEdit, &QPlainTextEdit::cursorPositionChanged, this, &TextEditor::updateCursorPosition);
    
    connect(this, &QDialog::finished, this, [this]() {
        if (vec && index >= 0 && index < vec->size()) {
            vec->removeAt(index);
        }
    });
    
    setAttribute(Qt::WA_DeleteOnClose);
}

void TextEditor::saveContents() {
    if (filename.isEmpty()) {
        saveAs();
        return;
    }
    
    QFile file(filename);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QMessageBox::warning(this, "Error", "Could not save file: " + filename);
        return;
    }
    
    QString content = m_textEdit->toPlainText();
    QTextStream out(&file);
    out << content;
    file.close();
    
    m_modified = false;
    updateWindowTitle();
    m_statusBar->showMessage("File saved", 2000);
}

void TextEditor::saveAs() {
    QString newFileName = QFileDialog::getSaveFileName(
        this, "Save File As", filename, "GLSL Files (*.glsl *.frag *.vert);;All Files (*)");
    
    if (!newFileName.isEmpty()) {
        filename = newFileName;
        saveContents();
    }
}

void TextEditor::findText() {
    bool ok;
    QString searchText = QInputDialog::getText(this, "Find", "Enter text to find:", 
                                                QLineEdit::Normal, m_lastSearchText, &ok);
    if (ok && !searchText.isEmpty()) {
        m_lastSearchText = searchText;
        findNext();
    }
}

void TextEditor::findNext() {
    if (m_lastSearchText.isEmpty()) {
        findText();
        return;
    }
    
    QTextCursor cursor = m_textEdit->textCursor();
    QTextDocument::FindFlags flags;
    
    QTextCursor found = m_textEdit->document()->find(m_lastSearchText, cursor, flags);
    
    if (found.isNull()) {
        
        cursor.movePosition(QTextCursor::Start);
        found = m_textEdit->document()->find(m_lastSearchText, cursor, flags);
        
        if (found.isNull()) {
            m_statusBar->showMessage("Text not found: " + m_lastSearchText, 3000);
            return;
        }
    }
    
    m_textEdit->setTextCursor(found);
    m_statusBar->showMessage("Found: " + m_lastSearchText, 2000);
}

void TextEditor::replaceText() {
    bool ok;
    QString searchText = QInputDialog::getText(this, "Replace", "Find:", 
                                                QLineEdit::Normal, m_lastSearchText, &ok);
    if (!ok || searchText.isEmpty()) return;
    
    QString replaceWith = QInputDialog::getText(this, "Replace", "Replace with:", 
                                                 QLineEdit::Normal, "", &ok);
    if (!ok) return;
    
    QMessageBox::StandardButton reply = QMessageBox::question(
        this, "Replace All", 
        "Replace all occurrences of '" + searchText + "' with '" + replaceWith + "'?",
        QMessageBox::Yes | QMessageBox::No);
    
    if (reply == QMessageBox::Yes) {
        QString text = m_textEdit->toPlainText();
        int count = text.count(searchText);
        text.replace(searchText, replaceWith);
        m_textEdit->setPlainText(text);
        m_statusBar->showMessage("Replaced " + QString::number(count) + " occurrence(s)", 3000);
    }
}

void TextEditor::gotoLine() {
    bool ok;
    int lineNumber = QInputDialog::getInt(this, "Go to Line", "Line number:", 
                                           1, 1, m_textEdit->document()->blockCount(), 1, &ok);
    if (ok) {
        QTextCursor cursor(m_textEdit->document()->findBlockByLineNumber(lineNumber - 1));
        m_textEdit->setTextCursor(cursor);
        m_textEdit->centerCursor();
    }
}

void TextEditor::increaseFontSize() {
    m_fontSize += 2;
    if (m_fontSize > 72) m_fontSize = 72;
    updateFontSize();
}

void TextEditor::decreaseFontSize() {
    m_fontSize -= 2;
    if (m_fontSize < 8) m_fontSize = 8;
    updateFontSize();
}

void TextEditor::resetFontSize() {
    m_fontSize = 24;
    updateFontSize();
}

void TextEditor::updateFontSize() {
    QString styleSheet = QString(
        "QPlainTextEdit { "
        "color: white; "
        "font-size: %1px; "
        "font-family: 'Courier New', Courier, monospace; "
        "background-color: black; "
        "}"
    ).arg(m_fontSize);
    
    m_textEdit->setStyleSheet(styleSheet);
    m_textEdit->setTabStopDistance(4 * m_textEdit->fontMetrics().horizontalAdvance(' '));
}

void TextEditor::updateCursorPosition() {
    QTextCursor cursor = m_textEdit->textCursor();
    int line = cursor.blockNumber() + 1;
    int col = cursor.columnNumber() + 1;
    m_lineColLabel->setText(QString("Line: %1, Col: %2").arg(line).arg(col));
}

void TextEditor::setIndex(QVector<TextEditor *> *v, int i) {
    index = i;
    vec = v;
}

void TextEditor::closeEvent(QCloseEvent *event) {
    if (m_modified) {
        QMessageBox::StandardButton reply = QMessageBox::question(
            this, "Unsaved Changes", 
            "The document has been modified. Do you want to save your changes?",
            QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
        
        if (reply == QMessageBox::Save) {
            saveContents();
            event->accept();
        } else if (reply == QMessageBox::Discard) {
            event->accept();
        } else {
            event->ignore();
        }
    } else {
        event->accept();
    }
}