refresh(update) widget in Nuke (vfx soft) - python

I need refresh(update) GUI widget(pySide) in Nuke(compositing software) after load or save nuke script.
callback: nuke.addOnScriptSave() and nuke.addOnScriptLoad()
import nuke
from PySide import QtGui, QtCore
from nukescripts import panels
class Info(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.initUI()
def scriptName(self):
sName = (nuke.root().name()).split('/')#split name from root
return sName
def initUI(self):
self.lbl1 = QtGui.QLabel("script name : " , self)
layout = QtGui.QHBoxLayout()#main layout
layout.addWidget(self.lbl1)
self.setLayout(layout)
self.updateInfo()
def updateInfo(self):
scriptName = self.scriptName()
self.lbl1.setText("script name : " + scriptName[-1].split('.')[0])#set name
panels.registerWidgetAsPanel('Info', 'Info_script', 'infoscript')

The simple solution is to add the callback registration to your widget's __init__:
class Info(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.initUI()
nuke.addOnScriptSave(self.updateInfo)
if nuke.root().name() == 'Root' and not nuke.modified():
# No reason to add a scriptLoad callback if opening a
# script would spawn a new Nuke process.
nuke.addOnScriptLoad(self.updateInfo)
# Rest of the class definition omitted for brevity
This approach does have a notable downside: Because Nuke's callback registry will now contain at least one reference to the widget's updateInfo method, the widget can never be garbage-collected by Python. This will probably never be too big of a deal in real life, since you will probably only ever create a very small number of panel instances, but if you ever created, say, 1000 instances, you would have 1000 callbacks registered that would never be unregistered (even if the panels were closed).
Unfortunately, Nuke doesn't really give you any hooks to implement when your widget is being removed from the UI, so it's tough to have a sure-fire way of knowing it's time to unregister your callbacks. The closest you can get is probably using a showEvent/hideEvent combo and some guesswork, like so:
class Info(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.initUI()
self.callbacksRegistered = False
def addCallbacks(self):
nuke.addOnScriptSave(self.updateInfo)
if nuke.root().name() == 'Root' and not nuke.modified():
# No reason to add a scriptLoad callback if opening a
# script would spawn a new Nuke process.
nuke.addOnScriptLoad(self.updateInfo)
def removeCallbacks(self):
self.removeOnScriptSave(self.updateInfo)
self.removeOnScriptLoad(self.updateInfo)
def showEvent(self, event):
if not (self.callbacksRegistered or event.spontaneous()):
self.addCallbacks()
self.callbacksRegistered = True
def hideEvent(self, event):
if self.callbacksRegistered and not event.spontaneous():
# "Spontaneous" hide events are sent when the Nuke
# window is minimized, which we don't really care
# about.
self.removeCallbacks()
self.callbacksRegistered = False
# Rest of the class definition omitted for brevity
The callback registration is moved into the showEvent method, and the callbacks are unregistered when the widget is hidden by something other than an OS-level window operation. This is pretty reliable, except that your callbacks will also be unregistered when you change to another tab in the widget's pane (if it's docked). They will obviously be re-added when your tab is activated again though. This is a pretty minor penalty, but I still think it's worth pointing out that while this approach is pretty close to ideal, it isn't quite perfect.
Anyway, I'll leave it up to you to decide which of these two you like better, but I hope this helps.

Related

Setting up QStackedWidget properly, signal cannot see function/slot

I am working on developing a GUI with PyQt5. This is my first step into OOP, and I'm trying to teach myself as I go. I'm struggling with understanding when classes inherit methods/attributes etc and what methods they have available -- I guess it is a scope-related question? I have produced a MWE to my GUI below. In total, there will be many more pages and signals/slots.
What I want:
The stack should initialize with the "MainMenu" widget/object showing (left image below). Clicking on "Next Page" button should switch the stack order to put the "OtherPage" widget/object on top (right image below). I am creating each page as a class, thinking this would be a good way to organize my project. Is this good or bad practice?
What happens now:
The GUI works (initializes) if the line nextPg.clicked.connect(self.drawOtherPage()) is commented out, but of course then clicking on the button does nothing. I can switch the initial stack order so that "other" widget is on top of the stack and it shows up fine, so I think that class is also working. When the above line is included in the code, the following error is thrown:
in __init__
nextPg.clicked.connect(self.drawOtherPage())
AttributeError: 'MainMenu' object has no attribute 'drawOtherPage'
What I've tried
I thought that the call to super() was supposed to allow the child class (in this case MainMenu) to inherit the methods from the parent class (RootInit). Therefore, I would think this should make the drawOtherPage method available to the button connect signal. Obviously, the error isa result of the method not being available.
What am I doing wrong? Should I be creating these "page" widgets in methods instead? Do they need to be under the RootInit class or can they live in the top level of the .py file? I'm trying to follow best practices as the project will become pretty large in the end. Fortunately, most of it should be pages with variations based on what buttons were clicked to get there -- I therefore thought classes would be helpful. Please be harsh on the code and my python/PyQt vernacular, trying to learn -- thanks!
import sys, os
from PyQt5.QtWidgets import *
from PyQt5 import QtGui, QtCore
class RootInit(QMainWindow):
# root window
def __init__(self, parent=None):
QMainWindow.__init__(self)
self.root = QWidget()
self.stack = QStackedWidget()
rootLayout = QVBoxLayout()
rootLayout.addWidget(self.stack)
self.root.setLayout(rootLayout)
self.setCentralWidget(self.root)
self.initializeGUI()
def initializeGUI(self):
self.main = MainMenu(self) # build MainMenu (class)
self.other = OtherPage(self) # build OtherPage (class)
self.stack.addWidget(self.main)
self.stack.addWidget(self.other)
def drawMain(self):
self.stack.setCurrentIndex(0)
def drawOtherPage(self):
self.stack.setCurrentIndex(1)
class MainMenu(QWidget):
# class for main menu
def __init__(self, parent=None):
QWidget.__init__(self, parent)
super().__init__()
mainLayout = QGridLayout() # layout for entire main menu
quitBtn = QPushButton("Quit")
quitBtn.clicked.connect(QtCore.QCoreApplication.instance().quit)
nextPg = QPushButton("Next page")
nextPg.clicked.connect(self.drawOtherPage())
mainLayout.addWidget(quitBtn, 0, 0)
mainLayout.addWidget(nextPg, 0, 1)
self.setLayout(mainLayout)
class OtherPage(QWidget):
# class for another menu
def __init__(self, parent=None):
QWidget.__init__(self, parent)
label = QLabel("test label")
layout = QGridLayout() #
layout.addWidget(label, 0, 0)
self.setLayout(layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
root = RootInit()
root.setWindowTitle("Title")
root.show()
sys.exit(app.exec_())
Your code has the following errors:
The variable self refers to the same instance of the class, in your case self refers to an instance of MainMenu, and if we observe MainMenu it does not have any drawOtherPage() method.
Another mistake in your case is to call the parent's constructor twice:
class MainMenu(QWidget):
# class for main menu
def __init__(self, parent=None):
QWidget.__init__(self, parent)
super().__init__()
In the first constructor you are assigning a parent, and in the second, you are not. To clarify in python there are several ways to call the parent's constructor:
QWidget.__init__(self, parent)
super(MainMenu, self).__init__(parent)
super().__init__(parent)
so you should only use one of them.
Another error is that a signal is connected through the name of a function, the function must not be evaluated using parentheses
and for the last use of functions or methods that involve several objects should be done in a place where both objects can access, in your case you can take advantage of what you are going to RootInit as parent of MainMenu: self.main = MainMenu(self), and access the connection to that element through the method parent().
All of the above entails modifying the MainMenu class to the following:
class MainMenu(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
mainLayout = QGridLayout() # layout for entire main menu
quitBtn = QPushButton("Quit")
quitBtn.clicked.connect(QtCore.QCoreApplication.instance().quit)
nextPg = QPushButton("Next page")
nextPg.clicked.connect(self.parent().drawOtherPage)
mainLayout.addWidget(quitBtn, 0, 0)
mainLayout.addWidget(nextPg, 0, 1)
self.setLayout(mainLayout)

PyQt - Hide MainWindow and show QDialog without the taskbar icon disappearing

I've been using the code from this example PyQt: How to hide QMainWindow:
class Dialog_02(QtGui.QMainWindow):
def __init__(self, parent):
super(Dialog_02, self).__init__(parent)
# ensure this window gets garbage-collected when closed
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
...
def closeAndReturn(self):
self.close()
self.parent().show()
class Dialog_01(QtGui.QMainWindow):
...
def callAnotherQMainWindow(self):
self.hide()
self.dialog_02 = Dialog_02(self)
self.dialog_02.show()
It works, however when opening a second window, the window's task bar icon doesn't show. I've tried using QtGui.QDialog for the Dialog_02 as well but that gives me the same result.
How do I go about solving this?
Edit: I'm on Windows 10
Just guessing (because I don't know what platform you're on, and I don't use a task-bar myself, so I cant really test it), but try getting rid of the parent:
class Dialog_02(QtGui.QMainWindow):
def __init__(self, other_window):
super(Dialog_02, self).__init__()
# ensure this window gets garbage-collected when closed
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self._other_window = other_window
...
def closeAndReturn(self):
self.close()
self._other_window.show()

GTK: Problems redrawing widgets on Gtk.ApplicationWindow

I'm creating a GTK GUI in Python for an already existing program. Imagine the following structure:
program
|----gtk
|-----application.py
|-----mainwindow.py
|-----mainwidgets.py
Of course this is simplified. But the GUI works somewhat like this: application.py is a Gtk.Application that creates instances of the objects in mainwidgets, provides back-end functions which are passed as callbacks to mainwindow.py, which places the widgets in a Gtk.ApplicationWindow.
There are a couple of cases where I'm having trouble. I'll go over two which I think are related.
Let's go for the simpler one first. There's a simple button defined in mainwidget, which is used as a statusbar in my program.
class Statusbar(Gtk.Widget):
def __init__(self, on_button_do):
super(Gtk.Widget, self).__init__()
self.callback = on_button_do
self.button_label_int = 0
self.button = Gtk.Button.new_with_label(str(button_label_int)
def increment_button_label(self):
self.button_label_int += 1
self.button.set_label(str(button_label))
Then, when the mainwindow receives a signal, it is instructed to call
statusbar.increment_button_label(). This crashes almost immediately with a segmentation fault, no more info given about the problem, as soon as that signal is received.
class AppWindow(Gtk.ApplicationWindow):
__gsignals__ = {
"new_log": (GObject.SIGNAL_RUN_FIRST, None, ())
}
def __init__(self, statusbar, sidebar, *args, **kwargs):
super(Gtk.ApplicationWindow, self).__init__(*args, **kwargs)
self.statusbar = statusbar
self.sidebar = sidebar
self.notificationBox = Gtk.Box()
self.notificationBox.pack_start(self.statusbar.get_button(), True, True, 0)
# MAINBOX: THE BIGGER BOX OF ALL THE LITTLE BOXES
self.mainBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.mainBox.pack_start(self.
self.mainBox.pack_start(self.notificationBox, False, False, 0)
self.add(self.mainBox)
self.show_all()
def do_new_log(self):
"""What should the window do when it gets a new_log signal"""
statusbar.increment_button_label()
Another (related?) problem having to with updating the display of widgets: my sidebar. The sidebar is actually just a Gtk.ListStore and a Gtk.TreeView, nothing fancy. In application.py you'd find code like this:
def do_startup(self):
Gtk.Application.do_startup(self) # deep GTK magic
self.sidebar = Sidebar(self.workspace_manager,
self.changeWorkspace,
CONF.getLastWorkspace())
And then that instance of sidebar is passed to mainwindow.py to be put in a box. Everything works fine for now.
The problem comes when I try to add information to the sidebar. The back-end part of it works, because if I restart the application, I can see the new entry in the sidebar. And the sidebar gets information from this backend:
class Sidebar(Gtk.Widget):
def __init__(self, workspace_manager, callback_to_change_workspace, conf):
super(Gtk.Widget, self).__init__()
self.callback = callback_to_change_workspace
self.ws_manager = workspace_manager
self.lastWorkspace = conf
def createTitle(self):
title = Gtk.Label()
title.set_text("Workspaces")
return title
def workspaceModel(self):
self.workspace_list_info = Gtk.ListStore(str)
for ws in self.ws_manager.getWorkspacesNames():
treeIter = workspace_list_info.append([ws])
if ws == self.lastWorkspace:
self.defaultSelection = treeIter
def workspaceView(self):
self.lst = Gtk.TreeView(self.workspace_list_info)
renderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn("Workspaces", renderer, text=0)
self.lst.append_column(column)
# select by default the last active workspace
if self.defaultSelection is not None:
self.selectDefault = self.lst.get_selection()
self.selectDefault.select_iter(self.defaultSelection)
self.selection = self.lst.get_selection()
self.selection.connect("changed", self.callback)
What maindindow tries to do is put sidebar.lst into a box. That works fine. The thing is when I add a workspace via a dialog box, I doesn't show up, as I stated above.
Any idea about what could cause these problems? Is this way of organizing my problem not all right for some reason for GTK? I think the code itself is fine: the workspaces are added, after all, and GTK doesn't crash. It just doesn't do it fine. Also the button, at first, is displayed just fine, it even emits its signal. But as soon as I try to change its label, everything explodes.
OK, so this actually had to do with multiple threads and signals. It was solved by overriding the emit signal.
class _IdleObject(GObject.GObject):
"""
Override GObject.GObject to always emit signals in the main thread
by emmitting on an idle handler
"""
def __init__(self):
GObject.GObject.__init__(self)
def emit(self, *args):
GObject.idle_add(GObject.GObject.emit, self, *args)

PyQt widget keyboard focus

First off -- thanks for this group! I started delving into PyQt a month or so ago. In that time, I've bumped up against many questions, and virtually always found an answer here.
Until now.
I have a workaround for this, but I think it's a kluge and there probably is a proper way. I'd like to understand better what's going on.
Here's the code:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class FormWidget(QWidget):
def __init__(self, parent):
super(FormWidget, self).__init__(parent)
# Create view with image in it
self.image = QGraphicsPixmapItem(QPixmap())
self.scene = QGraphicsScene()
self.scene.addItem(self.image)
self.view = QGraphicsView(self.scene)
self.hlayout = QHBoxLayout()
self.hlayout.addWidget(self.view)
self.setLayout(self.hlayout)
# self.view.keyPressEvent = self.keyPressEvent
def keyPressEvent(self, event):
key = event.key()
mod = int(event.modifiers())
print(
"<{}> Key 0x{:x}/{}/ {} {} {}".format(
self,
key,
event.text(),
" [+shift]" if event.modifiers() & Qt.SHIFT else "",
" [+ctrl]" if event.modifiers() & Qt.CTRL else "",
" [+alt]" if event.modifiers() & Qt.ALT else ""
)
)
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
form = FormWidget(self)
self.setCentralWidget(form)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
sys.exit(app.exec_())
As is, all keyboard input is detected by the overloaded keyPressEvent() function, except arrow keys. I've found enough posts talking about this to have a sense that it is because the child widget (self.view) is receiving them. I presume the child widget is, in fact, receiving all the keystrokes, but ignoring the ones that are getting through, and sucking up the arrow keys, which is why they aren't getting to the parent's keyPressEvent() function. That seems to be so, because if I uncomment the line in the middle:
self.view.keyPressEvent = self.keyPressEvent
It behaves as I expect -- the parent's keyPressEvent() gets all the keystrokes, arrows included.
How would I tell the child widget to ignore all keystrokes? I thought maybe this:
self.view.setFocusPolicy(Qt.NoFocus)
When I add that, keyPressEvent() doesn't see any keystrokes at all.
I suppose I could overload keyPressEvent() for the child as well, and just explicitly pass everything up to the parent. But that doesn't seem better than my kluge.
I think I must be misunderstanding something here.
Thanks. Just looking to learn ...
By default, a QWidget does not accept the keyboard focus, so you need to enable it explicitly:
class FormWidget(QWidget):
def __init__(self, parent):
...
self.setFocusPolicy(Qt.StrongFocus)
Rather than subclassing the child widget or attempting to prevent keystrokes from reaching it, you should consider using an eventFilter to capture events on the child widget. You will see all events before the child widget, and you can suppress or transform them.
http://doc.qt.io/qt-5.5/qobject.html#eventFilter

How to connect to parent SIGNAL in PyQt?

I have a MainWindow that looks like this:
def __init__(self, parent = None):
QMainWindow.__init__(self, parent)
self.setupUi(self)
self.showMaximized()
menu=mainMenu.MainMenu()
classification=classificationMain.ClassificationMain()
self.stackedWidget.addWidget(menu)
self.stackedWidget.addWidget(classification)
self.stackedWidget.setCurrentWidget(menu)
self.stackedWidget.showFullScreen()
#connections
menu.pushButton.clicked.connect(self.showClassification)
classification.backButton.clicked.connect(self.showMainWindow)
def showClassification(self ):
self.stackedWidget.setCurrentIndex(3)
def showMainWindow(self):
self.stackedWidget.setCurrentIndex(2)
The MainWindows waits for signal from the rest of the dialogs. Now, the Classification dialog has another StackedWidget in it, since it works as a main window for an important part of the application. It looks like:
class ClassificationMain(QDialog, Ui_Dialog):
def __init__(self, parent = None):
QDialog.__init__(self, parent)
self.setupUi(self)
choose=choosePatient.ChoosePatient()
self.stackedWidget.addWidget(choose)
self.stackedWidget.setCurrentWidget(choose)
Now, I want to reload the data inside ChoosePatient every time the button "Show Classification" from MainMenu is clicked, but now the data is loaded only once in the line classification=classificationMain.ClassificationMain() of MainWindow.
I was thinking I had to connect a slot inside ChoosePatient with the click of "Show Classification" button inside MainMenu, but I would need an instance of MainMenu, which is not possible.
How can a method of ChoosePatient can be execute every time the button in the "parent" window is clicked? (also, please tell me if this is not the right way to work with pyqt windows)
You need to save references to your composed widgets, and also to expose some public methods to the parents:
class ClassificationMain(QDialog, Ui_Dialog):
def __init__(self, parent = None):
QDialog.__init__(self, parent)
self.setupUi(self)
self.chooseWidget=choosePatient.ChoosePatient()
self.stackedWidget.addWidget(self.chooseWidget)
self.stackedWidget.setCurrentWidget(self.chooseWidget)
def reloadPatients(self):
# whatever your operation should be on the ChoosePatient
self.chooseWidget.reload()
# MAIN WINDOW
def __init__(self, parent = None):
...
self.classification=classificationMain.ClassificationMain()
self.stackedWidget.addWidget(self.classification)
...
#connections
menu.pushButton.clicked.connect(self.showClassification)
def showClassification(self ):
self.stackedWidget.setCurrentIndex(3)
self.classification.reloadPatients()
You could also just skip the reloadPatients method and connect to the ChoosePatient directly if you want:
def showClassification(self ):
self.stackedWidget.setCurrentIndex(3)
self.classification.chooseWidget.reload()
My personal opinion is to make your custom classes wrap up the internal functionality nicely so that you only need to interface with it over the custom class, and not dig into its internals. That way you can change how it works inside without breaking the main window.

Categories