QGraphicsView ensureVisible() and centerOn() - c++

I am going to do pan/scale stuff on QGraphicsView.
So I read the documentation of QGraphicsView and see some utility functions like ensureVisible() and centerOn().
I think I understand what the documentation says but I can' t manage to write a working example.
Could you please write/suggest me an example code to understand the issue.

Ton pan the view by a certain amount (for example in your view's mouseMoveEvent()), assuming MyView is a subclass of QGraphicsView (all the following code was ported from Python, I didn't test it):
void MyView::moveBy(QPoint &delta)
{
QScrollBar *horiz_scroll = horizontalScrollBar();
QScrollBar *vert_scroll = verticalScrollBar();
horiz_scroll->setValue(horiz_scroll.value() - delta.x());
vert_scroll->setValue(vert_scroll.value() - delta.y());
}
To fit a rectangle specified in scene coordinates by zooming and panning:
void MyView::fit(QRectF &rect)
{
setSceneRect(rect);
fitInView(rect, Qt::KeepAspectRatio);
}
Note that if your scene contains non transformable items (with the QGraphicsItem::ItemIgnoresTransformations flag set), you'll have to take extra steps to compute their correct bounding box:
/**
* Compute the bounding box of an item in scene space, handling non
* transformable items.
*/
QRectF sceneBbox(QGraphicsItem *item, QGraphicsItemView *view=NULL)
{
QRectF bbox = item->boundingRect();
QTransform vp_trans, item_to_vp_trans;
if (!(item->flags() & QGraphicsItem::ItemIgnoresTransformations)) {
// Normal item, simply map its bounding box to scene space
bbox = item->mapRectToScene(bbox);
} else {
// Item with the ItemIgnoresTransformations flag, need to compute its
// bounding box with deviceTransform()
if (view) {
vp_trans = view->viewportTransform();
} else {
vp_trans = QTransform();
}
item_to_vp_trans = item->deviceTransform(vp_trans);
// Map bbox to viewport space
bbox = item_to_vp_trans.mapRect(bbox);
// Map bbox back to scene space
bbox = vp_trans.inverted().mapRect(bbox);
}
return bbox;
}
In that case the bounding rect of your objects becomes dependent on the view's zoom level, meaning that sometimes MyView::fit() won't fit exactly your objects (for example when fitting a selection of objects from a largely zoomed out view). A quick and dirty solution is to call MyView::fit() repeatedly until the bounding rect naturally "stabilizes" itself.

Related

how to scale graphics properly?

Now I need to draw some polylines according to their coordinates. These are coordinates of one poltline:
1.15109497070313E+02 2.73440704345703E+01
1.15115196228027E+02 2.73563938140869E+01
1.15112876892090E+02 2.73697128295898E+01
1.15108222961426E+02 2.73687496185303E+01
1.15081001281738E+02 2.73908023834229E+01
1.15078292846680E+02 2.73949108123779E+01
1.15073806762695E+02 2.74090080261230E+01
1.15063293457031E+02 2.74221019744873E+01
1.15059646606445E+02 2.74324569702148E+01
I've drawn these polylines and moved them to the center of window:
QPainter painter(this);
QPainterPath path;
for (auto& arc : layer.getArcs()) {
for (int i = 0; i < arc.pts_draw.size() - 1; i++)
{
QPolygonF polygon = QPolygonF(arc.pts_draw);
path.addPolygon(polygon);
}
}
// move all polylines to the center of window
QPointF offset = rect().center() - path.boundingRect().center();
painter.translate(offset);
painter.drawPath(path);
However, what I got in the window was this:
I think it's caused by the coordinates. All coordinates are very close to each other so the graphics will become too small when drawn in the window. So my problem is how to scale the graphics properly? In other words, how can I know the ratio of scaling?
On the QGraphicsView you can call scale(qreal sx, qreal sy) to scale the QGraphicsScene and all it's QGraphicsItems. If you wish to scale each item individually instead of the entire scene, then take each point in the polygon and use Euclidian geometry scaling to scale your polygon. Or you could use something called QTransform like this post did

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.

limit qwtPlotZoomer selection to canvas boundaries

The default behavior of qwtPlotZoomer seems to be that you can select a rectangle which exceeds the axis scale boundaries and canvas size, as seen in this screenshot from the realtime example:
I'd like to restrict the edges of the selection to be equal to the canvas or zoomRectboundaries. Is there any easy way to do this?
You could overload the move() method and bound the position to the contentsRect() of the canvas there ( or maybe better use the pickArea - what usually is the same ).
EDIT:
Based on Uwe's advice, I overloaded the move() function as follows, which prevents the picker's rect from being drawn off-screen.
void QZoomer::move(const QPoint& pos)
{
// bound the picker selection to the zoom base so you can't zoom off screen
QPoint boundedPos = pos;
if (boundedPos.x() > plot()->canvas()->contentsRect().right()) boundedPos.setX(plot()->canvas()->contentsRect().right());
if (boundedPos.x() < plot()->canvas()->contentsRect().left()) boundedPos.setX(plot()->canvas()->contentsRect().left());
if (boundedPos.y() < plot()->canvas()->contentsRect().top()) boundedPos.setY(plot()->canvas()->contentsRect().top());
if (boundedPos.y() > plot()->canvas()->contentsRect().bottom()) boundedPos.setY(plot()->canvas()->contentsRect().bottom());
QwtPlotPicker::move(boundedPos);
}
another way is to override the zoom function and bound the rect to the zoomBase. This limits the actual zoom to the zoom base, but still draws the picker outside the lines.
void Zoomer::zoom(const QRectF& rect)
{
// bound the zooming rect to the zoomBase dimensions
QRectF boundedRect = rect & zoomBase();
QwtPlotZoomer::zoom(boundedRect);
}

Proper data model for a 2D Tilemap (C++, Qt)

I made a small 2D level editor where you can create 2D tile based maps..however, the performance inside my application is really really bad. I am currently thinking to start all over again.
The Problem is, I currently use QGraphicsItem's to represent a single tile inside a QGraphicsScene. A tile has some properties..including an image. When a map is created, I create an item for each tile which draws an image for each tile..which basically is a lot of graphicitems and it slows down the whole application. This is the function that populates a map once it is created :
for(int i=0;i<map->m_rows;i++)
{
for(int j=0;j<map->m_cols;j++)
{
Tile* thetile=map->getAt(i,j);
if(thetile)
{
if(map->getType()==twoditor::RECTANGLETILE)
{
QGraphicsItem* item= new TileGraphicsItem(thetile);
m_scene->addItem(item);
}
else if(map->getType()==twoditor::HEXAGONTILE)
{
QGraphicsItem* item= new HexagonGraphicsItem(thetile);
m_scene->addItem(item);
}
}
}
}
This works for a map with 100x100 Tiles. But if i want to create even larger maps..the loading time is really unbearable..
Can someone give me advice for a better representation of a tile map? Are there other convenient ways to show a map and edit cells(tiles) inside it?
EDIT: TileGraphicItem paint function:
void TileGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,QWidget *widget){
setZValue(0);
if(!m_thetile->getImage().isNull())
{
painter->drawImage(0,0,m_thetile->getImage());
}
QPainterPath circle_path;
QRect duwagrect(boundingRect().x(),boundingRect().y(),boundingRect().width(),boundingRect().height());
circle_path.addRect(duwagrect);
m_pen.setStyle(Qt::SolidLine);
m_pen.setColor(Qt::black);
m_pen.setWidth(1);
painter->setPen(m_pen);
painter->drawPath(circle_path);
if(m_thetile->getProperty()->getBlocks())
{
QPainterPath circle_path;
QRect duwagrect(boundingRect().x()+2,boundingRect().y()+2,boundingRect().width()-3,boundingRect().height()-3);
circle_path.addRect(duwagrect);
m_pen.setStyle(Qt::DotLine);
m_pen.setColor(Qt::red);
m_pen.setWidth(2);
painter->setPen(m_pen);
painter->drawPath(circle_path);
}
if(this->isSelected())
{
QPainterPath circle_path;
QRect duwagrect(boundingRect().x()+2,boundingRect().y()+2,boundingRect().width()-3,boundingRect().height()-3);
circle_path.addRect(duwagrect);
m_pen.setStyle(Qt::SolidLine);
m_pen.setColor(Qt::green);
m_pen.setWidth(3);
painter->setPen(m_pen);
painter->drawPath(circle_path);
}
if(option->state & QStyle::State_MouseOver)
{
QPainterPath circle_path;
QRect duwagrect(boundingRect().x()+2,boundingRect().y()+2,boundingRect().width()-3,boundingRect().height()-3);
circle_path.addRect(duwagrect);
m_pen.setStyle(Qt::SolidLine);
m_pen.setColor(Qt::cyan);
m_pen.setWidth(2);
painter->setPen(m_pen);
painter->drawPath(circle_path);
}
}
Problem is that you are showing everything even things not needed.
You should create only visible items (items in some visible region).
Another faster approach is to create custom QGraphicsItem which paints hole map, and paint only visible tiles (no tiles as sub items).

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.