/
qtAdaptor.py
339 lines (266 loc) · 12.4 KB
/
qtAdaptor.py
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
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
"""
Adapt storytext to Qt
"""
# import logging
import traceback
# Generic (over toolkits) storytext
import storytext.guishared # , storytext.replayer
# Qt specialized submodules (similar to implementations for other GUI TK's.)
import widgetadapter
import describer
# interception modules
# import treeviewextract
import simulator
# For now, unique to Qt adaption
from idleHandlerSet import HandlerSet
from PySide.QtCore import QCoreApplication
# from ordereddict import OrderedDict
# modules that intercept widget factory
interceptionModules = [ simulator ] ## , treeviewextract ]
PRIORITY_STORYTEXT_IDLE = describer.PRIORITY_STORYTEXT_IDLE
class QtScriptEngine(storytext.guishared.ScriptEngine):
eventTypes = simulator.eventTypes
'''
map happeningName to a displayable description.
Accessed by guishared.getDisplayName().
happeningName must correspond to row in simulator.eventTypes.
happeningName must be name of a Qt signal or name of a Qt handler method for a QEvent
'''
signalDescs = {
"closeEvent": "window closed",
"clicked" : "button clicked",
"mouseMoveEvent" : "mouse moved",
"mousePressEvent" : "mouse button pressed",
}
# "destroyed" : "window destroyed",
columnSignalDescs = {
"toggled.true": "checked box in column",
"toggled.false": "unchecked box in column",
"edited": "edited cell in column",
"clicked": "clicked column header"
}
def __init__(self, universalLogging=True, **kw):
print "Qt ScriptEngine init"
# self.logger = logging.getLogger("qt engine")
storytext.guishared.ScriptEngine.__init__(self, universalLogging=universalLogging, **kw)
describer.setMonitoring(universalLogging)
if self.uiMap or describer.isEnabled():
self.performInterceptions()
print "Qt ScriptEngine init returns"
def setSUTReady(self):
'''
Called by SUT (harnessed) after event loop created.
Here we do things that depend on existence of event loop
(some Qt methods depend, few GTK methods depend.)
'''
print "SE.setSUTReady"
self.replayer.handlers.captureApplicationAndStartDelayedIdleHandlers()
self.uiMap.doSUTReady()
def createUIMap(self, uiMapFiles):
if uiMapFiles:
return simulator.UIMap(self, uiMapFiles)
def addUiMapFiles(self, uiMapFiles):
if self.uiMap:
self.uiMap.readFiles(uiMapFiles)
else:
self.uiMap = simulator.UIMap(self, uiMapFiles)
if self.replayer:
self.replayer.addUiMap(self.uiMap)
if not describer.isEnabled():
self.performInterceptions()
def performInterceptions(self):
print "Qt ScriptEngine performInterceptions"
eventTypeReplacements = {}
for mod in interceptionModules:
eventTypeReplacements.update(mod.performInterceptions())
for index, (widgetClass, currEventClasses) in enumerate(self.eventTypes):
if widgetClass in eventTypeReplacements:
self.eventTypes[index] = eventTypeReplacements[widgetClass], currEventClasses
def createReplayer(self, universalLogging=False):
print "createReplayer"
return UseCaseReplayer(self.uiMap, universalLogging, self.recorder)
def _createSignalEvent(self, eventName, signalName, widget, argumentParseData):
# print "_createSignalEvent", eventName, signalName, widget
stdSignalName = signalName.replace("_", "-")
'''
lkk ??? I don't understand why iterate over classes.
It seems to me that each row in simulator.eventTypes is a possible event for a class,
but we don't need to iterate over over all rows that the widget is a class of,
we just need the one row whose class matches the widget class and whose signalName matches passed signalName.
'''
eventClasses = self.findEventClassesFor(widget) + simulator.universalEventClasses
# print "Event classes for widget", eventClasses
for eventClass in eventClasses:
if eventClass.canHandleEvent(widget, stdSignalName, argumentParseData):
# Call class factory and return instance
return eventClass(eventName, widget, argumentParseData)
"""
print "FallbackEventClass for happening", stdSignalName
# lkk Why???
if simulator.fallbackEventClass.widgetHasHappeningSignature(widget, stdSignalName):
return simulator.fallbackEventClass(eventName, widget, stdSignalName)
"""
'''
lkk If we get here, it is not a programming error,
since simulator.eventTypes is just a spec of (widgetClass, event) pairs that storytext
is equipped to handle, but in some toolkits, event handlers are optional?
For this Qt adaptor, all rows in simulator.eventTypes should succeed?
'''
print "Programming error? _createSignalEvent returns None", eventClass
def getDescriptionInfo(self):
return "PyGTK", "gtk", "signals", "http://library.gnome.org/devel/pygtk/stable/class-gtk"
def addSignals(self, classes, widgetClass, currEventClasses, module):
print "addSignals"
try:
widget = widgetadapter.WidgetAdapter(widgetClass())
except:
widget = None
signalNames = set()
for eventClass in currEventClasses:
try:
className = self.getClassName(eventClass.getClassWithSignal(), module)
classes[className] = [ eventClass.signalName ]
except:
if widget:
signalNames.add(eventClass.getAssociatedSignal(widget))
else:
signalNames.add(eventClass.signalName)
className = self.getClassName(widgetClass, module)
classes[className] = sorted(signalNames)
def getSupportedLogWidgets(self):
print "getSupported"
return describer.Describer.supportedWidgets
# Use Qt idle handlers instead of a separate thread for replay execution
class UseCaseReplayer(storytext.guishared.IdleHandlerUseCaseReplayer):
def __init__(self, *args):
print "Qt UCR init"
self.handlers = HandlerSet()
self.startedIdleDescriber = False # Counter to prevent describing twice?
# Call to super will call self and thus must follow create self.handlers.
# super(UseCaseReplayer, self).__init__(*args) # if inherits new-style class object
storytext.guishared.IdleHandlerUseCaseReplayer.__init__(self, *args)
"""
# Anyone calling events_pending doesn't mean to include our logging events
# so we intercept it and return the right answer for them...
self.interceptEventsPendingMethod()
"""
def addUiMap(self, uiMap):
self.uiMap = uiMap
if not self.loggerActive:
self.tryAddDescribeHandler()
'''
lkk Reimplement to fix app shutdown problems
This is called:
1) when a usecase is at EOF
AND there are no more usecases
2) when a recorder is active
etc. (I don't really understand the complex state when this is called.)
Also, the generic code in guishared is gtk specific and doesn't remove the replayer idleHandler,
only sets the local self.idleHandler = None (and the idleHandler stops later when it gets
the return value of False.)
For our purpose, insure the replayer idleHandler is stopped and don't start the describeHandle
if the app is trying to quit.
'''
"""
The problem with app shutdown was fixed by adding app.quit() to SUT.
The problem was that automatic shutdown on last window closed did not shutdown app.
Possibly because still timers in the event loop?
Anyway, the solution of calling app.quit on main window close in the SUT
allows the default implementation of tryAddDescribeHandler to work.
We don't need what follows, which was an attempt to make sure there are no timers left in the event loop.
I leave this cruft here because I still don't understand why the describeHandler is reinstalled
and if it is preventing automatic app shutdown on last window close.
def tryAddDescribeHandler(self):
print "Qt reimplemented qtAdaptor.UseCaseReplayer.tryAddDescribeHandler"
# Stop the replayer idleHandler
self.handlers.stopAnyTimers()
'''
TODO: under what conditions should a describeHandler be started?
'''
# This is not right because it doesn't record any usecases on startup.
# if self.readingEnabled:
if not self.startedIdleDescriber:
# if self.isMonitoring():
self.makeDescribeHandler(self.handleNewWindows)
self.startedIdleDescriber = True # So won't start a second time
"""
def makeDescribeHandler(self, method):
#print "makeDescribeHandler with method", method
#traceback.print_stack() # debugging
self.logger.debug("makeDescribeHandler")
return self.handlers.idleAdd(method, priority=describer.PRIORITY_STORYTEXT_IDLE)
"""
All shortcut stuff excised.
In gtktoolkit, this was called from the shortcut stuff
def tryRemoveDescribeHandler(self):
print "tryRemoveDescribeHandler"
if not self.isMonitoring() and not self.readingEnabled: # pragma: no cover - cannot test code with replayer disabled
self.logger.debug("Disabling all idle handlers")
self._disableIdleHandlers() # inherited from guishared, calls self.removeHandler
if self.uiMap:
self.uiMap.windows = [] # So we regenerate everything next time around
"""
"""
TODO:
def interceptEventsPendingMethod(self):
self.orig_events_pending = gtk.events_pending
gtk.events_pending = self.events_pending
def events_pending(self): # pragma: no cover - cannot test code with replayer disabled
if not self.isActive():
self.logger.debug("Removing idle handler for descriptions")
self._disableIdleHandlers()
return_value = self.orig_events_pending()
if not self.isActive():
if self.readingEnabled:
self.enableReplayHandler()
else:
self.logger.debug("Re-adding idle handler for descriptions")
self.tryAddDescribeHandler()
return return_value
cruft?
def makeTimeoutReplayHandler(self, method, milliseconds):
return self.timeoutAdd(time=milliseconds, method=method, priority=describer.PRIORITY_STORYTEXT_REPLAY_IDLE)
"""
def makeIdleReplayHandler(self, method):
return self.handlers.idleAdd(method, priority=describer.PRIORITY_STORYTEXT_REPLAY_IDLE)
def shouldMonitorWindow(self, window):
"""
hint = window.get_type_hint()
if hint == gtk.gdk.WINDOW_TYPE_HINT_TOOLTIP or hint == gtk.gdk.WINDOW_TYPE_HINT_COMBO:
return False
elif isinstance(window.child, gtk.Menu) and isinstance(window.child.get_attach_widget(), gtk.ComboBox):
return False
else:
return True
"""
# TODO Qt
return True
def findWindowsForMonitoring(self):
# print "findWindowsForMonitoring"
return filter(self.shouldMonitorWindow, QCoreApplication.instance().topLevelWidgets())
def describeNewWindow(self, window):
# print "describeNewWindow", window
assert window is not None
if window.isVisible():
describer.describeNewWindow(window)
def callReplayHandlerAgain(self):
'''
Boolean result of replayIdleHandler.
For GTK:
- True causes the handler to continue, i.e. get called again next iteration of event loop.
- False is the opposite, i.e. stops the idle handler.
For Qt this is moot (but function must be implemented, its pure virtual in the base class?)
However, see elsewhere for how Qt handlers are stopped.
'''
return True
"""
gtk cruft
def runMainLoopWithReplay(self):
print "runMainLoopWithReplay"
while gtk.events_pending():
gtk.main_iteration()
if self.delay:
time.sleep(self.delay)
if self.isActive():
self.describeAndRun()
"""