Skip to content

Instantly share code, notes, and snippets.

@andr1972
Created March 31, 2018 19:31
Show Gist options
  • Save andr1972/0bf62c320d12b66f88a2b260a5e9f322 to your computer and use it in GitHub Desktop.
Save andr1972/0bf62c320d12b66f88a2b260a5e9f322 to your computer and use it in GitHub Desktop.
Qt: editor with line numbers
#ifndef LINENUMBERAREA_H
#define LINENUMBERAREA_H
#include <QWidget>
#include "numberededit.h"
class LineNumberArea : public QWidget
{
public:
LineNumberArea(NumberedEdit *editor) : QWidget(editor) {
numberedEdit = editor;
setParent(editor);
}
QSize sizeHint() const override {
return QSize(numberedEdit->lineNumberAreaWidth(), 0);
}
protected:
void paintEvent(QPaintEvent *event) override {
numberedEdit->lineNumberAreaPaintEvent(event);
}
private:
NumberedEdit *numberedEdit;
};
#endif // LINENUMBERAREA_H
QT += widgets
HEADERS = \
mainwindow.h \
linenumberarea.h \
numberededit.h
SOURCES = \
mainwindow.cpp \
main.cpp \
numberededit.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow window;
window.resize(640, 512);
window.show();
return app.exec();
}
#include <QtWidgets>
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
setupFileMenu();
editor = new NumberedEdit;
setCentralWidget(editor);
setWindowTitle(tr("line numbers"));
}
void MainWindow::openFile(const QString &path)
{
QString fileName = path;
if (fileName.isNull())
fileName = QFileDialog::getOpenFileName(this, tr("Open File"), "", "All Files (*)");
if (!fileName.isEmpty()) {
QFile file(fileName);
if (file.open(QFile::ReadOnly | QFile::Text))
{
QString text = file.readAll();
editor->setPlainText(text);
}
}
}
void MainWindow::setupFileMenu()
{
QMenu *fileMenu = new QMenu(tr("&File"), this);
menuBar()->addMenu(fileMenu);
fileMenu->addAction(tr("&Open..."), this, SLOT(openFile()), QKeySequence::Open);
fileMenu->addAction(tr("E&xit"), qApp, SLOT(quit()), QKeySequence::Quit);
}
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "numberededit.h"
QT_BEGIN_NAMESPACE
class QTextEdit;
QT_END_NAMESPACE
//! [0]
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
public slots:
void openFile(const QString &path = QString());
private:
void setupFileMenu();
NumberedEdit *editor;
};
//! [0]
#endif // MAINWINDOW_H
#include "numberededit.h"
#include "linenumberarea.h"
NumberedEdit::NumberedEdit(QWidget *parent) : QTextEdit(parent)
{
lineNumberArea = new LineNumberArea(this);
connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int)));
connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int)));
connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine()));
updateLineNumberAreaWidth(0);
}
NumberedEdit::~NumberedEdit()
{
delete lineNumberArea;
}
int NumberedEdit::lineNumberAreaWidth()
{
int digits = 1;
int max = qMax(1, document()->blockCount());
while (max >= 10) {
max /= 10;
++digits;
}
int space = 3 + fontMetrics().width(QLatin1Char('9')) * digits;
return space;
}
void NumberedEdit::updateLineNumberAreaWidth(int /* newBlockCount */)
{
setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
}
void NumberedEdit::updateLineNumberArea(const QRect &rect, int dy)
{
if (dy)
lineNumberArea->scroll(0, dy);
else
lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());
if (rect.contains(viewport()->rect()))
updateLineNumberAreaWidth(0);
}
void NumberedEdit::resizeEvent(QResizeEvent *e)
{
QTextEdit::resizeEvent(e);
QRect cr = contentsRect();
lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
}
FirstLineInfo NumberedEdit::getFirstLineInfo()
{
FirstLineInfo result;
QFontMetrics fm(font());
result.height = fm.ascent() + fm.descent() + fm.leading();
result.firstVisible = verticalScrollBar()->value()/result.height;
result.top = result.firstVisible*result.height - verticalScrollBar()->value();
return result;
}
void NumberedEdit::lineNumberAreaPaintEvent(QPaintEvent *event)
{
QPainter painter(lineNumberArea);
painter.fillRect(event->rect(), Qt::lightGray);
FirstLineInfo fli = getFirstLineInfo();
QTextBlock block = document()->findBlockByLineNumber(fli.firstVisible);
int blockNumber = fli.firstVisible;
int top = fli.top;
int bottom = top + fli.height;
while (block.isValid() && top <= event->rect().bottom()) {
if (block.isVisible() && bottom >= event->rect().top()) {
QString number = QString::number(blockNumber + 1);
painter.setPen(Qt::black);
painter.drawText(0, top+4, lineNumberArea->width(), fontMetrics().height(),
Qt::AlignRight, number);
}
block = block.next();
top = bottom;
bottom = top + fli.height;
blockNumber++;
}
}
#ifndef NUMBEREDEDIT_H
#define NUMBEREDEDIT_H
#include <QtWidgets>
class LineNumberArea;
struct FirstLineInfo
{
int height;
int firstVisible;
int top;
};
class NumberedEdit : public QTextEdit
{
Q_OBJECT
public:
explicit NumberedEdit(QWidget *parent = 0);
~NumberedEdit();
void lineNumberAreaPaintEvent(QPaintEvent *event);
int lineNumberAreaWidth();
protected:
void resizeEvent(QResizeEvent *event) override;
private slots:
void updateLineNumberAreaWidth(int newBlockCount);
void updateLineNumberArea(const QRect &, int);
private:
QWidget *lineNumberArea;
FirstLineInfo getFirstLineInfo();
};
#endif // NUMBEREDEDIT_H
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment