How to enlarge the hover area of a QGraphicsItem - qt

I have a QGraphicsScene with rather small point markers. I would like to enlarge the area of these markers to make dragging easier. The marker is a cross, +/- 2 pixels from the origin. I have reimplemented
QGraphicsItem::contains(const QPointF & point ) const
{
return QRectF(-10,-10,20,20);
}
and
void hoverEnterEvent(QGraphicsSceneHoverEvent* event)
{
setPen(QPen(Qt::red));
update();
}
but the marker only turns red when it is directly hit by the cursor (and even that is a bit picky). How can I enlarge the "hover area"?

As stated in the short comment:
Usually those things are handled via the bounding rect or the shape function, try overloading those. Take a look into the qt help of QGraphicsItem under shape (http://doc.qt.io/qt-4.8/qgraphicsitem.html#shape):
Returns the shape of this item as a QPainterPath in local coordinates.
The shape is used for many things, including collision detection, hit
tests, and for the QGraphicsScene::items() functions.
The default implementation calls boundingRect() to return a simple
rectangular shape, but subclasses can reimplement this function to
return a more accurate shape for non-rectangular items. For example, a
round item may choose to return an elliptic shape for better collision
detection. For example:
QPainterPath RoundItem::shape() const {
QPainterPath path;
path.addEllipse(boundingRect());
return path; } The outline of a shape can vary depending on the width and style of the pen used when drawing. If you want to include
this outline in the item's shape, you can create a shape from the
stroke using QPainterPathStroker.
This function is called by the default implementations of contains()
and collidesWithPath().
So what basically happens is that all functions that want to access the "Zone" which is associated with an item, call shape and then do e.g. a containment or collision detection with the resulting painterpath.
Thus if you have small items you should enlargen the shape zone.
Lets for instance consider a line that is your target, than your shape implementation could look like the following:
QPainterPath Segment::shape() const{
QLineF temp(qLineF(scaled(Plotable::cScaleFactor)));
QPolygonF poly;
temp.translate(0,pen.widthF()/2.0);
poly.push_back(temp.p1());
poly.push_back(temp.p2());
temp.translate(0,-pen.widthF());
poly.push_back(temp.p2());
poly.push_back(temp.p1());
QPainterPath path;
path.addPolygon(poly);
return path;
}
Pen is a member of the segment, and I use its width to enlarge the shape zone. But you can take anything else as well that has a good relation to the actual dimension of your object.

Related

Customizing shape of bounding rect

I am drawing a line using mouse clicks. The line is drawn using paint function as:
painter->drawLine(start_p, end_p);
The bounding rect of line is defined as:
QRectF Line::boundingRect() const
{
// bounding rectangle for line
return QRectF(start_p, end_p).normalized();
}
This shows the line painted. I get the bounding rect for this as shown:
I want to have the bounding rect according to the shape of the item, something like:
How to achieve this?
Edit
While selecting any of the overlapping lines, the one with bounding rect on top is selected(see figure below). Even making use of setZValue won't work here.
I want to implement this by minimizing the bounding rect to the shape of line.
If you have an item that is not shaped like a rectangle, or is a rotated rectangle use QGraphicsItem::shape.
This function should return a QPainterPath. You should be able to create your path by using QPainterPath::addPolygon.
Here is a small example:
QPainterPath Item::shape() const
{
QPainterPath path;
QPolygon polygon;
polygon << QPoint(0, 0);
polygon << QPoint(5, 5);
polygon << QPoint(width, height);
polygon << QPoint(width - 5, height - 5);
path.addPolygon(polygon);
return path;
}
You of course should calculate your points inside the path in a different way, but you get the point. Now when you click on an item, it will only select it if the click happened inside the shape defined by the QPainterPath.
If you ever need to make curvy lines, you can use QPainterPathStroker::createStroke as suggested by cmannett85.
There are two relevant functions in a QGraphicsItem that you should be interested in. The first is boundingRect. This, as you probably realise is a rectangle which encompasses the whole item. Qt uses this for such things as quickly calculating how much of an item is visible and simple item collision.
That's great if you have rectangular items; you can just override boundingRect() in any items you inherit from QGraphicsItem or QGraphicsObject.
If you have a shape that isn't regular and you want to do things such as collision with an item's shape, then theshape() function needs overriding too in your class.
This returns a QPainterPath, so you can do something like this: -
QPainterPath Line::shape()
{
QRectF rect(start_p, end_p).normalized();
// increase the rect beyond the width of the line
rect.adjust(-2, -2, 2, 2);
QPainterPath path;
path.addRect(rect);
return path; // return the item's defined shape
}
Now, you can use a painter to draw the shape() item, instead of the boundingRect() and collision will work as expected.
boundingRect is always used for optimize painting process of of scene. So you have have no room for manipulation here.
BUT if you want change area for mouse interaction there is shape method. By default this method returns QPainterPath rectangle received from boundingRect method.
So just override this method and provide desired shape.
QPainterPath YourGraphicsItem::shape() const {
static const qreal kClickTolerance = 10;
QPointF vec = end_p-start_p;
vec = vec*(kClickTolerance/qSqrt(QPointF::dotProduct(vec, vec)));
QPointF orthogonal(vec.y(), -vec.x());
QPainterPath result(start_p-vec+orthogonal);
result.lineTo(start_p-vec-orthogonal);
result.lineTo(end_p+vec-orthogonal);
result.lineTo(end_p+vec+orthogonal);
result.closeSubpath();
return result;
}
You must draw yourself bounding if you want some thing like this. let Qt have it's QRect for bounding and define your new QRect dependent to the corner of previous QRect, top-left and bottom-right. for example if the top-left corner is (2,2) your new QRect top-left is (1,2) and top-right is (2,1) and ....

Is there a way to specify selection area for QGraphicsItem?

I have some kind of histogram drawn with QGraphicsRectItem; some of these rectangles are long, some short. While it is no problem to select a long rectangle, one might have difficulties with the short ones.
So I was wondering if there is a way to specify custom area that would trigger mousePressEvent for the item, so rectangles would have the same size selection area.
Apart from rectangles I also draw some text on the same line. Would it be helpful to group them somehow and write mousePressEvent for the group instead?
Thank you!
It would be useful to see an example image of what you're asking, but I think I know what mean.
Personally, I'd just create my own class, inherited from QGraphicsItem (or QGraphicsObject, if you want signals and slots). This class can then provide a boundingRect() of the full area that you want to represent the area to be selected, but the paint() function only draw the visible part of the bar. Something like this: -
class Bar: public QGraphicsItem
{
Q_OBJECT
public:
Bar(int x, int y, int width, int height, int visibleBarHeight);
// returns the area of the object
QRectF boundingRect() const;
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0);
};
In the paint function, you would draw a rect up to the visible bar height, but in boundingRect, return the full rect. That way, the bar could visibly be very small, but the object is of full height and would respond to mouse selection above the visible area of the bar.
As for the text, you could either add it as a child to this object and signal the parent when it gets selected, or extend the boundingRect of this Bar class and render it in the paint function.
Note that boundingRect is the area represented by the object, in local coordinates. If you have an object that isn't defined by a rectangle, you'd also want to implement the shape() function. By default, shape() calls boundingRect().

Qt : draw triangle image

I need to do something similar to QPainter::drawImage, but drawing a triangle part of the given picture (into a triangular region of my widget) instead of working with rectangles.
Any idea how I could do that, besides painfully trying to redraw every pixel?
Thanks for your insights!
If it is feasible for you to use a QPixmap instead of a QImage, you can set a bitmap mask for the QPixmap which defines which of the pixels are shown and which are transparent:
myPixmap->setMask(myTriangleMask);
painter->drawPixmap(myPixmap);
Here is another solution based on QImage:
MaskWidget::MaskWidget(QWidget* parent) : QWidget(parent) {
img = QImage("Sample.jpg"); // The image to paint
mask = QImage("Mask.png"); // An indexed 2-bit colormap image
QPainter imgPainter(&img);
imgPainter.drawImage(0, 0, mask); // Paint the mask onto the image
}
void MaskWidget::paintEvent ( QPaintEvent * event ) {
QPainter painter(this);
painter.drawImage(10, 10, img);
}
Mask.png is an image file with the same size as Sample.jpg. It contains an alpha channel to support transparency. You can create this file easily with The GIMP, for example. I added an alpha channel, changed all areas I want to have painted to transparent and all other areas to white. To reduce the size, I finally converted it to an indexed 2-bit image.
You could even create the mask image programmatically with Qt, if you need your triangle be computed based on various parameters.

How to remove/clear previously drawn lines before redrawing new lines in Qt?

I am drawing few lines using paintEvent(QPaintEvent * event). Sometime later if I want to draw new lines then the previously drawn lines are not cleared/removed. How could I draw the new lines only, by removing/clearing old lines. Is there any property to clear the previously drawn lines.Please let me know.
void QGraphWidget::paintEvent(QPaintEvent * event)
{
const QRect & rect = event->rect();
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
drawLines(painter, rect);//calling painter.drawLine() inside
}
Thanks...
In my opinion, the correct approach is to clear the area before drawing new lines. This can be achieved either by using the autoFillBackground property as proposed by Stephen Chu, or by calling manually the QPainter::eraseRect() before drawing your lines in the QGraphWidget::paintEvent method.
void QGraphWidget::paintEvent(QPaintEvent * event)
{
const QRect & rect = event->rect();
QPainter painter(this);
painter.eraseRect(rect);
painter.setRenderHint(QPainter::Antialiasing);
drawLines(painter, rect);
}
Another option is to draw "negatively" the lines from the previous call to QGraphWidget::paintEvent. If you store the coordinate of your old lines, you might first draw lines using the background brush and then draw your new lines using the foreground brush. See code sample that might fit into you drawLines method. Note that if you draw something else in your widget, drawing the lines negatively might erase some of the other graphics which is why the first approach, erasing all the GraphWidget area, is better.
// save the current brush
QBrush currentBrush = painter.brush();
// draw the old line with the background brush
painter.setBrush(background());
painter.drawLine(oldLine);
// draw the new line with the current brush
painter.setBrush(currentBrush);
painter.drawLine(newLine);
You want to fill your widget with window background color before redraw. Set autoFillBackground to true after you create the widget and Qt will handle this for you
I don't think there is a specific call to remove the line. If you're repainting the entire area each time paintEvent() function is called you shouldn't see previous lines. If you're not repainting the area, you'll have to draw over the line yourself. This code for instance is drawing a line in a different position each time the method is invoked:
QPainter painter(this);
painter.setBrush(QBrush(Qt::red));
painter.drawRect(rect());
painter.setPen(QPen(Qt::yellow));
static int i = 0;
painter.drawLine(QPointF(i, i), QPointF(i, height() - i));
i++;
but "previous lines" are cleared completely. If you want to keep those you'll have to repaint only a specific area or you'll have to repaint those.
This is one way to delete whole line from QT.
me->setFocus();
int pos;
QTextCursor tc= me->textCursor();
pos=tc.columnNumber();
tc.select(QTextCursor::LineUnderCursor);
QString str=tc.selectedText();
tc.removeSelectedText();
tc.movePosition(QTextCursor::NextBlock,QTextCursor::MoveAnchor);
tc.insertText(str);
tc.insertBlock();
tc.movePosition(QTextCursor::PreviousBlock,QTextCursor::MoveAnchor);
tc.movePosition(QTextCursor::StartOfLine,QTextCursor::MoveAnchor);
me->setTextCursor(tc);
return true;

QPainter declared inside a run function creates artifact

I am rendering a QPixmap inside of a QThread. the code to paint is inside a function. If I declare the painter inside the drawChart function everything seems ok but if I declare the painter inside the run function the image is wrong in the sense that at the edge of a black and white area, the pixels at the interface are overlapped to give a grey. Does anyone know why this is so? Could it be because of the nature of the run function itself?
//This is ok
void RenderThread::run()
{
QImage image(resultSize, QImage::Format_RGB32);
drawChart(&image);
emit renderedImage(image, scaleFactor);
}
drawChart(&image)
{
QPainter painter(image);
painter.doStuff()(;
...
}
//This gives a image that seems to have artifacts
void RenderThread::run()
{
QImage image(resultSize, QImage::Format_RGB32);
QPainter painter(image);
drawChart(painter);
emit renderedImage(image, scaleFactor);
}
drawChart(&painter)
{
painter.doStuff();
...
}
//bad
.
//good
.
From C++ GUI Programming with Qt 4 by Jasmin Blanchette and Mark Summerfield:
One important thing to understand is
that the center of a pixel lies on
“half-pixel” coordinates. For example,
the top-left pixel covers the area
between points (0, 0) and (1, 1), and
its center is located at (0.5, 0.5).
If we ask QPainter to draw a pixel at,
say, (100, 100), it will approximate
the result by shifting the coordinate
by +0.5 in both directions, resulting
in the pixel centered at (100.5,
100.5) being drawn.
This distinction may seem rather
academic at first, but it has
important consequences in practice.
First, the shifting by +0.5 only
occurs if antialiasing is disabled
(the default); if antialiasing is
enabled and we try to draw a pixel at
(100, 100) in black, QPainter will
actually color the four pixels (99.5,
99.5), (99.5, 100.5), (100.5, 99.5), and (100.5, 100.5) light gray, to give
the impression of a pixel lying
exactly at the meeting point of the
four pixels. If this effect is
undesirable, we can avoid it by
specifying half-pixel coordinates, for
example, (100.5, 100.5).

Resources