Updating the contents in QMenu 'live' - python

I have a UI in which it consists of a few QPushButton and a QLineEdit and currently I am having trouble to 'update' the contents within this QMenu that was attached onto a QPushButton.
So assumingly, if there are already 2 cameras in my scene, and as I execute my UI, by pressing onto this setCameraBtn I will get the 2 cameras in the list. However, if I create a new camera where the UI is not yet close, how do I make my QMenu to read in the new camera, like a 'live-update'?
I tried creating another function where it re-read the cameras in scene and retabulate the camLs as well as a connection similar to the one that I have written in the createConnections but it does not seems to be reading in.
camLs = []
class orientCameraUI(QDialog):
def __init__(self, parent=None):
...
...
def initUI(self):
...
...
def createConnections(self):
self.connect(self.orientToCamBtn, SIGNAL('clicked()'), self.orientToCam)
def camMenu(self):
allCams = [cam for cam in cmds.listRelatives(cmds.ls(cameras=1),parent=1) if cam not in ['front','persp','side','top']]
camLs.extend(allCams)
menu = QMenu("menu", self.setCameraBtn)
for item in camLs:
menu.addAction(QAction(item, menu))
self.setCameraBtn.setMenu(menu)
menu.triggered.connect(self._camSelected)
def _camSelected(self, action):
self.currentCamTxt.setText(action.text())

This can be easily accomplished by firing a QThread instead the UI that periodically call a update camera method which checks for the current cameras in the scene and compares them with the one that the UI has already registered. And if there is an addition, change or deletion in the camera(s) then it updates the menu to reflect it.
Another solution is to use scriptJob.

Related

How to create Dialog without buttons window in PyQt5

Basically I have created a Dialog without buttons window as an alert box using Qt5 designer and compiled to Python using Pyuic5 which resulted into partial code as below:
class Ui_alertwindow(object):
def setupUi(self, alertwindow):
alertwindow.setObjectName("alertwindow")
alertwindow.resize(400, 300)
alertwindow.setStyleSheet("background:#222;")
# much more code
Later on, I will have to modify parts of this window which I could not using Qt5 designer, hence I created a separate class to make the changes, as below:
class alertWindowCustom(QDialog, Ui_alertwindow):
def __init__(self):
QDialog.__init__(self)
self.setupUi(self)
self.run()
def run(self):
print("Changes will be made here")
And then finally call this function in QMainWindow when a button is pressed:
def CreateAlertWindow(self):
alertWindow = alertWindowCustom()
alertWindowInput = alertWindow.exec_()
This creates a white dialog box while not responding and in the console I am getting below errors:
Changes will be made here
Unknown property justification
Unknown property justification
QBasicTimer::stop: Failed. Possibly trying to stop from a different thread
QBasicTimer::stop: Failed. Possibly trying to stop from a different thread
Can anyone tell me what is wrong here?

Why is QWidget being destroyed? (PyQt)

So I have the main window. When I click a button in the main window, a new widget is created (in a new window):
self.doorButton.clicked.connect(self.open_door)
def open_door(self):
self.doorwin = QtWidgets.QWidget()
self.doorui = doorwinActions(self.doors)
self.doorui.setupUi(self.doorwin)
self.doorwin.show()
The new QWidget or doorwin has only one widget - tableWidget
I use the object self.doors to populate the table. Now since I have a worker thread (QThread) updating the said object (self.doors), I use QTimer to repopulate the table every 1 second.
class doorwinActions(Ui_doorWin):
def __init__(self,doors):
self.doors = doors
# update setupUi
def setupUi(self, Widget):
super().setupUi(Widget)
Widget.move(self.left, self.top) # set location for window
Widget.setWindowTitle(self.title) # change title
self.timer = QTimer()
self.timer.timeout.connect(lambda:self.popTable())
self.timer.start(1000)
def popTable(self):
mutex.lock()
entries = len(self.doors)
self.tableWidget.setRowCount(entries)
for i in range(entries):
self.tableWidget.setItem(i,0,QtWidgets.QTableWidgetItem(str(self.doors[i][0])))
self.tableWidget.setItem(i,1,QtWidgets.QTableWidgetItem(self.doors[i][1].get_id()))
self.tableWidget.setItem(i,2,QtWidgets.QTableWidgetItem(self.doors[i][1].get_name()))
self.tableWidget.setItem(i,3,QtWidgets.QTableWidgetItem(str(self.doors[i][2])))
mutex.unlock()
When I run the program, it runs smoothly. It opens a new window. If the self.doors object is updated while the window is open, the GUI reflects the change.
BUT, the problem occurs if I reopen the window. If I close the window and then click on the button again, the program crashes with the error:
RuntimeError: wrapped C/C++ object of type QTableWidget has been deleted
From what I understand about my code, when I close the window, the whole widget window (and the table) is deleted. And when I click on the doorButton, new Qwidget/table are created. So, the question is, why would it delete something it just created?
What (sort of) works? - If I move the setup of the door window to the main window's setup, it works. So the open_door function would just be:
def open_door(self):
self.doorwin.show()
The rest would be in the main window setup. But the problem is, then even when I close the window, the QTimer is still going in the background, just eating up processing power.
So, either,
How do I stop the event when the window is closed OR
How do I stop the tableWidget from being deleted?
Your main problem is garbage collection.
When you do this:
def open_door(self):
self.doorwin = QtWidgets.QWidget()
self.doorui = doorwinActions(self.doors)
self.doorui.setupUi(self.doorwin)
self.doorwin.show()
You are creating a new QWidget. It has absolutely no other reference but self.doorwin. This means that if you call open_door again, you will be overwriting self.doorwin, and since the previous widget was not referenced anywhere else, it will get deleted along with all its contents.
Now, QTimers are tricky. You created a QTimer in doorwinActions and QTimers can be persistent even if they have no parent: they keep going on until they're stopped or deleted, and they can only be deleted explicitly or when their parent is deleted (with the exception of timers created with the static QTimer.singleShot() function).
Finally, you must remember that PyQt (like PySide) is a binding. It creates "connections" with the objects created in Qt (let's call them "C++ objects"), and through those bindings we can access those objects, their functions and so on, through python references.
But, and this is of foremost importance, both objects can have a different lifespan:
the python reference can be deleted, and the Qt object can still exist;
the Qt object can be destroyed, yet we still have the python object that referenced (past tense) it;
This is exactly what happens in your case: the self.doorui object is overwritten, but it has an object (the QTimer, self.timer) that is still alive, so the Python garbage collector will not delete it, and the timer is still able to call popTable. But, at that point, the widget (self.doorwin) and its contents have been destroyed on the "C++ side", which causes your crash: while self.tableWidget still exists as a python reference, the actual widget has been destroyed along with its parent widget, and calling its functions causes a fatal error as the binding cannot find the actual object.
Now, how to solve that?
There are various options, and it depends on what you need to do with that window. But, before that, there is something much more important.
You have been manually editing a file generated by pyuic, but those files are not intended for that. They are to be considered like "resource" files, used only for their purpose (the setupUi method), and never, EVER be manually edited. Doing that is considered bad practice and is conceptually wrong for many reasons - and their header clearly warns about that. To read more about the commonly accepted approaches for those files, read the official guidelines about using Designer.
One of those reasons is exactly related to the garbage collection issue explained above.
Note that subclassing the pyuic form class alone is also discouraged (and pointless if you want to extend the widget behavior); the most common, accepted and suggested practice is to create a class that inherits both from the Qt widget class and the UI class. The following code assumes that you have recreated the file with pyuic and named ui_doorwin.py:
# ...
from ui_doorwin import Ui_DoorWin
# ...
class DoorWin(QtWidgets.QWidget, Ui_DoorWin):
def __init__(self, doors):
super().__init__()
self.doors = doors
self.setupUi(self)
self.timer = QTimer(self) # <-- IMPORTANT! Note the "self" argument
self.timer.timeout.connect(self.popTable)
self.timer.start(1000)
def popTable(self):
# ...
With the above code you can be sure that whenever the widget gets deleted for any reason, the timer will be destroyed along with it, so the function will not be called trying to access objects that don't exist anymore.
If you need to keep using an existing instance of the window, the solution is pretty simple: create a None instance (or class) attribute and check if it already exists before creating a new one:
class SomeParent(QtWidgets.QWidget):
doorwin = None
# ...
def open_door(self):
if not self.doorwin:
self.doorwin = DoorWin()
self.doorwin.show()
The above code will not stop the table from updating, which is something you might not want, so you might choose to start and stop the timer depending on when the window is actually shown:
class DoorWin(QtWidgets.QWidget, Ui_DoorWin):
def __init__(self, doors):
super().__init__()
self.doors = doors
self.setupUi(self)
self.timer = QTimer(self)
self.timer.timeout.connect(self.popTable)
def showEvent(self, event):
if not event.spontaneous():
self.timer.start()
def hideEvent(self, event):
if not event.spontaneous():
self.timer.stop()
The event.spontaneous() check above is to prevent stopping the timer if the show/hide event is caused by system calls, like minimizing the window or changing desktop. It's up to you to decide if you want to keep the timer going on and process all data, even if the window is not shown.
Then, if you want to completely destroy the window when it's closed and when a new one is opened, do the following:
class DoorWin(QtWidgets.QWidget, Ui_DoorWin):
def __init__(self, doors):
# ... (as above)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
and then ensure that the widget exists (note that if it's closed by the user the reference still exists):
class SomeParent(QtWidgets.QWidget):
doorwin = None
# ...
def open_door(self):
if self.doorwin:
try:
self.doorwin.close()
except RuntimeError:
pass
self.doorwin = DoorWin()
self.doorwin.show()
I found the solution. My two proposed solutions are the same. If I stop the QTimer on window closing, it no longer gives me the error.
self.exitButton.clicked.connect(self.close_win)
def close_win(self):
self.timer.stop()
self.Widget.close()

Multiple QRadioButton error

I'm using PyQt5 to create a program. I created 3 Radio Buttons, but when I check the first button and check the second button after that. The program will run both of the functions which are connected to these buttons. How I can make it only run the function which is connected to that button. Thanks.
def __init__(self):
super(Program, self).__init__()
self.ui = Ui_APIManager()
self.ui.setupUi(self)
self.show()
self.ui.add_btn.toggled.connect(self.start)
self.ui.check_btn.toggled.connect(self.start)
self.ui.delete_btn.toggled.connect(self.start)
def start(self):
if self.ui.add_btn.isChecked():
self.ui.third_lbl.setEnabled(True)
self.ui.first_lbl.setText('Tool name')
self.ui.second_lbl.setText('ID')
self.ui.third_lbl.setText('Username')
self.ui.action_btn.clicked.connect(self.add_user)
elif self.ui.check_btn.isChecked():
self.ui.first_lbl.setText('Type of search')
self.ui.second_lbl.setText('Keyword')
self.ui.third_lbl.setEnabled(False)
self.ui.action_btn.clicked.connect(self.check_user)
elif self.ui.delete_btn.isChecked():
self.ui.first_lbl.setText('Type of search')
self.ui.second_lbl.setText('Keyword')
self.ui.third_lbl.setEnabled(False)
self.ui.action_btn.clicked.connect(self.delete_user)
Qt signals can have multiple slots attached to them. Every time you click a button the start function is adding another connection to the action_button.clicked signal.
You will need to disconnect any existing slots from the signal first to achieve the desired behaviour. You can disconnect everything from self.ui.action_btn all at once by calling its disconnect() function.
Rather than trying to reassign the roles of the GUI elements you have created, you would be better off creating separate widgets containing the elements for each checkbox state and switching between them. You might find QStackedWidget useful.

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.

Updating cell widgets changes in a Qtable without selecting the cell

well I'm using Pyqt4 in Maya2012 to make a reference editor alike ui. I working with a QtableWidget to make the reference list and i have subwidgets in each cell. One of the widgets is a checkbox that unload or reload the reference.
The problem i have is if a click directly in the checkbox without have the cell selected it doesn't do anything
this is my code:
def listConnections(self):
self.pos=self.sender().currentRow()
wid = self.list.ref_l.cellWidget(self.pos, 0).children()
self.text = self.list.list[self.pos]
self.ref()
for wt in wid:
if type(wt)== type(QCheckBox()):
wt.stateChanged.connect(self.changeState)
if type(wt)== type(QComboBox()):
wt.currentIndexChanged.connect(self.changeType)
I'm calling the function with a "itemSlectionChanged" signal because is the only way i knew i could detect the subwidgets.
All the subwidgets are made in the moment i fill the list.
Is there a way to make what i want?
Edit:
This is how i called the function
self.list.ref_l.itemSelectionChanged.connect(self.listConnections)
and this is how i create all the subwidgets in the cells
def fillList(self):
mayaRef = self.findRef()
if len(mayaRef)>0:
for count in range(0,len(mayaRef)):
self.ref_l.insertRow(count)
wid=QWidget()
cLayout=QHBoxLayout()
wid.setLayout(cLayout)
checkWid=QCheckBox()
nameWid=QLabel()
cLayout.addWidget(nameWid)
nameWid2=QLabel()
cLayout.addWidget(nameWid2)
comWid=QComboBox()
cLayout.addWidget(comWid)
self.ref_l.setCellWidget(count,0,wid)
self.ref_l is my QTable Widget, this is in another code that i'm calling with self.list in the original
You should set up all your connections right after you create the check boxes in fillList. Each item is associated with the path of the reference. You can use a QSignalMapper to map each check box to the path, then connect the signal mapper to your changeState slot. The signal mapper then calls that slot with the path you specified.

Categories

Resources