-
-
Notifications
You must be signed in to change notification settings - Fork 132
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add the ObjectCombo control which is a better ComboBox widget.
- Loading branch information
Showing
4 changed files
with
277 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
#------------------------------------------------------------------------------ | ||
# Copyright (c) 2013, Nucleic Development Team. | ||
# | ||
# Distributed under the terms of the Modified BSD License. | ||
# | ||
# The full license is in the file COPYING.txt, distributed with this software. | ||
#------------------------------------------------------------------------------ | ||
from PyQt4.QtCore import QTimer | ||
from PyQt4.QtGui import QComboBox | ||
|
||
from atom.api import Int, Typed | ||
|
||
from enaml.widgets.object_combo import ProxyObjectCombo | ||
|
||
from .q_resource_helpers import get_cached_qicon | ||
from .qt_control import QtControl | ||
|
||
|
||
# cyclic notification guard flags | ||
SELECTED_GUARD = 0x1 | ||
|
||
|
||
class ComboRefreshTimer(QTimer): | ||
""" A QTimer used for collapsing items refresh requests. | ||
This is a single shot timer which automatically cleans itself up | ||
when its timer event is triggered. | ||
""" | ||
def __init__(self, owner): | ||
""" Initialize a ComboRefreshTimer. | ||
Parameters | ||
---------- | ||
owner : QtObjectCombo | ||
The object combo which owns the timer. | ||
""" | ||
super(ComboRefreshTimer, self).__init__() | ||
self.setSingleShot(True) | ||
self.owner = owner | ||
|
||
def timerEvent(self, event): | ||
""" Handle the timer event for the timer. | ||
This handler will call the 'refresh_items' method on the owner | ||
and then release all references to itself and the owner. | ||
""" | ||
super(ComboRefreshTimer, self).timerEvent(event) | ||
owner = self.owner | ||
if owner is not None: | ||
del owner.refresh_timer | ||
self.owner = None | ||
owner.refresh_items() | ||
|
||
|
||
class QtObjectCombo(QtControl, ProxyObjectCombo): | ||
""" A Qt implementation of an Enaml ProxyObjectCombo. | ||
""" | ||
#: A reference to the widget created by the proxy. | ||
widget = Typed(QComboBox) | ||
|
||
#: A single shot refresh timer for queing combo refreshes. | ||
refresh_timer = Typed(ComboRefreshTimer) | ||
|
||
#: Cyclic notification guard. This a bitfield of multiple guards. | ||
_guard = Int(0) | ||
|
||
#-------------------------------------------------------------------------- | ||
# Default Value Handlers | ||
#-------------------------------------------------------------------------- | ||
def _default_refresh_timer(self): | ||
""" Get a refresh timer for the object combo box. | ||
""" | ||
return ComboRefreshTimer(self) | ||
|
||
#-------------------------------------------------------------------------- | ||
# Initialization API | ||
#-------------------------------------------------------------------------- | ||
def create_widget(self): | ||
""" Create the QComboBox widget. | ||
""" | ||
self.widget = QComboBox(self.parent_widget()) | ||
self.widget.setInsertPolicy(QComboBox.NoInsert) | ||
|
||
def init_widget(self): | ||
""" Create and initialize the underlying widget. | ||
""" | ||
super(QtObjectCombo, self).init_widget() | ||
self.refresh_items() | ||
self.widget.currentIndexChanged.connect(self.on_index_changed) | ||
|
||
#-------------------------------------------------------------------------- | ||
# Signal Handlers | ||
#-------------------------------------------------------------------------- | ||
def on_index_changed(self, index): | ||
""" The signal handler for the index changed signal. | ||
""" | ||
if not self._guard & SELECTED_GUARD: | ||
self._guard |= SELECTED_GUARD | ||
try: | ||
item = self.declaration.items[index] | ||
self.declaration.selected = item | ||
finally: | ||
self._guard &= ~SELECTED_GUARD | ||
|
||
#-------------------------------------------------------------------------- | ||
# Public API | ||
#-------------------------------------------------------------------------- | ||
def refresh_items(self): | ||
""" Refresh the items in the combo box. | ||
""" | ||
d = self.declaration | ||
selected = d.selected | ||
to_string = d.to_string | ||
to_icon = d.to_icon | ||
widget = self.widget | ||
self._guard |= SELECTED_GUARD | ||
try: | ||
widget.clear() | ||
target_index = -1 | ||
for index, item in enumerate(d.items): | ||
text = to_string(item) | ||
icon = to_icon(item) | ||
if icon is None: | ||
qicon = None | ||
else: | ||
qicon = get_cached_qicon(icon) | ||
if qicon is None: | ||
widget.addItem(text) | ||
else: | ||
widget.addItem(qicon, text) | ||
if item is selected: | ||
target_index = index | ||
widget.setCurrentIndex(target_index) | ||
finally: | ||
self._guard &= ~SELECTED_GUARD | ||
|
||
#-------------------------------------------------------------------------- | ||
# ProxyObjectCombo API | ||
#-------------------------------------------------------------------------- | ||
def set_selected(self, selected): | ||
""" Set the selected object in the combo box. | ||
""" | ||
if not self._guard & SELECTED_GUARD: | ||
self._guard |= SELECTED_GUARD | ||
try: | ||
d = self.declaration | ||
try: | ||
index = d.items.index(selected) | ||
except ValueError: | ||
index = -1 | ||
self.widget.setCurrentIndex(index) | ||
finally: | ||
self._guard &= ~SELECTED_GUARD | ||
|
||
def set_editable(self, editable): | ||
""" Set whether the combo box is editable. | ||
""" | ||
# The update is needed to avoid artificats (at least on Windows) | ||
widget = self.widget | ||
widget.setEditable(editable) | ||
widget.update() | ||
|
||
def request_items_refresh(self): | ||
""" Request a refresh of the combo box items. | ||
""" | ||
self.refresh_timer.start() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
#------------------------------------------------------------------------------ | ||
# Copyright (c) 2013, Nucleic Development Team. | ||
# | ||
# Distributed under the terms of the Modified BSD License. | ||
# | ||
# The full license is in the file COPYING.txt, distributed with this software. | ||
#------------------------------------------------------------------------------ | ||
from atom.api import ( | ||
Bool, Callable, List, Value, Typed, ForwardTyped, set_default, observe | ||
) | ||
|
||
from enaml.core.declarative import d_ | ||
|
||
from .control import Control, ProxyControl | ||
|
||
|
||
class ProxyObjectCombo(ProxyControl): | ||
""" The abstract defintion of a proxy ObjectCombo object. | ||
""" | ||
#: A reference to the ObjectCombo declaration. | ||
declaration = ForwardTyped(lambda: ObjectCombo) | ||
|
||
def set_selected(self, selected): | ||
raise NotImplementedError | ||
|
||
def set_editable(self, editable): | ||
raise NotImplementedError | ||
|
||
def request_items_refresh(self): | ||
raise NotImplementedError | ||
|
||
|
||
class ObjectCombo(Control): | ||
""" A drop-down list from which one item can be selected at a time. | ||
Use a combo box to select a single item from a collection of items. | ||
""" | ||
#: The list of items to display in the combo box. | ||
items = d_(List()) | ||
|
||
#: The selected item from the list of items. The default will be | ||
#: the first item in the list of items, or None. | ||
selected = d_(Value()) | ||
|
||
#: The callable to use to convert the items into unicode strings | ||
#: for display. The default is the builtin 'unicode'. | ||
to_string = d_(Callable(unicode)) | ||
|
||
#: The callable to use to convert the items into icons for | ||
#: display. The default is a lambda which returns None. | ||
to_icon = d_(Callable(lambda item: None)) | ||
|
||
#: Whether the text in the combo box can be edited by the user. | ||
editable = d_(Bool(False)) | ||
|
||
#: A combo box hugs its width weakly by default. | ||
hug_width = set_default('weak') | ||
|
||
#: A reference to the ProxyObjectCombo object. | ||
proxy = Typed(ProxyObjectCombo) | ||
|
||
#-------------------------------------------------------------------------- | ||
# Default Value Handlers | ||
#-------------------------------------------------------------------------- | ||
def _default_selected(self): | ||
""" The default value handler for the 'selected' member. | ||
""" | ||
items = self.items | ||
if len(items) > 0: | ||
return items[0] | ||
|
||
#-------------------------------------------------------------------------- | ||
# Observers | ||
#-------------------------------------------------------------------------- | ||
@observe(('items', 'to_string', 'to_icon')) | ||
def _refresh_proxy(self, change): | ||
""" An observer which requests an items refresh from the proxy. | ||
""" | ||
if self.proxy_is_active: | ||
self.proxy.request_items_refresh() | ||
|
||
@observe(('selected', 'editable')) | ||
def _update_proxy(self, change): | ||
""" An observer which sends state change to the proxy. | ||
""" | ||
# The superclass handler implementation is sufficient. | ||
super(ObjectCombo, self)._update_proxy(change) |