How to create custom panel without framing via Python in Nuke? - python

Could you tell me how to create custom panel in NUKE having no spacing (i.e. frameless window)?
At the moment it looks like this:
But I need it to look like this:

This is happening because the panel has several nested widgets each adding their own margin, so you'll need to iterate through the parent widgets and setContentsMargins on each.
"""
Get rid of the margins surrounding custom Panels
"""
import nuke
import PySide.QtCore as QtCore
import PySide.QtGui as QtGui
from nukescripts import panels
def set_widget_margins_to_zero(widget_object):
if widget_object:
target_widgets = set()
target_widgets.add(widget_object.parentWidget().parentWidget())
target_widgets.add(widget_object.parentWidget().parentWidget().parentWidget().parentWidget())
for widget_layout in target_widgets:
try:
widget_layout.layout().setContentsMargins(0, 0, 0, 0)
except:
pass
class Example_Window(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
layout = QtGui.QVBoxLayout()
label = QtGui.QLabel('Margins be-gone!')
label.setStyleSheet('QLabel{background: #eeffcc}')
layout.setContentsMargins(0,0,0,0)
layout.addWidget(label)
self.setLayout(layout)
expandingPolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Expanding)
label.setSizePolicy(expandingPolicy)
self.setSizePolicy(expandingPolicy)
def event(self, event):
if event.type() == QtCore.QEvent.Type.Show:
try:
set_widget_margins_to_zero(self)
except:
pass
return QtGui.QWidget.event(self, event)
panels.registerWidgetAsPanel('Example_Window', 'Example Widget',
'mwbp.Example_Widget')
to give credit where it's due, I found the solution a while ago here : https://gist.github.com/maty974/4739917 and have posted an integrated example widget.

The only way to make a panel that I know via Python is this (but it's with frame):
class myCustomPanel( nukescripts.PythonPanel ):
def __init__( self ):
nukescripts.PythonPanel.__init__( self, 'myCustomPanel' )
self.update = nuke.PyScript_Knob( 'update', 'Update Info' )
self.info = nuke.Multiline_Eval_String_Knob( 'info', 'Info' )
self.info.setEnabled( True )
self.addKnob( self.update )
self.addKnob( self.info )
def addInfoPanel():
global iPanel
iPanel = myCustomPanel()
return iPanel.addToPane()
paneMenu = nuke.menu( 'Pane' )
paneMenu.addCommand( 'myCustomPanel', addInfoPanel )

Related

Qt: Prevent drag-and-drop on child widgets and show forbidden cursor

I want to enable drag and drop functionality in some custom widgets, to allow reordering and rearranging the widgets within a layout. The basic functionality is working, but I'd like to prevent dropping a widget on itself, or on a child of itself. This is straightforward in the dropEvent method, but I can't find a way to also show the "forbidden" cursor while dragging, such that the user is aware that a drop will not be allowed.
The example below shows a test implementation, where the widgets "One" to "Five" can be dragged and dropped (no rearrangement will happen, only a message will be printed on the terminal, you may need to press Ctrl or something to initiate a drag). The problem line is the first ev.accept() in the dragEnterEvent method. By accepting the event, the cursor is shown in an "allowed" state (a grabbing hand for me). For example, trying to drag "One" and drop it onto "Three" appears as allowed, although nothing will happen. However, ignoring the event results in the event being propagated to the parent, so in that case dragging "Three" and dropping it onto "Three" results in "One" getting the drop instead. Setting the WA_NoMousePropagation attribute does not seem to make any difference.
So, what I would need is a way to "accept" the event, so that it will not be propagated to the parent, but still show a "forbidden" cursor, as if nobody accepted the event. Any ideas?
#!/usr/bin/env python3
import sys
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class WidgetMimeData(QtCore.QMimeData):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.itemObject = None
def hasFormat(self, mime):
if (self.itemObject and (mime == 'widgetitem')):
return True
return super().hasFormat(mime)
def setItem(self, obj):
self.itemObject = obj
def item(self):
return self.itemObject
class DraggableWidget(QGroupBox):
def __init__(self):
QWidget.__init__(self)
layout = QVBoxLayout()
self.setLayout(layout)
self.setAcceptDrops(True)
def addWidget(self, widget):
return self.layout().addWidget(widget)
def mouseMoveEvent(self, ev):
pixmap = QPixmap(self.size())
pixmap.fill(QtCore.Qt.transparent)
painter = QPainter()
painter.begin(pixmap)
painter.setOpacity(0.8)
painter.drawPixmap(0, 0, self.grab())
painter.end()
drag = QDrag(self)
mimedata = WidgetMimeData()
mimedata.setItem(self)
drag.setMimeData(mimedata)
drag.setPixmap(pixmap)
drag.setHotSpot(ev.pos())
drag.exec_(QtCore.Qt.MoveAction)
def dragEnterEvent(self, ev):
item = ev.mimeData().item()
if item.isAncestorOf(self):
#ev.ignore()
ev.accept()
else:
ev.accept()
def dropEvent(self, ev):
item = ev.mimeData().item()
if not item.isAncestorOf(self):
print('dropped on', self.layout().itemAt(0).widget().text())
ev.accept()
class HelloWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
w1 = DraggableWidget()
w1.addWidget(QLabel('One'))
w2 = DraggableWidget()
w2.addWidget(QLabel('Two'))
w3 = DraggableWidget()
w3.addWidget(QLabel('Three'))
w4 = DraggableWidget()
w4.addWidget(QLabel('Four'))
w5 = DraggableWidget()
w5.addWidget(QLabel('Five'))
w1.addWidget(w3)
w1.addWidget(w4)
w2.addWidget(w5)
layout = QVBoxLayout()
layout.addWidget(w1)
layout.addWidget(w2)
layout.addStretch(1)
centralWidget = QWidget(self)
centralWidget.setLayout(layout)
self.setCentralWidget(centralWidget)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mainWin = HelloWindow()
mainWin.show()
sys.exit( app.exec_() )
Probably the easiest way is to always accept the event in dragEnterEvent and in dragMoveEvent ignore it when the source of the event is an ancestor of self, i.e.
def dragEnterEvent(self, ev):
ev.accept()
def dragMoveEvent(self, ev):
item = ev.source()
if item.isAncestorOf(self):
ev.ignore()
By ignoring the event in dragMoveEvent you wouldn't need the check in dropEvent either and can simply do
def dropEvent(self, ev):
print('Dropped of', self.layout().itemAt(0).widget().text())
ev.accept()

Qcombobox with Qlabel and signal&slot

I have a Qgroupbox which contains Qcombobox with Qlabels, I want to select a value from Qcombobox and display the value as Qlabel. I have the complete code, even I do print value before and after within function every thing works as it should, Only display setText wont set text to Qlabel and update it.
Current screen
What I want
I've corrected signal code, when Qgroupbox in it Qcombobox appears or value would be changed, self.activation.connect(......) would emit an int of the index. to ensure that would work I print it-value inside the def setdatastrength(self, index), see figure below indeed it works, then argument would be passed to function self.concreteproperty.display_condata(it) would be called and do a print of value inside def display_condata(self, value) to make sure about value passing, as shown figure below, it does work. This line code self.con_strength_value.setText(fmt.format(L_Display))
wont assign value to Qlabel.
The script
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class secondtabmaterial(QtWidgets.QWidget):
def __init__(self, parent=None):
super(secondtabmaterial, self).__init__(parent)
self.concretewidgetinfo = ConcreteStrengthInFo()
Concrete_Group = QtWidgets.QGroupBox(self)
Concrete_Group.setTitle("&Concrete")
Concrete_Group.setLayout(self.concretewidgetinfo.grid)
class ConcreteStrengthComboBox(QtWidgets.QComboBox):
def __init__(self, parent = None):
super(ConcreteStrengthComboBox, self).__init__(parent)
self.addItems(["C12/15","C16/20","C20/25","C25/30","C30/37","C35/45"
,"C40/50","C45/55","C50/60","C55/67","C60/75","C70/85",
"C80/95","C90/105"])
self.setFont(QtGui.QFont("Helvetica", 10, QtGui.QFont.Normal, italic=False))
self.compressive_strength = ["12","16","20","25","30","35","40",
"45","50","55","60","70","80","90"]
class ConcreteProperty(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ConcreteProperty, self).__init__(parent)
self.setFont(QtGui.QFont("Helvetica", 10, QtGui.QFont.Normal, italic=False))
concretestrength_lay = QtWidgets.QHBoxLayout(self)
fctd = "\nfcd\n\nfctd\n\nEc"
con_strength = QtWidgets.QLabel(fctd)
self.con_strength_value = QtWidgets.QLabel(" ")
concretestrength_lay.addWidget(con_strength)
concretestrength_lay.addWidget(self.con_strength_value, alignment=QtCore.Qt.AlignRight)
self.setLayout(concretestrength_lay)
#QtCore.pyqtSlot(int)
def display_condata(self, value):
try:
L_Display = str(value)
print("-------- After ------")
print(L_Display, type(L_Display))
fmt = "{}mm"
self.con_strength_value.setText(fmt.format(L_Display))
except ValueError:
print("Error")
class ConcreteStrengthInFo(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ConcreteStrengthInFo, self).__init__(parent)
self.concreteproperty = ConcreteProperty()
self.concretestrengthbox = ConcreteStrengthComboBox()
self.concretestrengthbox.activated.connect(self.setdatastrength)
hbox = QtWidgets.QHBoxLayout()
concrete_strength = QtWidgets.QLabel("Concrete strength: ")
hbox.addWidget(concrete_strength)
hbox.addWidget(self.concretestrengthbox)
self.grid = QtWidgets.QGridLayout()
self.grid.addLayout(hbox, 0, 0)
self.grid.addWidget(self.concreteproperty, 1, 0)
#QtCore.pyqtSlot(int)
def setdatastrength(self, index):
it = self.concretestrengthbox.compressive_strength[index]
self.concreteproperty.display_condata(it)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = secondtabmaterial()
w.show()
sys.exit(app.exec_())
Above code is corrected and final. Now it works as it should.
I think the issue is that your receiving slot doesn't match any of the available .activated signals.
self.activated.connect(self.setdatastrength)
#QtCore.pyqtSlot()
def setdatastrength(self):
index = self.currentIndex()
it = self.compressive_strength[index]
print(it)
self.concreteproperty.display_condata(it)
The QComboBox.activated signal emits either an int of the index, or a str of the selected value. See documentation.
You've attached it to setdatastrength which accepts doesn't accept any parameters (aside from self, from the object) — this means it doesn't match the signature of either available signal, and won't be called. If you update the definition to add the index value, and accept a single int it should work.
self.activated.connect(self.setdatastrength)
#QtCore.pyqtSlot(int) # add the target type for this slot.
def setdatastrength(self, index):
it = self.compressive_strength[index]
print(it)
self.concreteproperty.display_condata(it)
After the update — the above looks now to be fixed, although you don't need the additional index = self.currentIndex() in setdatastrength it's not doing any harm.
Looking at your code, I think the label is being updated. The issue actually is that you can't see the label at all. Looking at the init for ConcreteProperty
class ConcreteProperty(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ConcreteProperty, self).__init__(parent)
self.setFont(QtGui.QFont("Helvetica", 10, QtGui.QFont.Normal, italic=False))
self.concretestrength_lay = QtWidgets.QHBoxLayout()
fctd = "\nfcd\n\nfctd\n\nEc"
con_strength = QtWidgets.QLabel(fctd)
self.con_strength_value = QtWidgets.QLabel(" ")
self.concretestrength_lay.addWidget(con_strength)
self.concretestrength_lay.addWidget(self.con_strength_value, alignment=QtCore.Qt.AlignLeft)
The reason the changes are not appearing is that you create two ConcreteProperty objects, one in ConcreteStrengthInfo and one in ConcreteStrengthComboBox. Updates to the combo box trigger an update of the ConcreteProperty attached to the combobox, not the other one (they are separate objects). The visible ConcreteProperty is unaffected.
To make this work, you need to move the signal attachment + the slot out of the combo box object. The following is a replacement for the two parts —
class ConcreteStrengthComboBox(QtWidgets.QComboBox):
def __init__(self, parent = None):
super(ConcreteStrengthComboBox, self).__init__(parent)
self.addItems(["C12/15","C16/20","C20/25","C25/30","C30/37","C35/45","C40/50","C45/55",
"C50/60","C55/67","C60/75","C70/85","C80/95","C90/105"])
self.setFont(QtGui.QFont("Helvetica", 10, QtGui.QFont.Normal, italic=False))
self.compressive_strength = ["12","16","20","25","30","35","40","45","50","55",
"60","70","80","90"]
class ConcreteStrengthInFo(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ConcreteStrengthInFo, self).__init__(parent)
hbox = QtWidgets.QHBoxLayout()
concrete_strength = QtWidgets.QLabel("Concrete strength: ")
hbox.addWidget(concrete_strength)
self.concreteproperty = ConcreteProperty()
self.concretestrengthbox = ConcreteStrengthComboBox()
hbox.addWidget(self.concretestrengthbox)
self.concretestrengthbox.activated.connect(self.setdatastrength)
self.vlay = QtWidgets.QVBoxLayout()
self.vlay.addLayout(hbox)
self.vlay.addLayout(self.concreteproperty.concretestrength_lay)
#QtCore.pyqtSlot(int)
def setdatastrength(self, index):
it = self.concretestrengthbox.compressive_strength[index]
print(it)
self.concreteproperty.display_condata(it)
This works for me locally.

context menu in QFileDialog

I would like to implement a custom context menu in a QFileDialog. In the code below, I managed to create a context menu to the main window, but I would like the menu to be displayed when a file is selected : how to know the right widget in the QFileDialog I should apply setContextMenuPolicy ?
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Window(QWidget):
def __init__(self):
QWidget.__init__(self)
self.myFileDialog = QFileDialog()
self.myFileDialog.setContextMenuPolicy(Qt.CustomContextMenu)
self.myFileDialog.customContextMenuRequested.connect(self.openMenu)
layout = QVBoxLayout()
layout.addWidget(self.myFileDialog)
self.setLayout(layout)
self.action_perso = QAction( "MyOwnMenu", self )
self.connect( self.action_perso, SIGNAL("triggered()"), self.test )
def openMenu(self, position):
menu = QMenu()
menu.addAction(self.action_perso)
menu.exec_(self.myFileDialog.mapToGlobal(position))
def test(self):
print("coucou")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
I found a solution, which may not be the best. It relies on two elements:
Thank to a personnal function objectTree (shown here but not used) that lists recursively all child objects of the QFileDialog, I identified the right widget, i.e. the QTreeView (I understood that the QTreeView is the right widget by trying to hide successively all QListView and QTreeView widgets). Hence, I can select it by its objectName with self.findChild(QTreeView, "treeView")
Application of setContextMenuPolicy( Qt.ActionsContextMenu ) to this QTreeView. I have also tried to implement a setContextMenuPolicy(Qt.CustomContextMenu), and it worked partially: my menu did appear but under the origin menu that was not unactivaded !
Below is the code I propose :
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class CustomWidget(QFileDialog):
def __init__(self, parent=None):
super(CustomWidget,self).__init__(parent)
# fetch the QTreeView in the QFileDialog
self.myTree = self.findChild(QTreeView, "treeView")
# set the context menu policy to ActionsContextMenu
self.myTree.setContextMenuPolicy( Qt.ActionsContextMenu )
# Define a new action
self.action_perso = QAction( "MyOwnMenu", self.myTree )
self.myTree.addAction( self.action_perso )
# connect this action to a personnal function
self.connect( self.action_perso, SIGNAL("triggered()"), self.myFunction )
def myFunction(self):
print("coucou")
def objectTree(self, objet, plan, j):
""" list recursively all child objects of objet to fetch the right widget """
n = len( objet.children() )
for i, child in enumerate( objet.children() ):
#print("\t"*j, end="")
plan_sup = plan+"."+str(i)
#print( plan_sup, child )
if isinstance(child, QTreeView):
self.listViews.append(child)
self.objectTree(child, plan_sup, j+1)
class MainWidget(QWidget):
def __init__(self, parent=None):
super(MainWidget,self).__init__(parent)
#creation of main layout
mainLayout = QVBoxLayout()
# creation of a widget inside
self.monWidget = CustomWidget()
mainLayout.addWidget( self.monWidget )
self.setLayout( mainLayout )
self.show()
app = QApplication(sys.argv)
window = MainWidget()
sys.exit(app.exec_())

How can i set labels of QHeaderView in PyQt?

In Pyqt, I am trying to make the QHeaderView of a QTableWidget respond to right mouse clicks.
I have subclassed QHeaderView and i have overloaded the mousePressEvent.
Then i can set it as as the header of my custom QTableWidget, the DataTable class. However i don't understand how to set the labels of the header.
Thanks for helping!
Here is some code.
class Header( QtGui.QHeaderView ):
def __init__ ( self, parent ):
QtGui.QHeaderView.__init__( self, QtCore.Qt.Vertical, parent=parent )
def mousePressEvent( self, event ):
if event.button() == QtCore.Qt.RightButton:
do_stuff()
class DataTable( QtGui.QTableWidget ):
def __init__ ( self ):
QtGui.QTableWidget.__init__( self )
self.setShowGrid(True)
self.header = Header( parent = self )
self.header.setClickable(True)
self.setHorizontalHeader( self.header )
def set_header( self, labels ):
???
This sample code should be useful:
import sys
import string
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Header(QHeaderView):
def __init__(self, parent=None):
super(Header, self).__init__(Qt.Horizontal, parent)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.ctxMenu)
self.hello = QAction("Hello", self)
self.hello.triggered.connect(self.printHello)
self.currentSection = None
def printHello(self):
data = self.model().headerData(self.currentSection, Qt.Horizontal)
print data.toString()
def ctxMenu(self, point):
menu = QMenu(self)
self.currentSection = self.logicalIndexAt(point)
menu.addAction(self.hello)
menu.exec_(self.mapToGlobal(point))
class Table(QTableWidget):
def __init__(self, parent=None):
super(Table, self).__init__(parent)
self.setHorizontalHeader(Header(self))
self.setColumnCount(3)
self.setHorizontalHeaderLabels(['id', 'name', 'username'])
self.populate()
def populate(self):
self.setRowCount(10)
for i in range(10):
for j,l in enumerate(string.letters[:3]):
self.setItem(i, j, QTableWidgetItem(l))
if __name__ == '__main__':
app = QApplication(sys.argv)
t = Table()
t.show()
app.exec_()
sys.exit()
Short answer:
The QTableWidget has a function called "setHorizontalHeaderLabels", simply supply your headers to it. Like so:
your_table_widget.setHorizontalHeaderLabels(["name", "phone number", "email"])
I would have added this as a comment to pedrotech, but I don't have enough rep for comments.

Multiple Context menus in PyQt based on mouse location

I have a window with multiple tables using QTableWidget (PyQt). I created a popup menu using the right click mouse and it works fine.
However, I need to create different popup menu based on which table the mouse is hovering over at the time the right mouse is clicked. How can I get the mouse to tell me which table it is hovering over?
or, put in another way, how to implement a method so as to have a specific context menu based on mouse location?
I am using Python and PyQt.
My popup menu is developed similar to this code (PedroMorgan answer from Qt and context menu):
class Foo( QtGui.QWidget ):
def __init__(self):
QtGui.QWidget.__init__(self, None)
# Toolbar
toolbar = QtGui.QToolBar()
# Actions
self.actionAdd = toolbar.addAction("New", self.on_action_add)
self.actionEdit = toolbar.addAction("Edit", self.on_action_edit)
self.actionDelete = toolbar.addAction("Delete", self.on_action_delete)
# Tree
self.tree = QtGui.QTreeView()
self.tree.setContextMenuPolicy( Qt.CustomContextMenu )
self.connect(self.tree, QtCore.SIGNAL('customContextMenuRequested(const QPoint&)'), self.on_context_menu)
# Popup Menu
self.popMenu = QtGui.QMenu( self )
self.popMenu.addAction( self.actionEdit )
self.popMenu.addAction( self.actionDelete )
self.popMenu.addSeparator()
self.popMenu.addAction( self.actionAdd )
def on_context_menu(self, point):
self.popMenu.exec_( self.tree.mapToGlobal(point) )
One way is to subclass QTableWidget and then implement your own contextMenuEvent method. Then you can set different handling of the context menu event for each of your instances. Here's a small example.
from PyQt4 import QtGui, QtCore
import sys
class MyTableWidget(QtGui.QTableWidget):
def __init__(self, name='Table1', parent=None):
super(MyTableWidget, self).__init__(parent)
self.name = name
def contextMenuEvent(self, event):
menu = QtGui.QMenu(self)
Action = menu.addAction("I am a " + self.name + " Action")
Action.triggered.connect(self.printName)
menu.exec_(event.globalPos())
def printName(self):
print "Action triggered from " + self.name
class Main(QtGui.QWidget):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
layout = QtGui.QVBoxLayout(self)
self.table1 = MyTableWidget(name='Table1', parent=self)
self.table2 = MyTableWidget(name='Table2', parent=self)
layout.addWidget(self.table1)
layout.addWidget(self.table2)
self.setLayout(layout)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Main()
main.show()
app.exec_()

Categories