Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/Experior.Activity/sbgui.py
blob: 3cf2c774a81140749fb997d85c300a896bbc38e1 (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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
#! /usr/bin/env python
# -*- encoding: utf-8 -*-

#sbgui.py

#This file is part of sugarbot.

#sugarbot 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 3 of the License, or
#(at your option) any later version.

#sugarbot 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 sugarbot.  If not, see <http://www.gnu.org/licenses/>.

import gobject
import os
import pygtk
import sys
import time
pygtk.require('2.0')
import gtk
import logging
import sugar

from gobject            import idle_add
from gtk                import gdk
from gtk.gdk            import event_handler_set
from sbexecutionengine  import sbExecutionEngine
from sugar              import graphics
from sugar.graphics.toolbutton import Palette
from widgetIdentifier   import *

class sbGUI(gobject.GObject):
    """
    Responsible for tracking all identifiable* widgets instantiated by GTK.

    *Identifiable widgets are widgets for which a valid identifier can be
    found, via any of the identifiers enumerated by widgetIdentifier.identifiers.
    """
    def __init__(self,sugarbotInstance,rpcServer):
        """
        Initialize internal variables, and register self as an event handler.
        """
        # Necessary, due to inheritance
        gobject.GObject.__init__(self)

        # Get the RPC server connection and the sugarbot instance, as well
        # as the execution engine.
        self.sugarbot   = sugarbotInstance
        self.rpc        = rpcServer
        self.engine     = sbExecutionEngine(self,self.rpc)
        self.log        = logging.getLogger('sbGUI')

        # Keep track of the amount of idle time...
        self.idletime   = 0
        self.idletimeout= 6

        # In order to keep track of all of the windows, we set up a
        # dict of Windows.  For each window, the key is its id (as provided
        # by id(theWindowObject).  The value of each dictionary entry is a
        # dictionary of Widgets, set up the same way -- the id() of the
        # widget is the key, and the
        self.trackedWidgets = {}    # Maps id(object) -> widget
        self.names = {}             # Maps identifier -> widget

        # Register one of our methods to intercept all GTK events
        self.registerEventHandler()


    def eventHandler(self,event=None):
        """ Intercepts all GDK events.  We then send them off to a separate
        handler method (to keep things clean), and then have GTK execute
        whatever the event is supposed to do.
        """
        if event is not None:
            gtk.main_do_event(event)
            self.handleEvent(event)

        if event.type is not gtk.gdk.EXPOSE:
            self.idletime = 0

        return True


    def getWidgetIdentifier(self,widget):
        """
        Returns the Widget's unique identifier (for example, a button's
        label, or a gkt.Entry's name), or None if it does not have one.
        Also, this function filters out many uninitialized identifiers,
        such as "GtkToolbar" or "GtkButton"
        @param widget - The widget whose identifier is to be retrieved.
        """
        if not isinstance(widget,gtk.Widget):
            raise "_getWidgetName must take a gtk.Widget object as its argument"

        # ---- GENERIC APPROACH ----
        # Assuming that the widget was named explicitly by the developers,
        # getting the name should be very straightforward, with no specialized
        # cases or special name-detecting.
        widId = widgetIdentifier(widget)
        ident = widId.getIdentifier()

        if ident is not None:
            return ident

        # ---- SPECIALIZED APPROACH ----
        # Check to see if we have an identifier for the specific type
        # of widget before we iterate through all of the different identifiers
        # hoping to get a hit.
        if identifiers.has_key(widget.__class__):
            widId = identifiers[widget.__class__](widget)
            ident = widId.getIdentifier()

            if ident is not None:
                return ident

        # ---- BRUTE FORCE ----
        # The widget was not named explicitly by the developers, and we do
        # not have a case for the specific widget class.
        # Iterate through all of our potential identifiers, since we
        # very likely have a widget class that it inherits from.
        # This method is undesirable, since the identifiers in the dictionary
        # widgetIdentifiers may be in a different order each time the
        # program is run, due to the non-ordered nature of dictionaries.
        for identifier in identifiers:
            if isinstance(widget, identifier):
                widId = identifiers[identifier](widget)
                ident = widId.getIdentifier()

                if ident is not None:
                    break

        # At this point, we either have a valid widget identifier, or None.
        return ident


    def addWidget(self,widget):
        """
        Add a widget to be tracked internally.  Widgets are tracked by their
        id().  Additionally, add it to the names{} dictionary, so that we
        can quickly look up widgets by the name.
        """

        # Don't do anything if we already have this widget by ID
        if self.trackedWidgets.has_key(id(widget)):
            return

        # Make sure we are working on a widget
        if not isinstance(widget, gtk.Widget):
            return

        # Containers might have children.  Check them all.
        if isinstance(widget, gtk.Container):       # gtk.Container can have
            for child in widget.get_children():     # any number of children
                self.addWidget(child)

        if isinstance(widget, gtk.Bin):             # gtk.Bin can only have
            self.addWidget(widget.get_child())      # one child.

        if isinstance(widget, gtk.Notebook):        # gtk.Notebook can have
            numPages = widget.get_n_pages()         # many children.
            for count in range(0, numPages):
                page = widget.get_nth_page(count)
                self.addWidget(page)
            return


        # Get the widget's identifier & id.  If the widget cannot be reliably
        # identified, there is no use in tracking it.
        identifier  = self.getWidgetIdentifier(widget)
        _id         = id(widget)
        if identifier is None:
            return

        # Simply keep track of the widgets by ID
        self.trackedWidgets[_id]=widget

        # Keep track of the widgets by identifier.
        # Check to see if it's already being tracked.
        if self.names.has_key(identifier):
            raise KeyError, "Already tracking a widget by identifier %s" \
                                % identifier

        # Track the little bugger
        else:
            self.log.debug("Tracking widget id %i by identifier %s" % (_id, identifier))
            self.names[identifier] = widget


    def delWidget(self,widget):
        """
        Remove a widget from internal tracking.  Widgets can be removed by
        their id() or by the gtk.Widget object.

        TODO: I don't think this function ever gets called.
        """
        if not isinstance(widget, gtk.Widget):
            raise "Called delWidget on non-Widget object"
            return

        # Get id(widget)
        identifier  = self.getWidgetIdentifier(widget)
        _id         = id(widget)

        # Remove the widget from the trackedWidgets dict if it is tracked.
        if self.trackedWidgets.has_key(_id):
            del self.trackedWidgets[_id]
            del self.names[identifier]

        else:    # Tried to call delWidget on non-tracked Widget
            return

    def getWidgetByName(self,widgetName):
        """
        Checks to see if we are tracking a Widget with a given name.
        If so, return the Widget.  If not, return None.
        """
        if widgetName in self.names:
            return self.names[widgetName]
        return None

    def handleEvent_firehose(self,event):
        """
        For exploratory testing.  Don't bother trying to figure out what this
        does, it will change without notification.
        Pretty much just outputs a firehose of events.  Useful for exploratory
        testing, and that's pretty much it.
        """
        if not hasattr(self,'classes'):
            self.classes = []

        eventType = event.type
        print "----------------------------------------"
        print event.type
        if event.window:
            # print event.window
            try:
                widget = event.window.get_user_data()
                print widget.__class__
                self.classes[widget.__class__] = 1
            except:
                raise
        print "----------------------------------------"
        print ""
        print ""

    def handleEvent(self,event):
        """
        Handles all GDK events.  We first filter them so that we know
        they pertain to a window, and then we further drill down based on the
        type of action.  This method is used to achieve two goals:
        [1] Build a database of all windows and widgets
        [2] Allow us to see actions as they happen.  This could lead to
            recording functionality in the future.
        """
        try:
            # Does the event have a Window that it belongs to?
            if (not event.window):
                return

            # Get some information on the widget.  If it doesn't work, just
            # gracefully fail.  Exceptions happen with the following events:
            # (maybe more, but these are what I've observed):
            # - GDK_OWNER_CHANGE
            widget = event.window.get_user_data()
            eventType = event.type

            # -------- HANDLE WIDGET INSTANTIATION --------
            # MAP events are generated when a widget is initially displayed
            # on the screen.  In most cases, any naming or configuration that
            # is going to be performed *has been* performed.
            if eventType == gdk.MAP:
                self.addWidget(widget)

            # -------- HANDLE WIDGET DESTRUCTION --------
            # UNMAP events are generated when a widget
            # is being taken off the screen.  Generally, this will happen at
            # the end of an application's execution.  However, to maintain
            # flexibility (e.g. the possibility of dialog windows), handle
            # the UNMAP event here.
            elif eventType == gdk.UNMAP:
                self.delWidget(widget)
        except ValueError:
            pass
        except: # Oops!
            raise

    def idleHandler(self, event=None):

        if self.engine.isComplete():
            return False

        if self.idletime is 0:
            self.idletime = time.time()
            self.lasttime = self.idletime

        if self.lasttime + 1 < time.time():
            self.lasttime += 1

        if self.idletime + self.idletimeout < time.time():
            self.engine.executePy()
            self.idletime = 0

        return True

    def registerEventHandler(self):
        """
        Registers the method self.eventHandler as the function that
        will receive all GDK events.  This allows us to snoop on GDK.
        """
        if not event_handler_set:
            raise NotImplementedError
        else:
            gobject.idle_add(self.idleHandler)
            event_handler_set(self.eventHandler)