PDA

View Full Version : Problem with a click-and-drag event, and a question on good practice



Aunvre
29th July 2014, 03:10
Hi everyone, this is my first post here, so I apologize if this is the wrong category to be posting in.

I'm trying to design a node graph editor, similar to those seen in Blender, the Unreal Engine, and Unity. As part of this, I am trying to make a widget extended off of QGraphicsView that, when displayed, a large grid is in the background, and this view can be scrolled by holding down a mouse button and dragging it. However, the code I have right now doesn't work quite right, as the grid doesn't update it's position with the drag offsets.

NodeGraphWidget.h:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QGraphicsView>
#include <QKeyEvent>
#include <QPoint>

class NodeGraphWidget : public QGraphicsView
{
Q_OBJECT

public:
NodeGraphWidget(QWidget *parent = 0);

public slots:
//void zoomIn();
//void zoomOut();

protected:
void keyPressEvent(QKeyEvent *event);
void drawBackground(QPainter *painter, const QRectF &rect);
void mousePressEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);

float mScale;
int mOffsetX;
int mOffsetY;
bool mMiddleMouseDown;

QPoint mOldMousePos, mNewMousePos;
private:
};

#endif // MAINWINDOW_H

NodeGraphWidget.cpp:
(Note that I purposefully set the offsets to a constant, just to test if the graph would move)

#include "NodeGraphWidget.h"

#include <QBrush>
#include <QDebug>
#include <QEvent>
#include <QGraphicsScene>
#include <QKeyEvent>

NodeGraphWidget::NodeGraphWidget(QWidget *parent)
: QGraphicsView(parent)
{
QGraphicsScene *scene = new QGraphicsScene(this);
scene->setItemIndexMethod(QGraphicsScene::NoIndex);
scene->setSceneRect(0, 0, 400, 400);
setScene(scene);
setViewportUpdateMode(BoundingRectViewportUpdate);
setCacheMode(CacheBackground);
setRenderHint(QPainter::Antialiasing);
setTransformationAnchor(AnchorUnderMouse);
//this->ch
scale(qreal(1.0), qreal(1.0));
//setMinimumSize(400, 400);
setWindowTitle(tr("Test"));

mOffsetX = 0;
mOffsetY = 0;
mScale = 1.f;
}

void NodeGraphWidget::keyPressEvent(QKeyEvent *event)
{
Q_UNUSED(event);
qDebug() << event->key();
switch(event->key())
{
default: setWindowTitle("FUCK");
}
}

void NodeGraphWidget::drawBackground(QPainter *painter, const QRectF &rect)
{
Q_UNUSED(rect);
qDebug() << "redraw bkg, " << mOffsetX << " " << mOffsetY;

QRectF sceneRect = this->sceneRect();
painter->setBrush(QBrush(QColor(64,64,64)));
painter->fillRect(sceneRect, Qt::SolidPattern);

painter->setPen(QColor(80, 80, 80));
const int gridSize = 150;
for(int y = -3; y < (int)(height()*mScale)%gridSize; y++)
{
for(int x = -3; x < (int)(width()*mScale)%gridSize; x++)
{
painter->drawRect((x*gridSize)-(mOffsetX), (y*gridSize)-(mOffsetY), gridSize, gridSize);
}
}
}

void NodeGraphWidget::mousePressEvent(QMouseEvent *event)
{
switch(event->button())
{
case Qt::MiddleButton:
qDebug() << "DOWN";
mMiddleMouseDown = true;
mOldMousePos = event->localPos().toPoint();
break;
}
}

void NodeGraphWidget::mouseMoveEvent(QMouseEvent *event)
{
if(mMiddleMouseDown == true)
{
qDebug() << "DRAG";
mNewMousePos = event->localPos().toPoint();
mOffsetX = 75;// mOldMousePos.x() - mNewMousePos.x();
mOffsetY = 75;// mOldMousePos.y() - mNewMousePos.y();
invalidateScene();
}
}

void NodeGraphWidget::mouseReleaseEvent(QMouseEvent *event)
{
switch(event->button())
{
case Qt::MiddleButton:
qDebug() << "UP";
mMiddleMouseDown = false;
//mOldMousePos = QPoint(0, 0);
//mNewMousePos = QPoint(0, 0);
mOffsetX = 0;
mOffsetY = 0;
break;
}
}

At my qDebug() points, the offset variables are indeed being set, it's just when the view is "redrawn" it isn't being offset. Can anybody offer any help on this? I'm totally stumped.

Which brings me to my next question: is it good practice to have a widget that isn't specified as a scrollable one to be able to do so? I mostly did it so the scrollbars wouldn't show up (and yes, I know that they can be disabled on the scrollable widget classes). It just seemed like a good idea in hindsight. Now that I'm here, however, is this at all good practice? Or should I switch to a scrollable widget to extend off of? Any suggestions would be greatly appreciated, for either of my issues.

Thanks.

wysota
29th July 2014, 12:09
Basically in drawBackground() all drawing is done in scene coordinates and not in view coordinates and it seems to me you are trying to do it the other way.


#include <QtWidgets>


class GraphicsView : public QGraphicsView {
public:
GraphicsView(QWidget *parent = 0) : QGraphicsView(parent) {
setCacheMode(CacheBackground);
}
protected:
void drawBackground(QPainter *painter, const QRectF &rect) {
const int gridSize = 80;
painter->save();
painter->fillRect(rect, Qt::white);
QPen pen;
pen.setColor(Qt::gray);
pen.setWidth(0);
painter->setPen(pen);
int i = rect.top() / gridSize;
while(i <= rect.bottom()) {
painter->drawLine((int)rect.left(), i, (int)rect.right(), i);
i+=gridSize;
}
i = rect.left() / gridSize;
while(i<= rect.right()) {
painter->drawLine(i, (int)rect.top(), i, (int)rect.bottom());
i+=gridSize;
}

painter->restore();

}
};

int main(int argc, char **argv) {
QApplication app(argc, argv);
GraphicsView view;
view.setScene(new QGraphicsScene(0,0,5000,4000));
view.show();
return app.exec();
}

BTW. The code posted here has some funny behavior if you scroll the view too fast but you should get the general picture of what I mean.

Aunvre
29th July 2014, 15:07
Thanks wysota, after looking at your example, I picked mine apart and tweaked it some, and now it's working.

BTW, I just threw in an invalidateScene() call at the start of the drawBackground() and that seemed to fix the weird display problem.

wysota
29th July 2014, 16:05
BTW, I just threw in an invalidateScene() call at the start of the drawBackground() and that seemed to fix the weird display problem.

Bad idea. This way you are redrawing the whole scene (including items). "My" artifacts come most probably from flooring before assignment to "i" variable. They can easily be overcome by assigning 0 instead (with a slight performance penalty).