1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
|
"""
This module exposes a common pattern on developing HIG applications, you often
have more then one condtions affecting a widget's sensitive state, worst
sometimes these conditions are not centralised and may even be created by
plugins. To help solve this problem you can use a
L{SensitiveController} or a L{SignalBind}.
"""
__license__ = "MIT <http://www.opensource.org/licenses/mit-license.php>"
__author__ = "Tiago Cogumbreiro <cogumbreiro@users.sf.net>"
__copyright__ = "Copyright 2005, Tiago Cogumbreiro"
import gobject
import weakref
class SensitiveClient:
"""
The L{SensitiveClient} can affect the widget's target state by changing the
method L{SensitiveClient.set_sensitive}.
When no references exist to this client it's unregistred from its
controller.
"""
def __init__(self, counter):
self.counter = counter
self._sensitive = True
def set_sensitive(self, sensitive):
"""
Set this client's sensitive state.
"""
if self._sensitive == sensitive:
return
self._sensitive = sensitive
if sensitive:
self.counter.dec()
else:
self.counter.inc()
def __del__(self):
# unregister from parent
self.counter.dec()
class _Counter:
"""
The Counter object uses a weakref to the callback, so if you by any chance
loose its reference the Counter object will no longer use it.
"""
def __init__(self, callback = None):
self.__count = 0
if self.__callback is not None:
self.__callback = callback
def __callback(self, amount):
"""This is a no-op"""
def inc(self):
self.__count += 1
self.__callback(self.__count)
def dec(self):
self.__count -= 1
self.__callback(self.__count)
class SensitiveController:
"""
The L{SensitiveController} is the class responsible for maintaining
the widget sensitive state. Whenever you want to add a new condition that
affects your widget you create a new client and then use that client as if
it was your condition::
lbl = gtk.Label("My widget")
cnt = SensitiveController(lbl)
client = cnt.create_client()
client.set_sensitive(len(lbl.get_text()) > 0)
If you create more clients in your controller your widget will only be
sensitive when B{all} its clients are set to C{True}, if one is set to
insensitive the widget will be insensitive as well.
When this object has no references back it will make the widget sensitive.
"""
class _Callback:
def __init__(self, widget):
self.widget = widget
def callback(self, counter):
assert counter >= 0
self.widget.set_sensitive(counter <= 0)
def __init__(self, widget):
self.widget = widget
cb = self._Callback(widget)
self.__counter = _Counter(cb.callback)
widget.set_sensitive(True)
def __on_change(self, counter):
assert counter >= 0
self.widget.set_sensitive(counter <= 0)
def create_client(self):
"""
It will create one more client to the controller.
@rtype: L{SensitiveClient}
"""
return SensitiveClient(self.__counter)
def __del__(self):
self.widget.set_sensitive(True)
class SignalBind(object):
"""
The L{SignalBind} helps you connect a signal from a widget that
will affect the sensitive state of your target widget. For example if we want
a button the be sensitive only when a text entry has some text in it we do the
following::
btn = gtk.Button()
cnt = rat.sensitive.SensitiveController(btn)
entry = gtk.Entry()
sig_bind = rat.sensitive.SignalBind(cnt)
sig_bind.bind(entry, "text", "changed", lambda txt: len(txt) > 0)
Summing it up, the L{SignalBind} connects a property and a signal
of a certain widget to a controller.
Reference counting is thought of, this means that if the L{SignalBind}
has not references back to it it will call the L{SignalBind.unbind} method.
"""
class _Callback:
"""
The callback object is used to remove circular dependencies.
It exists for private use only.
"""
def __init__(self, affecter, property, condition, client):
self.property = property
self.condition = condition
self.client = weakref.ref(client)
self.__call__(affecter)
def __call__(self, src, *args):
value = self.condition(src.get_property(self.property))
self.client().set_sensitive(value)
def __init__(self, controller):
self.controller = controller
self.source = None
self.client = None
def bind(self, affecter, property, signal, condition):
"""
Connects the affecter through a certain signal to the sensitive binder.
@param affecter: the widget that has a certain property, which will be
triggered by a certain signal.
@param property: the property which will be evaluated by the condition
when the signal is called
@param signal: the signal that is triggered when the property is changed
@param condition: the condition is a function that accepts one value
which is the property's value.
"""
assert self.source is None, "call unbind() before bind()"
self.client = self.controller.create_client()
cb = self._Callback(affecter, property, condition, self.client)
self.source = affecter.connect(signal, cb.__call__)
def unbind(self):
"""
Calling the unbind method will remove the L{SensitiveController}
registration and the source associated with the signal it's
listening to.
"""
assert self.source is not None, "bind() must be called before unbind()"
assert self.client is not None, "There's a bug in this program"
gobject.source_remove(self.source)
self.source = None
self.client = None
def __del__(self):
if self.source is not None:
self.unbind()
|