I have a floating widget which should follow mouse.
Now I have another widget, which sometimes changes its position.
Before any other widgets resize, everything's ok. After the change my coordinates always go astray and the floating widget is shifted, it floats at a distance from the mouse. I've noticed that the shift is somehow connected to window size, it grows when the size is bigger.
I pass to widget mouse coordinates with QCursor::pos();, and I also tried sending QPoint from other background widgets, over which it should float with mouseMoveEvent(QMouseEvent *event) and then QPoint{ mapToGlobal( { event->pos() } )};. These both render the same coordinates, and the same shift occurs.
E.g.
On a small window
Floater's coordinates QPoint(255,136)
Another widget's coordinates:
QPoint(0,0)
MapToGlobal from another widget: QPoint(255,136)
On a large window:
Floater's coordinates QPoint(205,86)
Another widget's coordinates: QPoint(0,0)
MapToGlobal from another widget: QPoint(205,86)
Can't grasp the problem, why it renders wrong coordinates.
The system is Qt 5.12.3. Any help will be appreciated.
UPD: The minimal reproducible example.
.h
class Area : public QWidget
{
Q_OBJECT
public:
void moveArea();
};
.cpp
void moveArea::Area()
{
move(QCursor::pos());
}
QCursor::pos() returns global (i.e. screen) coordinates but widget's position is measured in its parent's coordinate system if it has a parent. And is measured in global coordinates if and ONLY if the widget does not have any parent. So this code
void Area::moveArea()
{
move(QCursor::pos());
}
will move the upper left corner of Area object to the mouse cursor position ONLY if Area object is a top-level window, i.e. when it has no parent. If it has parent (which I believe is your case), you need to map global coordinates to parent's coordinates by changing your code to move(parentWidget()->mapFromGlobal(QCursor::pos()));.
Related
I have a mainwindow.cpp and mainwindow.h file, a ui file containing the graphicsview. I load an image into a QGraphicsPixmapItem from a QImage. I add that pixmapitem named 'item' to a scene. And i set the scene with the following ui->graphicsView->setScene(scene);. I have a zoom out, a zoom in button and a fit to window button.
void MainWindow::on_actionZoom_In_triggered()
{
ui->graphicsView->scale(1.25,1.25);
//item->update(1.25,1.25,1.25,1.25);
//scene->update(1.25,1.25,1.25,1.25);
}
void MainWindow::on_actionZoom_Out_triggered()
{
ui->graphicsView->scale(0.75,0.75);
//item->update(0.75,0.75,0.75,07.5);
//scene->update(0.75,0.75,0.75,07.5);
}
void MainWindow::on_actionFit_Window_triggered()
{
ui->graphicsView->fitInView(scene->sceneRect(), Qt::KeepAspectRatio);
}
To get a point i use QPointF point = ui->graphicsView->mapFromParent(event->pos());
This only work when the image has not been zoomed out, zoomed in or fitted to window.
I have the feeling that it gets the points/coordinates from the situation when the image originally loaded. So the graphicsview scales, the item scales (i think), but i have the idea that the scene doesn't.
Either i want to call the method mousePressEvent from QGraphicsView (because that may work) or have the area from where the coordinates are collected from enlarged.
Steps:
Loading Image
Drawing Polygon (Picture not transformed)
Wrong coordinates, still in original area
I would like to know whether it is possible to shift part of a drawing by copying its pixels rather than redrawing it.
I work in an embedded environment, where performance is a key factor. We use Qt 4.8.
I have a set of real-time data points that I want to draw. I define the following class:
class SetOfDataPoints : public QGraphicsItem
{
public:
<constructor>
QRectF boundingRect() const { ... }
void paint(QPainter* painter,
const QStyleOptionGraphicsItem* option,
QWidget* widget = NULL) { ... }
<other methods>
};
At regular intervals, I read a new data point, add it to the instance of SetOfDataPoints, and shift the SetOfDataPoints to the left (by calling QGraphicsItem::moveBy() on the SetOfDataPoints), so the new data point becomes visible. As a result, SetOfDataPoints::paint() gets called, and in that method I have to draw the entire set of data points. The drawing currently consists only of line segments that connect the data points, but will become more elaborate in the future.
Now, it feels inefficient to redraw the whole set of data points, when most of the graph is actually just shifted to the left. I would like to shift the pixels of the unchanged part of the graph to the left, and draw only the one line segment that connects the last two points. At least I would like to try, and measure how much that improves performance.
Is there a way to do that in Qt 4.8?
This won't work in general:
Your item doesn't exist as any pixels until it's rendered. You don't know how many pixels it is drawn on, or even if there are any, since there are 0 or more views your scene is shown on, and the item might be visible to various extent on these views.
There are transformations applied to your item. It doesn't have to be rectangular.
Your item is composited with the items below it, unless it is completely opaque.
Your item, shown on a view, is composited with the backing store of the widget the view is on.
You can optimize for special cases. If your item is not cached, then it's always painted on a view, and the widget argument of paint will point to that widget. You then have direct access to the backing store, and the painter gives you the transformation used to go from item coordinates to the backing store's device coordinates. You can then inspect the path on the widget tree from the view to the window for opacity. If all intervening widgets paint opaque, and your item has an orientation-preserving transformation, you can certainly do a blit on the image, and redraw only a small part of the item.
If your item is cached, it should then be cached in device coordinates. You can do the blitting too, as you're painting on a pixmap. That pixmap is then composited onto the backing store of the window the view is on. There's a separate cache pixmap for each view.
When blitting, you must always recognize how much of the previous pixels are correct. For each view or cache pixmap, you should keep a region that is valid. That region initally should be empty.
I have a QGraphicsScene with a QGraphicsPixmapItem on it, showing an image. I also have a view showing the scene. I have a mousePressEvent() function defined, looking for mouse clicks on the view.
The view is setup to contain the pixmap:
//pixmap is a QGraphicsPixmapItem *
view->fitInView(pixmap, Qt::KeepAspectRatio);
I can get the position of a mouse click in the view's coordinate system:
//e is my QMouseEvent *
QPoint local_pt = view->mapFromGlobal(e->globalPos());
Now I'd like to map this point to the original image coordinates, using any combination of the QGraphicsView, QGraphicsScene, QGraphicsPixmapItem
I've tried pixmap->boundingRect(), which gives me a QRectF(0,0 778x582), which has the appropriate dimensions (of the original image), but I do not see how the x and y coordinates are related to the local point of the click.
How do I get position of the mouse click in the original image coordinates?
EDIT:
This is what ultimately worked:
//e is my QMouseEvent *
QPoint local_pt = view->mapFromGlobal(e->globalPos());
QPointF img_coord_pt = view->mapToScene(local_pt);
img_coord_pt is (0,0) when I click the top left corner of the image, and (image width, image height) at the bottom right corner.
QGraphicsView::mapToScene maps screen coordinates to scene coordinates, then use QGraphicsView::itemAt to get the item and QGraphicsItem::mapFromScene to map scene global coordinates to item local coordinates.
There are inverses of these, of course.
I get my mouse coordinates like this:
winX = QCursor::pos().x();
These are coordinates for the whole screen. I need to get coordinates that are relative to my OpenGL widget window's viewport, so I can use the gluUnProject function.
How can I do that?
You might want to have a look at QWidget's mapFromGlobal(const QPoint & pos) method.
As per the documentation this:
translates the global screen coordinate pos to widget coordinates.
I'm displaying a map built as rectangle of QGraphicsPixmapitem items (each item stands for one map tile). Because my map is quite large (around 30 MB of PNG files) I want to be able to load pixmaps on demand only when they're visible for user in QGraphicsView and unload when they became invisible.
Is there any way to figure out visible scene rectangle?
This gives you the visible scene rectangle:
sceneRect = graphicsView.mapToScene(graphicsView.rect()).boundingRect()
In case there is a shear or rotation transformation present it gives you the bounding rectangle of the visible scene area. If you do not have such transformations (only shift or zoom) the returned rectangle is the exact scene area.
Now for your real problem of efficiently display a huge tiles map in a scene? You could load the tiles in background and first evaluate if your Qt framework isn't already optimized for big pixmap that are outside the visible range. 30 MB also doesn't sound so big that it wouldn't fit into memory.
QGraphicsView inherits the QWidget::geometry() function. You can use this to determine its location and size within its parent widget. (Outside of its constructor)
The QGrapicsScene can be larger than the QGraphicsView. The default QGraphicsView will add horizontal and vertical scroll bars to house the QGraphicsScene. I imagine you would like to do something like this:
//create a QGraphicsScene (for this example *scene) that is the size of your entire map.
QGraphicsScene *scene=new QGraphicsScene(0,0,mapWidth,mapHeight);
//create a QGraphicsView* named view that is the size of your visible area
//I'm assuming visibleHeight and visibleWidth do not change (this is your viewing window)
QGraphicsView *view=new QGraphicsView(0,0,visibleWidth,visibleHeight);
view->setScene(scene);
Have the user control the x and y position of the scene that triggers some custom signal like sceneMoved(int,int). Before you redraw the scene, call a slot to check the new position of the scene:
connect(this,SIGNAL(sceneMoved(int,int)),this,SLOT(drawScene(int,int)));
void SomeClass::drawScene(int newX, int newY){
//if you already have a pointer to the scene do this, or call
//QGraphicsView::scene();
int oldX=scene->geometry()->x();
int oldY=scene->geometry()->y();
//now that you have your oldX, oldY, newX, and newY, visibleWidth, visibleHeight
//you can determine what you need to redraw, what you need to delete, and what can stay
}
There is still a lot of if..else, but you get the point. I suggest trying to segment your map into squares the size of your visible area.