c++qtqt-creatorqtexteditqtextcursor

Why my QTextEdit inherited rich text editor always crash when I try to call textCursor()?


I am trying to build a rich text editor that can summon all the common rich text formatting, such as bold, italic and underline, using Qt version 6. After building the rich text formatting functions which work very fine without an error, I want to build an advanced formatting checker also. In order to translate codes from Python to C++ (see the answer of this question), I built a function which located in an inherited class from the QTextEdit.

When I try to run my codes in Qt Creator IDE without debugging, I found that the code I wrote cannot be run and always crash without any reason.

Enabling the built-in debugger of the Qt Creator, I saw that it said QTextCursor <no such value> and textCursor <no such value>. But I included <QTextCursor> properly, and checked the *.pro file (I'm running qmake) too.

QTextCursor:  cursor: -1689080758 textCursor:

It stopped at the second level of the Low Level DeBugger.

LLDB stopped at 2nd level

Update:

If I "repair" the check formatting function by just returning true, the QAction set checked will also crash without a reason. Looks very strange.

Such an annoying problem. Any help will be thanks.

Environment:

Minimal reproductive example:

main.cpp:

#include "mainwindow.h"

#include <QApplication>

int main(int argc, char* argv[])
{
    QApplication app(argc, argv);
    MainWindow w;
    w.show();
    return app.exec();
}

mainwindow.h:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QTextDocument>
#include <QCheckBox>
#include <QTextFrame>
#include <QTextFrameFormat>
#include <QMenuBar>
#include <QMenu>
#include <QMainWindow>
#include <QAction>

#include "Pagination/PagesTextEdit.h"

class MainWindow : public QMainWindow
{
    Q_OBJECT

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

private slots:
    void checkFormatting();

private:
    QMenuBar* m_menuBar;

    QMenu* m_menuFormat;
    QAction* m_actionFormatTextBold;
    QAction* m_actionFormatTextItalic;
    QAction* m_actionFormatTextUnderline;

    QTextDocument* doc;
    PagesTextEdit* m_paginationWidget;
};
#endif // MAINWINDOW_H

mainwindow.cpp:

#include "mainwindow.h"

#include <QString>
#include <QScrollBar>
#include <QApplication>
#include <QTextCursor> // <- important

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    QMenuBar* m_menuBar;
    m_menuBar = new QMenuBar();
    this->setMenuBar(m_menuBar);

    QMenu* m_menuFormat;
    m_menuFormat = new MenuFormat();
    m_menuFormat->setText(QString::fromStdString("F&ormat"));
    m_menuBar->addMenu(m_menuFormat);

    ActionFormatTextBold* m_actionFormatTextBold;
    m_actionFormatTextBold = new ActionFormatTextBold(this);
    m_menuFormatText->addAction(m_actionFormatTextBold);

    ActionFormatTextItalic* m_actionFormatTextItalic;
    m_actionFormatTextItalic = new ActionFormatTextItalic(this);
    m_menuFormatText->addAction(m_actionFormatTextItalic);

    ActionFormatTextUnderline* m_actionFormatTextUnderline;
    m_actionFormatTextUnderline = new ActionFormatTextUnderline(this);
    m_menuFormatText->addAction(m_actionFormatTextUnderline);

    PagesTextEdit* m_paginationWidget;
    m_paginationWidget = new PagesTextEdit;
    QTextDocument* doc;
    doc = new QTextDocument;
    doc->setDefaultFont(QFont("Times New Roman", defaultFontSize));
    m_paginationWidget->setDocument(doc);

    m_paginationWidget->setPageFormat(QPageSize::PageSizeId::A4);
    m_paginationWidget->setPageMargins(QMarginsF(20, 20, 20, 20));
    m_paginationWidget->setUsePageMode(true);
    m_paginationWidget->setPageNumbersAlignment(Qt::AlignHCenter | Qt::AlignTop);
    m_paginationWidget->setShowPageNumbers(false);
    m_paginationWidget->setTabChangesFocus(false);
    m_paginationWidget->setAcceptDrops(true);
    m_paginationWidget->setAcceptRichText(true);

    setCentralWidget(m_paginationWidget);

    connect(m_paginationWidget, &PagesTextEdit::cursorPositionChanged,
            this, &MainWindow::checkFormatting);

    connect(m_actionFormatTextBold, &QAction::triggered,
            m_paginationWidget, &PagesTextEdit::textFormatBold);
    connect(m_actionFormatTextItalic, &QAction::triggered,
            m_paginationWidget, &PagesTextEdit::textFormatItalic);
    connect(m_actionFormatTextUnderline, &QAction::triggered,
            m_paginationWidget, &PagesTextEdit::textFormatUnderline);
    
    showMaximized();
}

MainWindow::~MainWindow()
{
}

void MainWindow::checkFormatting() {
    bool bold = m_paginationWidget->isBold();
    m_actionFormatTextBold->setChecked(bold); // <- m_actionFormatTextBold also crash without checking formatting
}

PagesTextEdit.h:

#ifndef PAGESTEXTEDIT_H
#define PAGESTEXTEDIT_H

#include <QTextEdit>

#include "PageMetrics.h"

class PagesTextEdit : public QTextEdit
{
    Q_OBJECT

public:
    explicit PagesTextEdit(QWidget* parent = 0);

    void setPageFormat(QPageSize::PageSizeId _pageFormat);
    void setPageMargins(const QMarginsF& _margins);
    bool usePageMode() const;

    bool isBold();
    bool isItalic();
    bool isUnderline();

public slots:
    void textFormatBold(bool isChecked);
    void textFormatItalic(bool isChecked);
    void textFormatUnderline(bool isChecked);

    void setUsePageMode(bool _use);
    void setAddSpaceToBottom(bool _addSpace);
    void setShowPageNumbers(bool _show);
    void setPageNumbersAlignment(Qt::Alignment _align);

protected:
    void paintEvent(QPaintEvent* _event);
    void resizeEvent(QResizeEvent* _event);

private:
    void updateViewportMargins();
    void updateVerticalScrollRange();
    void paintPagesView();
    void paintPageNumbers();
    void paintPageNumber(QPainter* _painter, const QRectF& _rect, bool _isHeader, int _number);

private slots:
    void aboutVerticalScrollRangeChanged(int _minimum, int _maximum);
    void aboutDocumentChanged();
    void aboutUpdateDocumentGeometry();
    void aboutMergeCharFormat(QTextCharFormat format);

private:
    QTextDocument* m_document;
    bool m_usePageMode;
    bool m_addBottomSpace;
    bool m_showPageNumbers;
    Qt::Alignment m_pageNumbersAlignment;
    PageMetrics m_pageMetrics;
};

#endif // PAGESTEXTEDIT_H

PagesTextEdit.cpp:

#include "PagesTextEdit.h"

#include <QAbstractTextDocumentLayout>
#include <QPainter>
#include <QScrollBar>
#include <QTextFrame>
#include <QTextCharFormat>
#include <QTextCursor> // included

PagesTextEdit::PagesTextEdit(QWidget *parent) :
    QTextEdit(parent),
    m_document(0),
    m_usePageMode(false),
    m_addBottomSpace(true),
    m_showPageNumbers(true),
    m_pageNumbersAlignment(Qt::AlignTop | Qt::AlignRight)
{
    setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);

    aboutDocumentChanged();
    connect(this, SIGNAL(textChanged()), this, SLOT(aboutDocumentChanged()));

    connect(verticalScrollBar(), SIGNAL(rangeChanged(int,int)),
        this, SLOT(aboutVerticalScrollRangeChanged(int,int)));

    setLineWrapMode(QTextEdit::LineWrapMode::WidgetWidth);
}

void PagesTextEdit::setPageFormat(QPageSize::PageSizeId _pageFormat) {
    m_pageMetrics.update(_pageFormat);
    repaint();
}

void PagesTextEdit::setPageMargins(const QMarginsF& _margins) {
    m_pageMetrics.update(m_pageMetrics.pageFormat(), _margins);
    repaint();
}

bool PagesTextEdit::usePageMode() const {
    return m_usePageMode;
}

void PagesTextEdit::setUsePageMode(bool _use) {
    if (m_usePageMode != _use) {
        m_usePageMode = _use;
        repaint();
    }
}

void PagesTextEdit::setAddSpaceToBottom(bool _addSpace) {
    if (m_addBottomSpace != _addSpace) {
        m_addBottomSpace = _addSpace;
        repaint();
    }
}

void PagesTextEdit::setShowPageNumbers(bool _show) {
    if (m_showPageNumbers != _show) {
        m_showPageNumbers = _show;
        repaint();
    }
}

void PagesTextEdit::setPageNumbersAlignment(Qt::Alignment _align) {
    if (m_pageNumbersAlignment != _align) {
        m_pageNumbersAlignment = _align;
        repaint();
    }
}

void PagesTextEdit::paintEvent(QPaintEvent* _event) {
    updateVerticalScrollRange();
    paintPagesView();
        paintPageNumbers();
    QTextEdit::paintEvent(_event);
}

void PagesTextEdit::resizeEvent(QResizeEvent* _event) {
    updateViewportMargins();
    updateVerticalScrollRange();
    QTextEdit::resizeEvent(_event);
}

void PagesTextEdit::updateViewportMargins() {
    QMargins viewportMargins;

    if (m_usePageMode) {

        int pageWidth = m_pageMetrics.pxPageSize().width();
                int pageHeight = m_pageMetrics.pxPageSize().height();

        const int DEFAULT_TOP_MARGIN = 20;
        const int DEFAULT_BOTTOM_MARGIN = 20;
        {
            int leftMargin = 0;
            int rightMargin = 0;

            if (width() > pageWidth) {
                const int BORDERS_WIDTH = 4;
                const int VERTICAL_SCROLLBAR_WIDTH =
                        verticalScrollBar()->isVisible() ? verticalScrollBar()->width() : 0;
                leftMargin = rightMargin =
                        (width() - pageWidth - VERTICAL_SCROLLBAR_WIDTH - BORDERS_WIDTH) / 2;
            }

            int topMargin = DEFAULT_TOP_MARGIN;

            int bottomMargin = DEFAULT_BOTTOM_MARGIN;
            int documentHeight = pageHeight * document()->pageCount();
            if ((height() - documentHeight) > (DEFAULT_TOP_MARGIN + DEFAULT_BOTTOM_MARGIN)) {
                const int BORDERS_HEIGHT = 2;
                const int HORIZONTAL_SCROLLBAR_HEIGHT =
                        horizontalScrollBar()->isVisible() ? horizontalScrollBar()->height() : 0;
                bottomMargin =
                    height() - documentHeight - HORIZONTAL_SCROLLBAR_HEIGHT - DEFAULT_TOP_MARGIN - BORDERS_HEIGHT;
            }

            viewportMargins = QMargins(leftMargin, topMargin, rightMargin, bottomMargin);
        }
    }

    setViewportMargins(viewportMargins);

        aboutUpdateDocumentGeometry();
}

void PagesTextEdit::updateVerticalScrollRange() {
    if (m_usePageMode) {

        const int pageHeight = m_pageMetrics.pxPageSize().height();
        const int documentHeight = pageHeight * document()->pageCount();
        const int maximumValue = documentHeight - viewport()->height();
        if (verticalScrollBar()->maximum() != maximumValue) {
            verticalScrollBar()->setMaximum(maximumValue);
        }
    }
    else {
        const int SCROLL_DELTA = 800;
        int maximumValue =
                document()->size().height() - viewport()->size().height()
                + (m_addBottomSpace ? SCROLL_DELTA : 0);
        if (verticalScrollBar()->maximum() != maximumValue) {
            verticalScrollBar()->setMaximum(maximumValue);
        }
    }
}

void PagesTextEdit::paintPagesView() {
    if (m_usePageMode) {
        qreal pageWidth = m_pageMetrics.pxPageSize().width();
        qreal pageHeight = m_pageMetrics.pxPageSize().height();

        QPainter p(viewport());
        QPen spacePen(palette().window(), 9);
        QPen borderPen(palette().dark(), 1);

        qreal curHeight = pageHeight - (verticalScrollBar()->value() % (int)pageHeight);
        const int x = pageWidth + (width() % 2 == 0 ? 2 : 1);
        const int horizontalDelta = horizontalScrollBar()->value();

        if (curHeight - pageHeight >= 0) {
            p.setPen(borderPen);
            p.drawLine(0, curHeight - pageHeight, x, curHeight - pageHeight);
        }

        while (curHeight <= height()) {
            p.setPen(spacePen);
            p.drawLine(0, curHeight-4, width(), curHeight-4);
            p.setPen(borderPen);
            p.drawLine(0, curHeight-8, x, curHeight-8);
                        p.drawLine(0, curHeight, x, curHeight);
            p.drawLine(0 - horizontalDelta, curHeight - pageHeight, 0 - horizontalDelta, curHeight - 8);
            p.drawLine(x - horizontalDelta, curHeight - pageHeight, x - horizontalDelta, curHeight - 8);

            curHeight += pageHeight;
        }
        if (curHeight >= height()) {
            p.setPen(borderPen);
            p.drawLine(0 - horizontalDelta, curHeight-pageHeight, 0 - horizontalDelta, height());
            p.drawLine(x - horizontalDelta, curHeight-pageHeight, x - horizontalDelta, height());
        }
    }
}

void PagesTextEdit::paintPageNumbers() {
    if (m_usePageMode && !m_pageMetrics.pxPageMargins().isNull() && m_showPageNumbers) {
        QSizeF pageSize(m_pageMetrics.pxPageSize());
        QMarginsF pageMargins(m_pageMetrics.pxPageMargins());

        QPainter p(viewport());
        p.setFont(document()->defaultFont());
        p.setPen(QPen(palette().text(), 1));
        qreal curHeight = pageSize.height() - (verticalScrollBar()->value() % (int)pageSize.height());
        qreal leftMarginPosition = pageMargins.left() - horizontalScrollBar()->value();
        qreal marginWidth = pageSize.width() - pageMargins.left() - pageMargins.right();
        int pageNumber = verticalScrollBar()->value() / pageSize.height() + 1;
        if (curHeight - pageMargins.top() >= 0) {
            QRectF topMarginRect(leftMarginPosition, curHeight - pageSize.height(), marginWidth, pageMargins.top());
            paintPageNumber(&p, topMarginRect, true, pageNumber);
        }
        while (curHeight < height()) {
            QRect bottomMarginRect(leftMarginPosition, curHeight - pageMargins.bottom(), marginWidth, pageMargins.bottom());
            paintPageNumber(&p, bottomMarginRect, false, pageNumber);
            ++pageNumber;
            QRect topMarginRect(leftMarginPosition, curHeight, marginWidth, pageMargins.top());
            paintPageNumber(&p, topMarginRect, true, pageNumber);
            curHeight += pageSize.height();
        }
    }
}

void PagesTextEdit::paintPageNumber(QPainter* _painter, const QRectF& _rect, bool _isHeader, int _number) {
    if (_isHeader) {
        if (m_pageNumbersAlignment.testFlag(Qt::AlignTop)) {
            _painter->drawText(_rect, Qt::AlignVCenter | (m_pageNumbersAlignment ^ Qt::AlignTop),
                QString::number(_number));
        }
    }
    else {
        if (m_pageNumbersAlignment.testFlag(Qt::AlignBottom)) {
            _painter->drawText(_rect, Qt::AlignVCenter | (m_pageNumbersAlignment ^ Qt::AlignBottom),
                QString::number(_number));
        }
    }
}

void PagesTextEdit::aboutVerticalScrollRangeChanged(int _minimum, int _maximum) {
    Q_UNUSED(_minimum);
    updateViewportMargins();

    int scrollValue = verticalScrollBar()->value();
    if (scrollValue > _maximum) {
        updateVerticalScrollRange();
    }
}

void PagesTextEdit::aboutDocumentChanged() {
    if (m_document != document()) {
        m_document = document();
        connect(document()->documentLayout(), SIGNAL(update()), this, SLOT(aboutUpdateDocumentGeometry()));
    }
}

void PagesTextEdit::aboutUpdateDocumentGeometry() {
    QSizeF documentSize(width() - verticalScrollBar()->width(), -1);
    if (m_usePageMode) {
        int pageWidth = m_pageMetrics.pxPageSize().width();
        int pageHeight = m_pageMetrics.pxPageSize().height();
        documentSize = QSizeF(pageWidth, pageHeight);
    }
    if (document()->pageSize() != documentSize) {
        document()->setPageSize(documentSize);
    }

    if (document()->documentMargin() != 0) {
        document()->setDocumentMargin(0);
    }
    QMarginsF rootFrameMargins = m_pageMetrics.pxPageMargins();
    QTextFrameFormat rootFrameFormat = document()->rootFrame()->frameFormat();
    if (rootFrameFormat.leftMargin() != rootFrameMargins.left()
        || rootFrameFormat.topMargin() != rootFrameMargins.top()
        || rootFrameFormat.rightMargin() != rootFrameMargins.right()
        || rootFrameFormat.bottomMargin() != rootFrameMargins.bottom()) {
        rootFrameFormat.setLeftMargin(rootFrameMargins.left());
        rootFrameFormat.setTopMargin(rootFrameMargins.top());
        rootFrameFormat.setRightMargin(rootFrameMargins.right());
        rootFrameFormat.setBottomMargin(rootFrameMargins.bottom());
        document()->rootFrame()->setFrameFormat(rootFrameFormat);
    }
}

void PagesTextEdit::aboutMergeCharFormat(QTextCharFormat format) {
    // no problem
    QTextCursor cur = textCursor();
    if (!cur.hasSelection()) {
        cur.select(QTextCursor::SelectionType::WordUnderCursor);
    }
    cur.mergeCharFormat(format);
}

void PagesTextEdit::textFormatBold(bool isChecked) {
    // no problem
    QTextCharFormat form;
    if (isChecked) {
        int weight = QFont::Bold;
        form.setFontWeight(weight);
    } else {
        int weight = QFont::Normal;
        form.setFontWeight(weight);
    }
    aboutMergeCharFormat(form);
}

void PagesTextEdit::textFormatItalic(bool isChecked) {
    // no problem
    QTextCharFormat form;
    if (isChecked) {
        bool italic = true;
        form.setFontItalic(italic);
    } else {
        bool italic = false;
        form.setFontItalic(italic);
    }
    aboutMergeCharFormat(form);
}

void PagesTextEdit::textFormatUnderline(bool isChecked) {
    // no problem
    QTextCharFormat form;
    if (isChecked) {
        bool underline = true;
        form.setFontUnderline(underline);
    } else {
        bool underline = false;
        form.setFontUnderline(underline);
    }
    aboutMergeCharFormat(form);
}

bool PagesTextEdit::isBold() {
    bool bold = false;

    QTextCursor cursor = textCursor(); // problem here
    // Refer to the pictures...
    // QTextCursor: <no such value>
    // textCursor: <no such value>

    // BUT I truthly included ALL the QTextCursors and QTextEdits in above

    if (cursor.hasSelection()) {
        int selectionStart = (cursor.selectionStart() + 1);
        int selectionEnd = (cursor.selectionEnd() + 1);

        for (int pos = selectionStart; pos < selectionEnd; pos++) {
            cursor.setPosition(pos);
            QTextCharFormat form = cursor.charFormat();
            if (form.fontWeight() >= QFont::Bold) {
                bold = true;
            } else {
                bold = false;
                break;
            }
        }
    } else {
        bold = (cursor.charFormat().fontWeight() >= QFont::Bold);
    }

    return bold;
}

Any advice would be greatly appreciated!!! Thanks!


Solution

  • lemme see ur code...

    this code isn't related to the matter of object inheritance but the header/source control. use

    //directly without using such useless shadow declarations
    m_menuFormat = new QMenu(this);
    m_menuFormat->setTitle('format');
    m_menuBar->addMenu(m_menuFormat);
    

    instead of

    //go ahead with python or java but don't trouble cpp
    MenuFormat m_menuFormat;
    //unless u want to see 15:49:26: /Users/Scott/Documents/test/test.app/Contents/MacOS/test crashed.
    m_menuFormat = ...;
    ...;
    

    while it's a must to make sure that m_menuFormat appears in the list of the header file.

    take it easy.