Skip to content
Snippets Groups Projects
Commit 2c2135a4 authored by Luke Campagnola's avatar Luke Campagnola
Browse files

Major updates to ComboBox:

- Essentially a graphical interface to dict; all items have text and value
- Assigns previously-selected text after list is cleared and repopulated
- Get, set current value
parent 9de30115
No related branches found
No related tags found
No related merge requests found
from ..Qt import QtGui, QtCore from ..Qt import QtGui, QtCore
from ..SignalProxy import SignalProxy from ..SignalProxy import SignalProxy
from ..ordereddict import OrderedDict
from ..python2_3 import asUnicode
class ComboBox(QtGui.QComboBox): class ComboBox(QtGui.QComboBox):
"""Extends QComboBox to add extra functionality. """Extends QComboBox to add extra functionality.
- updateList() - updates the items in the comboBox while blocking signals, remembers and resets to the previous values if it's still in the list
* Handles dict mappings -- user selects a text key, and the ComboBox indicates
the selected value.
* Requires item strings to be unique
* Remembers selected value if list is cleared and subsequently repopulated
* setItems() replaces the items in the ComboBox and blocks signals if the
value ultimately does not change.
""" """
def __init__(self, parent=None, items=None, default=None): def __init__(self, parent=None, items=None, default=None):
QtGui.QComboBox.__init__(self, parent) QtGui.QComboBox.__init__(self, parent)
self.currentIndexChanged.connect(self.indexChanged)
self._ignoreIndexChange = False
#self.value = default self._chosenText = None
self._items = OrderedDict()
if items is not None: if items is not None:
self.addItems(items) self.setItems(items)
if default is not None: if default is not None:
self.setValue(default) self.setValue(default)
def setValue(self, value): def setValue(self, value):
ind = self.findText(value) """Set the selected item to the first one having the given value."""
text = None
for k,v in self._items.items():
if v == value:
text = k
break
if text is None:
raise ValueError(value)
self.setText(text)
def setText(self, text):
"""Set the selected item to the first one having the given text."""
ind = self.findText(text)
if ind == -1: if ind == -1:
return raise ValueError(text)
#self.value = value #self.value = value
self.setCurrentIndex(ind) self.setCurrentIndex(ind)
def value(self):
"""
If items were given as a list of strings, then return the currently
selected text. If items were given as a dict, then return the value
corresponding to the currently selected key. If the combo list is empty,
return None.
"""
if self.count() == 0:
return None
text = asUnicode(self.currentText())
return self._items[text]
def ignoreIndexChange(func):
# Decorator that prevents updates to self._chosenText
def fn(self, *args, **kwds):
prev = self._ignoreIndexChange
self._ignoreIndexChange = True
try:
ret = func(self, *args, **kwds)
finally:
self._ignoreIndexChange = prev
return ret
return fn
def blockIfUnchanged(func):
# decorator that blocks signal emission during complex operations
# and emits currentIndexChanged only if the value has actually
# changed at the end.
def fn(self, *args, **kwds):
prevVal = self.value()
blocked = self.signalsBlocked()
self.blockSignals(True)
try:
ret = func(self, *args, **kwds)
finally:
self.blockSignals(blocked)
# only emit if the value has changed
if self.value() != prevVal:
self.currentIndexChanged.emit(self.currentIndex())
return ret
return fn
@ignoreIndexChange
@blockIfUnchanged
def setItems(self, items):
"""
*items* may be a list or a dict.
If a dict is given, then the keys are used to populate the combo box
and the values will be used for both value() and setValue().
"""
prevVal = self.value()
def updateList(self, items): self.blockSignals(True)
prevVal = str(self.currentText())
try: try:
self.blockSignals(True)
self.clear() self.clear()
self.addItems(items) self.addItems(items)
self.setValue(prevVal)
finally: finally:
self.blockSignals(False) self.blockSignals(False)
if str(self.currentText()) != prevVal: # only emit if we were not able to re-set the original value
if self.value() != prevVal:
self.currentIndexChanged.emit(self.currentIndex()) self.currentIndexChanged.emit(self.currentIndex())
\ No newline at end of file def items(self):
return self.items.copy()
def updateList(self, items):
# for backward compatibility
return self.setItems(items)
def indexChanged(self, index):
# current index has changed; need to remember new 'chosen text'
if self._ignoreIndexChange:
return
self._chosenText = asUnicode(self.currentText())
def setCurrentIndex(self, index):
QtGui.QComboBox.setCurrentIndex(self, index)
def itemsChanged(self):
# try to set the value to the last one selected, if it is available.
if self._chosenText is not None:
try:
self.setText(self._chosenText)
except ValueError:
pass
@ignoreIndexChange
def insertItem(self, *args):
raise NotImplementedError()
#QtGui.QComboBox.insertItem(self, *args)
#self.itemsChanged()
@ignoreIndexChange
def insertItems(self, *args):
raise NotImplementedError()
#QtGui.QComboBox.insertItems(self, *args)
#self.itemsChanged()
@ignoreIndexChange
def addItem(self, *args, **kwds):
# Need to handle two different function signatures for QComboBox.addItem
try:
if isinstance(args[0], basestring):
text = args[0]
if len(args) == 2:
value = args[1]
else:
value = kwds.get('value', text)
else:
text = args[1]
if len(args) == 3:
value = args[2]
else:
value = kwds.get('value', text)
except IndexError:
raise TypeError("First or second argument of addItem must be a string.")
if text in self._items:
raise Exception('ComboBox already has item named "%s".' % text)
self._items[text] = value
QtGui.QComboBox.addItem(self, *args)
self.itemsChanged()
def setItemValue(self, name, value):
if name not in self._items:
self.addItem(name, value)
else:
self._items[name] = value
@ignoreIndexChange
@blockIfUnchanged
def addItems(self, items):
if isinstance(items, list):
texts = items
items = dict([(x, x) for x in items])
elif isinstance(items, dict):
texts = items.keys()
else:
raise TypeError("items argument must be list or dict.")
for t in texts:
if t in self._items:
raise Exception('ComboBox already has item named "%s".' % t)
for k,v in items.items():
self._items[k] = v
QtGui.QComboBox.addItems(self, texts)
self.itemsChanged()
@ignoreIndexChange
def clear(self):
self._items = OrderedDict()
QtGui.QComboBox.clear(self)
self.itemsChanged()
import pyqtgraph as pg
pg.mkQApp()
def test_combobox():
cb = pg.ComboBox()
items = {'a': 1, 'b': 2, 'c': 3}
cb.setItems(items)
cb.setValue(2)
assert str(cb.currentText()) == 'b'
assert cb.value() == 2
# Clear item list; value should be None
cb.clear()
assert cb.value() == None
# Reset item list; value should be set automatically
cb.setItems(items)
assert cb.value() == 2
# Clear item list; repopulate with same names and new values
items = {'a': 4, 'b': 5, 'c': 6}
cb.clear()
cb.setItems(items)
assert cb.value() == 5
# Set list instead of dict
cb.setItems(items.keys())
assert str(cb.currentText()) == 'b'
cb.setValue('c')
assert cb.value() == str(cb.currentText())
assert cb.value() == 'c'
cb.setItemValue('c', 7)
assert cb.value() == 7
if __name__ == '__main__':
cb = pg.ComboBox()
cb.show()
cb.setItems({'': None, 'a': 1, 'b': 2, 'c': 3})
def fn(ind):
print "New value:", cb.value()
cb.currentIndexChanged.connect(fn)
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment