Qt - Adding a drawing to a Layout - c++

I'm trying to make an application where the user can input some drawings (gestures) that will then be saved and displayed in a gallery on the top of the screen:
When the user presses "Validate", the drawing is supposed to be displayed on the scroll area on the top. However, for some reason, my code is not working the way I intended it to. It saves the drawing with no problem, but when I tell it to add it to the top, nothing happens.
Code here:
void MainWindow::galleryUpdate()
{
for (int i = 0; i < gestureTemplates.size(); i++)
{
QPolygonF scaledGesture = gestureTemplates[i].scaleToSquare(gestureTemplates[i]);
StrokeDrawer * strD = new StrokeDrawer();
QPalette Pal(palette());
Pal.setColor(QPalette::Background, Qt::white);
strD->setMinimumSize(50, 50);
strD->setAutoFillBackground(true);
strD->setPalette(Pal);
galleryList.append(strD);
topZone->addWidget(strD);
strD->setStroke(scaledGesture);
}
}
gestureTemplates is a vector of GestureTemplate (a custom class inheriting from QPolygonF) containing all the drawings. The first line inside the for simply scales the drawing to fit in a square, and returns a QPolygonF.
StrokeDrawer is the class used to display the drawing (code below). I then try to fill it with a white background, save it to galleryList which is a list of StrokeDrawer for each drawing, and then add it to the top, by using topZone->addWidget(strD), where topZone is a HBoxLayout.
I also use the setStroke method to set the drawing to the StrokeDrawer (this method also calls the update() function in the class, which calls its paintEvent, which should take care of actually drawing the QPolygonF).
Initially, I tried to do addWidget by directly using the QPolygonF but that didn't work. That's why I'm using this StrokeDrawer class (which just displays the drawing, it doesn't even allow making changes to it).
By debugging the code on QtCreator, everything works fine until the addWidget line, where nothing happens. I mean, even if the drawing is not being correctly displayed, it should at least show a small, white, 50x50 square, or am I missing something here?
Some code for StrokeDrawer:
void StrokeDrawer::setStroke(QPolygonF g)
{
gesture = g;
update();
}
void StrokeDrawer::paintEvent(QPaintEvent* e)
{
QWidget::paintEvent(e);
QPainter pait(this);
if (!gesture.empty())
pait.drawPolyline(gesture);
}

Related

Change Cursor Image While Hovering Over An Object Drawn At OpenGL And Displaying Object's Variables

I am currently working on a project that I use C++, OpenGL, Qt 5.9.2 and Microsoft Visual Studio Professional 2015 on a 64 bit Operating System, Windows 10 Pro.
I have a user interface that I have created and in that user interface, there is a QGLWidget, that I am using for draw processes, with other widgets like push buttons and a dockWidget. I have a class Ball and it has variables(distance(double) and angle(int)) that determines where an instance of that Ball is going to be drawn inside the QGLWidget. Ball class has got 2 more variables that is, id(int), model(String) and year(int) Every other draw process draws lines except Ball drawings.
Drawings are 2 dimensional.
Every Ball has the same color(rgb)!
First problem: I want to left click to one of the Ball instances and I want to display it's id, model and year at The dockWidget.
Second Problem: While doing the stuff that I have mentioned at the First Problem section. I want the cursor image to change while hovering above any of the Ball instances, and change back to default Windows mouse cursor while not.
I have created a function that checks if the MouseEvent is LeftClick:
void DisplayManager::mousePressEvent(QMouseEvent* ev) {
if (ev->buttons() & Qt::LeftButton) { // Balls Are Green
if(// CHECK IF THERE IS A BALL AT THE CLICKED COORDINATES) {
// DISPLAY THE X and Y OF THE BALL AT THE DOCK WIDGET
}
}
}
This is my initializeGL function: (DisplayManager is the name of my QGLWidget)
void DisplayManager::initializeGL() {
glEnable(GL_COLOR_MATERIAL); // Enables the changing of the draw color with glColor() functions
glColor3f(0.0, 1.0, 0.0);
glEnable(GL_DEPTH_TEST);
glClearColor(0, 0, 0, 1); //sets a black background 1 0 0 1
}
On the basis this is a Picking problem and there are several information about it at the internet but I am not using GLUT and I am not using any shader. So in the light of all these I was unable to find any effective solution or clue about how can I accomplish all that I want.
I would be really glad if someone could help me with at least one of these problems.
I have currently finished working with the project. I thought that I should provide an answer to my question in case someone with a similar problem comes across with my question in the future.
void DisplayManager::mousePressEvent(QMouseEvent* ev) {
// The processes that are being executed after a left mouse button click on the DisplayManager.
if (ev->buttons() & Qt::LeftButton) {
double aspectRatio = openGLWidgetWidth / openGLWidgetHeight;
int xOfClicked = ev->x() - (openGLWidgetWidth / 2);
int yOfClicked = - (ev->y() - (openGLWidgetHeight / 2));
// The variable to be used for calculating fault tolerance of the click event.
int distanceBetweenPressAndDraw = 0;
// Executes the processes inside for every ball in vector.
for (int i = 0; i < ballVector.length(); i++) {
// Calculates the screen coordinates of the i'th ball.
int rangeOfBallInScreenDistance = rangeToScreenDistance(ballVector.at(i).range);
double screenXOfBall = rangeOfBallInScreenDistance * sin(ballVector.at(i).degree * DEGTORAD);
double screenYOfBall = rangeOfBallInScreenDistance * cos(ballVector.at(i).degree * DEGTORAD);
// Calculates the distance between pressed position and the i'th ball according to the screen coordinates.
distanceBetweenPressAndDraw = sqrt(pow((screenXOfBall - xOfClicked), 2) + pow((screenYOfBall - yOfClicked), 2));
// Decides if the clicked position is a ball (considering the fault tolerance).
if (distanceBetweenPressAndDraw < 10) {
emit printXY(QPointF(xOfClicked, yOfClicked)); // Prints the screen coordinates of the clicked positions (At the Dock Widget inside Main Window).
}
}
}
}
This was the solution for my First Problem. I would be glad though if someone could answer my Second problem in a comment or answer somehow.

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.

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.

QGraphicsRectItem and QGraphicsScene problems at Scene change

what I want to do is the following:
I have a little GUI with a QGraphicsView. In this graphics View I load a picture:
// m_picture is QPixmap
// image is QImage
// m_graphic is QGraphicsScene
// graphicsView is QGraphicsView
m_picture.convertFromImage(image);
m_graphic->addPixmap(m_picture);
ui->graphicsView->setScene(m_graphic);
This doesn't cause any problems and I can always load a new image without problems.
Now in addition to just display the pictures I want to give the user the ability to draw a rectangle on them ( to "focus" on a specific area ). Actually the user just types in the coordinates in four text boxes on the GUI ( x,y, width,heigth). After providing the coordinates the User presses a button and the rectangle at the following coordinates shall be displayed.
I accomplished this with this code:
void tesseract_gui::show_preview_rect()
{
int x,y,h,w;
x = ui->numBox_x->value();
y = ui->numBox_y->value();
h = ui->numBox_h->value();
w = ui->numBox_w->value();
if( rect_initialized )
{
m_graphic->removeItem(m_rect);
}
else
{
rect_initialized = true;
}
m_rect->setPen(QPen(Qt::red));
m_rect->setRect(x,y,h,w);
m_graphic->addItem(m_rect);
return;
}
The remove call is because I always want to display just one rectangle.
Now as I mentioned this works fine. But if the user now loads another picture ( with the calls at the top of my post ) the program crashes when I try to draw a new rectangle. I
get a Segmentation fault at the call of
m_rect->setPen(QPen(Qt::red));
If I call
m_graphic->removeItem(m_rect);
after loading a new picture I get
QGraphicsScene::removeItem: item 0x8c04080's scene (0x0) is different from this scene (0x8c0a8b0)
and then it crashes with the same Error at setPen.
What I don't get is, I don't change the scene. I just add another picture to it ( or overwrite it) .
Well any suggestions how I could do this right?
Best Regards
// edit:
I tried to do it just with everytime a new rectangle like this:
void tesseract_gui::show_preview_rect()
{
int x,y,h,w;
x = ui->numBox_x->value();
y = ui->numBox_y->value();
h = ui->numBox_h->value();
w = ui->numBox_w->value();
m_graphic->clear();
m_graphic->addRect(x,y,h,w);
return;
}
Problem at this is that with the clear() call it also clears the picture itself from my GraphicsView... so no solution there
// edit:
As suggested I got rid of the Warning like this:
if( m_rect->scene() != 0 )
{
m_graphic->removeItem(m_rect);
}
m_rect->setPen(QPen(Qt::red));
m_rect->setRect(x,y,h,w);
m_graphic->addItem(m_rect);
I know it's not the best way but I tried it also this way ( did not work for me ):
I added the item in the constructor:
m_graphic->addItem(m_rect);
and then
m_rect->setPen(QPen(Qt::red));
m_rect->setRect(x,y,h,w);
m_graphic->update();
and I get the "same" error as always ( Program crashes at m_rect->setPen() )
So it seems the problem always occurs when I already added the rectangle to the graphic, THEN changed the image of m_graphic and then did any operation with m_rect. ( Actually I guess m_graphic takes ownership of m_rect and so this causes the segmentation fault ... ? )
The message QGraphicsScene::removeItem: item 0x8c04080's scene (0x0) is different from this scene (0x8c0a8b0) tells you m_rect is not in any scene at the time you call it. It's probably removed somewhere else in your code or you have 2 variables with the same name in the class hierarchy.
Also, you don't need to remove it from scene to change it. Just change it while it's in the scene. It will get repainted with the new color and geometry in the next paint event.
Even if you REALLY want to remove it before changing it, just check if it's in a scene by calling QGraphicsItem::scene(). There's no need for the init check variable.

Need help in optimizing a drawing code ...

I needed some help in trying to optimize this code portion ... Basically here's the thing .. I'm making this 'calligraphy pen' which gives the calligraphy effect by simply drawing a lot of adjacent slanted lines ... The problem is this: When I update the draw region using update() after every single draw of a slanted line, the output is correct, in the sense that updates are done in a timely manner, so that everything 'drawn' using the pen is immediately 'seen' the drawing.. however, because a lot (100s of them) of updates are done, the program slows down a little when run on the N900 ...
When I try to do a little optimization by running update after drawing all the slanted lines (so that all lines are updated onto the drawing board through a single update() ), the output is ... odd .... That is, immediately after drawing the lines, they lines seem broken (they have vacant patches where the drawing should have happened as well) ... however, if I trigger a redrawing of the form window (say, by changing the size of the form), the broken patches are immediately fixed !! When I run this program on my N900, it gets the initial broken output and stays like that, since I don't know how to enforce a redraw in this case ...
Here is the first 'optimized' code and output (partially correct/incorrect)
void Canvas::drawLineTo(const QPoint &endPoint)
{
QPainter painter(&image);
painter.setPen(QPen(Qt::black,1,Qt::SolidLine,Qt::RoundCap,Qt::RoundJoin));
int fx=0,fy=0,k=0;
qPoints.clear();
connectingPointsCalculator2(qPoints,lastPoint.x(),lastPoint.y(),endPoint.x(),endPoint.y());
int i=0;
int x,y;
for(i=0;i<qPoints.size();i++)
{
x=qPoints.at(i).x();
y=qPoints.at(i).y();
painter.setPen(Qt::black);
painter.drawLine(x-5,y-5,x+5,y+5); **// Drawing slanted lines**
}
**//Updating only once after many draws:**
update (QRect(QPoint(lastPoint.x()-5,lastPoint.y()-5), QPoint(endPoint.x()+5,endPoint.y()+5)).normalized());
modified = true;
lastPoint = endPoint;
}
Image right after scribbling on screen:
http://img823.imageshack.us/img823/8755/59943912.png
After re-adjusting the window size, all the broken links above are fixed like they should be ..
Here is the second un-optimized code (its output is correct right after drawing, just like in the second picture above):
void Canvas::drawLineTo(const QPoint &endPoint)
{
QPainter painter(&image);
painter.setPen(QPen(Qt::black,1,Qt::SolidLine,Qt::RoundCap,Qt::RoundJoin));
int fx=0,fy=0,k=0;
qPoints.clear();
connectingPointsCalculator2(qPoints,lastPoint.x(),lastPoint.y(),endPoint.x(),endPoint.y());
int i=0;
int x,y;
for(i=0;i<qPoints.size();i++)
{
x=qPoints.at(i).x();
y=qPoints.at(i).y();
painter.setPen(Qt::black);
painter.drawLine(x-5,y-5,x+5,y+5); **// Drawing slanted lines**
**//Updating repeatedly during the for loop:**
update(QRect(QPoint(x-5,y-5), QPoint(x+5,y+5)).normalized());//.adjusted(-rad,-rad,rad,rad));
}
modified = true;
int rad = (myPenWidth / 2) + 2;
lastPoint = endPoint;
}
Can anyone see what the issue might be ?
Sorry if I misunderstood, but have you tried to use the "double buffer" approach? Instead of drawing directly on the screen, you "draw" your points and lines to a memory buffer. After that, you just copy the buffer to the screen. This is faster and avoids flickering.
As I understand you should find min and max of x and y processed in your for-loop and use them in update(QRect(QPoint(minX-5, minY-5), QPoint(maxX+5, maxY+5)).normalized());
I'm not sure exactly what your issue is with the broken lines, but I can offer you this advice: keep your pen around. Instead of this:
for(i=0;i<qPoints.size();i++)
{
// ...
painter.setPen(Qt::black);
painter.drawLine(x-5,y-5,x+5,y+5); **// Drawing slanted lines**
// ...
}
do this:
QPen black_pen(Qt::black);
for(i=0;i<qPoints.size();i++)
{
// ...
painter.setPen(black_pen);
painter.drawLine(x-5,y-5,x+5,y+5); **// Drawing slanted lines**
// ...
}
Even more, if you are repeatedly calling your drawLineTo function with the same pen every time, store the pen in your class and keep it around. At my company, we've found that to vastly reduce drawing times where we can take advantage of it. (One instance on a large image cut drawing times in half.)
One other note: I'm not sure what type the image you are painting is, but I'm assuming it is a QImage. When you are done drawing, if you will be using the unmodified image repeatedly, you might convert it once to a QPixmap. The QPixmap class is stored in a way that is supposed to be ready for blitting directly to the screen (but it a lot slower to modify in many cases, because of that).