pyQt4 QGraphicsView on mouse event help needed - python

I have done a fair amount of searching for this problem, which is probably trivial. However I am new to pyQT and am completely stuck. Any help would be appreciated.
I simply want to place, move and draw objects on a QGraphicsView widget using QGraphicsScene.
The following code to handle mouse press events works, but it fires when the mouse is clicked anywhere in the form and not just in the QGraphicViewer (also as the result of this the object is subsequently placed in the wrong place).
Here is an extract from the code I'm using now
def mousePressEvent(self, ev): #QGraphicsSceneMouseEvent
if ev.button()==Qt.LeftButton:
item = QGraphicsTextItem("CLICK")
item.setPos(ev.x(), ev.y())
#item.setPos(ev.scenePos())
self.scene.addItem(item)
I know I should be using the QGraphicsSceneMouseEvent and I can see how this is implemented in C++; but I have no idea how to get this to work in Python.
Thanks

Try extending QtGui.QGraphicsScene and using its mousePressEvent and the coordinates from scenePos(). Something like:
class QScene(QtGui.QGraphicsScene):
def __init__(self, *args, **kwds):
QtGui.QGraphicsScene.__init__(self, *args, **kwds)
def mousePressEvent(self, ev):
if ev.button() == QtCore.Qt.LeftButton:
item = QtGui.QGraphicsTextItem("CLICK")
item.setPos(ev.scenePos())
self.addItem(item)

Related

PyQt live editing/drawing of Delegate

So I currently have a list of video clips being displayed in a QListView and have created a custom Delegate to paint preview thumbnails for them using data from a QStandardItemModel.
Ultimately I want to be able to animate the thumbnails as you mouse over them so they play a preview of the clip (by showing only a couple frames). I want two versions of this. One that just plays, and another that shows frames based upon your mouse position (further towards the left is the beginning of the clip, and as you move towards the right, it scrubs through).
Right now I am trying to figure out how to implement the animation piece. Should I be using the Delegate to draw frames and be updating a custom frame data on my model that the Delegate will use to know what frames to draw with a reimplemented paint function (already have the framework of a paint function there)? Or will this be too resource intensive? And then what is the best way to do that? I looked into editor widgets, but those seem to be not seem to edit model data/update delegates in realtime and instead only upon finishing editing. Also I would like this to initialize on mouseover and that doesn't seem to be an option in the built-in edit triggers.
class AnimatedThumbDelegate(QItemDelegate):
def __init__(self,parent=None):
super().__init__(parent)
self.height=200
self.width=self.height*1.77
self.frames=10
def paint(self,painter,option,index):
painter.save()
image=index.data(FootageItem.FILMSTRIP)
if not image.isNull():
painter.drawPixmap(QRect(option.rect),image,QRect(image.width()/self.frames*index.data(FootageItem.FRAME),0,image.width()/self.frames,image.height()))
painter.restore()
def sizeHint(self,option,index):
return QSize(self.width,self.height)
This delegate paints a portion of a strip image that I am using for preview frames and does so by referencing framedata. FootageItem is just a QStandardItem class that helps construct the data I want to store for these clips and I am just using indices from it here. It fetches a QPixmap from my model. The filmstrip image I am using looks like this:
Can I use an editor widget to update values and force a repaint on the delegate object based upon mouseMoveEvents? And then can I make editors appear on mouseovers? Or should I look at reimplementing QListView to update the delegate with mouse events? Or is there another way I haven't discovered?
I made a widget that behaves roughly how I would want the updating frames portion of this to work, but was hoping to port it over to a delegate class instead of a QWidget so I could have it display data from a table and utilize the Model/View programming that QT has to offer.
class ScrollingThumbnail(QWidget):
def __init__(self,parent,image,rect):
super().__init__(parent)
self.image=image
self.paused=False
self.frame=5
self.frames=10
self.bar=False
self.vis=True
self.thumbRect=rect
self.setMouseTracking(True)
def leaveEvent(self):
self.stop()
def mouseMoveEvent(self,e):
if(e.pos().y()>self.height*0.6):
self.bar=True
self.pause()
self.frame=int(e.pos().x()/(self.width/self.frames))
self.repaint()
elif self.paused:
self.paused=False
self.bar=False
self.play()
def paintEvent(self,e):
qP=QPainter()
qP.begin(self)
if self.vis:
self.drawWidget(qP)
qP.end()
def drawWidget(self,qP):
if not self.image.isNull():
qP.drawPixmap(self.thumbRect,self.image,QRect(self.image.width()/self.frames*self.frame,0,self.image.width()/self.frames,self.image.height()))
if self.bar:
pen=QPen(QColor(255,255,255,50),self.height/60,cap=Qt.RoundCap)
qP.setPen(pen)
off=self.height/20
inc=((self.width-(off*2))/(self.frames-1))
qP.drawLine(off,self.height-off-20,off+(inc)*(self.frame),self.height-off-20)
def play(self):
if not self.paused:
self.frame=(self.frame+1)%self.frames
self.repaint()
self.timer=threading.Timer(0.1,self.play)
self.timer.start()
def pause(self):
try:
self.timer.cancel()
except:
pass
self.paused=True
def stop(self):
try:
self.timer.cancel()
except:
pass
self.frame=5
self.repaint()
It uses a threading timer as a cheap hacked way of playing frames in a loop. Probably will look more into better ways to achieve this (possibly using QThread?) Also a gif of it in action as far as desired behavior: i.imgur.com/aKoKs3m.gifv
Cheers,
Alex
Think I figured out a way to go about it using some signals and connecting some slots. Here are the proof of concept classes.
Created this editor class that will update the frame data via signal based upon the mouse position, and then will destroy the editor when your cursor leaves the widget/item. It doesn't really need a paintevent, but I just put one in there so I could see when editing was active.
class TestEditor(QWidget):
editingFinished = Signal()
updateFrame = Signal()
def __init__(self,parent):
super().__init__(parent)
self.setMouseTracking(True)
self.frame=5
def mouseMoveEvent(self,e):
currentFrame=int(e.pos().x()/(self.width()/10))
if self.frame!=currentFrame:
self.frame=currentFrame
self.updateFrame.emit()
def leaveEvent(self,e):
self.frame=5
self.updateFrame.emit()
self.editingFinished.emit()
def paintEvent(self,e):
painter=QPainter()
painter.begin(self)
painter.setBrush(QColor(255,0,0,100))
painter.drawRect(self.rect())
painter.end()
My Delegate connects the editor signals to some basic functions. One will close the editor and the other will update the Frame Data on my model.
class TestDelegate(QItemDelegate):
def __init__(self,parent):
super().__init__(parent)
self.height=200
self.width=300
def createEditor(self,parent,option,index):
editor=TestEditor(parent)
editor.editingFinished.connect(self.commitEditor)
editor.updateFrame.connect(self.updateFrames)
return editor
def setModelData(self, editor, model, index):
model.setData(index,editor.frame,TestData.FRAME)
def paint(self,painter,option,index):
painter.save()
painter.setBrush(QColor(0,255,0))
painter.drawRect(option.rect)
painter.setPen(QColor(255,255,255))
painter.setFont(QFont('Ariel',20,QFont.Bold))
painter.drawText(option.rect,Qt.AlignCenter,str(index.data(TestData.FRAME)))
painter.restore()
def sizeHint(self,option,index):
return QSize(self.width,self.height)
def commitEditor(self):
editor = self.sender()
self.closeEditor.emit(editor)
def updateFrames(self):
editor=self.sender()
self.commitData.emit(editor)
Then all I had to do is enable mouse tracking and connect the "entered" signal to the "edit()" slot on my viewer
dataView=QListView()
dataView.setViewMode(1)
dataView.setMovement(0)
dataView.setMouseTracking(True)
dataView.entered.connect(dataView.edit)
Also had a super simple class for constructing test data items. Basically just set an empty Role to 5 for frame data.
class TestData(QStandardItem):
FRAME=15
def __init__(self,data=None):
super().__init__(data)
self.setData(5,self.FRAME)
It currently isn't fully functional as far as including a "play" function that will scrub through frames automatically. But I think that should be easy enough to set up. Also need to figure out how to now handle selections because the editor becomes active when you move over an item effectively blocking it from being selected. Currently looking into maybe implementing a Mouse up event that will then update the selection model attached to my viewer.

How to place an image at a certain position in QtGraphicsView/QtGraphicsScene (PyQt4)?

I'm creating an application using Qt Designer and PyQt4. I want to know how I can add an image to the QtGraphicsView widget to my desired location.
For example, when I click on the QtGraphicsView widget, I want to add an image to that exact location. I've searched online but I couldn't find anything that was of any help.
I've created a scene subclass to manage the items that will be displayed in the QtGraphicsView widget. I'm able to get the coordinates of the place where I click but I don't know how to place the item in that certain position. Below is my code:
class graphicsScene(QtGui.QGraphicsScene):
def __init__(self, parent=None):
super(graphicsScene, self).__init__(parent)
def mousePressEvent(self, event):
position = QtCore.QPointF(event.scenePos())
pixmap = QtGui.QPixmap("host.png")
pixmap_scaled = pixmap.scaled(30, 30, QtCore.Qt.KeepAspectRatio)
self.itemAt(pixmap_scaled,position.x(),position.y())
self.addPixmap(pixmap_scaled)
print "pressed here: " + str(position.x()) + ", " + str(position.y())
self.update()
def mouseReleaseEvent(self, event):
position = QtCore.QPointF(event.scenePos())
print "released here: " + str(position.x()) + ", " + str(position.y())
self.update()
class form(QtGui.QMainWindow):
def __init__(self):
super(mininetGUI, self).__init__()
self.ui = uic.loadUi('form.ui')
self.scene = graphicsScene()
self.ui.view.setScene(self.scene)
Use addItem(your_pixmap_object) to add the QPixmap to the scene. Then you can use setPos(...) on the returned QGraphicsItem (this is returned when you use addItem(...) and the insertion of the item into the scene was succcessful).The point that you pass to your setPos(...) would be the one that is part of the event (as you have done already by calling event.scenePos()).
pixmap = QPixmap(...)
sceneItem = self.addItem(pixmap)
sceneItem.setPos(event.scenePos())
If you want to use QGraphicsPixmapItem the procedure is the same as the one above but just use self.addPixmap(...) as you have done in your code.
There is one thing that you might want to handle in addition to just placing the item - the case where you press the mouse button, move the cursor somewhere else in the scene while still pressing the button and then release it. This will insert the item at the starting position of your movement event (while pressing the button and moving) however this might not be what you want to do. You have to think about whether it will be better to handle the insertion inside the mouseReleaseEvent(...) instead. It really depends on how you want things to work for this particular scenario.

Customising location-sensitive context menu in QTextEdit

I am trying to adjust the context menu in a QTextEdit. I have succeeded in getting access to and then displaying the default menu with the following code:
class LinkTextBrowser(QTextBrowser):
def contextMenuEvent(self, event):
menu = self.createStandardContextMenu(event.pos())
# do stuff to menu here
menu.popup(event.globalPos())
However, this does not work for location-sensitive clicks. The case in question is the "Copy Link Location" item in a QTextBrowser's right click menu, which is only enabled if you right click on a link, for obvious reasons. I can't get it to ever be enabled. I suspect I am passing the wrong position to createStandardContextMenu, but I can't figure out the correct position to feed it.
I have tried both event.globalPos() and event.pos(), neither of which work. I also looked at the source code for QTextEdit, but didn't get anywhere. What position is it expecting?
Edit: Update: It appears the problem is the scrolling in the TextBrowser; if I scroll to the top of the window and use event.pos() it behaves. I don't have working code yet, but correcting for the scroll is the solution.
(Specifically, I want to disconnect the signal emitted by the Copy Link Location action and connect it to my own function so I can adjust the URL before copying it to the clipboard, allowing me to make links absolute and so forth before copying, and I have no particular desire to re-write the working bits.)
Here is the working transform of the coordinates:
class LinkTextBrowser(QTextBrowser):
def contextMenuEvent(self, event):
self.link_pos = event.pos()
# correct for scrolling
self.link_pos.setX(self.link_pos.x() + self.horizontalScrollBar().value())
self.link_pos.setY(self.link_pos.y() + self.verticalScrollBar().value())
menu = self.createStandardContextMenu(self.link_pos)
# do stuff to menu
menu.popup(event.globalPos())
Try self.mapToGlobal(event.pos()), it should take into account scroll position.
Maybe you can try something like:
QMenu *menu = new QMenu();
menu->addAction(...);
menu->exec(textEdit->mapToGlobal(pos));
It's C++ but I'm sure that you can easy convert it to python.

Tooltips or status bar not working in a PyQt app from Maya

I've got a PyQt4 QDialog that I'm launching from python in Autodesk Maya. I want to have a status bar in the window or, if need be, tooltips. Maya doesn't seem to approve of either. I've implemented it using the method described here:
http://www.qtcentre.org/threads/10593-QDialog-StatusBar
If I launch my app standalone, both work correctly. Running from Maya, though, the status updates get sent to the general Maya status bar (which is not very obvious if you're in a different window), and Maya seems to steal the events completely from me: if I monitor the events that my event() method is getting, it never gets a QEvent.StatusTip event. I've tried swapping my QDialog for a QMainWindow, but it doesn't seem to change anything.
Any suggestions for avenues to look down to get this working?
At the moment, I'm working around this in a horrible way: subclassing each of widgets I want to use, and adding a signal to send to the parent, self.setMouseTracking(True), and a mouseMoveEvent(self, e) that sends the signal to the parent. Then at the top of the tree I set the status bar. It's the sort of nasty code that makes me feel dirty, subclassing all the widget types, but it does seem to be working.
Any better suggestions VERY gratefully received!
I need to tackle this as well, so your post was quite helpful.
When I have encountered event issues like this before, I solved it by using installEventFilter on all widgets (the same filter), rather than subclassing. Then you can receive and accept the events to block them from Maya (or let them through, e.g. space bar for marking menus over your gui, etc)
Here is what I use to let Maya have the spacebar (marking menus), ctrl+A (attribute editor toggle) and ctrl+Z (undo). This would be added to your event filter:
if event.type() == QEvent.KeyPress:
key = event.key()
mod = event.modifiers()
if ((ctrla and key == Qt.Key_A and mod == Qt.ControlModifier) or # CTRL+A
(ctrlz and key == Qt.Key_Z and mod == Qt.ControlModifier) or # CTRL+Z
(space and key == Qt.Key_Space)): # Space Bar
event.ignore()
return True
return False
You would just need to do the opposite and use event.accept() and return False
For QWidgets, colts answer here is pretty good.
This is how I got it working for QActions:
class ActionFn(object):
def __init__(self, action):
self.action = action
def __call__(self):
self.action.parent()._displayStatusTip(self.action.statusTip())
Then after the action is created:
newAction._statusFn = _StatusTipActionFn(newAction)
newAction.hovered.connect(newAction._statusFn)
I hope this helps.

gtk treeview: place image buttons on rows

For each row in my treeview, I want 4 image buttons next to each other. They will act like radio buttons, with only one being activateable at a time. Each button has an 'on' and 'off' image.
How do I do this? I figured out how to put images there, and how to put togglebuttons, but this seems to require some more effort as there is no pre-built cellrenderer that does what I want.
Basically what'd solve my problem is figuring out how to make an image in a gtk.treeview clickable. any ideas?
Have a look at this 'http://www.daa.com.au/pipermail/pygtk/2010-March/018355.html'. It shows you how to make a gtk.CellRendererPixbuf activatable, and able to connect to a click event signal.
cell = CellRendererPixbufXt()
cell.connect('clicked', func)
Update
As pointed out this answer, or the reference given doesn't work as advertised. It's missing the do_activate method, which needs to emit the clicked signal. Once it's done that, then the cell.connect will work.
Sorry if this answer mislead anyone.
Here is a short version without kiwi requirement.
class CellRendererClickablePixbuf(gtk.CellRendererPixbuf):
__gsignals__ = {'clicked': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
(gobject.TYPE_STRING,))
}
def __init__(self):
gtk.CellRendererPixbuf.__init__(self)
self.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE)
def do_activate(self, event, widget, path, background_area, cell_area,
flags):
self.emit('clicked', path)
Here is what worked for me:
class CellRendererClickablePixbuf(gtk.CellRendererPixbuf):
gsignal('clicked', str)
def __init__(self):
gtk.CellRendererPixbuf.__init__(self)
self.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE)
def do_activate(self, event, widget, path, background_area, cell_area, flags):
self.emit('clicked', path)

Categories

Resources