-
Notifications
You must be signed in to change notification settings - Fork 49
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
feat: Add Separator to ComboBox #638
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #638 +/- ##
==========================================
+ Coverage 88.90% 89.16% +0.26%
==========================================
Files 39 39
Lines 4667 4689 +22
==========================================
+ Hits 4149 4181 +32
+ Misses 518 508 -10 ☔ View full report in Codecov by Sentry. |
This comment is likely to be unnecessary: ```pytb src/magicgui/schema/_guiclass.py:260: error: Unused "type: ignore" comment [unused-ignore] signals = {k: events[k] for k in events} # type: ignore ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Found 1 error in 1 file (checked 1 source file) ```
4e31f07
to
31395b1
Compare
Hey @tlambert03, I've completed the necessary changes, and I believe this Pull Request is now ready for your review. Tbh, this is my first time using Codecov, so I would greatly appreciate your feedback on its integration and any adjustments you might recommend. Thank you for your support! |
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.
this looks excellent! nice and simple too. Thanks @qin-yu!
I think the only thing that gave me a little pause was the thickness
parameter. Since it's not really "thickness" per-se, but rather "number of". Someone could also do Sep(), Sep()
if they really wanted multiples, and Qt itself doesn't really have thickness.
Did you add it cause it's something you know you want though?
Oops, I guess I went a bit too custom with my solution. I was working on this app using Napari, and I noticed that a single separator just wasn’t cutting it in terms of visibility. It took me a while to figure out that changing thickness/padding wasn’t straightforward. So, I ended up with this workaround in my own project. You’re right about using Sep(), Sep() for multiple separators. If you don't like fake thickness implementation, let's remove it and mention this workaround in docstrings. Thanks for the heads-up! @tlambert03 |
yeah, i'm not totally opposed to it, but maybe would prefer to consider alternatives before adding it to the public API :) let's just go with a parameter-free sentinel for now. |
Absolutely, I’m with you on maintaining a clean public API. I’ll go ahead and remove the parameter, then submit a new commit a bit later. Maybe our conversation here could serve as a good reference for future users. Do you think it would be beneficial to include a note in the docstring? |
sure, if you'd like, you could indicate that it can be used multiple times to create multiple lines 👍 |
Indeed, my experience with magicgui has been primarily within Napari, so my thoughts were anchored in that context 😢 I've updated the code again. Given that the Separator sentinel would remain identical, I've made it a singleton too. I've also adjusted the tests accordingly and updated the docs. I removed a comma to keep the docstring <= 88 limit. Maybe it's the best thing we can do, but it looks okay. |
one more question: can you perhaps look into how this could be implemented on the jupyter widgets backend? by modifying |
Thanks for the thorough reviews. I've just updated the |
Co-authored-by: Talley Lambert <[email protected]>
a770359
to
2fe9693
Compare
Regarding ipynb support nuances:
What do you think? @tlambert03 |
I just realised that |
- Fix pyapp-kit#639 - Note that `_mgui_set_choice()` is not used
I've adjusted Regarding def _mgui_set_choice(self, choice_name: str, data: Any) -> None:
"""Set the data associated with a choice."""
if data is Separator:
self._ipywidget.options = (*self._ipywidget.options, ("----", Separator))
else:
options = self._ipywidget.options
for item in options:
print(item, choice_name, data)
if (isinstance(item, tuple) and item[0] == choice_name) or item == choice_name:
options = [
item for item in options
if (not isinstance(item, tuple) or item[0] != choice_name) and item != choice_name
]
self._ipywidget.options = (*options, (choice_name, data)) |
(There is one failed test but I don't think it relates to this PR) |
Revert the behaviour change of ipynb duplicate items
Hey @tlambert03 I encountered an error in the CI workflow Test (3.10, ubuntu-latest, pyside6) / ubuntu-latest py3.10 pyside6, and I'm not sure how to resolve it. Could you take a look and provide some guidance? |
All checks passed. It looks like we're in a good position to wrap up the PR. I'm assuming there's nothing further I need to do on my end if you're planning to handle the squash merge? |
thank you @qin-yu! busy couple days here. yes I will merge soon after having one more look. thanks again! |
@@ -908,7 +908,7 @@ def _mgui_bind_change_callback(self, callback): | |||
self._event_filter.valueChanged.connect(callback) | |||
|
|||
def _mgui_get_count(self) -> int: | |||
"""Return the number of items in the dropdown.""" | |||
"""Return the number of items in the dropdown, including separator items.""" |
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.
this actually becomes a bit problematic. The protocol specifies that _mgui_get_count
should return the number of choices:
magicgui/src/magicgui/widgets/protocols.py
Lines 412 to 415 in 781a54a
@abstractmethod | |
def _mgui_get_count(self) -> int: | |
"""Return number of choices.""" | |
raise NotImplementedError |
so, it's not really up to the backend here to change those semantics. The ComboBox
itself (via CategoricalWidget
) declares __len__
to be the number of choices (not the number of widgets)
magicgui/src/magicgui/widgets/bases/_categorical_widget.py
Lines 105 to 107 in 781a54a
def __len__(self) -> int: | |
"""Return the number of choices.""" | |
return self._widget._mgui_get_count() |
so, assert len(combo_a) == len(combo_a.choices)
should technically be true, but is not currently so in the tests. So, I'm afraid this needs to account for that as well.
I know these are annoying things, but this feature is a revealing itself to be a bit tricky to implement in a way that isn't potentially surprising and too magical. It is blurring the lines between model (i.e. the values in choices
) and the view (how they look in the combobox) a bit.
i'm honestly not sure what the best thing to do here is. one alternative is to go back to what you started with, and just allow for choices
to include the separator. that is, when you call widget.choices
you do see all the Separator
singletons in there (they would need to be subbed back in for None
). Or, we just fix the count issue as well and stick with it as it is. That would then mean that it's basically impossible to query where the separators are, and whether they're in there (but you could also set them). Which do you prefer?
Indeed, the decision should be guided by the principle of least astonishment: opting for the approach that aligns with most developers' expectations and simplifies common tasks in ComboBox usage. Although integrating separators as selectable items offers transparency and consistency—a notion shaped by my experiences with Qt, where separators are considered part of the item set—it's essential to acknowledge that separators are typically not viewed as actionable components. Therefore, omitting separators from counts and choices presents a more user-friendly approach, simplifying development by removing the need to distinguish between separators and actual items. If you know that many of our users choose magicgui to bypass complexities in Qt, opting to conceal separators within the ComboBox would enhance simplicity. |
Co-authored-by: Talley Lambert <[email protected]>
It's pretty straightforward, actually! I just made the necessary adjustments to align with the protocol. |
@@ -248,7 +250,7 @@ def _mgui_get_current_choice(self) -> str: | |||
|
|||
def _mgui_set_choice(self, choice_name: str, data: Any) -> None: | |||
"""Set the data associated with a choice.""" | |||
self._ipywidget.options = (*self._ipywidget.options, (choice_name, data)) | |||
pass |
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.
this change looks suspicious. why are we removing the ability to set a single choice? Won't this change mess up calling CategoricalWidget.set_choice
?
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.
You're absolutely right. I may have gotten hung up on the protocol's definition of choice_name update. While my commit seemed to function without breaking tests, this change was completely unnecessary. I'm changing it later.
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 again @qin-yu . I think i'm happy to go with this now. Please just address the remaining comment on the new change to _IPySupportsChoices._mgui_set_choice
which i think needs to be reverted
Hi @tlambert03, just pushed the final change for this PR! Apologies for the delay – I was recently on holiday. It's been a valuable learning experience collaborating with you on this PR. Working on a well-maintained Python package with comprehensive CIs like this is a great way to improve contribution practices. Thanks for your patience and guidance! |
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.
it was very pleasant working with you too! Thanks a lot for the PR. hope you'll do it again :)
Hey Talley, please don't forget to make available v0.8.3 on conda-forge! |
looks like the release failed: https://github.com/pyapp-kit/magicgui/actions/runs/8476765032 ... sorry about that, will do again |
Thanks! But I think it failed again (actually no test can pass in the last few weeks) |
Yep I saw thanks, I'll fix when I get the chance. Won't be this weekend |
No worries, thanks for the information. Enjoy the rest of your weekend! |
Hey @tlambert03,
I looked into
magicgui
and added a very simple mechanism forComboBox
to add separators by introducing aSeparator
class. Please have a look and tell me if you like it or not. Do I need to do the documentation somewhere or someone is in charge of this?Many thanks,
Qin
Example
By giving a list
["a", Separator(), "b"]
to magicgui for creating a dropdown list, theComboBox
backend go through the list, check if the data is aSeparator
and insert a separator there using.insertSeparator()
.I put a modified version of the example script from your repo:
a
with unique elements:b
with duplicates: