Multiple Context menus in PyQt based on mouse location - python

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_()

Related

How can I create new buttons with buttons and plot them in QGraphicScene with an array in PyQt5

I have an application where I have several button widgets on a QGraphicScene and I am trying to make this button widgets to make new buttons on QGraphicScene when they are clicked.
My code is as follows:
buttons = []
class SeveralButtons(QtWidgets.QWidget):
def __init__(self,id,x,y):
super(SeveralButtons,self).__init__()
self.id = id
self.x = x
self.y = y
self.setGeometry(x,y,1,1)
self.button1 = QtWidgets.QPushButton("B1")
self.button2 = QtWidgets.QPushButton("B2")
self.button1.clicked.connect(self.p1b)
self.severalButtonsLayout = QtWidgets.QGridLayout()
self.severalButtonsLayout.addWidget(self.button1, 0,0,)
self.severalButtonsLayout.addWidget(self.button2, 0,1,)
self.setLayout(self.severalButtonsLayout)
def p1b(self):
ph = SeveralButtons(0,self.x-200,self.y-200)
buttons.append(ph)
UiWindow._scene.addWidget(ph)
And my main class is like this:
class UiWindow(QtWidgets.QMainWindow):
factor = 1.5
def __init__(self, parent=None):
super(UiWindow, self).__init__(parent)
self.setFixedSize(940,720)
self._scene = QtWidgets.QGraphicsScene(self)
self._view = QtWidgets.QGraphicsView(self._scene)
self._view.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)
self._view.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self._view.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.initButtons()
self.setCentralWidget(self._view)
def initButtons(self):
self.p = SeveralButtons(0,500,500)
buttons.append(self.p)
self._scene.addWidget(self.p)
def updateButtons(self,phrase):
for b in buttons:
if b != buttons[0]:
self._scene.addWidgets(b)
# ADD BUTTON WIDGET IN buttons ARRAY TO THE _scene OBJECT
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
ui = UiWindow()
ui.show()
sys.exit(app.exec_())
As it is shown in here I am trying to update widgets in main window with button click but I get QGraphicsProxyWidget::setWidget: cannot embed widget 0x24ac1a93000; already embedded error.
How can I overcome this problem or what is the sane way to make this work? My main goal in this program is that every button group can create their children button group when clicked. Doing this with classes is way to go or should I stick to methods in main window class when creating recursive widgets?
Thanks in advance.
EDIT:
class SeveralButtons(QtWidgets.QWidget):
b1Signal = QtCore.pyqtSignal()
def __init__():
self.button1 = QtWidgets.QPushButton()
self.button1.clicked.connect(self.b1)
...
def b1(self):
sb = SeveralButtons()
buttons.append(sb)
self.b1Signal.emit()
class UiWindow(QtWidgets.QMainWindow):
def __init__():
...
self.sb1 = SeveralButtons()
buttons.append(sb1)
self._scene.addWidget(self.sb1)
self.sb1.b1Signal.connect(self.updateButtons)
def updateButtons():
for b in buttons:
if b != buttons[0]:
self._scene.addWidget(b)
The SeveralButtons class should not be responsible of creating new buttons outside itself.
You should emit that signal and connect it to the function of its parent, which will then create a new instance of the same class and also connect the signal.
class SeveralButtons(QtWidgets.QWidget):
b1Signal = QtCore.pyqtSignal()
def __init__():
super().__init__()
layout = QtWidgets.QHBoxLayout(self)
self.button1 = QtWidgets.QPushButton()
self.button1.clicked.connect(self.b1Signal)
layout.addWidget(self.button1)
class UiWindow(QtWidgets.QMainWindow):
def __init__():
# ...
self.buttons = []
self.addButtons()
def addButtons():
newButtons = SeveralButtons()
newButtons.b1Signal.connect(self.addButtons)
self.buttons.append(newButtons)
self._scene.addWidget(newButtons)

How do I make a context menu for each item in a QListWidget?

I'm working on a QGIS plugin, where the UI is made with PyQt. I have a QListWidget and a function that fills it. I'd like to add a context menu for each item with only one option: to open another window.
I'm having trouble searching for info, since most of it works only on PyQt4 and I'm using version 5. The QListWidget that I want to add a context menu on is ds_list_widget. Here's some of the relevant code.
FORM_CLASS, _ = uic.loadUiType(os.path.join(
os.path.dirname(__file__), 'dialog_base.ui'))
class Dialog(QDialog, FORM_CLASS):
def __init__(self, parent=None):
...
self.p_list_widget = self.findChild(QListWidget, 'projects_listWidget')
self.p_list_widget.itemClicked.connect(self.project_clicked)
self.ds_list_widget = self.findChild(QListWidget, 'datasets_listWidget')
self.ds_list_widget.itemClicked.connect(self.dataset_clicked)
...
def project_clicked(self, item):
self.fill_datasets_list(str(item.data(Qt.UserRole)))
self.settings.setValue('projectIdValue', str(item.data(Qt.UserRole)))
def fill_datasets_list(self, project_id):
self.ds_list_widget.clear()
dataset_list = self.anotherClass.fetch_dataset_list(project_id)
for dataset in dataset_list:
#Query stuff from remote
...
item = QListWidgetItem(ds_name, self.ds_list_widget)
item.setIcon(self.newIcon(ds_img))
item.setData(Qt.UserRole, ds_id)
self.ds_list_widget.addItem(item)
self.ds_list_widget.setIconSize(self.iconSize)
Since your list-widget is created by Qt Designer, it is probably easiest to install an event-filter on it and trap the context-menu event. With that in place, the rest is quite straightforward - here is a simple demo:
import sys
from PyQt5 import QtCore, QtWidgets
class Dialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super(Dialog, self).__init__()
self.listWidget = QtWidgets.QListWidget()
self.listWidget.addItems('One Two Three'.split())
self.listWidget.installEventFilter(self)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.listWidget)
def eventFilter(self, source, event):
if (event.type() == QtCore.QEvent.ContextMenu and
source is self.listWidget):
menu = QtWidgets.QMenu()
menu.addAction('Open Window')
if menu.exec_(event.globalPos()):
item = source.itemAt(event.pos())
print(item.text())
return True
return super(Dialog, self).eventFilter(source, event)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Dialog()
window.setGeometry(600, 100, 300, 200)
window.show()
sys.exit(app.exec_())
PS:
You should also note that code like this:
self.p_list_widget = self.findChild(QListWidget, 'projects_listWidget')
is completely unnecessary. All the widgets from Qt Designer are automatically added as attributes to the form class using the object-name. So your code can be simplified to this:
self.projects_listWidget.itemClicked.connect(self.project_clicked)
self.datasets_listWidget.itemClicked.connect(self.dataset_clicked)
there is no need to use findChild.
In addition to the answer above, you can also set multiple QAction() submenu items to do multiple things. As you would a normal menu.
One way is to edit your eventFilter so that menu.exec() becomes a variable:
def eventFilter(self, source, event):
if (event.type() == QtCore.QEvent.ContextMenu and source is self.listWidget):
menu = QtWidgets.QMenu()
open_window_1 = QAction("Open Window 1")
open_window_2 = QAction("Open Window 2")
menu.addAction(open_window_1)
menu.addAction(open_window_2)
menu_click = menu.exec(event.globalPos())
try:
item = source.itemAt(event.pos())
except Exception as e:
print(f"No item selected {e}")
if menu_click == open_window_1 :
print("Opening Window 1...")
# Your code here
if menu_click == open_window_2 :
print("Opening Window 2...")
# Your code here
# and so on... You can now add as many items as you want
return True
return super(Dialog, self).eventFilter(source, event)

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 to create custom panel without framing via Python in Nuke?

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 )

dropEvent() not getting called

I am trying to perform a drag and drop operation from a QTreeWidget to QGraphicsView. dragStart() works, and dragEnterEvent() works, but dropEvent() is never being called. Also the pixmap is not showing up until the cursor enters the QGraphicsView, which isn't a problem, but I just thought it would appear as soon as the drag started. Here is my startDrag function:
def on_list_startDrag(self, supportedActions):
#Retreive the item that was clicked on
currentPart = self.ui.list.currentItem()
part = currentPart.text(0)
drag = QtGui.QDrag(self.ui.list)
mime = QtCore.QMimeData()
print(part)
#retreive that associated graphics file
icon = QtGui.QIcon('drawings/FULL/' + part + '.svg')
pixmap = icon.pixmap(102,122)
selected = QtGui.QImage('drawings/FULL/' + part + '.svg')
data = pickle.dumps(selected)
mime.setData('application/x-item', data)
#mime.setImageData(QtGui.QImage('drawings/FULL/' + part + '.svg'))
drag.setMimeData(mime)
drag.setHotSpot(QtCore.QPoint(pixmap.width()/2, pixmap.height()/2))
drag.setPixmap(pixmap)
drag.exec_()
Here is the the dragEnterEvent:
def on_workArea_dragEnterEvent(self, event):
print(event.format())
if (event.mimeData().hasFormat('application/x-item')):
event.accept()
print('accepted')
else:
event.ignore()
Finally the dropEvent code:
def on_workArea_dropEvent(self, event):
print('dropped')
When I start the drag and drop operation is happening the cursor has the circle with the slash like the widget doesn't accept drops, but I set the QGraphicsView, workArea, to accept drops. Can someone please help me get the drop working and explain why the pixmap doesn't show up behind the cursor until the cursor is over the QGraphicsView. Thank you.
You need to implement dragMoveEvent() as well, or dropEvent() won't be called. This is also what causes it to show a proper drop icon, instead of the slashed-circle "can't drop here" icon.
I've checked your code and it does look and work fine for me; both dropEvent and pixmap work as expected. Perhaps there's smth else in your code which causes this unwanted behaviour you're describing. As for the dropEvent, you might have problems connecting slot to a signal, which leads to your code not being called. I've made a small example which does drag and drop between treeview and grahicsview and loads and presents a pixmap:
import sys
from PyQt4 import QtGui, QtCore
class TestTreeView(QtGui.QTreeView):
def __init__(self, parent = None):
super(TestTreeView, self).__init__(parent)
self.setDragEnabled(True)
def startDrag(self, dropAction):
print('tree start drag')
icon = QtGui.QIcon('/home/image.png')
pixmap = icon.pixmap(64, 64)
mime = QtCore.QMimeData()
mime.setData('application/x-item', '???')
drag = QtGui.QDrag(self)
drag.setMimeData(mime)
drag.setHotSpot(QtCore.QPoint(pixmap.width()/2, pixmap.height()/2))
drag.setPixmap(pixmap)
drag.start(QtCore.Qt.CopyAction)
class TestGraphicsView(QtGui.QGraphicsView):
def __init__(self, parent = None):
super(TestGraphicsView, self).__init__(parent)
self.setAcceptDrops(True)
def dragEnterEvent(self, event):
print('graphics view drag enter')
if (event.mimeData().hasFormat('application/x-item')):
event.acceptProposedAction()
print('accepted')
else:
event.ignore()
def dropEvent(self, event):
print('graphics view drop')
event.acceptProposedAction()
class MainForm(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainForm, self).__init__(parent)
self.model = QtGui.QStandardItemModel()
for k in range(0, 4):
parentItem = self.model.invisibleRootItem()
for i in range(0, 4):
item = QtGui.QStandardItem(QtCore.QString("item %0 %1").arg(k).arg(i))
parentItem.appendRow(item)
parentItem = item
self.setMinimumSize(300, 400)
self.view = TestTreeView(self)
self.view.setModel(self.model)
self.view.setMinimumSize(300, 200)
self.graphicsView = TestGraphicsView(self)
self.graphicsView.setGeometry(0, 210, 300, 400)
self.layout = QtGui.QVBoxLayout(self.centralWidget())
self.layout.addWidget(self.view)
self.layout.addWidget(self.graphicsView)
def main():
app = QtGui.QApplication(sys.argv)
form = MainForm()
form.show()
app.exec_()
if __name__ == '__main__':
main()
hope this helps, regards

Categories