from PyQt5 import QtWidgets, QtCore
from ... import format, limit
[docs]class LVTextEdit(QtWidgets.QLineEdit):
"""
Expanded text edit.
Maintains internally stored consistent value (which can be, e.g., accessed from different threads).
"""
def __init__(self, parent, value=None):
QtWidgets.QLineEdit.__init__(self, parent)
self.returnPressed.connect(self._on_enter)
self.editingFinished.connect(self._on_edit_done)
self._value=None
if value is not None:
self.set_value(value)
self.textChanged.connect(self._on_change_text)
def _on_edit_done(self):
self.set_value(self.text())
self.value_entered.emit(self._value)
def _on_enter(self):
self._on_edit_done()
self.clearFocus()
def _on_change_text(self, text):
if not self.isModified():
self.set_value(text)
[docs] def keyPressEvent(self, event):
if event.key()==QtCore.Qt.Key_Escape:
self.clearFocus()
self.show_value()
else:
QtWidgets.QLineEdit.keyPressEvent(self,event)
value_entered=QtCore.pyqtSignal("PyQt_PyObject")
"""Signal emitted when value is entered (regardless of whether it stayed the same)"""
value_changed=QtCore.pyqtSignal("PyQt_PyObject")
"""Signal emitted when value is changed"""
[docs] def get_value(self):
"""Get current numerical value"""
return self._value
[docs] def show_value(self, interrupt_edit=False):
"""
Display currently stored numerical value
If ``interrupt_edit==True`` and the edit is currently being modified by the user, don't update the display.
"""
if (not self.hasFocus()) or interrupt_edit:
self.setText(self._value)
[docs] def set_value(self, value, notify_value_change=True, interrupt_edit=False):
"""
Set current numerical value.
If ``notify_value_change==True``, emit the `value_changed` signal; otherwise, change value silently.
If ``interrupt_edit==True`` and the edit is currently being modified by the user, don't update the display (but still update the internally stored value).
"""
value_changed=False
value=str(value)
if self._value!=value:
self._value=value
if notify_value_change:
self.value_changed.emit(self._value)
value_changed=True
self.show_value(interrupt_edit=interrupt_edit)
return value_changed
[docs]class LVNumEdit(QtWidgets.QLineEdit):
"""
Labview-style numerical edit.
Maintains internally stored consistent value (which can be, e.g., accessed from different threads).
Supports different number representations, metric perfixes (in input or output), keyboard shortcuts (up/down for changing number, escape for cancelling).
"""
def __init__(self, parent, value=None, num_limit=None, num_format=None):
QtWidgets.QLineEdit.__init__(self, parent)
self.num_limit=limit.as_limiter(num_limit) if num_limit is not None else limit.NumberLimit()
self.num_format=format.as_formatter(num_format) if num_format is not None else format.FloatFormatter()
self.returnPressed.connect(self._on_enter)
self.editingFinished.connect(self._on_edit_done)
self._value=None
if value is not None:
self.set_value(value)
else:
self.set_value(0)
if self._value is None:
raise ValueError("can't assign a safe default value")
self.textChanged.connect(self._on_change_text)
def _on_edit_done(self):
self.set_value(self._read_input())
self.value_entered.emit(self._value)
def _on_enter(self):
self._on_edit_done()
self.clearFocus()
def _on_change_text(self, text):
if not self.isModified():
try:
value=format.str_to_float(str(self.text()))
self.set_value(value)
except ValueError:
pass
[docs] def keyPressEvent(self, event):
k=event.key()
if k==QtCore.Qt.Key_Escape:
self.show_value(interrupt_edit=True)
self.clearFocus()
elif k in [QtCore.Qt.Key_Up,QtCore.Qt.Key_Down]:
try:
str_value=str(self.text())
num_value=format.str_to_float(str_value)
cursor_order=self.get_cursor_order()
if cursor_order!=None:
step=10**(cursor_order)
if k==QtCore.Qt.Key_Up:
self.set_value(num_value+step,interrupt_edit=True)
else:
self.set_value(num_value-step,interrupt_edit=True)
except ValueError:
self.show_value(interrupt_edit=True)
else:
QtWidgets.QLineEdit.keyPressEvent(self,event)
def _read_input(self):
try:
return format.str_to_float(str(self.text()))
except ValueError:
return self._value
[docs] def change_limiter(self, limiter, new_value=None):
"""Change current numerical limiter"""
self.num_limit=limit.as_limiter(limiter)
if new_value is None:
new_value=self._value
new_value=self._coerce_value(new_value,coerce_on_limit=True)
if new_value!=self._value:
self.set_value(new_value)
[docs] def set_number_limit(self, lower_limit=None, upper_limit=None, action="ignore", value_type=None):
"""
Set number limit.
`lower_limit` and `upper_limit` set the value limits (``None`` means no limit).
`action` specifies the action on out-of-limit: either ``"ignore"`` (return to the previously stored value), or ``"coerce"`` (coerce to the closest limit)
`value_type` can be either ``"float"`` (any floating point number is accepted), or ``"int"`` (round to the nearest integer).
"""
limiter=limit.NumberLimit(lower_limit=lower_limit,upper_limit=upper_limit,action=action,value_type=value_type)
self.change_limiter(limiter)
[docs] def get_cursor_order(self):
"""Get a decimal order of the text cursor"""
str_value=str(self.text())
cursor_pos=self.cursorPosition()
return format.pos_to_order(str_value,cursor_pos)
[docs] def set_cursor_order(self, order):
"""Move text cursor to a given decimal order"""
if order is not None:
new_cursor_pos=format.order_to_pos(str(self.text()),order)
self.setCursorPosition(new_cursor_pos)
def _coerce_value(self, value, coerce_on_limit=False):
for _ in range(10):
str_value=self.num_format(value)
num_value=format.str_to_float(str_value)
try:
new_value=self.num_limit(num_value)
except limit.LimitError:
if coerce_on_limit and isinstance(self.num_limit,limit.NumberLimit):
if self.num_limit.range[0] is not None and num_value<self.num_limit.range[0]:
new_value=self.num_limit.range[0]
else:
new_value=self.num_limit.range[1]
else:
raise
if new_value==value:
return new_value
value=new_value
raise ValueError("couldn't coerce the new value")
[docs] def repr_value(self, value):
"""Return representation of `value` according to the current numerical format"""
return self.num_format(value)
value_entered=QtCore.pyqtSignal("PyQt_PyObject")
"""Signal emitted when value is entered (regardless of whether it stayed the same)"""
value_changed=QtCore.pyqtSignal("PyQt_PyObject")
"""Signal emitted when value is changed"""
[docs] def get_value(self):
"""Get current numerical value"""
return self._value
[docs] def show_value(self, interrupt_edit=False, preserve_cursor_order=True):
"""
Display currently stored numerical value
If ``interrupt_edit==True`` and the edit is currently being modified by the user, don't update the display.
If ``preserve_cursor_order==True`` and the display value is being edited, keep the decimal order of the cursor position after change.
"""
if (not self.hasFocus()) or interrupt_edit:
if preserve_cursor_order and self.hasFocus():
cursor_order=self.get_cursor_order()
self.setText(self.num_format(self._value))
if cursor_order is not None:
self.set_cursor_order(cursor_order)
else:
self.setText(self.num_format(self._value))
[docs] def set_value(self, value, notify_value_change=True, interrupt_edit=False, preserve_cursor_order=True):
"""
Set and display current numerical value.
If ``notify_value_change==True``, emit the `value_changed` signal; otherwise, change value silently.
If ``interrupt_edit==True`` and the edit is currently being modified by the user, don't update the display (but still update the internally stored value).
If ``preserve_cursor_order==True`` and the display value is being edited, keep the decimal order of the cursor position after change.
"""
value_changed=False
try:
value=self._coerce_value(value)
if self._value!=value:
self._value=value
if notify_value_change:
self.value_changed.emit(self._value)
value_changed=True
except limit.LimitError:
pass
self.show_value(interrupt_edit=interrupt_edit,preserve_cursor_order=preserve_cursor_order)
return value_changed