Programmatic QGraphicsView scrolling not updating properly - c++

I have a custom class derived from QGraphicsView that implements a slot call scrollHorizontal(int dx), inside the code is simply
void CustomView::scrollHorizontal(int dx){
scrollContentsBy(dx, 0);
}
My problem is, scrolling like this works but doesn't update the scene properly, instead any pixels found on the edge of the view are repeated instead of having a fresh call to the item's paint() method.
I've attempted calling update() after, but nothing happens. I tried enabling scrolling by dragging and updates work fine! But I need it done programmatically, and since I have the scroll bars hidden things like horizontalScrollBar()->setValue() do not scroll the view.
I also tried :
scrollContentsBy(dx, 0);
this->scene()->invalidate(sceneRect());
this->update();
update:
QPointF center = mapToScene(viewport()->rect().center());
centerOn(center.x() - dx, center.y());
update();
is working, but now my top view is scrolling slower than my bottom view, which is a new problem. They are linked with signals and slots, in the bottom view i have scrollContentsBy(int dx, int dy) overrided to emit horizontalScroll(dx); which is caught by the above slot in the top view.
Any ideas why the scrolls happen at different rates? Might it have something to do with the scroll bars being a part of the bottom view effectively making it a "smaller" window?
update 2:
The different scroll rates seems to stem from some rounding happening to give me an integer based "center" using mapToScene(viewport()->rect().center()); , as you scroll and the slower you scroll the more this error adds up, the faster you scroll the less total error.
Is there a way for me to get around this? I don't see any way to get a floating point center point.
update 3:
So I have this mostly solved, turns out the mapToScene was needed(code I found elsewhere on the web).
I fixed this by storing QPointF of FP calculated center of the viewport, now the amount of error when scrolling the two views is unnoticeable.
The final issue is, the views no longer line up when you scroll ANY amount to the right, and then resize the window then scroll again. I assume this has something to do with the logical ordering of when the center point is calculated and when the centering happens.
Right now I use the following code snippet in QGraphicsScene::ResizeEvent() and elsewhere that updates the center as needed
QRectF viewPort(viewport()->rect());
QPointF rectCenter((viewPort.x() + viewPort.x() + viewPort.width())/2.0, (viewPort.y() + viewPort.y() + viewPort.height())/2.0);
viewCenter = rectCenter;
and my horizontalScroll(int dx) slot
void CustomView::horizontalScroll(int dx)
{
viewCenter.setX(viewCenter.x() - dx);
centerOn(viewCenter.x(), viewCenter.y());
update();
}
How can I fix the issue when re-sizing the window breaking the alignment of the two views? If any more clarification is needed please just ask, I can try to give skeletons of what I'm referring to if need be.
Update 4:
Rough code Skeleton
Class HeaderView:
class HeaderView View : public QGraphicsView
{
Q_OBJECT
public:
HeaderView(QWidget * parent = 0);
HeaderView(QGraphicsScene * scene, QWidget * parent = 0);
private:
QPointF viewCenter;
protected:
void resizeEvent ( QResizeEvent * event );
public slots:
void horizontalScroll(int);
void addModel(qreal, qreal, const QString&);
};
HeaderView.cpp
void HeaderView::resizeEvent(QResizeEvent *event)
{
QGraphicsView::resizeEvent(event);
QRectF viewPort(viewport()->rect());
QPointF rectCenter((viewPort.x() + viewPort.x() + viewPort.width())/2.0, (viewPort.y() + viewPort.y() + viewPort.height())/2.0);
viewCenter = rectCenter;
}
void HeaderView::horizontalScroll(int dx)
{
viewCenter.setX(viewCenter.x() - dx);
centerOn(viewCenter.x(), viewCenter.y());
update();
}
Class EventView:
class EventView : public QGraphicsView
{
Q_OBJECT
public:
EventView(QWidget * parent = 0);
EventView(QGraphicsScene * scene, QWidget * parent = 0);
QRectF visibleRect();
protected:
void scrollContentsBy ( int dx, int dy );
signals:
void horizontalScroll(int);
};
EventView.cpp
void EventView::scrollContentsBy(int dx, int dy)
{
QGraphicsView::scrollContentsBy(dx, dy);
if(dx != 0){
emit horizontalScroll(dx);
}
}
Somwhere in Class MainWindow:
connect(eventView, SIGNAL(horizontalScroll(int)), headerView, SLOT(horizontalScroll(int));

I've worked with QGraphicsView in Qt 4.6.3 - 4.7.2 and have to argue that you can use the respective QScrollBar in the following way:
//graphics view initialization
QGraphicsView *graphicsView = new QGraphicsView(parent);
QGraphicsScene *scene = new QGraphicsScene(0,0,widthOfScene,heightOfScene,parent);
graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
graphicsView->setScene(scene);
//in another method
QScrollBar* yPos=graphicsView->verticalScrollBar();
yPos->setValue((int) newValue);
It does not matter if they are hidden or not. They will still respond to setValue(int) as long as you have a graphics scene that is larger than the graphics view.
The QGraphicsView will also respond to ensureVisible, which moves the scrollbars to the appropriate location.

You are not supposed to call scrollContentsBy as explained here: http://qt-project.org/doc/qt-4.8/qabstractscrollarea.html#scrollContentsBy
I don't know if you can still call the hidden scrollbar to scroll it. If not, translate is an option.

Did you try to use the scroll bars? Hiding them doesn't make them non-existent, and the documentation says you should use QScrollBar::setValue to scroll to a given position.
Another option would be to use QGraphicsView::centerOn(QPointF) in conjunction with the current center point -- as you've also tried -- but directly calculating the center point within your method (do not precalculate and store the center point), by using QGraphicsView::mapToScene(int,int):
void CustomView::horizontalScroll(int dx)
{
QPointF viewCenter = mapToScene(width() / 2, height() / 2);
viewCenter += QPointF(dx, 0); // Why did you subtract instead of add dx?
centerOn(viewCenter); // BTW, you don't need to do .x(), .y()
// You can remove update(); as this is already called in centerOn().
}
Please note that if you have, as you said, "scrollContentsBy(int dx, int dy) overrided to emit horizontalScroll(dx)", you also have to call the super class method so that the view can scroll itself:
void CustomView::scrollContentsBy(int dx, int dy)
{
emit horizontalScrolled(dx); // (You should call it different than the slot!)
QGraphicsView::scrollContentsBy(dx, dy); // <-- This is what I mean!
}

Related

Qt how to use paintEvent function to refresh two or more widgets silmultaneously

Now I am using Qt5 to draw Kline of stocks. Two classes are defined for price bar and volume bar, then I add them to a widget by a QSplitter. However, When I move mouse forward or backward and the price-bar chart updates, the volume-bar section will not repaint simultaneously until I put mouse under volume widget. I tried some ideas, but it didn't work. Many thanks.
A part of codes like this:
pvolume = new VolumeBar(this);
pvolume->setObjectName(tr("volume_bar"));
pvolume->setFocusPolicy(Qt::StrongFocus);
pkline = new PriceBar(this);
pkline->setObjectName("price_bar");
pkline->setFocusPolicy(Qt::StrongFocus);
QSplitter *splitterMain = new QSplitter(Qt::Vertical, 0);
splitterMain->insertWidget(0, pkline);
splitterMain->insertWidget(1, pvolume);
splitterMain->setStretchFactor(0, 4);
splitterMain->setStretchFactor(1, 1);
For pricebar, the paintEvent function is overrided as follows,
void PriceBar::paintEvent(QPaintEvent *event)
{
KLineGrid::paintEvent(event); // parent of VolumeBar and PriceBar
drawLine(); // draw price bar
}
For VolumeBar, it is overrided like this,
void VolumeBar::paintEvent(QPaintEvent *event)
{
KLineGrid::paintEvent(event);
drawYtick(); // y axis
drawVolume();
}

Qt QXYSeries and ChartView - modify hovered behavior to trigger within a range

I have a scatter plot represented by a QXYSeries and viewed with a ChartView from Qt Charts 5.7.
I want to hover my mouse over the plot, have "hovered" trigger within a certain distance, rather than only when my cursor is directly on top of a point. Imagine a circle around the mouse, that will trigger hovered whenever any part of the series is within it.
Is there a way to get this behavior?
Eventually, I got this behavior by creating a class that inherits from QChartView and overriding mouseMoveEvent(QMouseEvent* event) thusly:
void ScatterView::mouseMoveEvent(QMouseEvent* event)
{
if(!this->chart()->axisX() || !this->chart()->axisY())
{
return;
}
QPointF inPoint;
QPointF chartPoint;
inPoint.setX(event->x());
inPoint.setY(event->y());
chartPoint = chart()->mapToValue(inPoint);
handleMouseMoved(chartPoint);
}
void ScatterView::handleMouseMoved(const QPointF &point)
{
QPointF mousePoint = point;
qreal distance(0.2); //distance from mouse to point in chart axes
foreach (QPointF currentPoint, scatterSeries->points()) {
qreal currentDistance = qSqrt((currentPoint.x() - mousePoint.x())
* (currentPoint.x() - mousePoint.x())
+ (currentPoint.y() - mousePoint.y())
* (currentPoint.y() - mousePoint.y()));
if (currentDistance < distance) {
triggerPoint(currentPoint);
}
}
}

QT Graphic scene/view - moving around with mouse

I created my own classes (view and scene) to display image and objects I added to it, even got zoom in/out function implemented to my view, but now I have to add new functionality and I don't even know how to start looking for it.
Whenever I press the scroll button of my mouse and hold it - I wish to move around the scene, to see different parts of it - just like I would with sliders. It is supposed to be similar to any other program allowing to zoom in/out to image and move around zoomed picture to see different parts of it.
Unfortunately - I don't even know how to look for some basic, because "moving" and similar refer to dragging objects around.
EDIT 1
void CustomGraphicView::mouseMoveEvent(QMouseEvent *event)
{
if(event->buttons() == Qt::MidButton)
{
setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
translate(event->x(),event->y());
}
}
Tried this - but it is working in reverse.
I suppose you know how to handle events using Qt.
So, to translate (move) your view use the QGraphicsView::translate() method.
EDIT
How to use it:
void CustomGraphicsView::mousePressEvent(QMouseEvent* event)
{
if (e->button() == Qt::MiddleButton)
{
// Store original position.
m_originX = event->x();
m_originY = event->y();
}
}
void CustomGraphicsView::mouseMoveEvent(QMouseEvent* event)
{
if (e->buttons() & Qt::MidButton)
{
QPointF oldp = mapToScene(m_originX, m_originY);
QPointF newP = mapToScene(event->pos());
QPointF translation = newp - oldp;
translate(translation.x(), translation.y());
m_originX = event->x();
m_originY = event->y();
}
}

How to zoom in a QgraphicsView using pushbuttons?

I'm building a very simple image editor on Qt creator.I have my image displayed on a QGraphicsView and i want to give the user the ability to zoom in and out by a pushbutton.
I've searched a lot and found how to zoom in and out through the mouse wheel.As i am very new to Qt i can't adjust it to the pushbutton because i don't understand everything clearly.
I' ve tried this(without understanding completely what i'm doing)but the result isn't the wanted.It zooms in only once and quite abruptly.I want a smoother zoom and as many times as i want.
void MainWindow::on_pushButton_clicked(){
QMatrix matrix;
ui->graphicsView->setTransformationAnchor(QGraphicsView::AnchorViewCenter);
matrix.scale(1.0,1.0);
ui->graphicsView->setMatrix(matrix);
ui->graphicsView->scale(1,-1);
}
I would be very grateful if you guys can help
Below is how I implemented zooming in my subclass of QGraphicsView. Note that you'd need to pass in different values of "zoom" to get different magnifications as the zoom factor is an absolute value, not a relative one.
(The optMousePos argument can be set to point to a QPoint indicating the spot that should be the central-point of the zoom transformation, or it can be left NULL if you don't care about that. I use it because I zoom in and out based on the user turning the wheel in his mouse, and when doing that, the user usually wants to zoom in towards the point where his mouse point is currently positioned, rather than in towards the center of the graphics area)
qreal _zoom = 0.0;
[...]
void MyQGraphWidgetSubclass :: SetZoomFactor(qreal zoom, const QPoint * optMousePos)
{
if ((zoom != _zoom)&&(zoom >= 0.02f)&&(zoom <= 1000000.0f))
{
QPointF oldPos;
if (optMousePos) oldPos = mapToScene(*optMousePos);
// Remember what point we were centered on before...
_zoom = zoom;
QMatrix m;
m.scale(_zoom, _zoom);
setMatrix(m);
if (optMousePos)
{
const QPointF newPos = mapFromScene(oldPos);
const QPointF move = newPos-*optMousePos;
horizontalScrollBar()->setValue(move.x() + horizontalScrollBar()->value());
verticalScrollBar()->setValue(move.y() + verticalScrollBar()->value());
}
}
}
void MyQGraphWidgetSubclass :: wheelEvent(QWheelEvent* event)
{
QPoint pos = event->pos();
SetZoomFactor(_zoom*pow(1.2, event->delta() / 240.0), &pos);
event->accept();
}

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.