Source code for ccpn.ui.gui.widgets.RadioButtons

#=========================================================================================
# Licence, Reference and Credits
#=========================================================================================
__copyright__ = "Copyright (C) CCPN project (https://www.ccpn.ac.uk) 2014 - 2022"
__credits__ = ("Ed Brooksbank, Joanna Fox, Victoria A Higman, Luca Mureddu, Eliza Płoskoń",
               "Timothy J Ragan, Brian O Smith, Gary S Thompson & Geerten W Vuister")
__licence__ = ("CCPN licence. See https://ccpn.ac.uk/software/licensing/")
__reference__ = ("Skinner, S.P., Fogh, R.H., Boucher, W., Ragan, T.J., Mureddu, L.G., & Vuister, G.W.",
                 "CcpNmr AnalysisAssign: a flexible platform for integrated NMR analysis",
                 "J.Biomol.Nmr (2016), 66, 111-124, http://doi.org/10.1007/s10858-016-0060-y")
#=========================================================================================
# Last code modification
#=========================================================================================
__modifiedBy__ = "$modifiedBy: Ed Brooksbank $"
__dateModified__ = "$dateModified: 2022-03-23 20:18:59 +0000 (Wed, March 23, 2022) $"
__version__ = "$Revision: 3.1.0 $"
#=========================================================================================
# Created
#=========================================================================================
__author__ = "$Author: Luca Mureddu $"
__date__ = "$Date: 2017-04-07 10:28:41 +0000 (Fri, April 07, 2017) $"
#=========================================================================================
# Start of code
#=========================================================================================

from functools import partial
from PyQt5 import QtWidgets, QtCore

from ccpn.ui.gui.widgets.Base import Base
from ccpn.ui.gui.widgets.RadioButton import RadioButton, EditableRadioButton, RadioButtonWithSubSelection
from ccpn.ui.gui.widgets.Icon import Icon
from ccpn.ui.gui.widgets.Widget import Widget
from ccpn.util.Logging import getLogger


CHECKED = QtCore.Qt.Checked
UNCHECKED = QtCore.Qt.Unchecked


[docs]class RadioButtons(QtWidgets.QWidget, Base): def __init__(self, parent, texts=None, selectedInd=None, exclusive=True, callback=None, direction='h', tipTexts=None, objectNames=None, icons=None, initButtons=True, **kwds): super().__init__(parent) Base._init(self, setLayout=True, **kwds) if texts is None: texts = [] self.texts = texts direction = direction.lower() buttonGroup = self.buttonGroup = QtWidgets.QButtonGroup(self) self.isExclusive = exclusive buttonGroup.setExclusive(self.isExclusive) if not tipTexts: tipTexts = [None] * len(texts) if not objectNames: objectNames = [None] * len(texts) # added functionality for icons # icons is a list of str/tuple # # e.g. icons = ('icons/strip-row', 'strip-column') # icons = ( ('icons/strip-row', (24,24)), # ('strip-column', (24,24)) # ) # where (24,24) is the size of the bounding box containing the icon if not icons: icons = [None] * len(texts) self.radioButtons = [] if initButtons: self.setButtons(texts, selectedInd, direction, tipTexts, objectNames, icons=icons) # for i, text in enumerate(texts): # if 'h' in direction: # grid = (0, i) # else: # grid = (i, 0) # button = RadioButton(self, text, tipText=tipTexts[i], grid=grid, hAlign='l') # self.radioButtons.append(button) # # buttonGroup.addButton(button) # buttonGroup.setId(button, i) # # if selectedInd is not None: # self.radioButtons[selectedInd].setChecked(True) buttonGroup.buttonClicked.connect(self._callback) self.setCallback(callback)
[docs] def setButtons(self, texts=None, selectedInd=None, direction='h', tipTexts=None, objectNames=None, silent=False, icons=None): """Change the buttons in the button group """ # clear the original buttons selected = self.getSelectedText() for btn in self.radioButtons: self.buttonGroup.removeButton(btn) btn.deleteLater() self.radioButtons = [] # rebuild the button list for i, text in enumerate(texts): if 'h' in direction: grid = (0, i) else: grid = (i, 0) button = RadioButton(self, text, tipText=tipTexts[i], grid=grid, hAlign='l') self.radioButtons.append(button) self.buttonGroup.addButton(button) self.buttonGroup.setId(button, i) if objectNames and objectNames[i]: button.setObjectName(objectNames[i]) # set icons if required - these will automatically go to the left of the text if icons and icons[i]: thisIcon = icons[i] if isinstance(thisIcon, str): # icon list item only contains a name button.setIcon(Icon(thisIcon)) elif isinstance(thisIcon, (list, tuple)): # icon item contains a list/tuple if thisIcon and isinstance(thisIcon[0], str): #first item is a string name button.setIcon(Icon(thisIcon[0])) # second value must be tuple of integer, length == 2 if len(thisIcon) == 2: iconSize = thisIcon[1] if isinstance(iconSize, (list, tuple)) and len(iconSize) == 2 and \ all(isinstance(iconVal, int) for iconVal in iconSize): # set the iconSize button.setIconSize(QtCore.QSize(*iconSize)) self.texts = texts if selectedInd is not None: try: self.radioButtons[selectedInd].setChecked(True) except: getLogger().debug(f'setButtons: could not set selectedInd {selectedInd}') elif selected: if selected in self.texts: self.set(selected, silent=silent) else: getLogger().debug(f'setButtons: could not set selected {selected}') elif self.radioButtons: self.radioButtons[0].setChecked(True)
[docs] def getRadioButton(self, text): for rb in self.radioButtons: if rb.text() == text: return rb else: raise ValueError('radioButton %s not found in the list' % text)
[docs] def get(self): texts = [] for i in self.radioButtons: if i.isChecked(): texts.append(i.text()) if self.isExclusive: # could still be undefined return texts[-1] if texts else None else: return texts
[docs] def getIndex(self): ixs = [] for i, rb in enumerate(self.radioButtons): if rb.isChecked(): ixs.append(i) if self.isExclusive: # if exclusive then one-and-only-one MUST be set return ixs[-1] if ixs else 0 else: return ixs
@property def isChecked(self): return self.buttonGroup.checkedButton() is not None
[docs] def set(self, text, silent=False): if text in self.texts: i = self.texts.index(text) self.setIndex(i) if self.callback and not silent: self.callback() else: self.deselectAll()
[docs] def getSelectedText(self): for radioButton in self.radioButtons: if radioButton.isChecked(): name = radioButton.text() if name: return name
[docs] def setIndex(self, i): if self.isExclusive: self.deselectAll() try: self.radioButtons[i].setChecked(True) except: getLogger().debug(f'setIndex: could not set index {i}')
[docs] def deselectAll(self): self.buttonGroup.setExclusive(False) for i in self.radioButtons: i.setChecked(False) self.buttonGroup.setExclusive(self.isExclusive)
[docs] def setCallback(self, callback): self.callback = callback
def _callback(self, button): if self.callback and button: # button = self.buttonGroup.buttons[ind] # FIXME the callback should also pass in the selected value. like pulldown checkbox etc... # e.g. self.callback(self.get()) self.callback() def _getSaveState(self): """ Internal. Called for saving/restoring the widget state. """ return self.get() def _setSavedState(self, value): """ Internal. Called for saving/restoring the widget state. """ return self.set(value)
def _fillMissingValuesInSecondList(aa, bb, value): if not value: value = '' if bb is None: bb = [value] * len(aa) if len(aa) != len(bb): if len(aa) > len(bb): m = len(aa) - len(bb) bb += [value] * m else: raise NameError('Lists are not of same length.') return aa, bb
[docs]class RadioButtonsWithSubCheckBoxes(QtWidgets.QWidget, Base): """ Re-implementation of RadioButtons with the option to add a list of Checkboxes. Direction: only vertical. exclusive only, checkBoxesTexts = # orderedDictionary { RadioButtonText1: { CheckBoxTexts: ['A','B','C'], CheckBoxTipTexts: ['A','B','C'], CheckBoxCheckedText:['B'], CheckBoxCallbacks: [None, None, None] }, ... } """ def __init__(self, parent, texts=None, selectedInd=0, callback=None, tipTexts=None, objectNames=None, ##checkBoxes checkBoxesDictionary = None, # see docs **kwds): super().__init__(parent) Base._init(self, setLayout=True, **kwds) self.texts = texts self.parent = parent texts, tipTexts = _fillMissingValuesInSecondList(texts, tipTexts, value='') self.isExclusive = True self.direction = 'v' self.callback = callback self.radioButtons = [] self.checkBoxesDictionary = checkBoxesDictionary or {} self._setButtons(texts=texts, selectedInd=selectedInd, tipTexts=tipTexts, checkBoxesDictionary=self.checkBoxesDictionary) def _setButtons(self, texts, selectedInd, tipTexts, checkBoxesDictionary): self.radioButtons = [] for i, radioButtonText in enumerate(texts): checkBoxesDict = checkBoxesDictionary.get(radioButtonText) _tiptext = tipTexts[i] _checked = i == selectedInd radioButtonWithSubSelection = RadioButtonWithSubSelection(self, text=radioButtonText, checked=_checked, tipText=_tiptext, checkBoxDictionary=checkBoxesDict, grid=(i+1,0)) radioButtonWithSubSelection.radioButton.clicked.connect( partial(self._buttonClicked, radioButtonWithSubSelection, i)) self.radioButtons.append(radioButtonWithSubSelection)
[docs] def setIndex(self, i): if self.isExclusive: self.deselectAll() self.radioButtons[i].setChecked(True)
def _buttonClicked(self, button, index): if not self.isExclusive: self.radioButtons[index].setChecked(button.isChecked()) self._callback(button) else: self.setIndex(index) self._callback(button) def _callback(self, button): if self.callback and button: self.callback(self.get())
[docs] def deselectAll(self): for i in self.radioButtons: i.setChecked(False)
[docs] def getSelectedText(self): for radioButton in self.radioButtons: if radioButton.isChecked(): name = radioButton.getText() if name: return name
[docs] def get(self): """ :return: A dictionary of selected radioButton text and a list of Selected CheckBoxes text """ dd = {} for radioButton in self.radioButtons: if radioButton.isChecked(): dd[radioButton.getText()] = radioButton.getSelectedCheckBoxes() return dd
[docs] def getRadioButtonByText(self, text): for rb in self.radioButtons: if rb.getText() == text: return rb
[docs]class EditableRadioButtons(Widget, Base): """ Re-implementation of RadioButtons with the option to edit a selection """ def __init__(self, parent, texts=None, backgroundTexts=None, editables=None, selectedInd=None, callback=None, direction='h', tipTexts=None, objectNames=None, icons=None, exclusive=True, **kwds): super().__init__(parent, setLayout=True, **kwds) if texts is None: texts = [] self.texts = texts direction = direction.lower() self.direction = direction self.isExclusive = exclusive texts, editables = _fillMissingValuesInSecondList(texts, editables, value=False) texts, tipTexts = _fillMissingValuesInSecondList(texts, tipTexts, value='') texts, backgroundTexts = _fillMissingValuesInSecondList(texts, backgroundTexts, value='') texts, icons = _fillMissingValuesInSecondList(texts, icons, value=None) self.radioButtons = [] self.callback = callback self._setButtons(texts=texts, editables=editables, selectedInd=selectedInd, direction=direction, tipTexts=tipTexts, backgroundTexts=backgroundTexts, objectNames=objectNames, icons=icons, )
[docs] def setButtons(self, *args, **kwargs): self._setButtons(*args, **kwargs)
def _setButtons(self, texts=None, editables=None, selectedInd=None, direction='h', tipTexts=None, objectNames=None, backgroundTexts=None, silent=False, icons=None): """Change the buttons in the button group """ texts, editables = _fillMissingValuesInSecondList(texts, editables, value=False) texts, tipTexts = _fillMissingValuesInSecondList(texts, tipTexts, value='') texts, backgroundTexts = _fillMissingValuesInSecondList(texts, backgroundTexts, value='') selected = self.getSelectedText() for btn in self.radioButtons: btn.deleteLater() self.radioButtons = [] # rebuild the button list for i, text in enumerate(texts): if 'h' in direction: grid = (0, i) else: grid = (i, 0) button = EditableRadioButton(self, text=text, editable=editables[i], tipText=tipTexts[i], backgroundText=backgroundTexts[i], callbackOneditingFinished=False) #callback=self.callback, button.lineEdit.editingFinished.connect(partial(self._editingFinishedCallback, button, i)) button.radioButton.clicked.connect(partial(self._buttonClicked, button, i)) self.radioButtons.append(button) layout = self.getLayout() layout.addWidget(button, *grid) if objectNames and objectNames[i]: button.radioButton.setObjectName(objectNames[i]) button.radioButton.setObjectName('radioButton_' + objectNames[i]) button.lineEdit.setObjectName('lineEdit_' + objectNames[i]) if icons and icons[i]: thisIcon = icons[i] if isinstance(thisIcon, str): button.setIcon(Icon(thisIcon)) elif isinstance(thisIcon, (list, tuple)): if thisIcon and isinstance(thisIcon[0], str): button.setIcon(Icon(thisIcon[0])) if len(thisIcon) == 2: iconSize = thisIcon[1] if isinstance(iconSize, (list, tuple)) and len(iconSize) == 2 and \ all(isinstance(iconVal, int) for iconVal in iconSize): button.setIconSize(QtCore.QSize(*iconSize)) self.texts = texts if selectedInd is not None: try: self.radioButtons[selectedInd].setChecked(True) except: getLogger().debug(f'setButtons: could not set selectedInd {selectedInd}') elif selected: if selected in self.texts: self.set(selected, silent=silent) else: getLogger().debug(f'setButtons: could not set selected {selected}') elif self.radioButtons: self.radioButtons[0].setChecked(True)
[docs] def getRadioButton(self, text): for rb in self.radioButtons: if rb.text() == text: return rb else: raise ValueError('radioButton %s not found in the list' % text)
[docs] def get(self): texts = [] for i in self.radioButtons: if i.isChecked(): texts.append(i.text()) if self.isExclusive: return texts[-1] if texts else None else: return texts
[docs] def getIndex(self): ixs = [] for i, rb in enumerate(self.radioButtons): if rb.isChecked(): ixs.append(i) if self.isExclusive: # if exclusive then one-and-only-one MUST be set return ixs[-1] if ixs else 0 else: return ixs
[docs] def set(self, text, silent=False): if self.isExclusive: self.deselectAll() if text in self.texts: i = self.texts.index(text) self.setIndex(i) if self.callback and not silent: self.callback()
[docs] def setExclusive(self, value): # raise ValueError('Not implemented yet') self.isExclusive = value
[docs] def getSelectedText(self): for radioButton in self.radioButtons: if radioButton.isChecked(): name = radioButton.text() if name: return name
[docs] def setIndex(self, i): if self.isExclusive: self.deselectAll() try: self.radioButtons[i].setChecked(True) except: getLogger().debug(f'setIndex: could not set index {i}')
def _buttonClicked(self, button, index): if not self.isExclusive: self.radioButtons[index].setChecked(button.isChecked()) self._callback(button) else: self.setIndex(index) self._callback(button)
[docs] def deselectAll(self): for i in self.radioButtons: i.setChecked(False)
[docs] def setCallback(self, callback): self.callback = callback
def _callback(self, button): if self.callback and button: self.callback(self.get()) def _editingFinishedCallback(self, button, index): if button: self._buttonClicked(button, index) def _getSaveState(self): """ Internal. Called for saving/restoring the widget state. """ return self.getIndex() def _setSavedState(self, value): """ Internal. Called for saving/restoring the widget state. """ return self.setIndex(value)
[docs]def main(): from ccpn.ui.gui.widgets.Application import TestApplication from ccpn.ui.gui.popups.Dialog import CcpnDialog def _testCallback(self, *args): print('GET:', self.get()) print('INDEX', self.getIndex()) print('SELECTED:', self.getSelectedText()) def _testCall(*args): print('ddd', args) app = TestApplication() popup = CcpnDialog(windowTitle='Test radioButtons', setLayout=True) buttonGroup = QtWidgets.QButtonGroup(popup) # radioButtons = EditableRadioButtons(parent=popup, texts=['a', ''], tipTexts=['', ''], # editables=[False, True], grid=(0, 0), # callback=_testCall, direction='v') checkBoxesDict = { '_txt1': {'CheckBoxTexts': ['_cb1Txt1', '_cb1Txt2', '_cb1Txt3']}, '_txt2': {'CheckBoxTexts': ['_cb2Txt1', '_cb2Txt2', '_cb3Txt3']}, } rbs = RadioButtonsWithSubCheckBoxes(parent=popup, texts=['_txt1','_txt2'], tipTexts=[''], checkBoxesDictionary=checkBoxesDict, grid=(1, 0)) # radioButtons.setCallback(partial(testCallback, radioButtons)) # for i in range(10): # button = RadioButton(popup, text='TEST', grid=(i, 0), # callback=testCall) # partial(self.assignSelect # buttonGroup.addButton(button) popup.raise_() popup.exec() app.start()
if __name__ == '__main__': main()