Qt bounding rect / shape for item interaction - c++

I am doing a Qt program where I have rectangles linked with wires (placed by the user with mouse events). Every wire checks if there is a rectangle at the beginning and the end of it. If it is the case, the wire is placed.
I recently wanted to change my rectangles into horizontal lines (better visual), so I wrote:
QRectF myRect(-15, 0, 30, 1);
Instead of a regular rect. The problem is that now it is too hard to trace my wires because the user must link 2 lines with the mouse, which is almost impossible.
I tried to change bounding rect/shape but none of them works:
QRectF Port::boundingRect()
{
return QRectF(-15, 0, 30, 10);
}
QPainterPath Port::shape()
{
QPainterPath path;
path.addRect(-15, 0, 30, 10);
return path;
}
I think the problem is that bounding rect & shape are only used for selecting.
I also tried to use an image (desperate solution), but I can't find a way to add an image/pixmap to my QpainterPath.
I know I can use a line instead of a flat rectangle, but the problem is still the same.
Thanks for helping me :)

Use the QPainterPath for the check rectangle as you have mentioned
Get the End points of the Wire in QPointF( Two Points)
Use bool QPainterPath::contains(const QPointF & point)to check if the wire is within the QPainterPath (Two checks for the start and end point of the wire)
or this in case of a wire image that is dragged and dropped,
Use the QPainterPath for the check rectangle
If wire is an Image get the QRect of the Wire using QPixMap::rect()
Use bool QPainterPath::contains(const QRectF & rectangle) to check if the wire is within the QPainterPath

Related

Qt drawing a ring / circle with a hole

I need to draw a circle with QPainter. When I used drawEllipse function like :
void UserClass::Draw(QPainter &painter) {
painter.save();
painter.setBrush( GetColor() );
QPoint centerPosition = GetCenterPosition();
painter.drawEllipse( centerPosition, m_CircleOuterRadius, m_CircleOuterRadius);
painter.setBrush(QColor(0, 0, 0, 0));
painter.drawEllipse( centerPosition, m_CircleInnerRadius, m_CircleInnerRadius);
painter.restore();
}
Unfortunately result is not what I desired. I want to have inner circle not be filled. That is why I put alpha value as zero but ofcourse it didn't work. How can I have a circle which is not until a certain radius with qt ?
You should create a QPainterPath then add the two circles to it via addEllipse(), the outer first, then the inner. This will effectively give you a shape that is the outer circle with the inner circle punched as a hole.
Then you fill the painter path with a green brush, which will result in a hollow ring. Afterwards, if you want the white outlines, you can stroke the path with a white pen as well.
Also note that the painter path can be created only once and stored for reuse instead of creating it anew every time you redraw.

How to appropriately get position of QGraphicsRectItem after drag-release?

I wanted to have an online monitoring system that could tell where the shape is currently, but am getting very weird coordinates of the item, also the dimensions of it get higher by 1 each time I create new one and drag it.
Initial position (map size is 751 by 751, checked by outputting to qDebug(), scene bound to yellow space) :
Dragging it to the left top corner.
As you can see in the beginning it was on (200;200), but after dragging it is on (-201;-196). After deleting it and creating new shape on the same position with the same properties, new shape can't be seen because it is outside of the map, which suggests that edits don't show correct data.
Here is the code of updating the edits:
void CallableGraphicsRectItem::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
{
QGraphicsRectItem::mouseReleaseEvent(event);
ptr->updateEdits(this);
}
Here is what I managed to cut down into updateEdits():
void MainWindow::updateEdits(QAbstractGraphicsShapeItem* item)
{
//stuff not related to scene
auto posReal = item->scenePos();
auto pos = posReal.toPoint();
//create QString from coordinates
QString coordinate;
coordinate.setNum(pos.x());
ui->leftXEdit->setText(coordinate);
coordinate.setNum(pos.y());
ui->upperYEdit->setText(coordinate);
//get width and height for rect, radius for circle
auto boundingRectReal = item->sceneBoundingRect();
auto boundingRect = boundingRectReal.toRect();
ui->widthEdit->setText(QString::number(boundingRect.width()));
//disables height edit for circles, not really relevant
if (!items[currentShapeIndex].isRect)
{
ui->heightEdit->setDisabled(true);
}
else
{
ui->heightEdit->setDisabled(false);
ui->heightEdit->setText(QString::number(boundingRect.height()));
}
}
Here is how I anchor the QGraphicsScene to the left top corner of the yellow area:
scene->setSceneRect(0, 0, mapSize.width() - 20, mapSize.height() - 20);
ui->graphicsView->setScene(scene);
How can I report the right data to the edits?
You're better off overriding the itemChange method and using the ItemPositionHasChanged notification. You have to set the ItemSendsGeometryChanges flag on the item so that it receives these notifications.
I'm not sure that your item's final position has been set when you're still in the mouseReleaseEvent method. Tracking it in itemChange will ensure that the data is valid, and this kind of thing is what it's for.
Also, note that "pos" is in the item's parent coordinates, and "boundingRect" is in the item's coordinate space. You should use "scenePos" and "sceneBoundingRect" if you want to be sure you're using scene coordinates. If the item doesn't have a parent, then "pos" and "scenePos" will return the same values, but "boundingRect" and "sceneBoundingRect" will generally differ.

Shuffling rendered primitives

This is just an experiment I'm doing, but suppose that we have three rectangles that are drawn:
void thisDialog::paintEvent(QPaintEvent *ev)
{
rectangle = new QRect(40, 80, 40, 90);
painter = new QPainter(this);
brush = new QBrush(QColor(QColor::green);
painter->fillRect(...);
painter->drawRect(...);
rectangle2 = new QRect(140, 80, 40, 90);
// and draw
rectangle3 = new QRect(240, 80, 40, 90);
// and draw...
}
And now I want to somehow shuffle the rectangles drawn. My solution is to use std::random_shuffle. The plan is we push all the rectangles into a vector thereof:
vector<QRect*> vecRect;
vecRect.push_back(rectangle);
// push back other rectangles
std::random_shuffle(vecRect.begin(), vecRect.end());
At this point, the order of the rectangles have been shuffled. The only problem is that although they're shuffled I have to somehow reflect that in their positions and also update what's shown on-screen for the changes to be apparent. So, I propose creating a function called shuffle. I want only the x-values to change, so the y-values and the spacings of the rectangles should be left alone.
void shuffle()
{
std::random_shuffle(vecRect.begin(), vecRect.end());
for (auto it : vecRect)
{
// do something to the x-values in rectangle; e.g.
it->setX(/*set something...*/);
}
}
To shuffle the rectangles along the x-axis and maintain the y-axis, and maintain the spacing in between I'd have to get a random number and add the spacing and ensure that it is within the window boundaries say 800x600.
Since I am new to Qt I have to know how Qt handles primitive positions, and so I guess that setX() from the docs would modify the x-position of the rectangle. My other enquiry is: how do we update the positions so they're reflected on what's drawn on-screen? Whenever I update positions it doesn't seem to take effect. I've tried update() but that doesn't seem to work.
Btw, I call the foresaid function through a key press, so if the key 'S' is pressed then the function is called.
TLDR; How can I shuffle x-positions in these rectangles and update their positions rendered?

What is the fastest way to get QWidget pixel color under mouse?

I need to get the color of pixel under mouse, inside mouseMoveEvent of a QWidget (Breadboard). Currently I have this code->
void Breadboard::mouseMoveEvent(QMouseEvent *e)
{
QPixmap pixmap = QPixmap::grabWindow(winId());
QRgb color = pixmap.toImage().pixel(e->x(), e->y());
if (QColor(color) == terminalColor)
QMessageBox::information(this, "Ter", "minal");
}
Take a look at (scaled down) screenshot below-
When user moves his mouse on breadboard, the hole should get highlighted with some different color (like in red circle). And when the mouse exits, the previous color (grey) should be restored. So I need to do following steps-
Get color under mouse
According to color, floodfill the hole. (Different holes are distinguished using color)
On mouse out, restore the color. There would be wires going over holes, so I can't update the small rectangle (hole) only.
What is the fastest way of doing this? My attempt to extract color is not working i.e the Message box in my above code never displays. Moreover I doubt if my existing code is fast enough for my purpose. Remember, how fast you will be moving your mouse on breadboard.
Note - I was able to do this using wxWidgets framework. But due to some issues that project got stalled. And I am rewriting it using Qt now.
You are invited to look at code https://github.com/vinayak-garg/dic-sim
The "idiomatic" way of doing this in Qt is completely different from what you're describing. You'd use the Graphics View Framework for this type of thing.
Graphics View provides a surface for managing and interacting with a large number of custom-made 2D graphical items, and a view widget for visualizing the items, with support for zooming and rotation.
You'd define your own QGraphicsItem type for the "cells" in the breadboard that would react to hover enter/leave events by changing their color. The connections between the cells (wires, resistors, whatever) would also have their own graphics item types with the features you need for those.
Here's a quick and dirty example for you. It produces a 50x50 grid of green cells that become red when the mouse is over them.
#include <QtGui>
class MyRect: public QGraphicsRectItem
{
public:
MyRect(qreal x, qreal y, qreal w, qreal h)
: QGraphicsRectItem(x,y,w,h) {
setAcceptHoverEvents(true);
setBrush(Qt::green);
}
protected:
void hoverEnterEvent(QGraphicsSceneHoverEvent *) {
setBrush(Qt::red);
update();
}
void hoverLeaveEvent(QGraphicsSceneHoverEvent *) {
setBrush(Qt::green);
update();
}
};
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QGraphicsScene scene;
for (int i=0; i<50; i++)
for (int j=0; j<50; j++)
scene.addItem(new MyRect(10*i, 10*j, 8, 8));
QGraphicsView view(&scene);
view.show();
return app.exec();
}
You could modify the hover event handlers to talk to your "main window" or "controller" indicating what's currently under the mouse so you can update your caption, legend box or tool palette.
For best speed, render only the portion of the widget you're interested in into a QPaintDevice (like a QPixmap). Try something like this:
void Breadboard::mouseMoveEvent(QMouseEvent *e)
{
// Just 1 pixel.
QPixmap pixmap(1, 1);
// Target coordinates inside the pixmap where drawing should start.
QPoint targetPos(0, 0);
// Source area inside the widget that should be rendered.
QRegion sourceArea( /* use appropriate coordinates from the mouse event */ );
// Render it.
this->render(&pixmap, targetPos, sourceArea, /* look into what flags you need */);
// Do whatever else you need to extract the color from the 1 pixel pixmap.
}
Mat's answer is better if you're willing to refactor your application to use the graphics view API.

Qt round rectangle, why corners are different?

I try to draw a round rectangle with drawRoundedRect method directly in a QPixmap (no render engine involve here exept pure Qt one ...), I double check the size of the rectangle versus the size of my pixmap :
Pixmap : QSize(50, 73)
Rectangle: QRect(0,0 48x11)
See plenty of space ...
EDIT: some code
pixmap = QPixmap(50,73); //example size that match my case
QRectF rect(0,0,48,11);
QPainter painter(&pixmap);
painter.setRenderHint(QPainter::TextAntialiasing);
painter.setWorldMatrixEnabled(false);
painter.setPen(QPen()); //no pen
painter.setBrush(QBrush(color));
painter.drawRoundedRect(rect, 2.0, 2.0);
I disabled world transformation ...
I set set transformation to unity ...
I tried several radius (1.0,2.0,3.0,4.0) ...
I change pen width, brush color ...
But it always ends with a rectamgle with 4 diferent corners ! Like that :
I directly ouptut the pixmap to a file to be sure I wasn't scraping it during the display ... same shape.
Anyone know about Qt round rectangle with small radius ? I saw somthing about it a long time ago but I don't remenber how to deal with it !
It looks like you're not using anti-aliasing (i.e. the QPainter::Antialiasing render hint). This is a Qt quirk that occurs without it. From what I've seen/heard, the Qt devs aren't terribly concerned with fixing this (most people want anti-aliasing anyway).
The work-around (besides just using anti-aliasing) is to draw the rect yourself with QPainter::drawLine() and QPainter::drawArc(). You might have to play with numbers until it looks right -- straight calculations tend to come out a pixel or two off. Also, you might find that even with this method the lower right corner is never exactly the same as the other corners.
If you're feeling mildly ambitious, you could try fixing this and submitting a patch to Qt.
Update: Arc drawing results changed in Qt 5. In my experience, it's a big improvement.
I know this is an old problem but for Qt5 users calling setRenderHint(QPainter::Qt4CompatiblePainting); on the QPainter seems to solve the problem.
Edit:
I found a solution for generating a perfect rounded rectangle together with border color and it looks the same as the rounded rectangles used by QPushButton's border for example. This is how I implemented the paintEvent to achieve this:
void MyButtonGroup::paintEvent(QPaintEvent * e)
{
int borderSize = 5;
QColor borderColor = Qt::red;
QColor backgroundColor = Qt::blue;
int borderRadius = 3;
QPen pen;
pen.setWidth(borderSize);
pen.setColor(borderColor);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(pen);
QRectF rect(rect().x() + borderSize / 2,
rect().y() + borderSize / 2,
rect().width() - borderSize,
rect().height() - borderSize);
if(borderSize % 2 == 0)
{
painter.drawRoundedRect(rect,
borderSize,
borderSize);
}
else
{
painter.drawRoundedRect(rect.translated(0.5, 0.5),
borderRadius,
borderRadius);
}
QBrush brush(backgroundColor);
pen.setBrush(brush);
painter.setBrush(brush);
if(borderSize % 2 == 0)
{
painter.drawRoundedRect(rect,
borderRadius,
borderRadius);
}
else
{
painter.drawRoundedRect(rect.translated(0.5, 0.5),
borderRadius,
borderRadius);
}
QWidget::paintEvent(e);
}
I'm posting this because I found it a bit hard to achieve this result:
Try adding half a pixel offset (e.g.: rect.translated(0.5,0.5) ):
QRectF rect(0,0,48,11);
painter.setRenderHint(QPainter::Antialiasing,false);
painter.drawRoundedRect( rect.translated(0.5,0.5), 2.0, 2.0 );
I suppose this has to do with the coordinate system placing an integer value between two pixels.
If you draw with antialiasing and use a pen of 1 pixel width then drawing at exact integer coordinates results in lines of 2 pixel width instead.
Only with this 0.5 pixel offset you'll get lines that are exactly 1 pixel wide.
QRectF rect(0,0,48,11);
painter.setRenderHint(QPainter::Antialiasing,true);
painter.setBrush(Qt::NoBrush);
painter.setPen( Qt::white );
painter.drawRoundedRect( rect.translated(0.5,0.5), 2.0,2.0 );
Best way do draw RoundRect is Path.
http://developer.nokia.com/community/wiki/Qt_rounded_rect_widget
void fillRoundRect(QPainter& painter, QRect r, int radius)
{
painter.setRenderHint(QPainter::Antialiasing,true);
QPainterPath rounded_rect;
rounded_rect.addRoundRect(r, radius, radius);
painter.setClipPath(rounded_rect);
painter.fillPath(rounded_rect,painter.brush());
painter.drawPath(rounded_rect);
}
try to play with render hints
1) disable antiAliasing;
2) enable SmoothPixmapTransform
but still no guarantee that it will help.
I have tried all tips from answers here but nothing works for me. But based on these code snippets I have found following solution:
As default set m_pPainter->setRenderHint(QPainter::Qt4CompatiblePainting, true) and only for rounded rectangles with width%2==0 disable it.
QRect rect = ConvertRectangle(rectangle);
int nPenWidth = m_pPainter->pen().width();
if ( nPenWidth % 2 == 0 )
m_pPainter->setRenderHint(QPainter::Qt4CompatiblePainting, false);
m_pPainter->drawRoundedRect(rect, dbRadiusX, dbRadiusY);
if ( nPenWidth % 2 == 0 )
m_pPainter->setRenderHint(QPainter::Qt4CompatiblePainting, true);