Skip to content

Conversation

@windelbouwman
Copy link
Contributor

Add Selection widget in the Qt backend.

See also #3914 .

PR Checklist:

  • All new features have been tested
  • All new features have been documented
  • I have read the CONTRIBUTING.md file
  • I will abide by the code of conduct

@johnzhou721 johnzhou721 mentioned this pull request Nov 26, 2025
32 tasks
Copy link
Contributor

@johnzhou721 johnzhou721 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I'm not part of the core team, and this broadly looks good, but I do have a couple of suggestions for improvement.

iOS failure is unrelated -- CI does weird things like that. It'll pass on rerun when you commit again (core team members can also rerun specific tasks on commits).

self._send_notifications = True

def qt_on_current_text_changed(self, index):
if self._send_notifications:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could use currentIndexChanged signal here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the signal used here make any difference? AFAICT, we get a signal even if the text remains the same. See the selection example - the on_change example handler has 2 copies of every entry, and changing from dubnium to dubnium triggers a change.

The only oddity here is that the handler accepts an index parameter, but the actual content (AFAICT) is the text. That wouldn't be the case if currentIndexChanged was used.

Copy link
Contributor

@johnzhou721 johnzhou721 Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind... I missed that the implementation removes and then readds an item. Good catch @freakboy3742

Edit -- nevermind... I was getting myself messed up a bit in the impl code here.

Copy link
Contributor Author

@windelbouwman windelbouwman Nov 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the signal used here make any difference? AFAICT, we get a signal even if the text remains the same. See the selection example - the on_change example handler has 2 copies of every entry, and changing from dubnium to dubnium triggers a change.

The only oddity here is that the handler accepts an index parameter, but the actual content (AFAICT) is the text. That wouldn't be the case if currentIndexChanged was used.

According to Qt documentation, the currentTextChanged signal only fires when the text actually changes (which is what we need in our case, and also the reason I used this signal instead of currentIndexChanged). So switching from the first dubnium to the second dubnium does not fire a currentTextChanged event.

Copy link
Contributor

@johnzhou721 johnzhou721 Nov 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@windelbouwman I think using currentIndexChanged would be better -- both GTK and Cocoa backends emit on_change when we change from first Dubium to the second Dubium. So that would be the desired behavior.

EDIT -- this should also remove the need to suspend notifications in the change(self, item) thing but I'm not very sure.

self.interface.on_change()

def clear(self):
print("Clear")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stray debug statement? (also one in insert())

Copy link
Member

@freakboy3742 freakboy3742 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of minor comments (and a merge conflict with the Switch widget); but otherwise this looks great.

self.interface.on_change()

def clear(self):
print("Clear")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stray debug

Suggested change
print("Clear")

self.native.clear()

def insert(self, index, item):
print("insert", index, item)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stray debug

Suggested change
print("insert", index, item)

self._send_notifications = True

def qt_on_current_text_changed(self, index):
if self._send_notifications:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the signal used here make any difference? AFAICT, we get a signal even if the text remains the same. See the selection example - the on_change example handler has 2 copies of every entry, and changing from dubnium to dubnium triggers a change.

The only oddity here is that the handler accepts an index parameter, but the actual content (AFAICT) is the text. That wouldn't be the case if currentIndexChanged was used.

Copy link
Contributor

@johnzhou721 johnzhou721 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hold on... I just noticed that color customization was not working properly. This is because the dropdown widget in Qt seems to use the usual "background" colorrole for painting the dropdown.

Patch that seems to fix this -- notice that I used different shadow variable names for the defualt colors because create runs before the usual default color variables gets set in base.py.

diff --git a/qt/src/toga_qt/widgets/selection.py b/qt/src/toga_qt/widgets/selection.py
index e87452f59..0da1d2c29 100644
--- a/qt/src/toga_qt/widgets/selection.py
+++ b/qt/src/toga_qt/widgets/selection.py
@@ -1,7 +1,10 @@
 from contextlib import contextmanager
 
+from PySide6.QtGui import QPalette
 from PySide6.QtWidgets import QComboBox
 from travertino.size import at_least
+from ..colors import native_color, toga_color
+
 
 from .base import Widget
 
@@ -12,6 +15,22 @@ class Selection(Widget):
         self.native.setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToContents)
         self.native.currentTextChanged.connect(self.qt_on_current_text_changed)
         self._send_notifications = True
+        self._button_default_color = toga_color(self.native.palette().color(QPalette.ColorRole.Button))
+        self._button_text_default_color = toga_color(self.native.palette().color(QPalette.ColorRole.ButtonText))
+
+    def set_background_color(self, color):
+        if color is None:
+            color = self._button_default_color
+        palette = self.native.palette()
+        palette.setColor(QPalette.ColorRole.Button, native_color(color))
+        self.native.setPalette(palette)
+
+    def set_color(self, color):
+        if color is None:
+            color = self._button_text_default_color
+        palette = self.native.palette()
+        palette.setColor(QPalette.ColorRole.ButtonText, native_color(color))
+        self.native.setPalette(palette)
 
     @contextmanager
     def suspend_notifications(self):
diff --git a/qt/tests_backend/widgets/selection.py b/qt/tests_backend/widgets/selection.py
index 9664c7cf1..26d29f044 100644
--- a/qt/tests_backend/widgets/selection.py
+++ b/qt/tests_backend/widgets/selection.py
@@ -1,7 +1,11 @@
 from PySide6.QtWidgets import QComboBox
+from PySide6.QtGui import QPalette
 
 from .base import SimpleProbe
 
+from toga_qt.colors import toga_color
+
+
 
 class SelectionProbe(SimpleProbe):
     native_class = QComboBox
@@ -23,3 +27,11 @@ class SelectionProbe(SimpleProbe):
 
     async def select_item(self):
         self.native.setCurrentIndex(1)
+
+    @property
+    def color(self):
+        return toga_color(self.native.palette().color(QPalette.ColorRole.ButtonText))
+
+    @property
+    def background_color(self):
+        return toga_color(self.native.palette().color(QPalette.ColorRole.Button))

EDIT in case it came off as so there's no intention to blame anyone at all. I also made the mistake of not noticing this first-review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants