PDA

View Full Version : 'Annotation' of lines in a QTextEdit



caduel
17th June 2007, 18:46
I'd like to display some text (e.g. some source code) and 'annotate' it by displaying something like icons, line numbers or so to its left.
Is there support for something like that in QTextEdit? Are there other (Qt?) classes to achieve such a 'line based annotation'?

Thank you for any input.
Best regards
Christoph

marcel
17th June 2007, 19:07
Well, not by default.
I believe fullmetalcoder could give you an answer on how to do this.
He wrote Edyuk and all...

Regards

fullmetalcoder
17th June 2007, 19:53
He wrote Edyuk and all...
Sure but in this case the whole Edyuk source would be too much... QCodeEdit, which is available as a separate module, would be a lot more helpful and should fit your needs : it has highlighting, parenthesis matching, code folding, text indenting, line marks, line numbering and all such features needed by a code editor. :)

P.S : I didn't mean to press the thank button but the quote one instead...:o

marcel
17th June 2007, 19:55
P.S : I didn't mean to press the thank button but the quote one instead...:o


Hey, no problem. You can take "thank you"s back. :)
It was possible, at least last time I checked.

Regards

caduel
18th June 2007, 08:38
Sure but in this case the whole Edyuk source would be too much... QCodeEdit, which is available as a separate module...

Ok, I take it QCodeEdit is a subset of Edyuk. I will take a look at it.

Speaking of that project

I could not find information on its license... would it be ok to include (and then perhaps modify) it in an inhouse, or even a commercially distributed app?
There are no FAQ on the site...
I might be able to help a bit with your project (after moving my place in the next few weeks). Is there a list of open tasks anywhere?


Best regards

fullmetalcoder
18th June 2007, 09:37
I could not find information on its license... would it be ok to include (and then perhaps modify) it in an inhouse, or even a commercially distributed app?
Well, until now I choose a special dual-licensing model :
embedding the source into a project, either verbatim or modified, make it fall under GPL because the source is part of Edyuk
anyway, using a shared library created from verbatim source is possible as specified by the LGPL
I dunno if it is possible because I don't own a commercial license myself but as the author of this code I could exceptionally grant someone the right of using a modified version into a proprietary software
There are no FAQ on the site...
Absolutely right but none asked questions that could be put on a FAQ... Remember : FAQ stands for Frequently Asked Questions...;)


I might be able to help a bit with your project (after moving my place in the next few weeks). Is there a list of open tasks anywhere?
Not yet but I could easily create one. Indeed, I'm now using the great WebIssues (http://webissues.mimec.org) as a bug/tasks tracker. Log at http://edyuk.tuxfamily.org/webissues through a WebIssues client using login anonymous and password anonymous. :)
As for contributing, you're welcome! However it might be a little complicated at the moment since I'm in the process of rewriting most of QCodeEdit from scratch (I've dropped QTextDocument which is way too slow and memory-consuming and thus not suited for code editing... Besides I'm working on two new generic syntax specs, much faster and much more flexible than the old one.)

wysota
18th June 2007, 11:09
Well, not by default.

Actually there is support for creating something like this, but I couldn't get it to work :)

fullmetalcoder
18th June 2007, 11:54
Actually there is support for creating something like this, but I couldn't get it to work :)
Really? All I found in the QTextDocument framework which could help achieving this : QTextBlock::userData() but then you've got to render marks on your own...

wysota
18th June 2007, 11:59
QAbstractScrollArea::setViewportMargins( int left, int top, int right, int bottom ) - this should let you add a widget to the text edit and render the icons in there. Views use that approach for inserting headers (QHeaderView instances) but I couldn't make it work for the text edit and I didn't want to waste too much time on this. Maybe someone else has more time, luck and/or skills to make it work.

caduel
18th June 2007, 21:24
Yes, setViewportMargins() and QTextEdit::cursorRect() might be enough to determine the y-coordinates of the lines display... still I should take a look at QCodeEdit

Christoph

elcuco
22nd June 2007, 21:51
I am getting a lot of:



QPainter::begin: Widget painting can only begin as a result of a paintEvent


debug calls, and my code is not effective (it does not draw). I think that simply subclassing QTextEdit and reimplementing [b]paintEvent( QPaintEvent * event )[b/] is not enough.

wysota:
What part is failing for you?

wysota
22nd June 2007, 22:40
wysota:
What part is failing for you?

I could not put my own widget in the space created by increasing the margin. It just didn't seem to work.

marcel
22nd June 2007, 22:47
Should work if you place the widgets manually( no layouts ).
Of course, you must take care of resize events to reposition the margin widgets accordingly.

I have seen it done for a QScrollArea.

Regards

elcuco
22nd June 2007, 23:22
For my whats failing is this code, the layout system has nothing to do, as the margins are inside the widget.

(edit: just remembered, parts of this code are based on QDevelop, so they are GPL)



#include <QTextEdit>

class QPaintEvent;

class newedit : public QTextEdit
{
Q_OBJECT
public:
newedit(QWidget* parent);

protected:
void paintEvent ( QPaintEvent * event );

};

#include "newedit.h"
#include <QApplication>
#include <QPainter>
#include <QTextBlock>
#include <QTextLayout>
#include <QScrollBar>

newedit::newedit( QWidget* parent )
: QTextEdit()
{
setViewportMargins( 50, 0, 0, 0 );
}

void newedit::paintEvent( QPaintEvent * event )
{
QTextEdit::paintEvent( event );

int contentsY = verticalScrollBar()->value();
qreal pageBottom = contentsY + viewport()->height();
int m_lineNumber = 1;
const QFontMetrics fm = fontMetrics();
const int ascent = fontMetrics().ascent() +1;

QPainter p( this );
for ( QTextBlock block = document()->begin(); block.isValid(); block = block.next(), m_lineNumber++ )
{
QTextLayout* layout = block.layout();
const QRectF boundingRect = layout->boundingRect();
QPointF position = layout->position();
if ( position.y() +boundingRect.height() < contentsY )
continue;
if ( position.y() > pageBottom )
break;

const QString txt = QString::number( m_lineNumber );

p.drawText( width() -fm.width( txt ) - 2, qRound( position.y() ) -contentsY +ascent, txt ); // -fm.width( "0" ) is an ampty place/indent
}

}

wysota
23rd June 2007, 00:11
Should work if you place the widgets manually( no layouts ).
I didn't use layouts. It just didn't want to work :) I probably had some simple mistake but was unable to pinpoint it, although I had the Q*View code for reference... I'll probably try it again some other day.


For my whats failing is this code, the layout system has nothing to do, as the margins are inside the widget.
From what I see you don't use a separate widget for the margin, so the code is "slightly" different.

elcuco
23rd June 2007, 00:23
as far as in understand from the api, the margins are still part of the control's responsibility.

the problem i am facing, is that when i get the paint callback called, i am trying to create a QPainter, it complains that it's not created on the paintEvent() event.

i hope something see's what i am missing.

fullmetalcoder
23rd June 2007, 11:33
as far as in understand from the api, the margins are still part of the control's responsibility. The margins are indeed drawn by the scroll area and to paint anything on them it is highly recommened to use a custom layout. Custom is important here because it implies using setViewportMargins() from the layout (actually a public wrapper function but it does not make such a difference).

t
he problem i am facing, is that when i get the paint callback called, i am trying to create a QPainter, it complains that it's not created on the paintEvent() event.

i hope something see's what i am missing.
QPainter MUST be initialized on viewport()!!! Indeed (nearly) *ALL* events recieved by a QAbstractScrollArea are redirected from its viewport... Besides it is generally a bad idea to draw the margins from the widget paint event... You should consider placing this code in another widget which would be parented to the editor and managed by the aforementioned custom layout. FYI I made such a custom layout, inspired of Qt 4 Border Layout example, for use with the next version of QCodeEdit.

Also I hope you consider me as a little more than "something"... ;)

elcuco
23rd June 2007, 14:24
I did not understand your first comment, but I disagree with you on the second comment:

As far as I understand from the code (I looked inside the sources of Qt), the QScrollArea is composed from a a layout in which they stick in 2 scroll bars, and a viewport. The viewport is what we see as the texteditor, the corner widget will sit generally on the right buttom corner, between the scroll bars.

The function discussed in this thread will make a margin for the central widget (the viewport). This is why my code before was not working: the main widget (which contains the main layout, which contains the scroll bars and the text editor) was nto drawn: only the view port.

I think that the parts outside of the margins should be handeled by a special widget inserted to the scroll area. Not sure how yet, even tough I have seen code which uses this API.

fullmetalcoder
23rd June 2007, 14:38
The function discussed in this thread will make a margin for the central widget (the viewport). This is why my code before was not working: the main widget (which contains the main layout, which contains the scroll bars and the text editor) was nto drawn: only the view port.
Correct me if I'm wrong but I don't think the viewport can be drawn without the scroll area (thus scroll bars) being painted... The only quirk is that widget placed in the margins are NOT updated when the scroll bars move... You MUST explicitely connect the QSlider::valueChanged(int) signal to your panel widgets (widgets put in the margins).


I think that the parts outside of the margins should be handeled by a special widget inserted to the scroll area. Not sure how yet, even tough I have seen code which uses this API.
You've seen my code? Well, I'd be happy to know how... The last snippet I'm working on are not even available yet on SVN and the previous versions of QCodeEdit did not make use of setViewportMargins()... As with any other widget I don't quite see how you could play with the internals of QAbstractScrollArea... If you're interested in the layout code I guess I can post it...

wysota
23rd June 2007, 15:16
FMC: If you managed to insert a "margin widget" into a scroll area, could you please present a minimal code that accomplishes that? We might be going in circles here and I'm really interested in finding a solution.

fullmetalcoder
23rd June 2007, 16:11
FMC: If you managed to insert a "margin widget" into a scroll area, could you please present a minimal code that accomplishes that? We might be going in circles here and I'm really interested in finding a solution.
Minimal? well... my code is not really minimal because the layout used has some complexities and I use a custom class, QPanel (inheriting from QWidget). As I don't feel like spending too much time on trimming down my code so I'll give you a layout-less sample... Fell free to ask for the layout code if you want something a little more flexible but then don't expect something that will work out of box... ;)

So here comes the minimum (not very handy nor fully functionnal but works...) :


#include <QPainter>
#include <QPaintEvent>

#include <QTextEdit>
#include <QTextBlock>
#include <QTextLayout>

#include <QScrollBar>
#include <QApplication>

class SampleArea : public QTextEdit
{
public:
SampleArea(QWidget *p = 0)
: QTextEdit(p)
{
QString txt = "eat me...\ndrink me...\n";

for ( int i = 0; i < 8; ++i )
txt += txt;

document()->setPlainText(txt);
}

inline void setMargins(int l, int r, int t, int b)
{ setViewportMargins(l, r, t, b); }
};

class SamplePanel : public QWidget
{
public:
SamplePanel(SampleArea *a)
: QWidget(a), m_area(a)
{
setFixedWidth(50);
setFixedHeight(a->height());

connect(a->verticalScrollBar() , SIGNAL( valueChanged(int) ),
this , SLOT ( update() ));

a->setMargins(50, 0, 0, 0);
}

protected:
virtual void paintEvent(QPaintEvent *e)
{
e->accept();

QPainter p(this);

int m_lineNumber = 1;
const QFontMetrics fm = fontMetrics();
const int ascent = fontMetrics().ascent() + 1;
QTextBlock block = m_area->document()->begin();
int contentsY = m_area->verticalScrollBar()->value();
qreal pageBottom = contentsY + m_area->viewport()->height();

for ( ; block.isValid(); block = block.next(), ++m_lineNumber )
{
QTextLayout* layout = block.layout();
QPointF position = layout->position();
const QRectF boundingRect = layout->boundingRect();

if ( position.y() + boundingRect.height() < contentsY )
continue;

if ( position.y() > pageBottom )
break;

const QString txt = QString::number( m_lineNumber );

p.drawText(width() - fm.width(txt) - 2, qRound(position.y()) - contentsY + ascent, txt);
}
}

private:
SampleArea *m_area;
};

int main (int argc, char **argv)
{
QApplication app(argc, argv);

SampleArea area;
SamplePanel panel(&area);

area.show();

return app.exec();
}

marcel
23rd June 2007, 16:29
Here's one. Just two buttons and a graphics view.

The cpp:


#include "scrollarea.h"
#include <QGraphicsView>
#include <QWidget>
#include <QPushButton>
#include <QScrollBar>

#define DEFAULT_MARGIN 30

scroller::scroller(QWidget* parent/* =null */)
:QGraphicsView(parent)
{
mLeftWidget = NULL;
mTopWidget = NULL;

setScene(new QGraphicsScene(this));
scene()->setSceneRect(QRectF(0, 0, 3000.0f, 3000.0f)); //just for scrolling purposes

QScrollBar* hbar = verticalScrollBar();
QScrollBar* vbar = horizontalScrollBar();
connect(hbar, SIGNAL(valueChanged(int)), this, SLOT(adjustMarginWidgets()));
connect(vbar, SIGNAL(valueChanged(int)), this, SLOT(adjustMarginWidgets()));
setViewportMargins(DEFAULT_MARGIN, DEFAULT_MARGIN, 0, 0 );
createMarginWidgets();
}

scroller::~scroller()
{

}

void scroller::createMarginWidgets()
{
mLeftWidget = new QPushButton("L", this);
mTopWidget = new QPushButton("T", this);

adjustMarginWidgets();
}

void scroller::adjustMarginWidgets()
{
QRect viewportRect = viewport()->geometry();
QRect lrect = QRect(viewportRect.topLeft(), viewportRect.bottomLeft());
lrect.adjust(-DEFAULT_MARGIN, 0, 0, 0);
mLeftWidget->setGeometry(lrect);

QRect trect = QRect(viewportRect.topLeft(), viewportRect.topRight());
trect.adjust(0, -DEFAULT_MARGIN, 0, 0);
mTopWidget->setGeometry(trect);
}

void scroller::createCornerWidget()
{

}

void scroller::resizeEvent(QResizeEvent *e)
{
QGraphicsView::resizeEvent(e);
adjustMarginWidgets();
}


The header:


#pragma once

#include <QGraphicsView>

class QWidget;
class QPushButton;

class scroller : public QGraphicsView
{
Q_OBJECT
public:
scroller(QWidget* = NULL);
~scroller();

protected:
virtual void resizeEvent(QResizeEvent *);

private slots:
void adjustMarginWidgets();

private:
void createMarginWidgets();
void createCornerWidget();
QPushButton *mLeftWidget;
QPushButton *mTopWidget;
};



You can just set this as the central widget in a main window, or whatever.

Regards