How to use custom drawing in QGraphicsViews in PyQt? - qt

I need to view QGraphicsScene in 2 QGraphicsViews with condition that they have different scale factors for items in scene. Closest function which I found is drawItems(), but as far I can understand, it must be called manually. How to repaint views automatically?
I have these two code fragments in program:
class TGraphicsView(QGraphicsView):
def __init__(self, parent = None):
print("__init__")
QGraphicsView.__init__(self, parent)
def drawItems(self, Painter, ItemCount, Items, StyleOptions):
print("drawItems")
Brush = QBrush(Qt.red, Qt.SolidPattern)
Painter.setBrush(Brush)
Painter.drawEllipse(0, 0, 100, 100)
...
Mw.gvNavigation = TGraphicsView(Mw) # Mw - main window
Mw.gvNavigation.setGeometry(0, 0, Size1, Size1)
Mw.gvNavigation.setScene(Mw.Scene)
Mw.gvNavigation.setSceneRect(0, 0, Size2, Size2)
Mw.gvNavigation.show()
__init__ works, Mw.gvNavigation is displayed and there are Mw.Scene items in it, but drawItems() isn't called.

The drawItems methods on QGraphicsView and QGraphicsScene objects have been deprecated in Qt 4.6 and have to be enabled using the IndirectPainting flag, but I would't recommend using deprecated features.
Here's another stack overflow question on a similar issue. One of the answers shows how to make the paint methods on individual items in a scene aware of which view is painting them, and use different paint code when drawn in different views.

Related

How to detect Qt mouse events only over QPainted objects

I am attempting to produce a compass widget programatically that looks much like this:
I want each "slice" of the compass to act as a button for interaction w/ the rest of the app. To that end, I figured making them out of QAbstractButtons made the most logical sense, and started down this path:
class compassWedge(QAbstractButton):
def __init__(self, start_angle, parent=None):
super().__init__(parent)
self.start = start_angle
self.setFixedSize(550, 550)
self.setMouseTracking(True)
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
brush = QBrush()
if self.underMouse():
brush.setColor(Qt.black)
else:
brush.setColor(Qt.white)
pen = QPen(Qt.black)
pen.setWidth(5)
painter.setBrush(brush)
painter.setPen(pen)
painter.drawPie(25, 25, 500, 500, self.start, 45 * 16)
painter.end()
class compassApplet(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setFixedSize(550, 550)
self.wedges = []
for start in range(0, 360 * 16, 45 * 16):
self.wedges.append(compassWedge(start, self))
And visually this works perfectly so far:
The problem is, for underMouse(), the entirety of the 550x550 slice widget area is considered. I want instead to detect when the mouse is inside the pixels generated within the paintEvent for each slice, i.e. the pie area created by painter.drawPie(...) in each object.
How can I accomplish this without having to do complicated geometry to check mouse position against pie-shaped areas?
Method 1: perform the geometric calculations yourself:
How can I accomplish this without having to do complicated geometry to check mouse position against pie-shaped areas?
Doing the geometry check yourself isn't that difficult:
Check if the mouse is inside the circle, i.e. distance between mouse and center of circle <= radius of circle.
Use atan2 to calculate the angle and use it to determine the correct segment
Method 2: Use QPieSeries
If you really do not want to implement the geometric calculations yourself, visualising the compass using a QPieSeries may be a solution as it provides a hovered function. However, it may be (more) difficult to obtain the exact desired visual representation.

Why Qt's addWidget() can accept 1 argument case?

statusBar()->addWidget(locationLabel);
statusBar()->addWidget(formulaLabel, 1);
Above code is from a Qt program.
And below description is from Qt help when I pressed F1 on addWidget.
As you can see, addWidget shall accept 2 arguments. My question is why above 1st sentence can be executed normally?
void QStatusBar::addWidget ( QWidget * widget, int stretch = 0 )
Adds the given widget to this status bar, reparenting the widget if it
isn't already a child of this QStatusBar object. The stretch parameter
is used to compute a suitable size for the given widget as the status
bar grows and shrinks. The default stretch factor is 0, i.e giving the
widget a minimum of space.
The widget is located to the far left of the first permanent widget
(see addPermanentWidget()) and may be obscured by temporary messages.
This is because C++ has the concept of default arguments, which means that a function declared as
void f(int i, int j=1);
can be called by
f(42);
and
f(42,43);
In the first case, the function call is equivalent to
f(42,1);
In your case,
addWidget(label);
is equivalent to
addWidget(label,0);

How to create an infinitely long line

Whats is the best way of creating a line (QGraphicsLineItem) which starts at some point on the scene and continues to infinity at some angle.
The way I presently do this is by calculating were the line intersects the view and drawing the line segment.
Is there a better way?
Could I for example set the lines length to some massive number?
You could define its paint() and shape() functions so they always use all the space available and needed inside the scene, i.e. inside the visible part of qgraphicsview.
Guidelines:
Examine mapping functions for qgraphicsview, qgraphicsscene and
qgraphicsitem (mapToScene, mapToItem, mapToView or something like that)
Define your shape() and paint() functions as if your
line is exactly long all over the view (by using the mapping functions above)
So, how ever the user moves his view, repaint will examine the space used by qgraphicsview and draw exactly that long. The illusion is created that the line goes on-and-on.

Qt/PyQt - frequently drawing pixmap to widget, partly not drawing correctly

I'm working on a Qt based application (actually in PyQt but I don't think that's relevant here), part of which involves plotting a potentially continuous stream of data onto a graph in real time.
I've implemented this by creating a class derived from QWidget which buffers incoming data, and plots the graph every 30ms (by default). In __init__(), a QPixmap is created, and on every tick of a QTimer, (1) the graph is shifted to the left by the number of pixels that the new data will take up, (2) a rectangle painted in the space, (3) the points plotted, and (4) update() called on the widget, as follows (cut down):
# Amount of pixels to scroll
scroll=penw*len(points)
# The first point is not plotted now, so don't shift the graph for it
if (self.firstPoint()):
scroll-=1
p=QtGui.QPainter(pm)
# Brush setup would be here...
pm.scroll(0-scroll, 0, scroll, 0, pm.width()-scroll, pm.height())
p.drawRect(pm.width()-scroll, 0, scroll, pm.height())
# pen setup etc happens here...
offset=scroll
for point in points:
yValNew = self.graphHeight - (self.scalePoint(point))
# Skip first point
if (not(self.firstPoint())):
p.drawLine(pm.width()-offset-penw, self.yVal, pm.width()-offset, yValNew)
self.yVal = yValNew
offset-=penw
self.update()
Finally, the paintEvent simply draws the pixmap onto the widget:
p = QtGui.QPainter(self)
p.drawPixmap(0, 0, self.graphPixmap)
As far as I can see, this should work correctly, however, when data is received very fast (i.e. the entire graph is being plotted on each tick), and the widget is larger than a certain size (approx 700px), everything to the left of the 700px area lags considerably. This is perhaps best demonstrated in this video: http://dl.dropbox.com/u/1362366/keep/Graph_bug.swf.html (the video is a bit laggy due to the low frame rate, but the effect is visible)
Any ideas what could be causing this or things I could try?
Thanks.
I'm not 100% sure if this is the problem or not, but I thought I might make at least some contribution.
self.update() is an asynchronous call, which will cause a paint event at some point later when the main event loop is reached again. So it makes me wonder if your drawing is having problems because of the sync issue between when you are modifying your pixmap vs when its actually getting used in the paintEvent. Almost seems like what you would need for this exact code to work is a lock in your paintEvent, but thats pretty naughty sounding.
For a quick test, you might try forcing the event loop to flush right after your call to update:
self.update()
QtGui.QApplication.processEvents()
Not sure that will fix it, but its worth a try.
This actually might be a proper situation to be using repaint() and causing a direct paint event, since you are doing an "animation" using a controlled framerate: self.repaint()
I noticed a similar question to yours, by someone trying to graph a heart monitor in real time: http://qt-project.org/forums/viewthread/10677
Maybe you could try restructuring your code similar to that. Instead of splitting the painting into two stages, he is using a QLabel as the display widget, setting the pixmap into the QLabel, and painting the entire graph immediately instead of relying on calls to the widget.update()

Make QGraphicsView do smooth centerOn

i am not really newbie in Qt, but there are a few things i don't know...
I am programming in Python, but feel free to post your answers in ANY language.
So, i have a few QGraphicsItem (s), positioned inside a QGraphicsScene, the scene is viewed with a normal QGraphicsView. Everything is normal.
My scene is very large, 10,000 x 10,000 pixels, all graphic items are scattered around.
For example :
# Creating Scene object.
scene = QtGui.QGraphicsScene()
scene.setSceneRect(0, 0, 10000, 10000)
# Creating View object.
view = QtGui.QGraphicsView()
view.setScene(scene)
# Adding some objects to scene.
# scene.addItem(...)
# ...
# The list of items.
items = scene.items()
# This is how i center on item.
view.centerOn(some_item)
view.fitInView(some_item, Qt.KeepAspectRatio)
My question is, how can i center the view on every item, using something similar to centerOn, but smoothly ?
Currently, centerOn goes FAST on next item, i want to move it slooowly, maybe using QPropertyAnimation with easing curve ?
I tried to move the view to the next item using view.translate(1, 1) in a big cicle, but the movement is too fast, just like centerOn.
I tried to put some waiting with time.sleep(0.01) between the translating, but the windows blocks untill the cicle exists... so it's bad.
Thank you very much !
I once used a QTimeLine (with EaseInOutCurve), connected it to a slot, and then used that value to translate the view rect, like this:
const QRectF r = ...calculate rect translated by timeline value and trajectory...
view->setSceneRect( r );
view->fitInView( r, Qt::KeepAspectRatio );
For the trajectory I used a QLineF with start and end position for the smooth scrolling.
Then one can use the value emitted by timeline nicely with QLineF::pointAt().
In my case I needed to set both SceneRect and fitInView to make it behave like I wanted.
I solved my problem by placing a high value on setSceneRect. Then I centralize the scene on an object or position.
example:
this->ui->graphicsView->setSceneRect (0,0,100000000, 100000000);
this->ui->graphicsView->centerOn(500,1030);
With the setSceneRect size GraphicsView does not work right.

Resources