I'm wanting to use an NSOpenPanel for an application I'm designing. Here's what I have so far:
#objc.IBAction
def ShowOpenPanel_(self, sender):
self.panel = NSOpenPanel.openPanel()
self.panel.setCanChooseFiles_(False)
self.panel.setCanChooseDirectories_(True)
NSLog(u'Starting OpenPanel')
self.panel.beginForDirectory_file_types_modelessDelegate_didEndSelector_contextInfo_(
self.defaults.objectForKey_(u'projpath'),
objc.nil,
objc.nil,
self,
objc.selector(self.OpenPanelDidEnd_returnCode_contextInfo_,
signature='v:#ii'),
objc.nil)
NSLog(u'OpenPanel was started.')
def OpenPanelDidEnd_returnCode_contextInfo_(self, panel, returnCode, context):
NSLog('Panel ended.')
if (returnCode == NSOKButton):
NSLog(u'User selected OK')
path = self.panel.filenames()[0]
self.defaults.setObject_forKey_(path, u'projpath')
del self.panel
The main two lines I'm concerned about are:
objc.selector(self.OpenPanelDidEnd_returnCode_contextInfo_,
signature='v:#ii'),
objc.nil) #this is the argument that gets passed as the void pointer
The third argument is supposed to be a void pointer. Since I don't intend to use that data, I'd rather just leave it empty. I've tried making the signature 'v:#iv' and tried using objc.NULL and python's None, and just about every combination of all these things. What is the best way to handle this?
I think you don't need to use objc.selector at all; try this instead:
#objc.IBAction
def ShowOpenPanel_(self, sender):
self.panel = NSOpenPanel.openPanel()
self.panel.setCanChooseFiles_(False)
self.panel.setCanChooseDirectories_(True)
NSLog(u'Starting OpenPanel')
self.panel.beginForDirectory_file_types_modelessDelegate_didEndSelector_contextInfo_(
self.defaults.objectForKey_(u'projpath'),
objc.nil,
objc.nil,
self,
self.OpenPanelDidEnd_returnCode_contextInfo_,
objc.nil)
NSLog(u'OpenPanel was started.')
I've also found that I need to decorate the end-of-panel function with PyObjCTools.AppHelper.endSheetMethod:
#PyObjCTools.AppHelper.endSheetMethod
def OpenPanelDidEnd_returnCode_contextInfo_(self, panel, returnCode, context):
NSLog('Panel ended.')
if (returnCode == NSOKButton):
NSLog(u'User selected OK')
path = self.panel.filenames()[0]
self.defaults.setObject_forKey_(path, u'projpath')
del self.panel
Here's how I would write what you have:
#objc.IBAction
def showOpenPanel_(self, sender):
panel = NSOpenPanel.openPanel()
panel.setCanChooseFiles_(False)
panel.setCanChooseDirectories_(True)
NSLog(u'Starting openPanel')
panel.beginForDirectory_file_types_modelessDelegate_didEndSelector_contextInfo_(
self.defaults.objectForKey_(u'projpath'), #forDirectory
None, #file
None, #types
self, #modelessDelegate
self.openPanelDidEnd_returnCode_contextInfo_, #didEndSelector
None) #contextInfo
NSLog(u'openPanel started')
#PyObjCTools.AppHelper.endSheetMethod
def openPanelDidEnd_returnCode_contextInfo_(self, panel, returnCode, context):
NSLog(u'Panel ended')
if returnCode != NSOKButton:
return
NSLog(u'User selected OK')
path = panel.filenames()[0]
self.defaults.setObject_forKey_(path, u'projpath')
Explanation of changes: I always use None rather than objc.nil and it hasn't messed me up yet; I don't think your panel needs to be a property of self since you get it in your return function; objc convention is to have the first letter of your function in lower case.
The right way to open the panel is:
#objc.IBAction
def showOpenPanel_(self, sender):
panel = NSOpenPanel.openPanel()
panel.setCanChooseFiles_(False)
panel.setCanChooseDirectories_(True)
NSLog(u'Starting openPanel')
panel.beginForDirectory_file_types_modelessDelegate_didEndSelector_contextInfo_(
self.defaults.objectForKey_(u'projpath'), #forDirectory
None, #file
None, #types
self, #modelessDelegate
'openPanelDidEnd:returnCode:contextInfo:', #didEndSelector
None) #contextInfo
NSLog(u'openPanel started')
Dan's code works as well, but IMHO my variant is slighly clearer: you don't pass the actual method but the name of the method that should be called.
Related
In my application I build a vtkAssembly Object and I can interact with it by using a custom InteractorStyle that expands the vtkInteractorStyleTrackballActor. I add the Interactor to the render window and set my custom style. I can rotate, scale and move the object, everything works as intended.
Because this is the only object in the render window, I'd like to allow the user to control the object from anywhere. More specifically: The user doesn't have to place the mouse exactly over the object in order to rotate, scale, etc.
I already have an Observer that triggers when the user clicks on the render window and not only the object itself, but how can I trigger the corresponding rotation of the object?
This is my custom vtkInteractorStyle class:
class SynchronousInteractorStyle(vtk.vtkInteractorStyleTrackballActor):
button_active = False
def __init__(self, process, parent=None):
self.process = process
self.AddObserver("LeftButtonPressEvent", self.left_button_press_event)
self.AddObserver("LeftButtonReleaseEvent", self.left_button_release_event)
self.AddObserver("MiddleButtonPressEvent", self.middle_button_press_event)
self.AddObserver("MiddleButtonReleaseEvent", self.middle_button_release_event)
self.AddObserver("RightButtonPressEvent", self.right_button_press_event)
self.AddObserver("RightButtonReleaseEvent", self.right_button_release_event)
def left_button_press_event(self, obj, event):
print("I'm triggered!")
self.button_active = True
self.OnLeftButtonDown()
return
def left_button_release_event(self, obj, event):
self.button_active = False
self.OnLeftButtonUp()
return
def middle_button_press_event(self, obj, event):
self.button_active = True
self.OnMiddleButtonDown()
return
def middle_button_release_event(self, obj, event):
self.button_active = False
self.OnMiddleButtonUp()
return
def right_button_press_event(self, obj, event):
self.button_active = True
self.OnRightButtonDown()
return
def right_button_release_event(self, obj, event):
self.button_active = False
self.OnRightButtonUp()
return
Thank you in advance.
UPDATE:
I think I found a possible way to get what I want to achieve.
There is the possibility to override the OnLeftButtonDown(interactor) method, but I can't figure out what functions I have to call in order to start, for example, a rotation.
This is a example of my override attempt, but interactor delivers a vtkInteractorStyleTrackballActor object and calling StartRotation() doesn't do anything.
def OnLeftButtonDown(interactor):
print(interactor)
interactor.StartRotate()
return
So I'm trying to add the "text" associated with a checked checkbox to a list as soon as they're checked, and I'm doing this:
class Interface(QtGui.QMainWindow):
def __init__(self):
super(Interface, self).__init__()
self.initUI()
self.shops=[]
def initUI(self):
widthPx = 500
heightPx = 500
self.setGeometry(100,100,widthPx,heightPx)
#menus
fileMenu = menuBar.addMenu("&File")
helpMenu = menuBar.addMenu("&Help")
#labels
shopList = _getShops()
for i, shop in enumerate(shopList):
cb = QtGui.QCheckBox(shop, self)
cb.move(20, 15*(i)+50)
cb.toggle()
cb.stateChanged.connect(self.addShop)
self.setWindowTitle("Absolute")
self.show()
def addShop(self, state):
if state == QtCore.Qt.Checked:
#I want to add the checkbox's text
self.shops.append('IT WORKS')
else:
self.shops.remove('IT WORKS')
But instead of adding "IT WORKS" I want to add the text associated with the checkbox that was just selected.
I usually pass additionnal parameters in my signals/slots using partial
Functools doc
You can use it to pass your checkbox text.
First, import partial:
from functools import partial
Then, change your connect() method and pass your checkbox text:
cb.stateChanged.connect( partial( self.addShop, shop) )
To finish, update your addShop() method:
def addShop(self, shop, state):
if state == Qt.Checked:
self.shops.append(shop)
else:
try:
self.shops.remove(shop)
except:
print ""
Notes:
I've added a try/except at the end because your checkboxes are checked by default. When you uncheck them, it tries to remove an unknow item from your self.shops list.
With this method, this is not the current checkbox text which is send to your method. It it the first text that was used to initialize your checkboxes. If, during the execution of your script, you modify the checkbox text, it will not be updated in your addShop method.
Update:
In fact, you can pass your checkbox in the partial:
cb.stateChanged.connect( partial( self.addShop, cb) )
and retrieve it this way:
def addShop(self, shop, state):
if state == Qt.Checked:
self.shops.append(shop.text())
else:
try:
self.shops.remove(shop.text())
except:
print ""
I'm setting up a custom PlotDataItem to recieve mouseDragEvents. I've adjusted this answer to my needs. For now I've just added a simple setData to the event to check if it's working. The custom PlotDataItem is as:
class CustomPlotItem(pg.PlotDataItem):
def __init__(self, *args, **kargs):
super().__init__(*args, **kargs)
def setParentItem(self, parent):
super().setParentItem(parent)
self.parentBox = self.parentItem().parentItem()
def mouseDragEvent(self, ev):
if ev.button() != QtCore.Qt.LeftButton:
ev.ignore()
return
if ev.isStart():
if self.parentBox.curveDragged != None or not self.mouseShape().contains(ev.pos()):
ev.ignore()
return
self.parentBox.curveDragged = self
elif ev.isFinish():
self.parentBox.curveDragged = None
return
elif self.parentBox.curveDragged != self:
ev.ignore()
return
self.setData([40,50,60,200],[20,50,80,500])
ev.accept()
The PlotDataItem is added to a custom ViewBox this implements curveDragged, so I know which curve is being dragged, if any. I've also disabled the ViewBox's mouseDragEvents for debugging purposes.
However when try to drag the line in the ViewBox, nothing happens. Also if I add an exception at the top of the mouseDragEvent nothing happens. This leads me to believe mouseDragEvent is not being called at all.
Im using Python 3.3 (Anaconda Distribution) and the develop version (0.9.9) of pyqtgraph.
I hope someone can help me with this :). Thanks in advance.
PlotDataItem is a wrapper around a PlotCurveItem and a ScatterPlotItem. As such, it does not actually have any graphics or a clickable shape of its own. I would try making a subclass of PlotCurveItem instead. If you really need to use PlotDataItem, then it is possible to modify it such that it inherits its shape from the wrapped curve:
class CustomPlotItem(pg.PlotDataItem):
def __init__(self, *args, **kargs):
super().__init__(*args, **kargs)
# Need to switch off the "has no contents" flag
self.setFlags(self.flags() & ~self.ItemHasNoContents)
def mouseDragEvent(self, ev):
print("drag")
if ev.button() != QtCore.Qt.LeftButton:
ev.ignore()
return
if ev.isStart():
print("start")
elif ev.isFinish():
print("finish")
def shape(self):
# Inherit shape from the curve item
return self.curve.shape()
def boundingRect(self):
# All graphics items require this method (unless they have no contents)
return self.shape().boundingRect()
def paint(self, p, *args):
# All graphics items require this method (unless they have no contents)
return
def hoverEvent(self, ev):
# This is recommended to ensure that the item plays nicely with
# other draggable items
print("hover")
ev.acceptDrags(QtCore.Qt.LeftButton)
Is there trivial or elegant way to differentiate between many same-type signal sources in PySide/PyQt?
I am learning PySide. I have written simple application, which multiplies two numbers from two different QLineEdit() objects. Result is displayed in third QLineEdit.
Multiplier and multiplicand QLineEdit.textChanged() signals are connected to one method (TxtChanged). In this method i have to differentiate between signal sources. After some trials I figured out some workaround based upon placeholder text (4 lines below "is there another way?" comment in my code)
code:
import sys
from PySide import QtGui, QtCore
class myGUI(QtGui.QWidget):
def __init__(self, *args, **kwargs):
QtGui.QWidget.__init__(self, *args, **kwargs)
self.multiplier = 0
self.multiplicand = 0
self.myGUIInit()
def myGUIInit(self):
# input forms
a1_label = QtGui.QLabel("a1")
a1_edit = QtGui.QLineEdit()
a1_edit.setPlaceholderText("a1")
a2_label = QtGui.QLabel("a2")
a2_edit = QtGui.QLineEdit()
a2_edit.setPlaceholderText("a2")
# output form
a1a2_label = QtGui.QLabel("a1*a2")
self.a1a2_edit = QtGui.QLineEdit()
self.a1a2_edit.setReadOnly(True)
# forms events
a1_edit.textChanged.connect(self.TxtChanged)
a2_edit.textChanged.connect(self.TxtChanged)
# grid
grid = QtGui.QGridLayout()
grid.setSpacing(10)
grid.addWidget(a1_label,1,0)
grid.addWidget(a1_edit,1,1)
grid.addWidget(a2_label,2,0)
grid.addWidget(a2_edit,2,1)
grid.addWidget(a1a2_label,3,0)
grid.addWidget(self.a1a2_edit,3,1)
self.setLayout(grid)
self.setGeometry(100,100,200,200)
self.setWindowTitle("a*b")
self.show()
def TxtChanged(self,text):
sender = self.sender()
sender_text = sender.text()
if sender_text == '': sender_text = '0'
# is there another way?
if sender.placeholderText() == 'a1':
self.multiplicand = sender_text
else:
self.multiplier = sender_text
product = int(self.multiplier) * int(self.multiplicand)
print(self.multiplier,self.multiplicand,product)
self.a1a2_edit.setText(str(product))
def main():
app = QtGui.QApplication(sys.argv)
mainWindow = myGUI()
sys.exit(app.exec_())
main()
best regards,
ostrzysz
You can use the functools.partial function - and therefore connect your signals to straight to your method/function but rather to a python object which will automatically call your function with some extra data you pass it:
from functools import partial
...
....
a1_edit.textChanged.connect(partial(self.TxtChanged, a1_edit))
a2_edit.textChanged.connect(partial(self.TxtChanged, a2_edit))
...
def TxtChanged(self,sender, text):
# and here you have the "sender" parameter as it was filled in the call to "partial"
...
partials is part of the stdlib, and is very readable, but one can always use lambda instead of partial for the same effect -
a1_edit.textChanged.connect(lambda text: self.TxtChanged(a1_edit, text))
In this way the object yielded by the lambda expression will be a temporary function that will use the values for "self" and "a1_edit" from the current local variables (at the time the button is clicked), and the variable named "text" will be supplied by Pyside's callback.
One thing that bugs me most in your code is that you are using placeholderText to differentiate. QObjects has another property called objectName that is more suitable for your task. And, you don't need to use sender.text() to get the text of QLineEdit. textChanged already sends it, so you will have it in your text parameter.
Also, using a dictionary instead of two separate variables (multiplier and multiplicand) will simplify your code further.
Here is the changed code:
class myGUI(QtGui.QWidget):
def __init__(self, *args, **kwargs):
QtGui.QWidget.__init__(self, *args, **kwargs)
self.data = {"multiplier": 0,
"multiplicand": 0}
self.myGUIInit()
def myGUIInit(self):
a1_label = QtGui.QLabel("a1")
a1_edit = QtGui.QLineEdit()
a1_edit.setObjectName("multiplicand")
a2_label = QtGui.QLabel("a2")
a2_edit = QtGui.QLineEdit()
a2_edit.setObjectName("multiplier")
# skipped the rest because same
def TxtChanged(self, text):
sender = self.sender()
# casting to int while assigning seems logical.
self.data[sender.objectName()] = int(text)
product = self.data["multiplier"] * self.data["multiplicand"]
print(self.data["multiplier"], self.data["multiplicand"], product)
self.a1a2_edit.setText(str(product))
Although #jsbueno and #Avaris answered your direct question about signal sources, I wouldn't relay on this sources in your concrete case. You can make instance members a1_edit and a2_edit:
...
self.a1_edit = QtGui.QLineEdit()
...
self.a2_edit = QtGui.QLineEdit()
...
It will simplify your TxtChanged function:
def TxtChanged(self,text):
try:
multiplier = int(self.a1_edit.text())
multiplicand = int(self.a2_edit.text())
except ValueError:
self.a1a2_edit.setText('Enter two numbers')
return
product = multiplier * multiplicand
print(multiplier, multiplicand, product)
self.a1a2_edit.setText(str(product))
Also, instead of handling ValueError exception, you can use QIntValidator for input controls:
self.int_validator = QtGui.QIntValidator()
self.a1_edit.setValidator(self.int_validator)
self.a2_edit.setValidator(self.int_validator)
This is my code:
def configure_event(self, widget):
if self.is_hiding:
self.window.present()
else:
self.window.iconify()
def delete_event(self, widget, data=None):
gtk.main_quit()
return True
def popup_menu(self):
self.menu = gtk.Menu()
self.about = gtk.MenuItem("about")
if self.is_hiding:
self.expand = gtk.MenuItem("show")
else:
self.expand = gtk.MenuItem("hide")
self.quit = gtk.MenuItem("quit")
self.about.connect("activate", self.about_monitor)
self.expand.connect("activate", self.configure_event)
self.quit.connect("activate", self.delete_event)
self.menu.popup(None, None, gtk.status_icon_position_menu, event_button, event_time, self.tray_icon)
self.menu.append(self.about)
self.menu.append(self.expand)
self.menu.append(self.monitor)
self.menu.append(self.quit)
self.menu.show_all()
delete_event works, but configure_event does not. Why?
look at the signatures of both of them:
def configure_event(self, widget):
def delete_event(self, widget, data=None):
delete_event has a third argument data (that has a default of None) but configure_event only has two.
although i don't know what the exception was i bet that the exception was:
TypeError: configure_event() takes exactly 2 arguments (3 given)
if so changing configure_event's signature to:
def configure_event(self, widget, data):
would fix it. note that i think the default value of None is unneeded as gtk will pass something in always.