|
|
|
|
@ -0,0 +1,817 @@
|
|
|
|
|
"""
|
|
|
|
|
NOTE: The required `Qt` module has changed to use the one that vendorized.
|
|
|
|
|
Remember to change to relative import when updating this.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import re
|
|
|
|
|
import logging
|
|
|
|
|
|
|
|
|
|
from collections import OrderedDict as odict
|
|
|
|
|
from Qt import QtCore, QtWidgets, QtGui
|
|
|
|
|
import qtawesome
|
|
|
|
|
|
|
|
|
|
__version__ = "0.5.2"
|
|
|
|
|
_log = logging.getLogger(__name__)
|
|
|
|
|
_type = type # used as argument
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
# Python 2
|
|
|
|
|
_basestring = basestring
|
|
|
|
|
except NameError:
|
|
|
|
|
_basestring = str
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class QArgumentParser(QtWidgets.QWidget):
|
|
|
|
|
"""User interface arguments
|
|
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
arguments (list, optional): Instances of QArgument
|
|
|
|
|
description (str, optional): Long-form text of what this parser is for
|
|
|
|
|
storage (QSettings, optional): Persistence to disk, providing
|
|
|
|
|
value() and setValue() methods
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
changed = QtCore.Signal(QtCore.QObject) # A QArgument
|
|
|
|
|
|
|
|
|
|
def __init__(self,
|
|
|
|
|
arguments=None,
|
|
|
|
|
description=None,
|
|
|
|
|
storage=None,
|
|
|
|
|
parent=None):
|
|
|
|
|
super(QArgumentParser, self).__init__(parent)
|
|
|
|
|
self.setAttribute(QtCore.Qt.WA_StyledBackground)
|
|
|
|
|
|
|
|
|
|
# Create internal settings
|
|
|
|
|
if storage is True:
|
|
|
|
|
storage = QtCore.QSettings(
|
|
|
|
|
QtCore.QSettings.IniFormat,
|
|
|
|
|
QtCore.QSettings.UserScope,
|
|
|
|
|
__name__, "QArgparse",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if storage is not None:
|
|
|
|
|
_log.info("Storing settings @ %s" % storage.fileName())
|
|
|
|
|
|
|
|
|
|
arguments = arguments or []
|
|
|
|
|
|
|
|
|
|
assert hasattr(arguments, "__iter__"), "arguments must be iterable"
|
|
|
|
|
assert isinstance(storage, (type(None), QtCore.QSettings)), (
|
|
|
|
|
"storage must be of type QSettings"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
layout = QtWidgets.QGridLayout(self)
|
|
|
|
|
layout.setRowStretch(999, 1)
|
|
|
|
|
|
|
|
|
|
if description:
|
|
|
|
|
layout.addWidget(QtWidgets.QLabel(description), 0, 0, 1, 2)
|
|
|
|
|
|
|
|
|
|
self._row = 1
|
|
|
|
|
self._storage = storage
|
|
|
|
|
self._arguments = odict()
|
|
|
|
|
self._desciption = description
|
|
|
|
|
|
|
|
|
|
for arg in arguments or []:
|
|
|
|
|
self._addArgument(arg)
|
|
|
|
|
|
|
|
|
|
self.setStyleSheet(style)
|
|
|
|
|
|
|
|
|
|
def setDescription(self, text):
|
|
|
|
|
self._desciption.setText(text)
|
|
|
|
|
|
|
|
|
|
def addArgument(self, name, type=None, default=None, **kwargs):
|
|
|
|
|
# Infer type from default
|
|
|
|
|
if type is None and default is not None:
|
|
|
|
|
type = _type(default)
|
|
|
|
|
|
|
|
|
|
# Default to string
|
|
|
|
|
type = type or str
|
|
|
|
|
|
|
|
|
|
Argument = {
|
|
|
|
|
None: String,
|
|
|
|
|
int: Integer,
|
|
|
|
|
float: Float,
|
|
|
|
|
bool: Boolean,
|
|
|
|
|
str: String,
|
|
|
|
|
list: Enum,
|
|
|
|
|
tuple: Enum,
|
|
|
|
|
}.get(type, type)
|
|
|
|
|
|
|
|
|
|
arg = Argument(name, default=default, **kwargs)
|
|
|
|
|
self._addArgument(arg)
|
|
|
|
|
return arg
|
|
|
|
|
|
|
|
|
|
def _addArgument(self, arg):
|
|
|
|
|
if arg["name"] in self._arguments:
|
|
|
|
|
raise ValueError("Duplicate argument '%s'" % arg["name"])
|
|
|
|
|
|
|
|
|
|
if self._storage is not None:
|
|
|
|
|
default = self._storage.value(arg["name"])
|
|
|
|
|
|
|
|
|
|
if default:
|
|
|
|
|
if isinstance(arg, Boolean):
|
|
|
|
|
default = bool({
|
|
|
|
|
None: QtCore.Qt.Unchecked,
|
|
|
|
|
|
|
|
|
|
0: QtCore.Qt.Unchecked,
|
|
|
|
|
1: QtCore.Qt.Checked,
|
|
|
|
|
2: QtCore.Qt.Checked,
|
|
|
|
|
|
|
|
|
|
"0": QtCore.Qt.Unchecked,
|
|
|
|
|
"1": QtCore.Qt.Checked,
|
|
|
|
|
"2": QtCore.Qt.Checked,
|
|
|
|
|
|
|
|
|
|
# May be stored as string, if used with IniFormat
|
|
|
|
|
"false": QtCore.Qt.Unchecked,
|
|
|
|
|
"true": QtCore.Qt.Checked,
|
|
|
|
|
}.get(default))
|
|
|
|
|
|
|
|
|
|
arg["default"] = default
|
|
|
|
|
|
|
|
|
|
arg.changed.connect(lambda: self.on_changed(arg))
|
|
|
|
|
|
|
|
|
|
label = (
|
|
|
|
|
QtWidgets.QLabel(arg["label"])
|
|
|
|
|
if arg.label
|
|
|
|
|
else QtWidgets.QLabel()
|
|
|
|
|
)
|
|
|
|
|
widget = arg.create()
|
|
|
|
|
icon = qtawesome.icon("fa.refresh", color="white")
|
|
|
|
|
reset = QtWidgets.QPushButton(icon, "") # default
|
|
|
|
|
reset.setToolTip("Reset")
|
|
|
|
|
reset.setProperty("type", "reset")
|
|
|
|
|
reset.clicked.connect(lambda: self.on_reset(arg))
|
|
|
|
|
|
|
|
|
|
# Shown on edit
|
|
|
|
|
reset.hide()
|
|
|
|
|
|
|
|
|
|
for widget in (label, widget):
|
|
|
|
|
widget.setToolTip(arg["help"])
|
|
|
|
|
widget.setObjectName(arg["name"]) # useful in CSS
|
|
|
|
|
widget.setProperty("type", type(arg).__name__)
|
|
|
|
|
widget.setAttribute(QtCore.Qt.WA_StyledBackground)
|
|
|
|
|
widget.setEnabled(arg["enabled"])
|
|
|
|
|
|
|
|
|
|
# Align label on top of row if widget is over two times heiger
|
|
|
|
|
height = (lambda w: w.sizeHint().height())
|
|
|
|
|
label_on_top = height(label) * 2 < height(widget)
|
|
|
|
|
alignment = (QtCore.Qt.AlignTop,) if label_on_top else ()
|
|
|
|
|
|
|
|
|
|
layout = self.layout()
|
|
|
|
|
layout.addWidget(label, self._row, 0, *alignment)
|
|
|
|
|
layout.addWidget(widget, self._row, 1)
|
|
|
|
|
layout.addWidget(reset, self._row, 2, *alignment)
|
|
|
|
|
layout.setColumnStretch(1, 1)
|
|
|
|
|
|
|
|
|
|
def on_changed(*_):
|
|
|
|
|
reset.setVisible(arg["edited"])
|
|
|
|
|
|
|
|
|
|
arg.changed.connect(on_changed)
|
|
|
|
|
|
|
|
|
|
self._row += 1
|
|
|
|
|
self._arguments[arg["name"]] = arg
|
|
|
|
|
|
|
|
|
|
def clear(self):
|
|
|
|
|
assert self._storage, "Cannot clear without persistent storage"
|
|
|
|
|
self._storage.clear()
|
|
|
|
|
_log.info("Clearing settings @ %s" % self._storage.fileName())
|
|
|
|
|
|
|
|
|
|
def find(self, name):
|
|
|
|
|
return self._arguments[name]
|
|
|
|
|
|
|
|
|
|
def on_reset(self, arg):
|
|
|
|
|
arg.write(arg["default"])
|
|
|
|
|
|
|
|
|
|
def on_changed(self, arg):
|
|
|
|
|
arg["edited"] = arg.read() != arg["default"]
|
|
|
|
|
self.changed.emit(arg)
|
|
|
|
|
|
|
|
|
|
# Optional PEP08 syntax
|
|
|
|
|
add_argument = addArgument
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class QArgument(QtCore.QObject):
|
|
|
|
|
"""Base class of argument user interface
|
|
|
|
|
"""
|
|
|
|
|
changed = QtCore.Signal()
|
|
|
|
|
|
|
|
|
|
# Provide a left-hand side label for this argument
|
|
|
|
|
label = True
|
|
|
|
|
# For defining default value for each argument type
|
|
|
|
|
default = None
|
|
|
|
|
|
|
|
|
|
def __init__(self, name, default=None, **kwargs):
|
|
|
|
|
super(QArgument, self).__init__(kwargs.pop("parent", None))
|
|
|
|
|
|
|
|
|
|
kwargs["name"] = name
|
|
|
|
|
kwargs["label"] = kwargs.get("label", camel_to_title(name))
|
|
|
|
|
kwargs["default"] = self.default if default is None else default
|
|
|
|
|
kwargs["help"] = kwargs.get("help", "")
|
|
|
|
|
kwargs["read"] = kwargs.get("read")
|
|
|
|
|
kwargs["write"] = kwargs.get("write")
|
|
|
|
|
kwargs["enabled"] = bool(kwargs.get("enabled", True))
|
|
|
|
|
kwargs["edited"] = False
|
|
|
|
|
|
|
|
|
|
self._data = kwargs
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return self["name"]
|
|
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
|
return "%s(\"%s\")" % (type(self).__name__, self["name"])
|
|
|
|
|
|
|
|
|
|
def __getitem__(self, key):
|
|
|
|
|
return self._data[key]
|
|
|
|
|
|
|
|
|
|
def __setitem__(self, key, value):
|
|
|
|
|
self._data[key] = value
|
|
|
|
|
|
|
|
|
|
def __eq__(self, other):
|
|
|
|
|
if isinstance(other, _basestring):
|
|
|
|
|
return self["name"] == other
|
|
|
|
|
return super(QArgument, self).__eq__(other)
|
|
|
|
|
|
|
|
|
|
def __ne__(self, other):
|
|
|
|
|
return not self.__eq__(other)
|
|
|
|
|
|
|
|
|
|
def create(self):
|
|
|
|
|
return QtWidgets.QWidget()
|
|
|
|
|
|
|
|
|
|
def read(self):
|
|
|
|
|
return self._read()
|
|
|
|
|
|
|
|
|
|
def write(self, value):
|
|
|
|
|
self._write(value)
|
|
|
|
|
self.changed.emit()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Boolean(QArgument):
|
|
|
|
|
"""Boolean type user interface
|
|
|
|
|
|
|
|
|
|
Presented by `QtWidgets.QCheckBox`.
|
|
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
name (str): The name of argument
|
|
|
|
|
label (str, optional): Display name, convert from `name` if not given
|
|
|
|
|
help (str, optional): Tool tip message of this argument
|
|
|
|
|
default (bool, optional): Argument's default value, default None
|
|
|
|
|
enabled (bool, optional): Whether to enable this widget, default True
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
def create(self):
|
|
|
|
|
widget = QtWidgets.QCheckBox()
|
|
|
|
|
widget.clicked.connect(self.changed.emit)
|
|
|
|
|
|
|
|
|
|
if isinstance(self, Tristate):
|
|
|
|
|
self._read = lambda: widget.checkState()
|
|
|
|
|
state = {
|
|
|
|
|
0: QtCore.Qt.Unchecked,
|
|
|
|
|
1: QtCore.Qt.PartiallyChecked,
|
|
|
|
|
2: QtCore.Qt.Checked,
|
|
|
|
|
"1": QtCore.Qt.PartiallyChecked,
|
|
|
|
|
"0": QtCore.Qt.Unchecked,
|
|
|
|
|
"2": QtCore.Qt.Checked,
|
|
|
|
|
}
|
|
|
|
|
else:
|
|
|
|
|
self._read = lambda: bool(widget.checkState())
|
|
|
|
|
state = {
|
|
|
|
|
None: QtCore.Qt.Unchecked,
|
|
|
|
|
|
|
|
|
|
0: QtCore.Qt.Unchecked,
|
|
|
|
|
1: QtCore.Qt.Checked,
|
|
|
|
|
2: QtCore.Qt.Checked,
|
|
|
|
|
|
|
|
|
|
"0": QtCore.Qt.Unchecked,
|
|
|
|
|
"1": QtCore.Qt.Checked,
|
|
|
|
|
"2": QtCore.Qt.Checked,
|
|
|
|
|
|
|
|
|
|
# May be stored as string, if used with QSettings(..IniFormat)
|
|
|
|
|
"false": QtCore.Qt.Unchecked,
|
|
|
|
|
"true": QtCore.Qt.Checked,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self._write = lambda value: widget.setCheckState(state[value])
|
|
|
|
|
widget.clicked.connect(self.changed.emit)
|
|
|
|
|
|
|
|
|
|
if self["default"] is not None:
|
|
|
|
|
self._write(self["default"])
|
|
|
|
|
|
|
|
|
|
return widget
|
|
|
|
|
|
|
|
|
|
def read(self):
|
|
|
|
|
return self._read()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Tristate(QArgument):
|
|
|
|
|
"""Not implemented"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Number(QArgument):
|
|
|
|
|
"""Base class of numeric type user interface"""
|
|
|
|
|
default = 0
|
|
|
|
|
|
|
|
|
|
def create(self):
|
|
|
|
|
if isinstance(self, Float):
|
|
|
|
|
widget = QtWidgets.QDoubleSpinBox()
|
|
|
|
|
widget.setMinimum(self._data.get("min", 0.0))
|
|
|
|
|
widget.setMaximum(self._data.get("max", 99.99))
|
|
|
|
|
else:
|
|
|
|
|
widget = QtWidgets.QSpinBox()
|
|
|
|
|
widget.setMinimum(self._data.get("min", 0))
|
|
|
|
|
widget.setMaximum(self._data.get("max", 99))
|
|
|
|
|
|
|
|
|
|
widget.editingFinished.connect(self.changed.emit)
|
|
|
|
|
self._read = lambda: widget.value()
|
|
|
|
|
self._write = lambda value: widget.setValue(value)
|
|
|
|
|
|
|
|
|
|
if self["default"] != self.default:
|
|
|
|
|
self._write(self["default"])
|
|
|
|
|
|
|
|
|
|
return widget
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Integer(Number):
|
|
|
|
|
"""Integer type user interface
|
|
|
|
|
|
|
|
|
|
A subclass of `qargparse.Number`, presented by `QtWidgets.QSpinBox`.
|
|
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
name (str): The name of argument
|
|
|
|
|
label (str, optional): Display name, convert from `name` if not given
|
|
|
|
|
help (str, optional): Tool tip message of this argument
|
|
|
|
|
default (int, optional): Argument's default value, default 0
|
|
|
|
|
min (int, optional): Argument's minimum value, default 0
|
|
|
|
|
max (int, optional): Argument's maximum value, default 99
|
|
|
|
|
enabled (bool, optional): Whether to enable this widget, default True
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Float(Number):
|
|
|
|
|
"""Float type user interface
|
|
|
|
|
|
|
|
|
|
A subclass of `qargparse.Number`, presented by `QtWidgets.QDoubleSpinBox`.
|
|
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
name (str): The name of argument
|
|
|
|
|
label (str, optional): Display name, convert from `name` if not given
|
|
|
|
|
help (str, optional): Tool tip message of this argument
|
|
|
|
|
default (float, optional): Argument's default value, default 0.0
|
|
|
|
|
min (float, optional): Argument's minimum value, default 0.0
|
|
|
|
|
max (float, optional): Argument's maximum value, default 99.99
|
|
|
|
|
enabled (bool, optional): Whether to enable this widget, default True
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Range(Number):
|
|
|
|
|
"""Range type user interface
|
|
|
|
|
|
|
|
|
|
A subclass of `qargparse.Number`, not production ready.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Double3(QArgument):
|
|
|
|
|
"""Double3 type user interface
|
|
|
|
|
|
|
|
|
|
Presented by three `QtWidgets.QLineEdit` widget with `QDoubleValidator`
|
|
|
|
|
installed.
|
|
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
name (str): The name of argument
|
|
|
|
|
label (str, optional): Display name, convert from `name` if not given
|
|
|
|
|
help (str, optional): Tool tip message of this argument
|
|
|
|
|
default (tuple or list, optional): Default (0, 0, 0).
|
|
|
|
|
enabled (bool, optional): Whether to enable this widget, default True
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
default = (0, 0, 0)
|
|
|
|
|
|
|
|
|
|
def create(self):
|
|
|
|
|
widget = QtWidgets.QWidget()
|
|
|
|
|
layout = QtWidgets.QHBoxLayout(widget)
|
|
|
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
x, y, z = (self.child_arg(layout, i) for i in range(3))
|
|
|
|
|
|
|
|
|
|
self._read = lambda: (
|
|
|
|
|
float(x.text()), float(y.text()), float(z.text()))
|
|
|
|
|
self._write = lambda value: [
|
|
|
|
|
w.setText(str(float(v))) for w, v in zip([x, y, z], value)]
|
|
|
|
|
|
|
|
|
|
if self["default"] != self.default:
|
|
|
|
|
self._write(self["default"])
|
|
|
|
|
|
|
|
|
|
return widget
|
|
|
|
|
|
|
|
|
|
def child_arg(self, layout, index):
|
|
|
|
|
widget = QtWidgets.QLineEdit()
|
|
|
|
|
widget.setValidator(QtGui.QDoubleValidator())
|
|
|
|
|
|
|
|
|
|
default = str(float(self["default"][index]))
|
|
|
|
|
widget.setText(default)
|
|
|
|
|
|
|
|
|
|
def focusOutEvent(event):
|
|
|
|
|
if not widget.text():
|
|
|
|
|
widget.setText(default) # Ensure value exists for `_read`
|
|
|
|
|
QtWidgets.QLineEdit.focusOutEvent(widget, event)
|
|
|
|
|
widget.focusOutEvent = focusOutEvent
|
|
|
|
|
|
|
|
|
|
widget.editingFinished.connect(self.changed.emit)
|
|
|
|
|
widget.returnPressed.connect(widget.editingFinished.emit)
|
|
|
|
|
|
|
|
|
|
layout.addWidget(widget)
|
|
|
|
|
|
|
|
|
|
return widget
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class String(QArgument):
|
|
|
|
|
"""String type user interface
|
|
|
|
|
|
|
|
|
|
Presented by `QtWidgets.QLineEdit`.
|
|
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
name (str): The name of argument
|
|
|
|
|
label (str, optional): Display name, convert from `name` if not given
|
|
|
|
|
help (str, optional): Tool tip message of this argument
|
|
|
|
|
default (str, optional): Argument's default value, default None
|
|
|
|
|
placeholder (str, optional): Placeholder message for the widget
|
|
|
|
|
enabled (bool, optional): Whether to enable this widget, default True
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
|
super(String, self).__init__(*args, **kwargs)
|
|
|
|
|
self._previous = None
|
|
|
|
|
|
|
|
|
|
def create(self):
|
|
|
|
|
widget = QtWidgets.QLineEdit()
|
|
|
|
|
widget.editingFinished.connect(self.onEditingFinished)
|
|
|
|
|
widget.returnPressed.connect(widget.editingFinished.emit)
|
|
|
|
|
self._read = lambda: widget.text()
|
|
|
|
|
self._write = lambda value: widget.setText(value)
|
|
|
|
|
|
|
|
|
|
if isinstance(self, Info):
|
|
|
|
|
widget.setReadOnly(True)
|
|
|
|
|
widget.setPlaceholderText(self._data.get("placeholder", ""))
|
|
|
|
|
|
|
|
|
|
if self["default"] is not None:
|
|
|
|
|
self._write(self["default"])
|
|
|
|
|
self._previous = self["default"]
|
|
|
|
|
|
|
|
|
|
return widget
|
|
|
|
|
|
|
|
|
|
def onEditingFinished(self):
|
|
|
|
|
current = self._read()
|
|
|
|
|
|
|
|
|
|
if current != self._previous:
|
|
|
|
|
self.changed.emit()
|
|
|
|
|
self._previous = current
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Info(String):
|
|
|
|
|
"""String type user interface but read-only
|
|
|
|
|
|
|
|
|
|
A subclass of `qargparse.String`, presented by `QtWidgets.QLineEdit`.
|
|
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
name (str): The name of argument
|
|
|
|
|
label (str, optional): Display name, convert from `name` if not given
|
|
|
|
|
help (str, optional): Tool tip message of this argument
|
|
|
|
|
default (str, optional): Argument's default value, default None
|
|
|
|
|
enabled (bool, optional): Whether to enable this widget, default True
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Color(String):
|
|
|
|
|
"""Color type user interface
|
|
|
|
|
|
|
|
|
|
A subclass of `qargparse.String`, not production ready.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Button(QArgument):
|
|
|
|
|
"""Button type user interface
|
|
|
|
|
|
|
|
|
|
Presented by `QtWidgets.QPushButton`.
|
|
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
name (str): The name of argument
|
|
|
|
|
label (str, optional): Display name, convert from `name` if not given
|
|
|
|
|
help (str, optional): Tool tip message of this argument
|
|
|
|
|
default (bool, optional): Argument's default value, default None
|
|
|
|
|
enabled (bool, optional): Whether to enable this widget, default True
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
label = False
|
|
|
|
|
|
|
|
|
|
def create(self):
|
|
|
|
|
widget = QtWidgets.QPushButton(self["label"])
|
|
|
|
|
widget.clicked.connect(self.changed.emit)
|
|
|
|
|
|
|
|
|
|
state = [
|
|
|
|
|
QtCore.Qt.Unchecked,
|
|
|
|
|
QtCore.Qt.Checked,
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
if isinstance(self, Toggle):
|
|
|
|
|
widget.setCheckable(True)
|
|
|
|
|
if hasattr(widget, "isChecked"):
|
|
|
|
|
self._read = lambda: state[int(widget.isChecked())]
|
|
|
|
|
self._write = (
|
|
|
|
|
lambda value: widget.setChecked(value)
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
self._read = lambda: widget.checkState()
|
|
|
|
|
self._write = (
|
|
|
|
|
lambda value: widget.setCheckState(state[int(value)])
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
self._read = lambda: "clicked"
|
|
|
|
|
self._write = lambda value: None
|
|
|
|
|
|
|
|
|
|
if self["default"] is not None:
|
|
|
|
|
self._write(self["default"])
|
|
|
|
|
|
|
|
|
|
return widget
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Toggle(Button):
|
|
|
|
|
"""Checkable `Button` type user interface
|
|
|
|
|
|
|
|
|
|
Presented by `QtWidgets.QPushButton`.
|
|
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
name (str): The name of argument
|
|
|
|
|
label (str, optional): Display name, convert from `name` if not given
|
|
|
|
|
help (str, optional): Tool tip message of this argument
|
|
|
|
|
default (bool, optional): Argument's default value, default None
|
|
|
|
|
enabled (bool, optional): Whether to enable this widget, default True
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class InfoList(QArgument):
|
|
|
|
|
"""String list type user interface
|
|
|
|
|
|
|
|
|
|
Presented by `QtWidgets.QListView`, not production ready.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
def __init__(self, name, **kwargs):
|
|
|
|
|
kwargs["default"] = kwargs.pop("default", ["Empty"])
|
|
|
|
|
super(InfoList, self).__init__(name, **kwargs)
|
|
|
|
|
|
|
|
|
|
def create(self):
|
|
|
|
|
class Model(QtCore.QStringListModel):
|
|
|
|
|
def data(self, index, role):
|
|
|
|
|
return super(Model, self).data(index, role)
|
|
|
|
|
|
|
|
|
|
model = QtCore.QStringListModel(self["default"])
|
|
|
|
|
widget = QtWidgets.QListView()
|
|
|
|
|
widget.setModel(model)
|
|
|
|
|
widget.setEditTriggers(widget.NoEditTriggers)
|
|
|
|
|
|
|
|
|
|
self._read = lambda: model.stringList()
|
|
|
|
|
self._write = lambda value: model.setStringList(value)
|
|
|
|
|
|
|
|
|
|
return widget
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Choice(QArgument):
|
|
|
|
|
"""Argument user interface for selecting one from list
|
|
|
|
|
|
|
|
|
|
Presented by `QtWidgets.QListView`.
|
|
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
name (str): The name of argument
|
|
|
|
|
label (str, optional): Display name, convert from `name` if not given
|
|
|
|
|
help (str, optional): Tool tip message of this argument
|
|
|
|
|
items (list, optional): List of strings for select, default `["Empty"]`
|
|
|
|
|
default (str, optional): Default item in `items`, use first of `items`
|
|
|
|
|
if not given.
|
|
|
|
|
enabled (bool, optional): Whether to enable this widget, default True
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
def __init__(self, name, **kwargs):
|
|
|
|
|
kwargs["items"] = kwargs.get("items", ["Empty"])
|
|
|
|
|
kwargs["default"] = kwargs.pop("default", kwargs["items"][0])
|
|
|
|
|
super(Choice, self).__init__(name, **kwargs)
|
|
|
|
|
|
|
|
|
|
def index(self, value):
|
|
|
|
|
"""Return numerical equivalent to self.read()"""
|
|
|
|
|
return self["items"].index(value)
|
|
|
|
|
|
|
|
|
|
def create(self):
|
|
|
|
|
def on_changed(selected, deselected):
|
|
|
|
|
try:
|
|
|
|
|
selected = selected.indexes()[0]
|
|
|
|
|
except IndexError:
|
|
|
|
|
# At least one item must be selected at all times
|
|
|
|
|
selected = deselected.indexes()[0]
|
|
|
|
|
|
|
|
|
|
value = selected.data(QtCore.Qt.DisplayRole)
|
|
|
|
|
set_current(value)
|
|
|
|
|
self.changed.emit()
|
|
|
|
|
|
|
|
|
|
def set_current(current):
|
|
|
|
|
options = model.stringList()
|
|
|
|
|
|
|
|
|
|
if current == "Empty":
|
|
|
|
|
index = 0
|
|
|
|
|
else:
|
|
|
|
|
for index, member in enumerate(options):
|
|
|
|
|
if member == current:
|
|
|
|
|
break
|
|
|
|
|
else:
|
|
|
|
|
raise ValueError(
|
|
|
|
|
"%s not a member of %s" % (current, options)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
qindex = model.index(index, 0, QtCore.QModelIndex())
|
|
|
|
|
smodel.setCurrentIndex(qindex, smodel.ClearAndSelect)
|
|
|
|
|
self["current"] = options[index]
|
|
|
|
|
|
|
|
|
|
def reset(items, default=None):
|
|
|
|
|
items = items or ["Empty"]
|
|
|
|
|
model.setStringList(items)
|
|
|
|
|
set_current(default or items[0])
|
|
|
|
|
|
|
|
|
|
model = QtCore.QStringListModel()
|
|
|
|
|
widget = QtWidgets.QListView()
|
|
|
|
|
widget.setModel(model)
|
|
|
|
|
widget.setEditTriggers(widget.NoEditTriggers)
|
|
|
|
|
widget.setSelectionMode(widget.SingleSelection)
|
|
|
|
|
smodel = widget.selectionModel()
|
|
|
|
|
smodel.selectionChanged.connect(on_changed)
|
|
|
|
|
|
|
|
|
|
self._read = lambda: self["current"]
|
|
|
|
|
self._write = lambda value: set_current(value)
|
|
|
|
|
self.reset = reset
|
|
|
|
|
|
|
|
|
|
reset(self["items"], self["default"])
|
|
|
|
|
|
|
|
|
|
return widget
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Separator(QArgument):
|
|
|
|
|
"""Visual separator
|
|
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
item1
|
|
|
|
|
item2
|
|
|
|
|
------------
|
|
|
|
|
item3
|
|
|
|
|
item4
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def create(self):
|
|
|
|
|
widget = QtWidgets.QWidget()
|
|
|
|
|
|
|
|
|
|
self._read = lambda: None
|
|
|
|
|
self._write = lambda value: None
|
|
|
|
|
|
|
|
|
|
return widget
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Enum(QArgument):
|
|
|
|
|
"""Argument user interface for selecting one from dropdown list
|
|
|
|
|
|
|
|
|
|
Presented by `QtWidgets.QComboBox`.
|
|
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
name (str): The name of argument
|
|
|
|
|
label (str, optional): Display name, convert from `name` if not given
|
|
|
|
|
help (str, optional): Tool tip message of this argument
|
|
|
|
|
items (list, optional): List of strings for select, default `[]`
|
|
|
|
|
default (int, optional): Index of default item, use first of `items`
|
|
|
|
|
if not given.
|
|
|
|
|
enabled (bool, optional): Whether to enable this widget, default True
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
def __init__(self, name, **kwargs):
|
|
|
|
|
kwargs["default"] = kwargs.pop("default", 0)
|
|
|
|
|
kwargs["items"] = kwargs.get("items", [])
|
|
|
|
|
|
|
|
|
|
assert isinstance(kwargs["items"], (tuple, list)), (
|
|
|
|
|
"items must be list"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
super(Enum, self).__init__(name, **kwargs)
|
|
|
|
|
|
|
|
|
|
def create(self):
|
|
|
|
|
widget = QtWidgets.QComboBox()
|
|
|
|
|
widget.addItems(self["items"])
|
|
|
|
|
widget.currentIndexChanged.connect(
|
|
|
|
|
lambda index: self.changed.emit())
|
|
|
|
|
|
|
|
|
|
self._read = lambda: widget.currentText()
|
|
|
|
|
self._write = lambda value: widget.setCurrentIndex(value)
|
|
|
|
|
|
|
|
|
|
if self["default"] is not None:
|
|
|
|
|
self._write(self["default"])
|
|
|
|
|
|
|
|
|
|
return widget
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
style = """\
|
|
|
|
|
QWidget {
|
|
|
|
|
/* Explicitly specify a size, to account for automatic HDPi */
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*[type="Button"] {
|
|
|
|
|
text-align:left;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*[type="Info"] {
|
|
|
|
|
background: transparent;
|
|
|
|
|
border: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QLabel[type="Separator"] {
|
|
|
|
|
min-height: 20px;
|
|
|
|
|
text-decoration: underline;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QPushButton[type="reset"] {
|
|
|
|
|
max-width: 11px;
|
|
|
|
|
max-height: 11px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def camelToTitle(text):
|
|
|
|
|
"""Convert camelCase `text` to Title Case
|
|
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
>>> camelToTitle("mixedCase")
|
|
|
|
|
"Mixed Case"
|
|
|
|
|
>>> camelToTitle("myName")
|
|
|
|
|
"My Name"
|
|
|
|
|
>>> camelToTitle("you")
|
|
|
|
|
"You"
|
|
|
|
|
>>> camelToTitle("You")
|
|
|
|
|
"You"
|
|
|
|
|
>>> camelToTitle("This is That")
|
|
|
|
|
"This Is That"
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
return re.sub(
|
|
|
|
|
r"((?<=[a-z])[A-Z]|(?<!\A)[A-Z](?=[a-z]))",
|
|
|
|
|
r" \1", text
|
|
|
|
|
).title()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
camel_to_title = camelToTitle
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _demo():
|
|
|
|
|
import sys
|
|
|
|
|
app = QtWidgets.QApplication(sys.argv)
|
|
|
|
|
|
|
|
|
|
parser = QArgumentParser()
|
|
|
|
|
parser.setWindowTitle("Demo")
|
|
|
|
|
parser.setMinimumWidth(300)
|
|
|
|
|
|
|
|
|
|
parser.add_argument("name", default="Marcus", help="Your name")
|
|
|
|
|
parser.add_argument("age", default=33, help="Your age")
|
|
|
|
|
parser.add_argument("height", default=1.87, help="Your height")
|
|
|
|
|
parser.add_argument("alive", default=True, help="Your state")
|
|
|
|
|
parser.add_argument("class", type=Enum, items=[
|
|
|
|
|
"Ranger",
|
|
|
|
|
"Warrior",
|
|
|
|
|
"Sorcerer",
|
|
|
|
|
"Monk",
|
|
|
|
|
], default=2, help="Your class")
|
|
|
|
|
|
|
|
|
|
parser.add_argument("options", type=Separator)
|
|
|
|
|
parser.add_argument("paths", type=InfoList, items=[
|
|
|
|
|
"Value A",
|
|
|
|
|
"Value B",
|
|
|
|
|
"Some other value",
|
|
|
|
|
"And finally, value C",
|
|
|
|
|
])
|
|
|
|
|
parser.add_argument("location", type=Double3)
|
|
|
|
|
|
|
|
|
|
parser.show()
|
|
|
|
|
app.exec_()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
import argparse
|
|
|
|
|
parser = argparse.ArgumentParser()
|
|
|
|
|
parser.add_argument("--version", action="store_true")
|
|
|
|
|
parser.add_argument("--demo", action="store_true")
|
|
|
|
|
|
|
|
|
|
opts = parser.parse_args()
|
|
|
|
|
|
|
|
|
|
if opts.demo:
|
|
|
|
|
_demo()
|
|
|
|
|
|
|
|
|
|
if opts.version:
|
|
|
|
|
print(__version__)
|