jbqt 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of jbqt might be problematic. Click here for more details.

@@ -0,0 +1,16 @@
1
+ """Collection of widget exports"""
2
+
3
+ from jbqt.widgets.chip_button import ChipButton
4
+ from jbqt.widgets.chips import ChipsWidget
5
+ from jbqt.widgets.multiselect import MultiSelectComboBox
6
+ from jbqt.widgets.simple import ClickableLabel, LongIntSpinBox
7
+ from jbqt.widgets.toast import Toast
8
+
9
+ __all__ = [
10
+ "ChipButton",
11
+ "ChipsWidget",
12
+ "MultiSelectComboBox",
13
+ "ClickableLabel",
14
+ "LongIntSpinBox",
15
+ "Toast",
16
+ ]
@@ -0,0 +1,204 @@
1
+ from re import Pattern
2
+ from typing import Callable
3
+
4
+ from PyQt6.QtCore import Qt, QEvent, QObject
5
+ from PyQt6.QtGui import QKeyEvent
6
+ from PyQt6.QtWidgets import (
7
+ QHBoxLayout,
8
+ QLineEdit,
9
+ QPushButton,
10
+ QFrame,
11
+ QWidget,
12
+ )
13
+
14
+ from jbqt.common import consts
15
+ from jbqt.common.consts import STYLES, COLORS
16
+ from jbqt.models import IChipButton
17
+ from jbqt.widgets.simple import ClickableLabel
18
+ from jbqt.dialogs import InputDialog
19
+ from jbqt.widgets.widget_utils import (
20
+ debug_scroll_pos,
21
+ preserve_scroll,
22
+ )
23
+
24
+
25
+ class ChipButton(IChipButton):
26
+ def __init__(
27
+ self,
28
+ tag: str,
29
+ on_update: Callable,
30
+ on_remove: Callable,
31
+ use_weight: bool = False,
32
+ weight_re: Pattern = None,
33
+ debug: bool = False,
34
+ ):
35
+ super().__init__()
36
+
37
+ self._tag = tag
38
+ self.weight: float = 1.0
39
+ self.use_weight: bool = use_weight
40
+
41
+ if use_weight:
42
+ weight_re = weight_re or consts.TAG_RE
43
+
44
+ if weight_re:
45
+ tag_match = weight_re.search(tag)
46
+
47
+ if tag_match:
48
+ re_tag, weight = tag_match.groups()
49
+ self._tag = re_tag
50
+ self.weight = float(weight)
51
+
52
+ self.on_update = on_update
53
+ self.on_remove = on_remove
54
+ self.installEventFilter(self)
55
+
56
+ # Layout for the tag button
57
+ # self.frame = QFrame(self, Qt.WindowType.ToolTip)
58
+ self.frame = QFrame()
59
+ frame_layout = QHBoxLayout(self.frame)
60
+ frame_layout.setSpacing(5)
61
+ frame_layout.setContentsMargins(10, 0, 10, 0)
62
+
63
+ self.tag_label = ClickableLabel(tag)
64
+ self.tag_label.setFixedHeight(24)
65
+ self.tag_label.clicked.connect(self.toggle_edit_mode)
66
+
67
+ self.weight_button: QPushButton = None
68
+
69
+ if use_weight:
70
+ self.weight_button = QPushButton(consts.ICONS.CODE(), "")
71
+ self.weight_button.clicked.connect(self.apply_weight)
72
+ self.weight_button.setFixedWidth(24)
73
+
74
+ self.remove_button = QPushButton(consts.ICONS.TRASH(), "")
75
+ self.remove_button.clicked.connect(lambda: self.on_remove(self))
76
+ self.remove_button.setFixedWidth(24)
77
+ self.remove_button.setStyleSheet(STYLES.BG_DARK_RED)
78
+
79
+ self.edit_line = QLineEdit(self)
80
+ self.edit_line.hide()
81
+
82
+ self.confirm_btn = QPushButton(consts.ICONS.CIRCLE_CHECK(COLORS.WHITE), "")
83
+ self.confirm_btn.clicked.connect(self.edit_tag)
84
+ self.confirm_btn.setFixedWidth(24)
85
+ self.confirm_btn.setStyleSheet(STYLES.BG_DARK_GREEN)
86
+ self.confirm_btn.hide()
87
+
88
+ self.cancel_btn = QPushButton(consts.ICONS.CIRCLE_TIMES(), "")
89
+ self.cancel_btn.clicked.connect(self.cancel)
90
+ self.cancel_btn.setFixedWidth(24)
91
+ self.cancel_btn.setStyleSheet(STYLES.BG_DARK_RED)
92
+ self.cancel_btn.hide()
93
+
94
+ self.widgets = [
95
+ self.tag_label,
96
+ self.edit_line,
97
+ self.confirm_btn,
98
+ self.cancel_btn,
99
+ self.weight_button,
100
+ self.remove_button,
101
+ ]
102
+ self.input_widgets = [self.edit_line, self.confirm_btn, self.cancel_btn]
103
+ for widget in self.widgets:
104
+ if widget:
105
+ frame_layout.addWidget(widget)
106
+
107
+ # Main layout
108
+ self.main_layout = QHBoxLayout(self)
109
+ self.main_layout.addWidget(self.frame)
110
+ self.main_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
111
+ # Apply styling to the frame
112
+ self.frame.setStyleSheet(
113
+ f"""
114
+ QFrame {{
115
+ border: 1px solid #AAA;
116
+ border-radius: 10px;
117
+ background-color: #343439;
118
+ max-height: 36px;
119
+ }}
120
+ QLabel {{
121
+ border: none;
122
+ }}
123
+ QPushButton {{
124
+ {STYLES.BG_GRAY}
125
+ }}
126
+ """
127
+ )
128
+ self.setLayout(self.main_layout)
129
+
130
+ @property
131
+ def text(self) -> str:
132
+ if self.weight == 1:
133
+ return self._tag
134
+ return f"({self._tag}){self.weight:g}"
135
+
136
+ @text.setter
137
+ def text(self, value: str) -> None:
138
+ self._tag = value
139
+
140
+ def eventFilter(self, obj: QObject, event: QKeyEvent) -> bool:
141
+ focus = consts.GlobalRefs.app.focusWidget()
142
+ """ if event.type() == QEvent.Type.KeyPress:
143
+ print("focus", focus) """
144
+ if event.type() == QEvent.Type.KeyPress and focus == self.edit_line:
145
+ match event.key():
146
+ case Qt.Key.Key_Return | Qt.Key.Key_Enter:
147
+ self.edit_tag()
148
+ case Qt.Key.Key_Escape:
149
+ self.cancel()
150
+ return super().eventFilter(obj, event)
151
+
152
+ def toggle_hidden(self, hide: bool = True) -> None:
153
+ for widget in self.widgets:
154
+ is_input = widget in self.input_widgets
155
+
156
+ if hide == is_input:
157
+ widget.hide()
158
+ else:
159
+ widget.show()
160
+
161
+ def cancel(self) -> None:
162
+ self.toggle_hidden()
163
+
164
+ def toggle_edit_mode(self):
165
+ self.edit_line.setText(self._tag)
166
+ self.edit_line.setMinimumWidth(self.tag_label.width() + 10)
167
+ self.toggle_hidden(False)
168
+
169
+ def emit_update(self, text: str = None, weight: float = None) -> None:
170
+ text = text or self._tag
171
+ if weight is None:
172
+ weight = self.weight
173
+ if weight == "":
174
+ self.on_remove(self)
175
+ return
176
+ prev_text = self.text
177
+ self._tag = text
178
+ self.weight = weight
179
+ self.tag_label.setText(self.text)
180
+ self.on_update(prev_text, self.text)
181
+
182
+ def update_weight(self, weight: float) -> None:
183
+ self.emit_update(weight=weight)
184
+
185
+ @preserve_scroll
186
+ def edit_tag(self, *_):
187
+ text = self.edit_line.text()
188
+ self.toggle_hidden()
189
+
190
+ self.emit_update(text=text)
191
+
192
+ def apply_weight(self):
193
+ weight_dialog = InputDialog(
194
+ parent=self,
195
+ title="Tag Weight",
196
+ msg_str=f"Enter weight for `{self.text}`:",
197
+ input_type=float,
198
+ minimum=0,
199
+ maximum=2,
200
+ singleStep=0.1,
201
+ value=self.weight,
202
+ )
203
+ weight_dialog.connect(self.update_weight)
204
+ weight_dialog.exec()
jbqt/widgets/chips.py ADDED
@@ -0,0 +1,232 @@
1
+ from re import Pattern
2
+
3
+ from PyQt6.QtCore import Qt, pyqtSignal, QObject, QEvent
4
+ from PyQt6.QtGui import QKeyEvent
5
+ from PyQt6.QtWidgets import (
6
+ QHBoxLayout,
7
+ QLabel,
8
+ QLineEdit,
9
+ QPushButton,
10
+ QScrollArea,
11
+ QVBoxLayout,
12
+ QWidget,
13
+ QComboBox,
14
+ )
15
+
16
+ import jb_utils as utils
17
+ from jbqt.common import consts
18
+ from jbqt.models import IChipsWidget
19
+ from jbqt.widgets.chip_button import ChipButton
20
+ from jbqt.widgets.multiselect import MultiSelectComboBox
21
+ from jbqt.widgets.widget_utils import debug_scroll_pos
22
+
23
+
24
+ class ChipsWidget(IChipsWidget):
25
+ valuesChanged = pyqtSignal(list)
26
+
27
+ def __init__(
28
+ self,
29
+ chips=None,
30
+ data: dict = None,
31
+ path: str = "",
32
+ label: str = "",
33
+ weight_re: Pattern = None,
34
+ use_weight: bool = False,
35
+ debug: bool = False,
36
+ ):
37
+ super().__init__()
38
+ self.values = chips or []
39
+ # Main layout
40
+ main_layout = QVBoxLayout()
41
+ self.debug = debug
42
+ if label:
43
+ main_layout.addWidget(QLabel(label))
44
+ self.installEventFilter(self)
45
+
46
+ self.use_weight = use_weight
47
+ if use_weight:
48
+ weight_re = weight_re or consts.TAG_RE
49
+
50
+ self.weight_re = weight_re
51
+ self.options = data
52
+ self.data = data or {}
53
+ self.data_path = path
54
+ self.list_widget: QComboBox | MultiSelectComboBox = None
55
+
56
+ if data is not None:
57
+ if isinstance(data, dict):
58
+ if path:
59
+ self.options = utils.get_nested(self.data, path, [])
60
+
61
+ self.list_widget = MultiSelectComboBox(
62
+ data=self.options, selected=self.values
63
+ )
64
+ self.list_widget.selectedChanged.connect(
65
+ self.handle_multiselect_change
66
+ )
67
+
68
+ elif isinstance(data, list):
69
+ self.list_widget = QComboBox()
70
+
71
+ # self.list_widget.addItems(self.options)
72
+ for option in self.options:
73
+ self.list_widget.addItem(option)
74
+ init_value = self.values[0] if self.values else ""
75
+ self.list_widget.setCurrentText(init_value)
76
+ self.list_widget.currentTextChanged.connect(
77
+ self.handle_multiselect_change
78
+ )
79
+ self.valuesChanged.connect(self.update_multiselect)
80
+ main_layout.addWidget(self.list_widget)
81
+
82
+ # Scroll area for the chips
83
+ scroll_area = QScrollArea()
84
+ scroll_area.setWidgetResizable(True)
85
+
86
+ # Widget inside the scroll area to hold the buttons
87
+ self.chip_widget = QWidget()
88
+ self.chip_layout = QHBoxLayout(self.chip_widget)
89
+ self.chip_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
90
+
91
+ # Add existing chips as buttons
92
+ self.set_items(emit=False)
93
+
94
+ scroll_area.setWidget(self.chip_widget)
95
+
96
+ # Layout for the input and add button
97
+ input_layout = QHBoxLayout()
98
+ self.input_field = QLineEdit(self)
99
+ self.add_button = QPushButton("Add", self)
100
+ self.add_button.clicked.connect(self.add_chip)
101
+
102
+ self.clear_button = QPushButton(consts.ICONS.TRASH("darkred"), "", self)
103
+ self.clear_button.clicked.connect(self.remove_all)
104
+ # self.clear_button.setStyleSheet("color: red")
105
+
106
+ input_layout.addWidget(self.input_field)
107
+ input_layout.addWidget(self.add_button)
108
+ input_layout.addWidget(self.clear_button)
109
+
110
+ main_layout.addWidget(scroll_area)
111
+ main_layout.addLayout(input_layout)
112
+
113
+ self.setLayout(main_layout)
114
+
115
+ def eventFilter(self, obj: QObject, event: QKeyEvent) -> bool:
116
+ focus = consts.GlobalRefs.app.focusWidget()
117
+
118
+ if event.type() == QEvent.Type.KeyPress and focus == self.input_field:
119
+ match event.key():
120
+ case Qt.Key.Key_Return | Qt.Key.Key_Enter:
121
+ self.add_chip()
122
+ return super().eventFilter(obj, event)
123
+
124
+ def get_unweight_chip(self, tag: str) -> str:
125
+ if self.weight_re and self.weight_re.search(tag):
126
+ return self.weight_re.search(tag).groups()[0]
127
+ return tag
128
+
129
+ def get_chip_list(self, tags: list[str]) -> list[str]:
130
+ return [self.get_unweight_chip(tag) for tag in tags]
131
+
132
+ def same_values(self, values: list[str]) -> bool:
133
+ return set(self.get_chip_list(values)) == set(
134
+ self.get_chip_list(self.values)
135
+ )
136
+
137
+ def handle_multiselect_change(self, selections: list[str] | str) -> None:
138
+ if isinstance(selections, str):
139
+ selections = [selections]
140
+
141
+ selections = utils.dedupe_list(selections)
142
+ if self.same_values(selections):
143
+ return
144
+
145
+ # self.values = selections
146
+ self.set_items(selections, debug=True)
147
+
148
+ def update_multiselect(self, values: list[str]) -> None:
149
+ if isinstance(self.list_widget, MultiSelectComboBox):
150
+ self.list_widget.set_selected(values, emit=False)
151
+ else:
152
+ value = values[0] if values else ""
153
+ self.list_widget.setCurrentText(value)
154
+
155
+ def clear_widgets(self) -> None:
156
+ while self.chip_layout.count():
157
+ widget = self.chip_layout.takeAt(0).widget()
158
+ if widget is not None:
159
+ widget.deleteLater()
160
+
161
+ def set_items(
162
+ self, values: list[str] = None, emit: bool = True, debug: bool = False
163
+ ) -> None:
164
+ self.clear_widgets()
165
+ # TODO: Temp fix for weighted/custom tags and incoming multiselect values
166
+ if values:
167
+ tags = self.get_tag_list(self.values)
168
+ for value in values:
169
+ if value not in tags:
170
+ self.values.append(value)
171
+
172
+ self.values = utils.dedupe_list(self.values)
173
+ self.values = [value for value in self.values if value]
174
+ for item in self.values:
175
+ if not consts.GlobalRefs.debug_set:
176
+ debug = True
177
+ consts.GlobalRefs.debug_set = True
178
+ else:
179
+ debug = False
180
+ chip_button = ChipButton(
181
+ item,
182
+ self.update_chip,
183
+ self.remove_chip,
184
+ debug=debug,
185
+ weight_re=self.weight_re,
186
+ use_weight=self.use_weight,
187
+ )
188
+ """ chip_button.setToolTip(
189
+ f"Tag Count: {consts.TAG_COUNTS.get(item, "N/A")}"
190
+ ) """
191
+
192
+ self.chip_layout.addWidget(chip_button)
193
+
194
+ if emit:
195
+ self.emit_changes()
196
+
197
+ def update_chip(self, prev_text: str, new_text: str) -> None:
198
+ idx = self.values.index(prev_text)
199
+ if idx >= 0:
200
+ self.values[idx] = new_text
201
+
202
+ def emit_changes(self):
203
+ self.valuesChanged.emit(self.values)
204
+
205
+ def add_chip(self):
206
+ text = self.input_field.text().strip()
207
+ if text and text not in self.values:
208
+ chip_button = ChipButton(text, self.update_chip, self.remove_chip)
209
+
210
+ self.chip_layout.addWidget(chip_button)
211
+ self.values.append(text)
212
+ self.emit_changes()
213
+ self.input_field.setText("")
214
+
215
+ def remove_chip(self, button: ChipButton):
216
+ self.chip_layout.removeWidget(button)
217
+ button.deleteLater() # Schedule button widget for deletion
218
+ self.values.remove(button.text)
219
+ self.emit_changes()
220
+
221
+ def add_chips(self, items: list[str]) -> None:
222
+ utils.update_list_values(self.values, items)
223
+ self.set_items()
224
+
225
+ def remove_chips(self, items: list[str]) -> None:
226
+ utils.remove_list_values(self.values, items)
227
+ self.set_items()
228
+
229
+ def remove_all(self, *_) -> None:
230
+ self.values = []
231
+ self.clear_widgets()
232
+ self.emit_changes()
@@ -0,0 +1,201 @@
1
+ from re import Pattern
2
+
3
+ from PyQt6.QtCore import QObject, pyqtSignal, Qt
4
+ from PyQt6.QtGui import QCursor, QMouseEvent, QWheelEvent
5
+ from PyQt6.QtWidgets import QComboBox, QListWidget, QListWidgetItem
6
+
7
+ import jb_utils as utils
8
+ from jbqt.common import consts, qt_utils
9
+
10
+
11
+ class MultiSelectComboBox(QComboBox):
12
+ selectedChanged = pyqtSignal(list)
13
+
14
+ def __init__(
15
+ self,
16
+ data: list | dict = None,
17
+ selected: list[str] = None,
18
+ multi_enabled: bool = True,
19
+ use_weight: bool = False,
20
+ weight_re: Pattern = None,
21
+ ):
22
+ super().__init__()
23
+
24
+ self.use_weight = use_weight
25
+ if use_weight:
26
+ weight_re = weight_re or consts.TAG_RE
27
+
28
+ self.weight_re = weight_re
29
+
30
+ self._multi_select_enabled: bool = multi_enabled
31
+ # Make the combo box read-only and use a custom view
32
+ self.setEditable(True)
33
+ self.lineEdit().setReadOnly(True)
34
+ # Override the mouse press event of the QLineEdit
35
+ self.lineEdit().installEventFilter(self)
36
+
37
+ self.view: QListWidget = QListWidget()
38
+ self._selected = selected or []
39
+
40
+ # Set the custom view
41
+ self.setModel(self.view.model())
42
+ self.setView(self.view)
43
+
44
+ # Add items and categories
45
+ if isinstance(data, dict):
46
+ for name, items in data.items():
47
+ self.add_group(name, items)
48
+ elif isinstance(data, list):
49
+ self.add_items(data)
50
+
51
+ # Connect the item clicked signal
52
+ self.view.itemClicked.connect(self.toggle_check_state)
53
+ self.update_display_text()
54
+
55
+ def eventFilter(self, obj: QObject, event: QMouseEvent) -> bool:
56
+
57
+ if (
58
+ obj == self.lineEdit()
59
+ and event.type() == QMouseEvent.Type.MouseButtonRelease
60
+ ):
61
+ self.showPopup()
62
+
63
+ return True
64
+ return super().eventFilter(obj, event)
65
+
66
+ def wheelEvent(self, event: QWheelEvent):
67
+ # Check if the mouse cursor is within the bounds of the line edit
68
+ pos = self.mapFromGlobal(QCursor.pos())
69
+ if self.lineEdit().rect().contains(pos):
70
+ # Consume the wheel event to prevent scrolling
71
+ return
72
+
73
+ # If not over the line edit, let the base class handle it (scrolling)
74
+ super().wheelEvent(event)
75
+
76
+ def toggle_check_state(self, item: QListWidgetItem):
77
+ # Toggle check state if the item is selectable
78
+ if item.flags() & Qt.ItemFlag.ItemIsUserCheckable:
79
+ current_state = item.checkState()
80
+
81
+ new_state = (
82
+ Qt.CheckState.Unchecked
83
+ if current_state == Qt.CheckState.Checked
84
+ else Qt.CheckState.Checked
85
+ )
86
+ item.setCheckState(new_state)
87
+ if not self._multi_select_enabled:
88
+ self.toggle_others(item)
89
+
90
+ self.update_display_text()
91
+ self._selected_changed()
92
+
93
+ def toggle_others(self, selected: QListWidgetItem) -> None:
94
+
95
+ for i in range(self.view.count()):
96
+ item = self.view.item(i)
97
+ if item != selected:
98
+ item.setCheckState(Qt.CheckState.Unchecked)
99
+ self.update_display_text()
100
+
101
+ def setMultiSelectEnabled(self, value: bool) -> None:
102
+ self._multi_select_enabled = value
103
+
104
+ def multiSelectEnabled(self) -> bool:
105
+ return self._multi_select_enabled
106
+
107
+ def add_items(self, items: list[str] | dict, indent: int = 0) -> None:
108
+
109
+ indent_str = " " * indent
110
+ if isinstance(items, dict):
111
+ for name, children in items.items():
112
+ self.add_group(name, children, indent + 1)
113
+ else:
114
+ for item_value in items:
115
+ if isinstance(item_value, tuple):
116
+ item = QListWidgetItem(f"{indent_str}{item_value[0]}")
117
+ item.setData(consts.LIST_ITEM_ROLE, item_value[1])
118
+
119
+ else:
120
+ item = QListWidgetItem(f"{indent_str}{item_value}")
121
+ item.setFlags(
122
+ Qt.ItemFlag.ItemIsUserCheckable | Qt.ItemFlag.ItemIsEnabled
123
+ )
124
+ value = item.data(consts.LIST_ITEM_ROLE) or item.text()
125
+ if value.strip() in self._selected:
126
+ item.setCheckState(Qt.CheckState.Checked)
127
+ else:
128
+ item.setCheckState(Qt.CheckState.Unchecked)
129
+ self.view.addItem(item)
130
+
131
+ def add_group(self, group_name: str, items: list[str], indent: int = 0):
132
+ indent_str = " " * indent
133
+ # Add group header
134
+ header_item = QListWidgetItem(indent_str + group_name)
135
+ header_item.setFlags(Qt.ItemFlag.ItemIsEnabled)
136
+ header_item.setData(Qt.ItemDataRole.UserRole, False) # Non-selectable
137
+ self.view.addItem(header_item)
138
+
139
+ # Add items with checkboxes
140
+ self.add_items(items, indent + 1)
141
+
142
+ def update_display_text(self):
143
+ selected_items = []
144
+ for i in range(self.view.count()):
145
+ item = self.view.item(i)
146
+ if item.checkState() == Qt.CheckState.Checked:
147
+ selected_items.append(item.text().strip())
148
+
149
+ text = ", ".join(selected_items)
150
+ self.lineEdit().setText(text)
151
+
152
+ def showPopup(self):
153
+ # Override showPopup to update text when the dropdown is shown
154
+ self.update_display_text()
155
+ super().showPopup()
156
+
157
+ def hidePopup(self):
158
+ # Override hidePopup to update text when closing dropdown
159
+ self.update_display_text()
160
+ super().hidePopup()
161
+
162
+ def same_selected(self, values: list[str]) -> bool:
163
+ return set(values) == set(self._selected)
164
+
165
+ def get_selected(self) -> list[str]:
166
+ return [
167
+ qt_utils.get_item_value(item)
168
+ for item in self.view.findItems("", Qt.MatchFlag.MatchContains)
169
+ if item.checkState() == Qt.CheckState.Checked
170
+ ]
171
+
172
+ def _selected_changed(self):
173
+ self.selectedChanged.emit(self.get_selected())
174
+
175
+ def get_unweight_chip(self, tag: str) -> str:
176
+ if self.weight_re and self.weight_re.search(tag):
177
+ return self.weight_re.search(tag).groups()[0]
178
+ return tag
179
+
180
+ def get_chip_list(self, tags: list[str]) -> list[str]:
181
+ return [self.get_unweight_chip(tag) for tag in tags]
182
+
183
+ def set_selected(self, values: list[str], emit: bool = True):
184
+ """if self.same_selected(values):
185
+ return"""
186
+
187
+ values = self.get_chip_list(values)
188
+ # Set the selected items and emit the signal
189
+ self._selected = utils.dedupe_list(values)
190
+ # Update the checked state of items based on the new selection
191
+ for i in range(self.view.count()):
192
+ item = self.view.item(i)
193
+
194
+ item.setCheckState(
195
+ Qt.CheckState.Checked
196
+ if qt_utils.get_item_value(item) in values
197
+ else Qt.CheckState.Unchecked
198
+ )
199
+ self.update_display_text()
200
+ if emit:
201
+ self._selected_changed()