Search code examples
c++qtqt4

Draw a line in column 80 of QPlainTextEdit


I'm writing a text editor and using Qt for the GUI. I'm a noob in Qt and I'm having trouble to do this.

I need to draw a line in the column 80 of the QPlainTextEdit but I really don't know how. I'm using QPainter but I just can't get it right, any help?


Solution

  • Here's how I'd do it. It's admittedly not entirely trivial. The inputs to determining the 80th column position are:

    1. 80 x the average character width in floating point. Using the integer value will magnify the roundoff error by a factor of 80. Thus use QFontMetricsF.

    2. The offset due to scrollbars comes from contentOffset(). It'd be bad to use horizontalScrollbar()->value(). The latter currently works, but relies on the implementation-specific detail. QPlainTextEdit happens to map scrollbar values to pixels -- who knows if it won't change tomorrow. It's not documented, thus falls under unspecified behavior.

    3. The QTextDocument implements its own margin, available via documentMargin().

    Another pitfall: you must paint on the viewport() in any class that derives from QAbstractScrollArea -- and QPlainTextEdit does so. If you don't, your paintEvent becomes a no-op. It's documented, but you must be clever enough to actually look into documentation. I'd consider it a bad corner case of an API that does something unexpected. In every other paintEvent, you simply create QPainter p or QPainter p(this) and it works.

    Note: this is tested, compileable code.

    //main.cpp
    #include <cmath>
    #include <QtWidgets>
    
    class Edit : public QPlainTextEdit
    {
    public:
        Edit(QWidget * parent = 0) : QPlainTextEdit(parent) {}
    protected:
        void paintEvent(QPaintEvent * ev)
        {
            QPlainTextEdit::paintEvent(ev);
            const QRect rect = ev->rect();
            const QFont font = currentCharFormat().font();
            int x80 = round(QFontMetricsF(font).averageCharWidth() * 80.0)
                    + contentOffset().x()
                    + document()->documentMargin();
            QPainter p(viewport());
            p.setPen(QPen("gray"));
            p.drawLine(x80, rect.top(), x80, rect.bottom());
            qDebug() << x80 << contentOffset() << document()->documentMargin() << font << endl;
        }
    };
    
    static QString filler()
    {
        QString str;
        for (char c = '0'; c < '9'; ++ c) {
            str.append(QString(10, c));
        }
        return str;
    }
    
    int main(int argc, char ** argv)
    {
        QApplication app(argc, argv);
        Edit ed;
        QTextCharFormat fmt = ed.currentCharFormat();
        fmt.setFontFamily("courier");
        fmt.setFontFixedPitch(true);
        ed.setCurrentCharFormat(fmt);
        ed.setLineWrapMode(QPlainTextEdit::NoWrap);
        qDebug() << fmt.font() << endl;
        ed.setPlainText(filler());
        ed.show();
        app.exec();
    }
    
    #include "main.moc"