Source code for pylablib.core.gui.widgets.button_selector

from .. import QtWidgets, Signal
from ...utils.general import unique_class
from ..utils import clean_layout

[docs] class ButtonSelector(QtWidgets.QWidget): """ Button selector widget. Similar to combo-box, but displays all options in a single row of buttons. """ def __init__(self, parent): super().__init__(parent) self._layout=QtWidgets.QHBoxLayout(self) self._buttons=[] self._index=-1 self._unselected_value=None self._index_values=None self._options=[] self._out_of_range_action="error" self._direct_index_action="ignore" self._event_only_on_changed=False
[docs] def set_out_of_range(self, action="error"): """ Set behavior when out-of-range value is applied. Can be ``"error"`` (raise error), ``"reset"`` (reset to no-value position), ``"reset_start"`` (reset to the first position) or ``"ignore"`` (keep current value). """ if action not in ["error","reset","reset_start","ignore"]: raise ValueError("unrecognized out-of-range action: {}".format(action)) self._out_of_range_action=action
[docs] def set_direct_index_action(self, action="error"): """ Set behavior when index values are specified, but direct indexing is used. Can be ``"ignore"`` (do not allow direct indexing and treat any value as index value), ``"value_default"`` (allow direct indexing, but prioritize index values with the same value), or ``"index_default"`` (allow direct indexing and prioritize it if index value with the same value exists). """ if action not in ["ignore","value_default","index_default"]: raise ValueError("unrecognized direct index action: {}".format(action)) self._direct_index_action=action
[docs] def index_to_value(self, idx): """Turn numerical index into value""" if (self._index_values is None) or (idx<-1) or (idx>=len(self._index_values)): return idx elif idx==-1: return self._unselected_value else: return self._index_values[idx]
[docs] def value_to_index(self, value): """Turn value into a numerical index""" try: if value==self._unselected_value: return -1 if self._index_values is None: return value if isinstance(value,int) and value>=0 and value<len(self._index_values): if self._direct_index_action=="value_default" and value in self._index_values: return self._index_values.index(value) if self._direct_index_action!="ignore": return value return self._index_values.index(value) except ValueError as err: if self._out_of_range_action=="error": raise ValueError("value {} is not among available options {}".format(value,self._index_values)) from err if self._out_of_range_action=="reset_start": return 0 return -1
keep=unique_class("keep")() none=unique_class("none")() def _display_index(self, index): for i,b in enumerate(self._buttons): if i!=index and b.isChecked(): b.setChecked(False) if i==index and not b.isChecked(): b.setChecked(True) def _get_displayed_index(self): for i,b in enumerate(self._buttons): if b.isChecked(): return i return -1 def _get_index_callback(self, i): def set_index(): self._display_index(i) if self._index!=i or not self._event_only_on_changed: self._index=i self.value_changed.emit(self.index_to_value(self._index)) return set_index def _set_buttons(self): self._clear_buttons() for i,l in enumerate(self._options): button=QtWidgets.QPushButton(self) button.setCheckable(True) button.clicked.connect(self._get_index_callback(i)) button.setText(str(l)) button.setObjectName("{}__b{}".format(self.objectName(),i)) self._layout.addWidget(button,stretch=1) self._buttons.append(button) def _clear_buttons(self): self._buttons=[] clean_layout(self._layout)
[docs] def iterbuttons(self): """Iterate over all selecting buttons""" for b in self._buttons: yield b
[docs] def set_buttons_width(self, width): """Set the fixed width of all the buttons""" for b in self._buttons: b.setFixedWidth(width)
[docs] def set_index_values(self, index_values, value=keep, index=None, unselected_value=keep): """ Set a list of values corresponding to combo box indices. Can be either a list of values, whose length must be equal to the number of options, or ``None`` (simply use indices). Note: if the number of combo box options changed (e.g., using ``addItem`` or ``insertItem`` methods), the index values need to be manually updated; otherwise, the errors might arise if the index is larger than the number of values. If `value` is specified, set as the new values. If `index` is specified, use it as the index of a new value; if both `value` and `index` are specified, the `value` takes priority. If `unselected_value` is supplied, it specifies which value corresponds to no combo box value being selected (by default, keep the current value). """ if unselected_value is not self.keep: self._unselected_value=unselected_value if index_values is not None: if len(index_values)!=len(self._options): raise ValueError("number of values {} is different from the number of options {}".format(len(index_values),len(self._options))) if self._unselected_value in index_values: raise ValueError("index values {} contain unselected value ({}), which is reserved to represent no selection".format(index_values,self._unselected_value)) curr_value=self.get_value() self._index_values=index_values if value is not ButtonSelector.keep: self.set_value(value) elif index is not None: self.set_value(self.get_index_values()[index]) else: self._index=-1 self._display_index(-1) try: self.set_value(curr_value) except ValueError: pass
[docs] def get_index_values(self): """Return the list of values corresponding to combo box indices""" return list(self._index_values) if self._index_values is not None else list(range(len(self._options)))
[docs] def get_options(self): """Return the list of labels corresponding to combo box indices""" return list(self._options)
[docs] def get_options_dict(self): """Return the dictionary ``{value: label}`` of the option labels""" return dict(zip(self.get_index_values(),self._options))
[docs] def set_options(self, options, index_values=None, value=keep, index=None, unselected_value=keep): """ Set new set of options. If `index_values` is not ``None``, set these as the new index values; otherwise, index values are reset. If `options` is a dictionary, interpret it as a mapping ``{option: index_value}``. If `value` is specified, set as the new values. If `index` is specified, use it as the index of a new value; if both `value` and `index` are specified, the `value` takes priority. If `unselected_value` is supplied, it specifies which value corresponds to no combo box value being selected (by default, keep the current value). """ if isinstance(options,dict): index_values=list(options) options=[options[v] for v in index_values] else: if index_values is None: index_values=list(range(len(options))) self._options=options self._set_buttons() self.set_index_values(index_values,value=value,index=index,unselected_value=unselected_value)
value_changed=Signal(object) """Signal emitted when value is changed"""
[docs] def get_value(self): """Get current numerical value""" return self.index_to_value(self._index)
[docs] def set_value(self, value, notify_value_change=True): """ Set current value. If ``notify_value_change==True``, emit the `value_changed` signal; otherwise, change value silently. """ if not self._options or value==self._unselected_value: return False index=self.value_to_index(value) if self._out_of_range_action=="ignore" and index==-1: return False index=max(-1,min(index,len(self._index_values)-1)) if self._index!=index: self._index=index self._display_index(index) if notify_value_change: self.value_changed.emit(self.index_to_value(self._index)) return True else: return False
[docs] def repr_value(self, value): """Return representation of `value` as a combo box text""" index=self.value_to_index(value) if index<0: return "N/A" return self._options[index]