Source code for pario.paredit

# Copyright (C) 2012, SINTEF Materials and Chemistry
# (See accompanying license files for details).
#
# By Jesper Friis

"""
Editor for Parameters objects.

Examples
--------
Create a Parameters object to edit

>>> import pario.parameters
>>> p = pario.parameters.Parameters()
>>> p._set('velocity', 5, 'm/s', doc='out speed')

Easiest way to use paredit is through the _edit() method

>>> p._edit()   #doctest: +SKIP

The same functionality is provided with :func:`pario.paredit.paredit`:

>>> paredit(p)  #doctest: +SKIP

"""

from __future__ import absolute_import
import textwrap

from PyQt4 import QtCore, QtGui
import numpy as np

from . import parameters
from .arrayedit import ArrayEdit



[docs]def paredit(par, dialog=None, names=None, group=None, title='Parameters', header=True, callback=None, wait=True): """Creates a parameter dialog and updates *par*. Parameters ---------- par : Parameters instance Parameters instance to edit. dialog : None | ParEdit instance If given, the existing *dialog* is made visible and raised. instead of creating a new dialog. names : None | sequence of strings If given, include only parameters included in names. group : None | string If given, include only parameters of this group. title : string Dialog title. header : bool Whether to add a header. callback : None | callable(par, done) A callback function that is called when the dialog is closed or the apply button is pressed. `callable` will be called with two arguments: par updated parameter instance done a boolean that is true if the dialog is closed wait : bool If True, this function does not return before the dialog is closed. Returns ------- QtGui.QDialog instance """ # Create a QApplication instance if no instance currently exists # (e.g. if the module is used directly from the interpreter) # It is important to globally store an reference to the application, # otherwise the dialog wil not show up. if QtGui.QApplication.startingUp(): global app app = QtGui.QApplication([]) if dialog is None: dialog = ParEdit(par, names=names, group=group, title=title, header=header, callback=callback) else: dialog.update() if wait: #dialog.setWindowModality(QtCore.Qt.WindowModal) dialog.exec_() elif dialog.isVisible(): dialog.raise_() dialog.activateWindow() else: dialog.show() return dialog #------------------------------------------------------------------ # Widgets showing the parameter value #------------------------------------------------------------------
[docs]class LineWidget(QtGui.QLineEdit): """A QLineEdit for editing floats, ints and strings.""" def __init__(self, name, value): QtGui.QLineEdit.__init__(self) self._ptype = type(value) self.set(value) def get(self): return self._ptype(self.text()) def set(self, value): self.setText(str(value))
[docs]class BoolWidget(QtGui.QCheckBox): """A QCheckBox for editing bools.""" def __init__(self, name, value): QtGui.QCheckBox.__init__(self) self.set(value) def get(self): return self.isChecked() def set(self, value): self.setCheckState(QtCore.Qt.Checked if value else QtCore.Qt.Unchecked)
[docs]class ArrayWidget(QtGui.QPushButton): """A button for opening a dialog for editing an array.""" def __init__(self, name, value): QtGui.QPushButton.__init__(self, 'edit table...') self.setToolTip('Press to open a new dialog for editing the table') def onClick(): def callback(arr, done): self._arr = arr dialog = ArrayEdit(self._arr, title=name, callback=callback) dialog.exec_() self.connect(self, QtCore.SIGNAL("clicked()", ), onClick) self.set(value) def get(self): return self._arr def set(self, value): self._arr = value
[docs]class OneOfWidget(QtGui.QWidget): """A set of radioboxes for selecting one of several possibilities. `possibilities` should be a comma-separated list of possible values or of the form "0=first option, 1=second option". If `value_wrap` is not None, it must be an integer determing the maximum width of the value field (by word-wrapping). """ def __init__(self, name, value, possibilities, widget=QtGui.QRadioButton, value_wrap=32): QtGui.QWidget.__init__(self) vbox = QtGui.QVBoxLayout() vbox.setSpacing(0) if isinstance(value, int): tokens = [] for n, label in [t.strip().split('=', 1) for t in possibilities.split(',')]: tokens.append([int(n), label]) else: tokens = [(None, t.strip()) for t in possibilities.split(',')] self._items = [] for n, label in tokens: if value_wrap: rb = widget('\n'.join(textwrap.wrap(label, value_wrap))) else: rb = widget(wrapped_label) vbox.addWidget(rb) self._items.append((n, label, rb)) self.setLayout(vbox) self.set(value) def get(self): for n, label, rb in self._items: if rb.isChecked(): return label if n is None else n def set(self, value): for n, label, rb in self._items: rb.setChecked((n is None and value == label) or (n is not None and value == n))
[docs]class SumOfWidget(OneOfWidget): """A set of checkboxes for selecting one or more of several possibilities. `possibilities` should be of the form "0=first flag, 1=second flag".""" def __init__(self, name, value, possibilities): OneOfWidget.__init__(self, name, value, possibilities, widget=QtGui.QCheckBox) def get(self): v = 0 for n, label, cb in self._items: if cb.isChecked(): v += n return v def set(self, value): for n, label, cb in reversed(sorted(self._items)): if value >= n: cb.setChecked(True) value -= n else: cb.setChecked(False) #------------------------------------------------------------------ # Main dialog #------------------------------------------------------------------
[docs]class ParEdit(QtGui.QDialog): """A class that allows graphical editing of model parameter. See paredit() for a description of the arguments. """ def __init__(self, par, parent=None, names=None, group=None, title='Parameters', header=True, callback=None): QtGui.QDialog.__init__(self, parent) self.par = par self.header = header self._callback = callback if names is None: if group is not None: #names = [name for name in par._names # if par._groups.get(name, None) == group] names = par._get_names(group=group) else: names = par._names self.names = names self.widgets = {} #self.editdialogs = {} self.setupUI() self.setWindowTitle(title) #self.show() def setupUI(self): par = self.par grid = QtGui.QGridLayout() #grid.setSpacing(8) has_doc = any([name in par._doc for name in par._names]) if self.header: for i, name in enumerate( ('Parameter', 'Value', 'Unit', 'Description')): if not has_doc and name == 'Description': continue label = QtGui.QLabel(name) font = label.font() font.setBold(True) label.setFont(font) grid.addWidget(label, 0, i) for i, name in enumerate(self.names): doc = par._doc.get(name, '') grid.addWidget(QtGui.QLabel(name), i + 1, 0) widget = None if isinstance(par[name], np.ndarray): widget = ArrayWidget(name, par[name]) elif isinstance(par[name], bool): widget = BoolWidget(name, par[name]) elif doc and 'One of:' in doc: doc, possibilities = par._doc[name].split('One of:') widget = OneOfWidget(name, par[name], possibilities) elif doc and 'Sum of:' in doc: doc, possibilities = par._doc[name].split('Sum of:') widget = SumOfWidget(name, par[name], possibilities) else: widget = LineWidget(name, par[name]) self.widgets[name] = widget grid.addWidget(widget, i + 1, 1) unit = par._units[name] if not unit or unit == '-': unit = '' grid.addWidget(QtGui.QLabel(unit), i + 1, 2) if doc: label = QtGui.QLabel(doc) label.setWordWrap(True) grid.addWidget(label, i + 1, 3) scrollwidget = QtGui.QWidget() scrollwidget.setLayout(grid) #scrollwidget.adjustSize() #scrollwidget.setSizeIncrement(20, 20) #size.setWidth(size.width() + 20) #size.setHeight(size.height() + 20) #scrollwidget.setSize(size) #size = scrollwidget.baseSize() #print('*** basesize:', size.width(), size.height()) scrollarea = QtGui.QScrollArea() scrollarea.setWidget(scrollwidget) #scrollarea.setMinimumWidth(scrollarea.minimumWidth() + 20) #scrollarea.setMinimumWidth(700) #scrollarea.resize(size.width() + 120, size.height() + 120) #scrollarea.adjustSize() buttonbox = QtGui.QHBoxLayout() saveButton = QtGui.QPushButton("Save") okButton = QtGui.QPushButton("OK") applyButton = QtGui.QPushButton("Apply") cancelButton = QtGui.QPushButton("Cancel") self.connect(saveButton, QtCore.SIGNAL("clicked()"), self.onSave) self.connect(okButton, QtCore.SIGNAL("clicked()"), self.onOK) self.connect(applyButton, QtCore.SIGNAL("clicked()"), self.onApply) self.connect(cancelButton, QtCore.SIGNAL("clicked()"), self.onCancel) buttonbox.addWidget(saveButton) buttonbox.addStretch(1) buttonbox.addWidget(okButton) buttonbox.addWidget(applyButton) buttonbox.addWidget(cancelButton) vbox = QtGui.QVBoxLayout() vbox = QtGui.QVBoxLayout() #vbox.addSpacing(10) #vbox.addStretch(1) vbox.addWidget(scrollarea) vbox.addLayout(buttonbox) self.setLayout(vbox) # Increase width to make horizontal scrollbar disappear size = self.sizeHint() size.setWidth(size.width() + 140) self.resize(size) # Keybindings: "Ctrl+Q" -> Cancel and "Ctrl+W" -> OK cancelAction = QtGui.QAction(self) cancelAction.setShortcut('Ctrl+Q') cancelAction.triggered.connect(self.onCancel) okAction = QtGui.QAction(self) okAction.setShortcut('Ctrl+W') okAction.triggered.connect(self.onOK)
[docs] def setpar(self): """Set parameters to new values.""" for name in self.names: self.par[name] = self.widgets[name].get()
[docs] def update(self): """Update dialog from parameters.""" for name in self.names: self.widgets[name].set(self.par[name])
def onOK(self): self.onApply() self.accept() def onCancel(self): if self._callback is not None: self._callback(self.par, False) self.reject() def onApply(self): self.setpar() if self._callback is not None: self._callback(self.par, True) def onSave(self): # with pyside, call instead QtGui.QFileDialog.getSaveFileName() filename, filter = QtGui.QFileDialog.getSaveFileNameAndFilter( parent=self, caption='Save to file') filename = str(filename) if filename: self.par._write(str(filename)) #def onEditTable(self, name): # if name in self.editdialogs: # if not self.editdialogs[name].isVisible(): # self.editdialogs[name].show() # self.editdialogs[name].activateWindow() # self.editdialogs[name].raise_() # else: # def callback(arr, done): # self.par[name] = arr # if done: # del self.editdialogs[name] # dialog = ArrayEdit(self.par[name], title=name, # callback=callback) # self.editdialogs[name] = dialog # dialog.show()
if __name__ == '__main__': import sys p = parameters.fromstring(''' 1 - model model selection. One of: 0=first model, 1=second model, 2=third model 10 - flags model flags. Sum of: 1=flag1, 2=flag2, 4=flag3, 8=flag4, 16=flag5 1.52e10 m^-2 rho_i dislocation density # a comment 0.3e-6 m delta subgrain size #------------------------------------------ # TREATMENT TABLE # time(s) T(C) Css PZ(Pa) 0.0 330. 5.3138E-3 0.0 1.0 330. 5.3138E-3 0.0 5.0 330. 5.3138E-3 0.0 10.0 330. 5.3138E-3 0.0 ''') r = p.TREATMENT_TABLE print('=' * 60) print(p) paredit(p) print('=' * 60) print(p) print('=' * 60) settings = parameters.Parameters() settings.advanced = True settings.name = 'abc' settings.myint = 42 settings.mufloat = 3.14 #settings._show() paredit(settings, title='Settings', header=False) print(settings)