-
Notifications
You must be signed in to change notification settings - Fork 38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
QCheckComboBox for Easy Multiple Items Selection #91
Conversation
@tlambert03 I think this is ready for your initial review. The example included is a good start.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the work @MosGeo! and very sorry for the delay.
to be honest i find this interface really strange 😅 ... I've never seen a dropdown like this where a single click toggles the check-state and a double click "selects/runs" the item... is that what's happening here? (actually, I don't know that I've seen many dropdowns used for multiple selection at all)
I'll ping @goanpeca and @Czaki for second/third opinions. If at least one of them also wants this in main here, then I'd be happy to go for it...
I might also like it a bit more if there wasn't an option for double click (i.e. if it was only a dropdown list of checkable uncheckable items)? though then it becomes a little strange to figure out how to close the thing :) Did you see this implemented in another app you use?
@tlambert03 Thanks for checking and no problem on waiting. This is a low priority thing anyway. Use of Check ComboboxCheck Combobox is pretty standard in control libraries nowadays. It goes by many names though (multiple select comobobox, check combobox, ...). Here are some examples for different programming languages:
Some notes:
Double Click IssueI agree that it is a weird behavior. I didn't catch it while testing as i always close it by clicking outside the box. It is actually a remanent of the original comobox. It shouldn't behave that way. I suggest I remove the behavior and make it do what is expected: double clicking on the item should check/uncheck twice. That is the behavior that is usually implemented in other ones (https://vuetifyjs.com/en/components/combobox/#multiple-combobox) |
@tlambert03 I think the behavior is now much better. Double click does not close the pop-up/run the item. I still have the issue of passing the test with macos. I don't know what to do with that as I do not have a macos! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see the value of this contribution but I think that it may be improved.
- lack of insetItem overwrite https://doc.qt.io/qt-6/qcombobox.html#insertItem
- lack of signal informing that selection was changed (
selectionUpdated = Signal(int, bool)
providing index and value) - lack of documentation in
docs/combobox.md
Hi @MosGeo thanks for working on this. To answer @tlambert03 question, yes I have seen this used in other places and I think it is useful now that the double click behavior has been fixed :) 🚀 I agree with @Czaki suggestions also. It would make the implementation complete and much better Thanks a lot! |
yeah, once you provided the links to the other examples, I get it better 😂 It's kinda the same thing as the labels selector in github: I think the primary things that confused me were the double click issue, and what seemed like an inability to "escape" the dropdown easily. (some of the examples you provided have OK/Cancel buttons which I think helped). Another nice thing that most of these "token selector" dropdowns have is some way to show what's currently selected (i.e. tokens with little "x" to remove it). That could come in a later PR, but that's the bit that made me realize what this was really going for. thanks again @MosGeo |
@tlambert03 base on your comment maybe we should also add a searchable and checkable combo box also? And to this PR. There should be also the following method (inspired by def checkedTexts(self) -> List[str]:
"""Returns the checked indices"""
texts = []
for i in range(self.count()):
item = self.model().item(i)
if item.checkState() == Qt.Checked:
texts.append(item.text())
return texts |
some typing and namespace suggestions diff --git a/src/superqt/combobox/_check_combobox.py b/src/superqt/combobox/_check_combobox.py
index a95e3e0..0149846 100644
--- a/src/superqt/combobox/_check_combobox.py
+++ b/src/superqt/combobox/_check_combobox.py
@@ -1,8 +1,8 @@
from enum import Enum, auto
-from typing import Any, List, Union
+from typing import Any, List, Union, cast
-from qtpy.QtCore import QEvent, Qt
-from qtpy.QtGui import QStandardItem
+from qtpy.QtCore import QEvent, QModelIndex, Qt
+from qtpy.QtGui import QStandardItem, QStandardItemModel
from qtpy.QtWidgets import QComboBox, QStyle, QStyleOptionComboBox, QStylePainter
@@ -24,10 +24,14 @@ class QCheckComboBox(QComboBox):
def __init__(self) -> None:
"""Initializes the widget"""
super().__init__()
- self.view().pressed.connect(self._handleItemPressed)
- self.view().doubleClicked.connect(self._handleItemPressed)
+ self.view().pressed.connect(self._handleItemPressed) # type: ignore
+ self.view().doubleClicked.connect(self._handleItemPressed) # type: ignore
self._changed = False
+ def model(self) -> QStandardItemModel:
+ # this is true, but annotated incorrectly in pyside2
+ return cast(QStandardItemModel, super().model())
+
def _update_label_text_with_selected_items(self) -> None:
checked_indices = self.checkedIndices()
selected_text_list = []
@@ -53,13 +57,13 @@ class QCheckComboBox(QComboBox):
"""Returns label type"""
return self._label_type
- def _handleItemPressed(self, index: int) -> None:
+ def _handleItemPressed(self, index: QModelIndex) -> None:
"""Updates item checked status"""
item = self.model().itemFromIndex(index)
- if item.checkState() == Qt.Checked:
- item.setCheckState(Qt.Unchecked)
+ if item.checkState() == Qt.CheckState.Checked:
+ item.setCheckState(Qt.CheckState.Unchecked)
else:
- item.setCheckState(Qt.Checked)
+ item.setCheckState(Qt.CheckState.Checked)
if self._label_type == QCheckComboBox.QCheckComboBoxLabelType.SELECTED_ITEMS:
self._update_label_text_with_selected_items()
@@ -73,7 +77,7 @@ class QCheckComboBox(QComboBox):
self.setItemChecked(self.count() - 1, checked=checked)
if (
self._label_type == QCheckComboBox.QCheckComboBoxLabelType.SELECTED_ITEMS
- and checked is True
+ and checked
):
self._update_label_text_with_selected_items()
@@ -89,7 +93,7 @@ class QCheckComboBox(QComboBox):
if (
self._label_type == QCheckComboBox.QCheckComboBoxLabelType.SELECTED_ITEMS
- and any(checked) is True
+ and any(checked)
):
self._update_label_text_with_selected_items()
@@ -102,14 +106,15 @@ class QCheckComboBox(QComboBox):
def itemChecked(self, index: int) -> bool:
"""Returns current checked state as boolean"""
item: QStandardItem = self.model().item(index, self.modelColumn())
- is_checked: bool = item.checkState() == Qt.Checked
- return is_checked
+ return bool(item.checkState() == Qt.CheckState.Checked)
def setItemChecked(self, index: int, checked: bool = True) -> None:
"""Sets the status"""
item: QStandardItem = self.model().item(index)
checked_state_old = item.checkState()
- checked_state_new = Qt.Checked if checked else Qt.Unchecked
+ checked_state_new = (
+ Qt.CheckState.Checked if checked else Qt.CheckState.Unchecked
+ )
# Stopping condition
if checked_state_old == checked_state_new:
@@ -130,7 +135,7 @@ class QCheckComboBox(QComboBox):
indecies = []
for i in range(self.count()):
item = self.model().item(i)
- if item.checkState() == Qt.Checked:
+ if item.checkState() == Qt.CheckState.Checked:
indecies.append(i)
return indecies
@@ -139,7 +144,7 @@ class QCheckComboBox(QComboBox):
indecies = []
for i in range(self.count()):
item = self.model().item(i)
- if item.checkState() == Qt.Unchecked:
+ if item.checkState() == Qt.CheckState.Unchecked:
indecies.append(i)
return indecies
@@ -149,5 +154,5 @@ class QCheckComboBox(QComboBox):
opt = QStyleOptionComboBox()
self.initStyleOption(opt)
opt.currentText = self._label_text
- painter.drawComplexControl(QStyle.CC_ComboBox, opt)
- painter.drawControl(QStyle.CE_ComboBoxLabel, opt)
+ painter.drawComplexControl(QStyle.ComplexControl.CC_ComboBox, opt)
+ painter.drawControl(QStyle.ControlElement.CE_ComboBoxLabel, opt)
diff --git a/tests/test_check_combobox.py b/tests/test_check_combobox.py
index c121cb5..0856ccb 100644
--- a/tests/test_check_combobox.py
+++ b/tests/test_check_combobox.py
@@ -99,7 +99,7 @@ def test_paint_event(qtbot: QtBot) -> None:
"""Simple test for paint event; execute without error"""
check_combobox = QCheckComboBox()
check_combobox.setLabelText("A new label")
- check_combobox.paintEvent(QEvent(QEvent.Paint))
+ check_combobox.paintEvent(QEvent(QEvent.Type.Paint))
def test_hidepopup(qtbot: QtBot) -> None:
|
ugh, sorry, that was ugly! 😂 I was trying a feature in vscode that I've never tried before to suggest edits without having to do the whole |
Codecov ReportPatch coverage:
Additional details and impacted files@@ Coverage Diff @@
## main #91 +/- ##
==========================================
- Coverage 85.30% 83.82% -1.48%
==========================================
Files 31 32 +1
Lines 2607 2721 +114
==========================================
+ Hits 2224 2281 +57
- Misses 383 440 +57
... and 11 files with indirect coverage changes Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. ☔ View full report in Codecov by Sentry. |
ugh... and now I've made a real mess 😂 I pushed my suggestions to main. I'm a little confused, and I think my vscode might also be, because it looks like you made these changes to your one observation I had: when you uncheck an item, whatever the last item was gets immediately rechecked the next time you click on the dropdown (note that I'm not trying to re-check item 2 in the movie below) Untitled.mov |
as for the mac error, I just don't think it likes the way you're directly calling paint event. Do you need to do it that way? Can you perhaps call |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add widgets to qtbot to prevent qt cleaning releated problems
@MosGeo, did I scare you off with my botched attempt to make suggestions above? or are you just busy :) I think this could go in, if we can just figure out what's going on with the reselection of stuff in https://github.com/napari/superqt/pull/91#issuecomment-1178225161? |
Sorry, yah, no problem. I was just busy with other stuff. I have to find a day to work on this. All comments here are good. Yes, I agree that is a big bug that I need to fix. My guess is that this was added when I introduced the fix to the double click. I felt it was a hacky way of doing it and I was not sure why it worked 🤣 I will work on this week and get back to you. I want to check on icon, signal and so on in other comments too. For documentation, honestly, I didn't know that the md files existed. Is it something of interest now or should we wait for the proper documentation? |
It's fine to add in a later PR. We desperately need real docs 🙃, the md files are completely undiscoverable |
Co-authored-by: Grzegorz Bokota <[email protected]>
Co-authored-by: Grzegorz Bokota <[email protected]>
Co-authored-by: Grzegorz Bokota <[email protected]>
Co-authored-by: Grzegorz Bokota <[email protected]>
Co-authored-by: Grzegorz Bokota <[email protected]>
Co-authored-by: Grzegorz Bokota <[email protected]>
Co-authored-by: Grzegorz Bokota <[email protected]>
Co-authored-by: Grzegorz Bokota <[email protected]>
Co-authored-by: Grzegorz Bokota <[email protected]>
Co-authored-by: Grzegorz Bokota <[email protected]>
Co-authored-by: Grzegorz Bokota <[email protected]>
Co-authored-by: Grzegorz Bokota <[email protected]>
@tlambert03 I am having issues reproducing. What Qt backend are you using? What OS? |
This implements the QCheckComboBox that is discussed in #89. A few notes:
addItems
andaddItem
includedchecked
parameters to quickly initializes the items while adding. We can remove that if you feel that it doesn't follow the "zen" of qt._handleItemPressed
test but I failed. For now, it is being tested like any other function.As always, I am open to suggestions and modifications. In terms of functionality, this is what I needed for my use case but others might have other ideas.
p.s. I see that the list of comboboxes in superqt is growing (enum, search) which is great.