agronholm / jython-swingutils (http://pypi.python.org/pypi/jython-swingutils/)

An assortment of utility classes and functions that ease the task of writing GUI applications with the Java Swing toolkit.

commit 67: c6f4e2cfec82
parent 66: 4a42cd901944
branch: default
* got rid of weak references * added property names in PropertyAdapter * prevent property event loopback in PropertyAdapter * require value holders as sources when binding components * automatically disable components when the value holder is empty
Alex Grönholm / agronholm
4 months ago

Changed (Δ1.0 KB):

raw changeset »

swingutils/binding.py (101 lines added, 74 lines removed)

Up to file-list swingutils/binding.py:

@@ -6,7 +6,7 @@ using weak references, and automatically
6
6
garbage collected.
7
7
8
8
"""
9
import weakref
9
from types import MethodType
10
10
11
11
from java.beans import PropertyChangeListener
12
12
from java.awt.event import ItemListener, FocusListener
@@ -16,7 +16,7 @@ from swingutils.beans import JavaBeanSup
16
16
from swingutils.events import addPropertyListener
17
17
18
18
__all__ = ('ValueHolder', 'bindProperty', 'bindCheckbox', 'bindComboBox',
19
           'bindTextComponent', 'bindFormattedTextField')
19
           'bindTextComponent')
20
20
21
21
22
22
class ValueHolder(JavaBeanSupport):
@@ -41,6 +41,9 @@ class ValueHolder(JavaBeanSupport):
41
41
            object.__setattr__(self, name, value)
42
42
        else:
43
43
            setattr(self._value, name, value)
44
    
45
    def __nonzero__(self):
46
        return self._value is not None
44
47
45
48
    def _getvalue(self):
46
49
        return self._value
@@ -49,6 +52,9 @@ class ValueHolder(JavaBeanSupport):
49
52
        self.firePropertyChange(event.propertyName, event.oldValue,
50
53
                                event.newValue)
51
54
55
    def _getProperties(self, value):
56
        return [p for p in dir(value) if not p.startswith('_')]
57
52
58
    def _setvalue(self, newValue):
53
59
        oldValue = self._value
54
60
        if oldValue and self.__wrapper:
@@ -63,14 +69,16 @@ class ValueHolder(JavaBeanSupport):
63
69
64
70
        # Notify listeners of changes in all properties, including properties
65
71
        # from both the old and new values
66
        oldProps = set([p for p in dir(oldValue) if not p.startswith('_')])
67
        newProps = set([p for p in dir(newValue) if not p.startswith('_')])
72
        oldProps = set(self._getProperties(oldValue))
73
        newProps = set(self._getProperties(newValue))
68
74
        properties = oldProps.union(newProps)
69
75
        properties.discard('value')
70
76
        for property in properties:
71
77
            oldVal = getattr(oldValue, property, None)
72
78
            newVal = getattr(newValue, property, None)
73
            self.firePropertyChange(property, oldVal, newVal)
79
            if not isinstance(oldVal, MethodType) and not \
80
                isinstance(newVal, MethodType):
81
                self.firePropertyChange(property, oldVal, newVal)
74
82
75
83
    value = property(_getvalue, _setvalue)
76
84
@@ -82,25 +90,42 @@ class ValueHolder(JavaBeanSupport):
82
90
83
91
84
92
class PropertyAdapter(PropertyChangeListener):
85
    def __init__(self, source, destination, converter, backConverter):
86
        self.source = weakref.ref(source, self.disconnect)
87
        self.destination = weakref.ref(destination, self.disconnect)
93
    def __init__(self, source, srcProperty, destination, dstProperty,
94
                 converter, backConverter):
95
        self.source = source
96
        self.srcProperty = srcProperty
97
        self.destination = destination
98
        self.dstProperty = dstProperty
88
99
        self.converter = converter
89
100
        self.backConverter = backConverter
101
        self._propertyChangeInProgress = False
90
102
91
103
    def propertyChange(self, event):
92
        src = self.source()
93
        dst = self.destination()
104
        if self._propertyChangeInProgress:
105
            return
106
        self._propertyChangeInProgress = True
107
108
        src = self.source
109
        srcProperty = self.srcProperty
110
        dst = self.destination
111
        dstProperty = self.dstProperty
94
112
        converter = self.converter
95
113
        if event.source is dst:
96
114
            dst = src
97
            src = self.destination()
115
            src = self.destination
116
            srcProperty = self.dstProperty
117
            dstProperty = self.srcProperty
98
118
            converter = self.backConverter
99
        value = getattr(src, event.propertyName)
100
        value = converter(value)
101
        setattr(dst, event.propertyName, value)
119
        
120
        try:
121
            value = getattr(src, srcProperty)
122
            if converter:
123
                value = converter(value)
124
            setattr(dst, dstProperty, value)
125
        finally:
126
            self._propertyChangeInProgress = False
102
127
103
    def disconnect(self, ref=None):
128
    def disconnect(self):
104
129
        src = self.source()
105
130
        if src:
106
131
            src.removePropertyChangeListener(self)
@@ -111,88 +136,88 @@ class PropertyAdapter(PropertyChangeList
111
136
112
137
113
138
class CheckBoxAdapter(PropertyChangeListener, ItemListener):
114
    def __init__(self, source, srcProperty, checkBox, converter,
139
    def __init__(self, holder, srcProperty, checkBox, converter,
115
140
                 backConverter):
116
        self.source = weakref.ref(source, self.disconnect)
141
        self.holder = holder
117
142
        self.srcProperty = srcProperty
118
        self.checkBox = weakref.ref(checkBox, self.disconnect)
143
        self.checkBox = checkBox
119
144
        self.converter = converter
120
145
        self.backConverter = backConverter
121
146
122
147
    def propertyChange(self, event):
123
        src = self.source()
148
        src = self.holder()
124
149
        checkBox = self.checkBox()
125
150
        value = getattr(src, self.srcProperty)
126
151
        value = self.converter(value)
127
152
        checkBox.selected = value
153
        self.checkBox.enabled = self.holder.value is not None
128
154
129
155
    def stateChanged(self, event):
130
        src = self.source()
156
        src = self.holder()
131
157
        checkBox = self.checkBox()
132
158
        value = self.backConverter(checkBox.selected)
133
159
        setattr(src, self.srcProperty, value)
134
160
135
    def disconnect(self, ref=None):
136
        src = self.source()
137
        if src:
138
            src.removePropertyChangeListener(self)
139
140
        checkBox = self.checkBox()
141
        if checkBox:
142
            checkBox.removeItemListener(self)
161
    def disconnect(self):
162
        self.holder.removePropertyChangeListener(self)
163
        self.checkBox.removeItemListener(self)
143
164
144
165
145
166
class ComboBoxAdapter(PropertyChangeListener, ItemListener):
146
    def __init__(self, source, srcProperty, comboBox, converter,
167
    def __init__(self, holder, srcProperty, comboBox, converter,
147
168
                 backConverter):
148
        self.source = weakref.ref(source, self.disconnect)
169
        self.holder = holder
149
170
        self.srcProperty = srcProperty
150
        self.comboBox = weakref.ref(comboBox, self.disconnect)
171
        self.comboBox = comboBox
151
172
        self.converter = converter
152
173
        self.backConverter = backConverter
153
174
154
175
    def propertyChange(self, event):
155
        src = self.source()
156
        combo = self.comboBox()
157
        value = getattr(src, self.srcProperty)
176
        value = getattr(self.holder, self.srcProperty)
158
177
        value = self.converter(value)
159
        combo.selectedItem = value
178
        self.comboBox.selectedItem = value
179
        self.comboBox.enabled = self.holder.value is not None
160
180
161
181
    def stateChanged(self, event):
162
182
        value = self.backConverter(event.item)
163
        setattr(self.source, self.srcProperty, value)
183
        setattr(self.holder, self.srcProperty, value)
164
184
165
    def disconnect(self, ref=None):
166
        src = self.source()
167
        if src:
168
            src.removePropertyChangeListener(self)
169
170
        combo = self.comboBox()
171
        if combo:
172
            combo.removeItemListener(self)
185
    def disconnect(self):
186
        self.holder.removePropertyChangeListener(self)
187
        self.comboBox.removeItemListener(self)
173
188
174
189
175
190
class TextComponentAdapter(PropertyChangeListener, FocusListener):
176
    def __init__(self, source, srcProperty, textComponent):
177
        self.source = weakref.ref(source, self.disconnect)
191
    def __init__(self, holder, srcProperty, textComponent):
192
        self.holder = holder
178
193
        self.srcProperty = srcProperty
179
        self.textComponent = weakref.ref(textComponent, self.disconnect)
194
        self.textComponent = textComponent
180
195
181
196
    def propertyChange(self, event):
182
        src = self.source()
183
        textComponent = self.textComponent()
184
        value = getattr(src, self.srcProperty)
185
        textComponent.text = value
197
        self.textComponent.text = event.newValue
198
        self.textComponent.enabled = self.holder.value is not None
186
199
187
200
    def focusLost(self, event):
188
        src = self.source()
189
        textComponent = self.textField()
190
        setattr(src, self.srcProperty, textComponent.text)
201
        setattr(self.holder, self.srcProperty, self.textComponent.text)
202
203
    def disconnect(self):
204
        self.holder.removePropertyChangeListener(self)
205
        self.textComponent.removeFocusListener(self)
206
207
208
class FormattedTextFieldAdapter(PropertyAdapter):
209
    def __init__(self, holder, srcProperty, textField):
210
        PropertyAdapter.__init__(self, holder, srcProperty, textField, 'value',
211
                                 None, None)
212
213
    def propertyChange(self, event):
214
        PropertyAdapter.propertyChange(self, event)
215
        self.destination.enabled = self.source.value is not None
191
216
192
217
193
218
def bindProperty(source, srcProperty, destination, dstProperty,
194
                 twoway=False, syncnow=False, converter=lambda v: v,
195
                 backConverter=lambda v: v):
219
                 twoway=False, syncnow=False, converter=None,
220
                 backConverter=None):
196
221
    """
197
222
    Connects a property in the source object to a property in the destination
198
223
    object. When the source property changes, the destination property is set
@@ -227,7 +252,8 @@ def bindProperty(source, srcProperty, de
227
252
        assert hasattr(destination, 'addPropertyChangeListener'), \
228
253
            'destination object has no addPropertyChangeListener method'
229
254
230
    adapter = PropertyAdapter(source, destination, converter, backConverter)
255
    adapter = PropertyAdapter(source, srcProperty, destination, dstProperty,
256
                              converter, backConverter)
231
257
    source.addPropertyChangeListener(srcProperty, adapter)
232
258
    if twoway:
233
259
        destination.addPropertyChangeListener(dstProperty, adapter)
@@ -239,41 +265,42 @@ def bindProperty(source, srcProperty, de
239
265
    return adapter
240
266
241
267
242
def bindCheckbox(source, srcProperty, checkBox, twoway=True):
268
def bindCheckbox(holder, srcProperty, checkBox, twoway=True):
243
269
    """
244
270
    Binds an object to a check box.
245
271
    
246
272
    :param checkBox: the check box to bind to
247
273
    :param twoway: `True` if changes in the check box state should also reflect
248
                   in `source`
274
                   in `holder`
249
275
    :type checkBox: :class:`javax.swing.JCheckBox`
250
276
    
251
277
    """
252
    adapter = CheckBoxAdapter(source, srcProperty, checkBox)
253
254
    source.addPropertyChangeListener(srcProperty, adapter)
278
    adapter = CheckBoxAdapter(holder, srcProperty, checkBox)
279
    holder.addPropertyChangeListener(srcProperty, adapter)
255
280
    if twoway:
256
281
        checkBox.addItemListener(adapter)
282
    checkBox.enabled = holder.value is not None
257
283
258
284
259
def bindComboBox(source, srcProperty, comboBox, twoway=True,
260
                 converter=lambda v: v, backConverter=lambda v: v):
261
    adapter = ComboBoxAdapter(source, srcProperty, comboBox, converter,
285
def bindComboBox(holder, srcProperty, comboBox, twoway=True,
286
                 converter=None, backConverter=None):
287
    adapter = ComboBoxAdapter(holder, srcProperty, comboBox, converter,
262
288
                              backConverter)
263
    source.addPropertyChangeListener(srcProperty, adapter)
289
    holder.addPropertyChangeListener(srcProperty, adapter)
264
290
    if twoway:
265
291
        comboBox.addItemListener(adapter)
292
    comboBox.enabled = holder.value is not None
266
293
267
294
268
def bindTextComponent(source, srcProperty, textComponent, twoway=True):
295
def bindTextComponent(holder, srcProperty, textComponent, twoway=True):
269
296
    if isinstance(textComponent, JFormattedTextField):
270
        bindProperty(source, srcProperty, textComponent, 'value', twoway)
297
        adapter = FormattedTextFieldAdapter(holder, srcProperty, textComponent)
298
        holder.addPropertyChangeListener(srcProperty, adapter)
299
        if twoway:
300
            textComponent.addPropertyChangeListener('value', adapter)
271
301
    else:
272
        adapter = TextComponentAdapter(source, srcProperty, textComponent)
273
        source.addPropertyChangeListener(srcProperty, adapter)
302
        adapter = TextComponentAdapter(holder, srcProperty, textComponent)
303
        holder.addPropertyChangeListener(srcProperty, adapter)
274
304
        if twoway:
275
305
            textComponent.addFocusListener(adapter)
276
277
278
def bindFormattedTextField(source, srcProperty, textField, twoway=True):
279
    bindProperty(source, srcProperty, textField, 'value', twoway)
306
    textComponent.enabled = holder.value is not None