class LogicRadioButtonGroup(QGroupBox): chosen = Signal(str) def __init__(self, direction, default, title='', **kwarg): super().__init__(title) if direction == 'vertical': buttonLayout = QVBoxLayout() else: # direction == 'horizontal' buttonLayout = QHBoxLayout() self.buttonGroup = QButtonGroup() self.setLayout(buttonLayout) for short_name, text in kwarg.items(): button = QRadioButton(text) button.clicked.connect(self.selected) if short_name == default: button.setChecked(True) self.buttonGroup.addButton(button) buttonLayout.addWidget(button) def setToDefault(self, default_option): for option in self.buttonGroup.buttons(): if option.text() == default_option: option.setChecked(True) else: option.setChecked(False) def value(self): checked = self.buttonGroup.checkedButton() return checked.text() def selected(self): self.chosen.emit(self.buttonGroup.checkedButton().text())
def __init__(self, blankOptionSelection=None, wildcard=None): super().__init__() self.blankOptionSelection = blankOptionSelection self.wildcard = wildcard self.setWindowTitle('Search Options') layout = QVBoxLayout() blankOptionsLabel = QLabel( 'How should blank spaces be interpreted in your search?') layout.addWidget(blankOptionsLabel) blankOptionsLayout = QVBoxLayout() self.blankOptionsGroup = QButtonGroup() asBlankOption = QRadioButton( 'Interpret as literal blanks, and only match blank slots') blankOptionsLayout.addWidget(asBlankOption) self.blankOptionsGroup.addButton(asBlankOption) self.blankOptionsGroup.setId(asBlankOption, 0) if self.blankOptionSelection == 'literal': asBlankOption.setChecked(True) asWildcardOption = QRadioButton( 'Interpret as wildcards, and match anything') blankOptionsLayout.addWidget(asWildcardOption) self.blankOptionsGroup.addButton(asWildcardOption) self.blankOptionsGroup.setId(asWildcardOption, 1) if self.blankOptionSelection == 'wildcard' or self.blankOptionSelection is None: asWildcardOption.setChecked(True) miniLayout = QHBoxLayout() asBlankWithWildcard = QRadioButton( 'Interpret as literal blanks, and use this character for wildcards: ' ) self.wildcardLineEdit = QLineEdit() self.wildcardLineEdit.setMaxLength(1) self.wildcardLineEdit.setMaximumWidth(30) self.blankOptionsGroup.addButton(asBlankWithWildcard) self.blankOptionsGroup.setId(asBlankWithWildcard, 2) if self.blankOptionSelection == 'both': asBlankWithWildcard.setChecked(True) self.wildcardLineEdit.setText(self.wildcard) miniLayout.addWidget(asBlankWithWildcard) miniLayout.addWidget(self.wildcardLineEdit) blankOptionsLayout.addLayout(miniLayout) layout.addLayout(blankOptionsLayout) buttonLayout = QHBoxLayout() ok = QPushButton('OK') ok.clicked.connect(self.accept) buttonLayout.addWidget(ok) cancel = QPushButton('Cancel') cancel.clicked.connect(self.reject) buttonLayout.addWidget(cancel) layout.addLayout(buttonLayout) self.setLayout(layout)
def __init__(self, direction, default, title='', **kwarg): super().__init__(title) if direction == 'vertical': buttonLayout = QVBoxLayout() else: # direction == 'horizontal' buttonLayout = QHBoxLayout() self.buttonGroup = QButtonGroup() self.setLayout(buttonLayout) for short_name, text in kwarg.items(): button = QRadioButton(text) button.clicked.connect(self.selected) if short_name == default: button.setChecked(True) self.buttonGroup.addButton(button) buttonLayout.addWidget(button)
def __init__(self, groupName): super().__init__(groupName) self.buttonGroup = QButtonGroup() self.extended = QRadioButton('Extended') self.notExtended = QRadioButton('Not extended') self.either = QRadioButton('Either') self.either.setChecked(True) self.buttonGroup.addButton(self.extended) self.buttonGroup.addButton(self.notExtended) self.buttonGroup.addButton(self.either) buttonLayout = QVBoxLayout() self.setLayout(buttonLayout) buttonLayout.addWidget(self.extended) buttonLayout.addWidget(self.notExtended) buttonLayout.addWidget(self.either)
class FingerOptionGroup(QGroupBox): fingerOptionDict_with_i = { 'thumb': {'Extended': r'(?P<thumb>.(?P<thumb_opposition>[LU]).(?<thumb_mcp>[HEei]).).+.\u2205/.+.....', 'Not extended': r'(?P<thumb>.(?P<thumb_opposition_1>[^LU]).(?<thumb_mcp_1>[HEei]).|_(?P<thumb_opposition_2>[LU]).(?<thumb_mcp_2>[^HEei]).|_(?P<thumb_opposition_3>[^LU]).(?<thumb_mcp_3>[^HEei]).).+.\u2205/.+.....', 'Either': r'(?P<thumb>.(?P<thumb_opposition>.).(?<thumb_mcp>.).).+.\u2205/.+.....'}, 'index': {'Extended': r'(?P<index>1(?P<index_mcp>[HEei])..)', 'Not extended': r'(?P<index>1(?P<index_mcp>[^HEei])..)', 'Either': r'(?P<index>1(?P<index_mcp>.)..)'}, 'middle': {'Extended': r'(?P<middle>.+2(?P<middle_mcp>[HEei])..)', 'Not extended': r'(?P<middle>.+2(?P<middle_mcp>[^HEei])..)', 'Either': r'(?P<middle>.+2(?P<middle_mcp>.)..)'}, 'ring': {'Extended': r'(?P<ring>.+3(?P<ring_mcp>[HEei])..)', 'Not extended': r'(?P<ring>.+3(?P<ring_mcp>[^HEei])..)', 'Either': r'(?P<ring>.+3(?P<ring_mcp>.)..)'}, 'pinky': {'Extended': r'(?P<pinky>.+4(?P<pinky_mcp>[HEei])..)', 'Not extended': r'(?P<pinky>.+4(?P<pinky_mcp>[^HEei])..)', 'Either': r'(?P<pinky>.+4(?P<pinky_mcp>.)..)'} } fingerOptionDict_without_i = { 'thumb': {'Extended': r'(?P<thumb>.(?P<thumb_opposition>[LU]).(?<thumb_mcp>[HEe]).).+.\u2205/.+.....', 'Not extended': r'(?P<thumb>.(?P<thumb_opposition_1>[^LU]).(?<thumb_mcp_1>[HEe]).|_(?P<thumb_opposition_2>[LU]).(?<thumb_mcp_2>[^HEe]).|_(?P<thumb_opposition_3>[^LU]).(?<thumb_mcp_3>[^HEe]).).+.\u2205/.+.....', 'Either': r'(?P<thumb>.(?P<thumb_opposition>.).(?<thumb_mcp>.).).+.\u2205/.+.....'}, 'index': {'Extended': r'(?P<index>1(?P<index_mcp>[HEe])..)', 'Not extended': r'(?P<index>1(?P<index_mcp>[^HEe])..)', 'Either': r'(?P<index>1(?P<index_mcp>.)..)'}, 'middle': {'Extended': r'(?P<middle>.+2(?P<middle_mcp>[HEe])..)', 'Not extended': r'(?P<middle>.+2(?P<middle_mcp>[^HEe])..)', 'Either': r'(?P<middle>.+2(?P<middle_mcp>.)..)'}, 'ring': {'Extended': r'(?P<ring>.+3(?P<ring_mcp>[HEe])..)', 'Not extended': r'(?P<ring>.+3(?P<ring_mcp>[^HEe])..)', 'Either': r'(?P<ring>.+3(?P<ring_mcp>.)..)'}, 'pinky': {'Extended': r'(?P<pinky>.+4(?P<pinky_mcp>[HEe])..)', 'Not extended': r'(?P<pinky>.+4(?P<pinky_mcp>[^HEe])..)', 'Either': r'(?P<pinky>.+4(?P<pinky_mcp>.)..)'} } def __init__(self, groupName): super().__init__(groupName) self.buttonGroup = QButtonGroup() self.extended = QRadioButton('Extended') self.notExtended = QRadioButton('Not extended') self.either = QRadioButton('Either') self.either.setChecked(True) self.buttonGroup.addButton(self.extended) self.buttonGroup.addButton(self.notExtended) self.buttonGroup.addButton(self.either) buttonLayout = QVBoxLayout() self.setLayout(buttonLayout) buttonLayout.addWidget(self.extended) buttonLayout.addWidget(self.notExtended) buttonLayout.addWidget(self.either) def setToDefault(self): self.extended.setChecked(False) self.notExtended.setChecked(False) self.either.setChecked(True) def getExtendedRegExp(self, includeI): finger = self.title().lower() if includeI: regExp = self.fingerOptionDict_with_i[finger]['Extended'] else: regExp = self.fingerOptionDict_without_i[finger]['Extended'] return regExp def getEitherRegExp(self, includeI): finger = self.title().lower() if includeI: regExp = self.fingerOptionDict_with_i[finger]['Either'] else: regExp = self.fingerOptionDict_without_i[finger]['Either'] return regExp def getNotExtendedRegExp(self, includeI): finger = self.title().lower() if includeI: regExp = self.fingerOptionDict_with_i[finger]['Not extended'] else: regExp = self.fingerOptionDict_without_i[finger]['Not extended'] return regExp def generateRegExp(self, includeI): finger = self.title() chosen = self.buttonGroup.checkedButton().text() if includeI: if finger == 'Thumb': regExp = self.fingerOptionDict_with_i['thumb'][chosen] elif finger == 'Index': regExp = self.fingerOptionDict_with_i['index'][chosen] elif finger == 'Middle': regExp = self.fingerOptionDict_with_i['middle'][chosen] elif finger == 'Ring': regExp = self.fingerOptionDict_with_i['ring'][chosen] else: regExp = self.fingerOptionDict_with_i['pinky'][chosen] else: if finger == 'Thumb': regExp = self.fingerOptionDict_without_i['thumb'][chosen] elif finger == 'Index': regExp = self.fingerOptionDict_without_i['index'][chosen] elif finger == 'Middle': regExp = self.fingerOptionDict_without_i['middle'][chosen] elif finger == 'Ring': regExp = self.fingerOptionDict_without_i['ring'][chosen] else: regExp = self.fingerOptionDict_without_i['pinky'][chosen] return regExp def value(self): checked = self.buttonGroup.checkedButton() return checked.text()
class FunctionalLoadDialog(QDialog): def __init__(self, corpus): super().__init__() self.corpus = corpus self.results = list() self.setWindowTitle('Functional Load') layout = QVBoxLayout() #Set up top row of radio button options contrastBox = QGroupBox('Contrast') contrastLayout = QHBoxLayout() self.contrastGroup = QButtonGroup() flexionOption = QRadioButton('Degrees of flexion') flexionOption.click() ductionOption = QRadioButton('Degree of duction') oppositionOption = QRadioButton('Thumb opposition') contactOption = QRadioButton('Thumb/finger contact') customOption = QRadioButton('Custom options') self.contrastGroup.addButton(flexionOption, id=0) self.contrastGroup.addButton(ductionOption, id=1) self.contrastGroup.addButton(oppositionOption, id=2) self.contrastGroup.addButton(contactOption, id=3) self.contrastGroup.addButton(customOption, id=4) contrastLayout.addWidget(flexionOption) contrastLayout.addWidget(ductionOption) contrastLayout.addWidget(oppositionOption) contrastLayout.addWidget(contactOption) contrastLayout.addWidget(customOption) contrastBox.setLayout(contrastLayout) #set up stacked widgets self.middleWidget = QStackedWidget() #Collapse degress of flexion flexionWidget = QWidget() flexionLayout = QHBoxLayout() self.flexionFingerSelection = QComboBox() self.flexionFingerSelection.addItems( ['Thumb', 'Index', 'Middle', 'Pinky', 'Ring', 'All']) self.flexionJointSelection = QComboBox() self.flexionJointSelection.addItems( ['Proximal', 'Medial', 'Distal', 'All']) #note: Thumb+Proximal not possible, and there's an alert window that will pop up if this combination is chosen flexionLayout.addWidget(self.flexionFingerSelection) flexionLayout.addWidget(self.flexionJointSelection) flexionWidget.setLayout(flexionLayout) #Collapse degrees of duction ductionWidget = QWidget() ductionLayout = QHBoxLayout() self.ductionFingerSelection = QComboBox() self.ductionFingerSelection.addItems([ 'Thumb/Finger', 'Index/Middle', 'Middle/Ring', 'Ring/Pinky', 'All' ]) ductionLayout.addWidget(self.ductionFingerSelection) ductionWidget.setLayout(ductionLayout) #Collapse thumb opposition oppositionWidget = QWidget() oppositionLayout = QHBoxLayout() oppositionWidget.setLayout(oppositionLayout) #Collapse thumb/finger contact contactWidget = QWidget() contactLayout = QHBoxLayout() contactWidget.setLayout(contactLayout) #Collapse custom slots customWidget = QWidget() customLayout = QHBoxLayout() customLayout.addWidget(QLabel('Merge this symbol: ')) self.customSymbo1A = QComboBox() self.customSymbo1A.addItem('') self.customSymbo1A.addItems(STANDARD_SYMBOLS) self.customSymbo1A.setEditable(True) customLayout.addWidget(self.customSymbo1A) customLayout.addWidget(QLabel('with this symbol: ')) self.customSymbolB = QComboBox() self.customSymbolB.addItem('') self.customSymbolB.addItems(STANDARD_SYMBOLS) self.customSymbolB.setEditable(True) customLayout.addWidget(self.customSymbolB) customLayout.addWidget(QLabel('in these slots: ')) self.customSlots = QLineEdit() customLayout.addWidget(self.customSlots) customLayout.addWidget( QLabel( '(separate numbers with commas, leave blank to merge symbols everywhere)' )) customWidget.setLayout(customLayout) #Build up middle widget self.middleWidget.addWidget(flexionWidget) self.middleWidget.addWidget(ductionWidget) self.middleWidget.addWidget(oppositionWidget) self.middleWidget.addWidget(contactWidget) self.middleWidget.addWidget(customWidget) #Connect slots and signals flexionOption.clicked.connect(self.changeMiddleWidget) ductionOption.clicked.connect(self.changeMiddleWidget) oppositionOption.clicked.connect(self.changeMiddleWidget) contactOption.clicked.connect(self.changeMiddleWidget) customOption.clicked.connect(self.changeMiddleWidget) #Bottom buttons (OK/Cancel) buttonLayout = QHBoxLayout() ok = QPushButton('OK') ok.clicked.connect(self.accept) cancel = QPushButton('Cancel') cancel.clicked.connect(self.reject) buttonLayout.addWidget(ok) buttonLayout.addWidget(cancel) layout.addWidget(contrastBox) layout.addWidget(self.middleWidget) layout.addLayout(buttonLayout) self.setLayout(layout) def changeMiddleWidget(self, e): self.middleWidget.setCurrentIndex(self.contrastGroup.id(self.sender())) def accept(self): index = self.middleWidget.currentIndex() if index == 0: if (self.flexionFingerSelection.currentText() == 'Thumb' and self.flexionJointSelection.currentText() == 'Proximal'): alert = QMessageBox() alert.setWindowTitle('Incompatible Options') alert.setText( 'Thumbs cannot be selected for proximal joint. Choose either "Medial" or "Distal"' ) alert.exec_() return self.calcByFlexion() elif index == 1: self.calcByDuction() elif index == 4: slots = self.customSlots.text() alert = QMessageBox() alert.setWindowTitle('Invalid slot numbers') alert.setText('Slot numbers must be between 1 and 34 (inclusive)') try: slots = [int(x.strip()) for x in slots.split(',')] except ValueError: alert.exec_() return if any(n > 34 or n < 1 for n in slots): alert.exec_() return self.calcCustom(slots) super().accept() def calculateEntropy(self, corpus=None): corpus_size = len(corpus) if corpus is not None else len(self.corpus) return corpus_size, sum([ 1 / corpus_size * log(1 / corpus_size) for n in range(corpus_size) ]) * -1 def calcByDuction(self): corpus_size, starting_h = self.calculateEntropy() duction = self.ductionFingerSelection.currentText() if duction == 'Thumb/Finger': slot = 3 elif duction == 'Index/Middle': slot = 19 elif duction == 'Middle/Ring': slot = 24 elif duction == 'Ring/Pinky': slot = 29 elif duction == 'All': slot = -1 if slot > 1: print('{} DUCTION'.format(duction.upper())) print('Starting size = {}\nStarting entropy = {}'.format( corpus_size, starting_h)) new_corpus = defaultdict(int) for word in self.corpus: ch = word.config1hand1.copy() ch[slot] = 'X' new_corpus[''.join(ch)] += 1 new_corpus_size, ending_h = self.calculateEntropy(new_corpus) print('After merging size = {}\nAfter merging entropy = {}'.format( len(new_corpus), ending_h)) print('Change in entropy = {}\n'.format(starting_h - ending_h)) else: print('{} DUCTION'.format(duction.upper())) print('Starting size = {}\nStarting entropy = {}'.format( corpus_size, starting_h)) new_corpus = defaultdict(int) for word in self.corpus: ch = word.config1hand1.copy() ch[2] = 'X' ch[19] = 'X' ch[24] = 'X' ch[29] = 'X' new_corpus[''.join(ch)] += 1 new_corpus_size, ending_h = self.calculateEntropy(new_corpus) print('After merging size = {}\nAfter merging entropy = {}'.format( len(new_corpus), ending_h)) print('Change in entropy = {}\n'.format(starting_h - ending_h)) result = [ corpus_size, starting_h, new_corpus_size, ending_h, starting_h - ending_h ] self.results = [result] def calcCustom(self, slots): corpus_size, starting_h = self.calculateEntropy() slots = [n - 1 for n in slots] # minus 1 because slot numbers starts at 1 but list indices start at 0 symbolA = self.customSymbo1A.currentText() symbolB = self.customSymbolB.currentText() print('Merging {} and {}'.format(symbolA, symbolB)) print('Starting size = {}\nStarting entropy = {}'.format( corpus_size, starting_h)) new_corpus = defaultdict(int) for word in self.corpus: ch = word.config1hand1.copy() for slot in slots: if ch[slot] in [symbolA, symbolB]: ch[slot] = 'X' new_corpus[''.join(ch)] += 1 new_corpus_size, ending_h = self.calculateEntropy(new_corpus) print('After merging size = {}\nAfter merging entropy = {}'.format( len(new_corpus), ending_h)) print('Change in entropy = {}\n'.format(starting_h - ending_h)) result = [ corpus_size, starting_h, new_corpus_size, ending_h, starting_h - ending_h ] self.results = [result] def calcByFlexion(self): corpus_size, starting_h = self.calculateEntropy() finger = self.flexionFingerSelection.currentText() joint = self.flexionJointSelection.currentText() jointDict = {'Proximal': 0, 'Medial': 1, 'Distal': 2, 'All': -1} fingerDict = { 'Thumb': 2, 'Index': 16, 'Middle': 21, 'Ring': 26, 'Pinky': 31, 'All': -1 } offset = jointDict[joint] slot = fingerDict[finger] slot += offset if slot > 0: #user chose particular fingers print('{} {} JOINTS'.format(finger.upper(), joint.upper())) print('Starting size = {}\nStarting entropy = {}'.format( corpus_size, starting_h)) new_corpus = defaultdict(int) for word in self.corpus: ch = word.config1hand1.copy() ch[slot] = 'X' new_corpus[''.join(ch)] += 1 new_corpus_size, ending_h = self.calculateEntropy(new_corpus) print('After merging size = {}\nAfter merging entropy = {}'.format( len(new_corpus), ending_h)) print('Change in entropy = {}\n'.format(starting_h - ending_h)) self.results = [[ corpus_size, starting_h, new_corpus_size, ending_h, starting_h - ending_h ]] else: #user chose an "All" option if joint == 'All' and finger != 'All': #all the joints on a particular finger slot = fingerDict[finger] print('ALL {} JOINTS'.format(finger.upper())) print('Starting size = {}\nStarting entropy = {}'.format( corpus_size, starting_h)) new_corpus = defaultdict(int) for word in self.corpus: ch = word.config1hand1.copy() ch[slot] = 'X' #proximal ch[slot + 1] = 'X' #medial if not finger == 'Thumb': ch[slot + 2] = 'X' #distal new_corpus[''.join(ch)] += 1 new_corpus_size, ending_h = self.calculateEntropy(new_corpus) print('After merging size = {}\nAfter merging entropy = {}'. format(len(new_corpus), ending_h)) print('Change in entropy = {}\n'.format(starting_h - ending_h)) self.results = [[ corpus_size, starting_h, new_corpus_size, ending_h, starting_h - ending_h ]] elif finger == 'All' and joint != 'All': #a particular joint on all the fingers if joint == 'Proximal': slot = 17 elif joint == 'Medial': slot = 18 elif joint == 'Distal': slot = 19 print('ALL {} JOINTS'.format(joint.upper())) print('Starting size = {}\nStarting entropy = {}'.format( corpus_size, starting_h)) # for finger,slot in [('INDEX', 17), ('MIDDLE',22), ('RING',27), ('PINKY',32)]: new_corpus = defaultdict(int) for word in self.corpus: ch = word.config1hand1.copy() ch[slot] = 'X' ch[slot + 5] = 'X' ch[slot + 10] = 'X' ch[slot + 15] = 'X' new_corpus[''.join(ch)] += 1 new_corpus_size, ending_h = self.calculateEntropy( new_corpus) print('After merging size = {}\nAfter merging entropy = {}'. format(len(new_corpus), ending_h)) print('Change in entropy = {}\n'.format(starting_h - ending_h)) self.results = [[ corpus_size, starting_h, new_corpus_size, ending_h, starting_h - ending_h ]] elif finger == 'All' and joint == 'All': results = list() for finger, slot in [('THUMB', 2), ('INDEX', 17), ('MIDDLE', 22), ('RING', 27), ('PINKY', 31)]: print('ALL {} JOINTS'.format(joint.upper())) print('Starting size = {}\nStarting entropy = {}'.format( corpus_size, starting_h)) new_corpus = defaultdict(int) for word in self.corpus: ch = word.config1hand1.copy() ch[slot] = 'X' ch[slot + 1] = 'X' if not finger == 'Thumb': ch[slot + 2] = 'X' new_corpus[''.join(ch)] += 1 new_corpus_size, ending_h = self.calculateEntropy( new_corpus) print( 'After merging size = {}\nAfter merging entropy = {}'. format(len(new_corpus), ending_h)) print('Change in entropy = {}\n'.format(starting_h - ending_h)) results.append([ corpus_size, starting_h, new_corpus_size, ending_h, starting_h - ending_h ]) self.results = results
def __init__(self, corpus): super().__init__() self.corpus = corpus self.results = list() self.setWindowTitle('Functional Load') layout = QVBoxLayout() #Set up top row of radio button options contrastBox = QGroupBox('Contrast') contrastLayout = QHBoxLayout() self.contrastGroup = QButtonGroup() flexionOption = QRadioButton('Degrees of flexion') flexionOption.click() ductionOption = QRadioButton('Degree of duction') oppositionOption = QRadioButton('Thumb opposition') contactOption = QRadioButton('Thumb/finger contact') customOption = QRadioButton('Custom options') self.contrastGroup.addButton(flexionOption, id=0) self.contrastGroup.addButton(ductionOption, id=1) self.contrastGroup.addButton(oppositionOption, id=2) self.contrastGroup.addButton(contactOption, id=3) self.contrastGroup.addButton(customOption, id=4) contrastLayout.addWidget(flexionOption) contrastLayout.addWidget(ductionOption) contrastLayout.addWidget(oppositionOption) contrastLayout.addWidget(contactOption) contrastLayout.addWidget(customOption) contrastBox.setLayout(contrastLayout) #set up stacked widgets self.middleWidget = QStackedWidget() #Collapse degress of flexion flexionWidget = QWidget() flexionLayout = QHBoxLayout() self.flexionFingerSelection = QComboBox() self.flexionFingerSelection.addItems( ['Thumb', 'Index', 'Middle', 'Pinky', 'Ring', 'All']) self.flexionJointSelection = QComboBox() self.flexionJointSelection.addItems( ['Proximal', 'Medial', 'Distal', 'All']) #note: Thumb+Proximal not possible, and there's an alert window that will pop up if this combination is chosen flexionLayout.addWidget(self.flexionFingerSelection) flexionLayout.addWidget(self.flexionJointSelection) flexionWidget.setLayout(flexionLayout) #Collapse degrees of duction ductionWidget = QWidget() ductionLayout = QHBoxLayout() self.ductionFingerSelection = QComboBox() self.ductionFingerSelection.addItems([ 'Thumb/Finger', 'Index/Middle', 'Middle/Ring', 'Ring/Pinky', 'All' ]) ductionLayout.addWidget(self.ductionFingerSelection) ductionWidget.setLayout(ductionLayout) #Collapse thumb opposition oppositionWidget = QWidget() oppositionLayout = QHBoxLayout() oppositionWidget.setLayout(oppositionLayout) #Collapse thumb/finger contact contactWidget = QWidget() contactLayout = QHBoxLayout() contactWidget.setLayout(contactLayout) #Collapse custom slots customWidget = QWidget() customLayout = QHBoxLayout() customLayout.addWidget(QLabel('Merge this symbol: ')) self.customSymbo1A = QComboBox() self.customSymbo1A.addItem('') self.customSymbo1A.addItems(STANDARD_SYMBOLS) self.customSymbo1A.setEditable(True) customLayout.addWidget(self.customSymbo1A) customLayout.addWidget(QLabel('with this symbol: ')) self.customSymbolB = QComboBox() self.customSymbolB.addItem('') self.customSymbolB.addItems(STANDARD_SYMBOLS) self.customSymbolB.setEditable(True) customLayout.addWidget(self.customSymbolB) customLayout.addWidget(QLabel('in these slots: ')) self.customSlots = QLineEdit() customLayout.addWidget(self.customSlots) customLayout.addWidget( QLabel( '(separate numbers with commas, leave blank to merge symbols everywhere)' )) customWidget.setLayout(customLayout) #Build up middle widget self.middleWidget.addWidget(flexionWidget) self.middleWidget.addWidget(ductionWidget) self.middleWidget.addWidget(oppositionWidget) self.middleWidget.addWidget(contactWidget) self.middleWidget.addWidget(customWidget) #Connect slots and signals flexionOption.clicked.connect(self.changeMiddleWidget) ductionOption.clicked.connect(self.changeMiddleWidget) oppositionOption.clicked.connect(self.changeMiddleWidget) contactOption.clicked.connect(self.changeMiddleWidget) customOption.clicked.connect(self.changeMiddleWidget) #Bottom buttons (OK/Cancel) buttonLayout = QHBoxLayout() ok = QPushButton('OK') ok.clicked.connect(self.accept) cancel = QPushButton('Cancel') cancel.clicked.connect(self.reject) buttonLayout.addWidget(ok) buttonLayout.addWidget(cancel) layout.addWidget(contrastBox) layout.addWidget(self.middleWidget) layout.addLayout(buttonLayout) self.setLayout(layout)