from PyQt4 import QtGui, QtCore
[docs]class ArgSetter(QtGui.QDialog):
'''Create an window to setup attributes graphically
useful as quick configurator for other functions/classes that need
keyword arguments that must be passed graphically
e.g. for py2exe/pyinstaller apps
'''
validators = {float: QtGui.QDoubleValidator,
int: QtGui.QIntValidator}
def __init__(self, title, argdict, stayOpen=False, saveLoadButton=False,
savePath='config.txt', loadPath='config.txt',
introduction=None, unpackDict=False):
'''
==================== =========================================================
Argument Comment
==================== =========================================================
title title of the window
argdict a dict of all arguments to set up:
e.g. {'arg1: {'value':1, 'limits':range(0,10)}, ...}
valid keywords are:
'value' -> the value of the argument
'limits' -> [list, tuple] if only specific values are valid
'unit' -> show a unit after the value
'tip' -> show a tool-tip
'dtype' -> the data-type of the value
* int, str, float, bool
* file, dir -> for selecting a file or folder
* line -> to add a horizontal separation line
for ordered entries use OrderedDict
stayOpen True -> dialog stays open after it is finished
useful if values should still be changeable
saveLoadButton True -> add 2 buttons to save and load these preferences
savePath Default save path
loadPath Default load path
unpackDict True, False | True:
==================== =========================================================
'''
QtGui.QDialog.__init__(self)
self._wantToClose = False
self.unpackDict = unpackDict
if stayOpen:
self.finished.connect(self.stayOpen)
self.setWindowTitle(title)
layout = QtGui.QGridLayout()
self.setLayout(layout)
if introduction:
pass
self.values = []
self.args = {}
for row, (name, val) in enumerate(argdict.iteritems()):
limits = val.get('limits', None)
value = str(val.get('value', ''))
unit = val.get('unit', None)
dtype = val.get('dtype', None)
tip = val.get('tip', None)
# NAME
nameLabel = QtGui.QLabel(name)
layout.addWidget(nameLabel, row, 0)
# LINE
if dtype == 'line':
nameLabel.setText('<b>%s</b>' %nameLabel.text())
line = QtGui.QFrame()
line.setFrameStyle(QtGui.QFrame.HLine | QtGui.QFrame.Raised)
layout.addWidget(line, row, 1, 1, 3)
# FILE/DIRECTORY
elif dtype in (file, dir):
wl = QtGui.QHBoxLayout()
q = QtGui.QLineEdit(value)
btn = QtGui.QPushButton()
if dtype == file:
fn = self._getFile
btn.setIcon(QtGui.QApplication.style().standardIcon(QtGui.QStyle.SP_FileIcon))
else:
fn = self._getFolder
btn.setIcon(QtGui.QApplication.style().standardIcon(QtGui.QStyle.SP_DirIcon))
btn.clicked.connect(lambda checked, fn=fn, lineEdit=q: fn(lineEdit))
wl.addWidget(q)
wl.addWidget(btn)
layout.addLayout(wl, row, 1)
self.values.append((name, q, str))
# STR/INT,BOOL, FLOAT
else:
if type(limits) in (list, tuple):
# create a QCombobox if LIMITS are given
q = QtGui.QComboBox()
q.text = q.currentText
for n, l in enumerate(limits):
limits[n] = str(l)
if value:
if value in limits:
limits.remove(value)
limits.insert(0,value)
q.addItems(limits)
else:
# if NO LIMITS given: create a lieEdit
q = QtGui.QLineEdit(value)
if dtype and dtype in self.validators:
q.setValidator(self.validators[dtype]())
if tip:
q.setToolTip(tip)
# VALUE
layout.addWidget(q, row, 1)
# UNIT
if unit:
layout.addWidget(QtGui.QLabel(str(unit)), row, 2)
self.values.append((name, q, dtype))
# SAVE/LOAD BUTTON
if saveLoadButton:
box = QtGui.QGroupBox('Preferences')
bLayout = QtGui.QGridLayout()
box.setLayout(bLayout)
btn_load = QtGui.QPushButton('Load')
self.label_load = QtGui.QLineEdit(loadPath)
btn_change_load = QtGui.QPushButton()
btn_change_load.setIcon(QtGui.QApplication.style().standardIcon(QtGui.QStyle.SP_DirIcon))
btn_change_load.clicked.connect(self._setLoadPath)
btn_load.clicked.connect(self._loadPreferences)
btn_save = QtGui.QPushButton('Save')
self.label_save = QtGui.QLineEdit(savePath)
btn_change_save = QtGui.QPushButton()
btn_change_save.setIcon(QtGui.QApplication.style().standardIcon(QtGui.QStyle.SP_DirIcon))
btn_change_save.clicked.connect(self._setSavePath)
btn_save.clicked.connect(self._savePreferences)
bLayout.addWidget(btn_load, 0,0)
bLayout.addWidget(self.label_load, 0,1)
bLayout.addWidget(btn_change_load, 0,2)
bLayout.addWidget(btn_save, 1,0)
bLayout.addWidget(self.label_save, 1,1)
bLayout.addWidget(btn_change_save, 1,2)
row += 1
layout.addWidget(box, row, 0, 1, 3)
# DONE BUTTON
self.btn_done = QtGui.QPushButton('done')
self.btn_done.clicked.connect(self.check)
layout.addWidget(self.btn_done, row+1,0, 1, 3)
@staticmethod
def _getFile(lineEdit):
filename = QtGui.QFileDialog.getOpenFileName(directory=lineEdit.text())
if filename:
lineEdit.setText(str(filename))
@staticmethod
def _getFolder(lineEdit):
folder = QtGui.QFileDialog.getExistingDirectory(directory=lineEdit.text())
if folder:
lineEdit.setText(str(folder))
def _setSavePath(self):
path = QtGui.QFileDialog.getSaveFileName(filter='*.txt')
self.label_save.setText(path)
def _setLoadPath(self):
path = QtGui.QFileDialog.getOpenFileName(filter='*.txt')
self.label_load.setText(path)
def _savePreferences(self):
if self.label_save.text() in ('', '[NOT SAVED]'):
self._setSavePath()
if self.label_save.text() == '':
self.label_save.setText('[NOT SAVED]')
return
with open(self.label_save.text(),'w') as f:
d = {}
for name, widget, _ in self.values:
d[name] = unicode(widget.text())
f.write(str(d))
def _loadPreferences(self):
if self.label_load.text() in ('', '[NOT LOADED]'):
self._setLoadPath()
if self.label_load.text() == '':
self.label_load.setText('[NOT LOADED]')
return
with open(self.label_load.text(), 'r') as f:
d = eval(f.read())
for name, widget, _ in self.values:
if isinstance(widget, QtGui.QComboBox):
for c in range(widget.count()):
if widget.itemText(c) == d[name]:
widget.setCurrentIndex(c)
else:
widget.setText(d[name])
[docs] def show(self):
'''
if the dialog is showed again or is not started with exec_
naming the done button to 'update' make more sense
because now the dialog doesn't block (anymore)
'''
self.btn_done.clicked.connect(lambda: self.btn_done.setText('update'))
QtGui.QDialog.show(self)
[docs] def run(self, processClass):
self.processClass = processClass
QtGui.QDialog.show(self)
self.btn_done.clicked.connect(self._startProcess)
def _startProcess(self):
self.btn_done.clicked.disconnect(self._startProcess)
if self.unpackDict:
p = self.processClass(**self.args)
else:
p = self.processClass(self.args)
self.btn_done.setText('update')
[docs] def check(self):
'''check whether all attributes are setted and have the right dtype'''
for name, valItem, dtype in self.values:
val = valItem.text()
if dtype:
try:
val = dtype(val)
except:
msgBox = QtGui.QMessageBox()
msgBox.setText('attribute %s has not the right dtype(%s)' %(name, str(dtype)))
msgBox.exec_()
self.args[name] = val
self.accept()
[docs] def closeEvent(self, evt):
self._wantToClose = True
QtGui.QDialog.closeEvent(self, evt)
[docs] def done(self, result):
'''save the geometry before dialog is close to restore it later'''
self._geometry = self.geometry()
QtGui.QDialog.done(self, result)
[docs] def stayOpen(self):
'''optional dialog restore'''
if not self._wantToClose:
self.show()
self.setGeometry(self._geometry)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
print("""TEST 1:
* ArgSetter will block until the button 'done' is pressed.
* If the dialog is not cancelled the arguments are printed""")
a = ArgSetter('CloseAfterDone', {
'show_output_after_start':{
'value': True,
'limits':[True, False],
'dtype':bool,
'tip':'foo bar'},
'webcam_index': {
'value':-1,
'limits':range(-1,10),
'dtype':int},
'output_file_name':{
'value':'output.csv',
'dtype':str},
'camera_calibration_File':{
'value':'',
'dtype':str},
'nAverageSteps': {
'value':3,
'limits':range(1,20),
'dtype':int},
'refreshRate': {
'value':100,
'dtype':int,
'unit':'ms'}
})
a.exec_()
if a.result():
print a.args
else:
print 'setup cancelled'
print("""TEST 2:
* ArgSetter won't block but update its agument every time 'update' is pressed. """)
a = ArgSetter('StayOpen', {
'one':{
'value': True,
'limits':[True, False],
'dtype':bool,
'tip':'foo bar'},
'two': {
'value':-1,
'limits':range(-1,10),
'dtype':int},
'three':{
'value':'output.csv',
'dtype':str},
'four':{
'value':'',
'dtype':str},
'fife': {
'value':3,
'limits':range(1,20),
'dtype':int},
'six': {
'value':100,
'dtype':int,
'unit':'ms'}
}, stayOpen=True, saveLoadButton=True)
class MyProcedure(QtGui.QTextEdit):
'''
a procedure taking the argument from ArgSetter
as self.opts
This shows the feature to interactively update argument using a
graphical interface
'''
def __init__(self, opts):
QtGui.QWidget.__init__(self)
self.setReadOnly(True)
self.opts = opts
self.timer = QtCore.QTimer()
self.timer.timeout.connect(lambda: self.setText(str(self.opts)))
self.timer.start(100)
self.show()
a.run(MyProcedure)
app.exec_()