Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src/sugar/tutorius/filters.py
blob: 594ad6a82599ba01ef45faf1e0420eeb21157294 (plain)
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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# Copyright (C) 2009, Tutorius.org
# Copyright (C) 2009, Vincent Vinet <vince.vinet@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

import gobject
import gtk
import logging
logger = logging.getLogger("filters")

from sugar.tutorius.gtkutils import find_widget
from sugar.tutorius.services import ObjectStore


class EventFilter(object):
    """
    Base class for an event filter
    """
    def __init__(self, next_state):
        """
        Constructor.
        @param next_state name of the next state
        """
        self._next_state = next_state
        self._callback = None

    def get_next_state(self):
        """
        Getter for the next state
        """
        return self._next_state

    def set_next_state(self, new_next_name):
        """
        Setter for the next state. Should only be used during construction of
        the event_fitler, not while the tutorial is running.
        """
        self._next_state = new_next_name
        
    next_state = property(fget=get_next_state, fset=set_next_state)

    def install_handlers(self, callback, **kwargs):
        """
        install_handlers is called for eventfilters to setup all
        necessary event handlers to be able to catch the desired
        event. 

        @param callback the callback function that will be called
        with the event filter as an argument when the event is catched
        and validated.
        @param **kwargs unused by this handler for now, allows subclasses
        to receive information about the context when installing

        Subclasses must call this super method to setup the callback if they 
        feel like cooperating
        """
        self._callback = callback

    def remove_handlers(self):
        """
        remove_handlers is called when a state is done so that all
        event filters can cleanup any handlers they have installed

        This function will also clear the callback function so that any 
        leftover handler that is triggered will not be able to change the
        application state.

        subclasses must call this super method to cleanup the callback if they
        collaborate and use this classe's do_callback()
        """
        self._callback = None

    def do_callback(self, *args, **kwargs):
        """
        Default callback function that calls the event filter callback
        with the event filter as only argument.
        """
        if self._callback:
            self._callback(self)

class TimerEvent(EventFilter):
    """
    TimerEvent is a special EventFilter that uses gobject
    timeouts to trigger a state change after a specified amount
    of time. It must be used inside a gobject main loop to work.
    """
    def __init__(self,next_state,timeout_s):
        """Constructor.

        @param next_state default EventFilter param, passed on to EventFilter
        @param timeout_s timeout in seconds
        """
        super(TimerEvent,self).__init__(next_state)
        self._timeout = timeout_s
        self._handler_id = None

    def install_handlers(self, callback, **kwargs):
        """install_handlers creates the timer and starts it"""
        super(TimerEvent,self).install_handlers(callback, **kwargs)
        #Create the timer
        self._handler_id = gobject.timeout_add_seconds(self._timeout, self._timeout_cb)

    def remove_handlers(self):
        """remove handler removes the timer"""
        super(TimerEvent,self).remove_handlers()
        if self._handler_id:
            try:
                #XXX What happens if this was already triggered?
                #remove the timer
                gobject.source_remove(self._handler_id)
            except:
                pass

    def _timeout_cb(self):
        """
        _timeout_cb triggers the eventfilter callback.

        It is necessary because gobject timers only stop if the callback they
        trigger returns False
        """
        self.do_callback()
        return False #Stops timeout

class GtkWidgetEventFilter(EventFilter):
    """
    Basic Event filter for Gtk widget events
    """
    def __init__(self, next_state, object_id, event_name):
        """Constructor
        @param next_state default EventFilter param, passed on to EventFilter
        @param object_id object fqdn-style identifier
        @param event_name event to attach to
        """
        super(GtkWidgetEventFilter,self).__init__(next_state)
        self._callback = None
        self._object_id = object_id
        self._event_name = event_name
        self._widget = None
        self._handler_id = None
    
    def install_handlers(self, callback, **kwargs):
        """install handlers
        @param callback default EventFilter callback arg
        @param activity keyword argument activity must be present to install
            the event handler into the activity's widget hierarchy
        """
        super(GtkWidgetEventFilter, self).install_handlers(callback, **kwargs)
        if not "activity" in kwargs:
            raise TypeError("activity argument is Mandatory")
        
        #find the widget and connect to its event
        self._widget = find_widget(kwargs["activity"], self._object_id)
        self._handler_id = self._widget.connect( \
                        self._event_name, self.do_callback )

    def remove_handlers(self):
        """remove handlers"""
        super(GtkWidgetEventFilter, self).remove_handlers()
        #if an event was connected, disconnect it
        if self._handler_id:
            self._widget.handler_disconnect(self._handler_id)
            self._handler_id=None

class GtkWidgetTypeFilter(EventFilter):
    """
    Event Filter that listens for keystrokes on a widget
    """
    def __init__(self, next_state, object_id, text=None, strokes=None):
        """Constructor
        @param next_state default EventFilter param, passed on to EventFilter
        @param object_id object tree-ish identifier
        @param text resulting text expected
        @param strokes list of strokes expected

        At least one of text or strokes must be supplied
        """
        super(GtkWidgetTypeFilter, self).__init__(next_state)
        self._object_id = object_id
        self._text = text
        self._captext = ""
        self._strokes = strokes
        self._capstrokes = []
        self._widget = None
        self._handler_id = None

    def install_handlers(self, callback, **kwargs):
        """install handlers
        @param callback default EventFilter callback arg
        """
        super(GtkWidgetTypeFilter, self).install_handlers(callback, **kwargs)
        logger.debug("~~~GtkWidgetTypeFilter install")
        activity = ObjectStore().activity
        if activity is None:
            logger.error("No activity")
            raise RuntimeWarning("no activity in the objectstore")

        self._widget = find_widget(activity, self._object_id)
        if self._widget:
            self._handler_id= self._widget.connect("key-press-event",self.__keypress_cb)
            logger.debug("~~~Connected handler %d on %s" % (self._handler_id,self._object_id) )

    def remove_handlers(self):
        """remove handlers"""
        super(GtkWidgetTypeFilter, self).remove_handlers()
        #if an event was connected, disconnect it
        if self._handler_id:
            self._widget.handler_disconnect(self._handler_id)
            self._handler_id=None

    def __keypress_cb(self, widget, event, *args):
        """keypress callback"""
        logger.debug("~~~keypressed!")
        key = event.keyval
        keystr = event.string
        logger.debug("~~~Got key: " + str(key) + ":"+ keystr)
        self._capstrokes += [key]
        #TODO Treat other stuff, such as arrows
        if key == gtk.keysyms.BackSpace:
            self._captext = self._captext[:-1]
        else:
            self._captext = self._captext + keystr

        logger.debug("~~~Current state: " + str(self._capstrokes) + ":" + str(self._captext))
        if not self._strokes is None and self._strokes in self._capstrokes:
            self.do_callback()
        if not self._text is None and self._text in self._captext:
            self.do_callback()