-
Notifications
You must be signed in to change notification settings - Fork 95
/
_interaction_helpers.py
462 lines (396 loc) · 13.3 KB
/
_interaction_helpers.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
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
# (C) Copyright 2004-2021 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
import warnings
from pyface.qt import QtCore, QtGui
from pyface.qt.QtTest import QTest
from traitsui.testing.tester._ui_tester_registry._compat import (
check_key_compat,
)
from traitsui.testing.tester.exceptions import Disabled
from traitsui.qt4.key_event_to_name import key_map as _KEY_MAP
def key_click(widget, key, delay):
"""Performs a key click of the given key on the given widget after
a delay.
Parameters
----------
widget : Qwidget
The Qt widget to be key clicked.
key : str
Standardized (pyface) name for a keyboard event.
e.g. "Enter", "Tab", "Space", "0", "1", "A", ...
Note: modifiers (e.g. Shift, Alt, etc. are not currently supported)
delay : int
Time delay (in ms) in which the key click will be performed.
"""
mapping = {name: event for event, name in _KEY_MAP.items()}
if key not in mapping:
raise ValueError(
"Unknown key {!r}. Expected one of these: {!r}".format(
key, sorted(mapping)
)
)
QTest.keyClick(
widget,
mapping[key],
QtCore.Qt.NoModifier,
delay=delay,
)
def check_q_model_index_valid(index):
"""Checks if a given QModelIndex is valid.
Parameters
----------
index : QModelIndex
Raises
------
LookupError
If the index is not valid.
"""
if not index.isValid():
row = index.row()
column = index.column()
raise LookupError(
"Unabled to locate item with row {!r} and column {!r}.".format(
row,
column,
)
)
# Generic Handlers ###########################################################
def displayed_text_qobject(widget):
'''Helper function to define handlers for various Qwidgets to handle
query.DisplayedText interactions.
Parameters
----------
widget : Qwidget
The Qwidget object with text to be displayed. Should be one of the
following QWidgets: 1) QtGui.QLineEdit 2) QtGui.QTextEdit or
3) QtGui.QLabel
Notes
-----
Qt SimpleEditors occassionally use QtGui.QTextEdit as their control, and
other times use QtGui.QLineEdit
'''
if isinstance(widget, QtGui.QLineEdit):
return widget.displayText()
elif isinstance(widget, QtGui.QTextEdit):
return widget.toPlainText()
else:
return widget.text()
def mouse_click_qwidget(control, delay):
"""Performs a mouce click on a Qt widget.
Parameters
----------
control : Qwidget
The Qt widget to be clicked.
delay : int
Time delay (in ms) in which click will be performed.
"""
# for QAbstractButtons we do not use QTest.mouseClick as it assumes the
# center of the widget as the location to be clicked, which may be
# incorrect. For QAbstractButtons we can simply call their click method.
if isinstance(control, QtGui.QAbstractButton):
if delay > 0:
QTest.qSleep(delay)
control.click()
else:
QTest.mouseClick(
control,
QtCore.Qt.LeftButton,
delay=delay,
)
def mouse_click_tab_index(tab_widget, index, delay):
"""Performs a mouse click on a tab at an index in a QtGui.QTabWidget.
Parameters
----------
tab_widget : QtGui.QTabWidget
The tab widget containing the tab to be clicked.
index : int
The index of the tab to be clicked.
delay : int
Time delay (in ms) in which click will be performed.
Raises
------
IndexError
If the index is out of range.
"""
if not 0 <= index < tab_widget.count():
raise IndexError(index)
tabbar = tab_widget.tabBar()
rect = tabbar.tabRect(index)
QTest.mouseClick(
tabbar,
QtCore.Qt.LeftButton,
QtCore.Qt.NoModifier,
rect.center(),
delay=delay,
)
def mouse_click_qlayout(layout, index, delay):
"""Performs a mouse click on a widget at an index in a QLayout.
Parameters
----------
layout : Qlayout
The layout containing the widget to be clicked
index : int
The index of the widget in the layout to be clicked
Raises
------
IndexError
If the index is out of range.
"""
if not 0 <= index < layout.count():
raise IndexError(index)
widget = layout.itemAt(index).widget()
mouse_click_qwidget(widget, delay)
def mouse_click_item_view(model, view, index, delay):
"""Perform mouse click on the given QAbstractItemModel (model) and
QAbstractItemView (view) with the given row and column.
Parameters
----------
model : QAbstractItemModel
Model from which QModelIndex will be obtained
view : QAbstractItemView
View from which the widget identified by the index will be
found and mouse click be performed.
index : QModelIndex
Raises
------
LookupError
If the index cannot be located.
Note that the index error provides more
"""
check_q_model_index_valid(index)
rect = view.visualRect(index)
QTest.mouseClick(
view.viewport(),
QtCore.Qt.LeftButton,
QtCore.Qt.NoModifier,
rect.center(),
delay=delay,
)
def mouse_dclick_item_view(model, view, index, delay):
""" Perform mouse double click on the given QAbstractItemModel (model) and
QAbstractItemView (view) with the given row and column.
Parameters
----------
model : QAbstractItemModel
Model from which QModelIndex will be obtained
view : QAbstractItemView
View from which the widget identified by the index will be
found and mouse double click be performed.
index : QModelIndex
Raises
------
LookupError
If the index cannot be located.
Note that the index error provides more
"""
check_q_model_index_valid(index)
rect = view.visualRect(index)
QTest.mouseDClick(
view.viewport(),
QtCore.Qt.LeftButton,
QtCore.Qt.NoModifier,
rect.center(),
delay=delay,
)
def key_sequence_item_view(model, view, index, sequence, delay=0):
""" Perform Key Sequence on the given QAbstractItemModel (model) and
QAbstractItemView (view) with the given row and column.
Parameters
----------
model : QAbstractItemModel
Model from which QModelIndex will be obtained
view : QAbstractItemView
View from which the widget identified by the index will be
found and key sequence be performed.
index : QModelIndex
sequence : str
Sequence of characters to be inserted to the widget identifed
by the row and column.
Raises
------
Disabled
If the widget cannot be edited.
LookupError
If the index cannot be located.
Note that the index error provides more
"""
check_q_model_index_valid(index)
widget = view.indexWidget(index)
if widget is None:
raise Disabled(
"No editable widget for item at row {!r} and column {!r}".format(
index.row(), index.column()
)
)
QTest.keyClicks(widget, sequence, delay=delay)
def key_click_item_view(model, view, index, key, delay=0):
""" Perform key press on the given QAbstractItemModel (model) and
QAbstractItemView (view) with the given row and column.
Parameters
----------
model : QAbstractItemModel
Model from which QModelIndex will be obtained
view : QAbstractItemView
View from which the widget identified by the index will be
found and key press be performed.
index : int
key : str
Key to be pressed.
Raises
------
Disabled
If the widget cannot be edited.
LookupError
If the index cannot be located.
Note that the index error provides more
"""
check_q_model_index_valid(index)
widget = view.indexWidget(index)
if widget is None:
raise Disabled(
"No editable widget for item at row {!r} and column {!r}".format(
index.row(), index.column()
)
)
key_click(widget, key=key, delay=delay)
def get_display_text_item_view(model, view, index):
""" Return the textural representation for the given model, row and column.
Parameters
----------
model : QAbstractItemModel
Model from which QModelIndex will be obtained
view : QAbstractItemView
View from which the widget identified by the index will be
found and key press be performed.
index : int
Raises
------
LookupError
If the index cannot be located.
Note that the index error provides more
"""
check_q_model_index_valid(index)
return model.data(index, QtCore.Qt.DisplayRole)
def mouse_click_combobox(combobox, index, delay):
"""Perform a mouse click on a QComboBox at a given index.
Paramters
---------
combobox : QtGui.ComboBox
The combobox to be clicked.
index : int
The index of the item in the combobox to be clicked.
delay : int
Time delay (in ms) in which each key click in the sequence will be
performed.
"""
if combobox:
q_model_index = combobox.model().index(index, 0)
check_q_model_index_valid(q_model_index)
mouse_click_item_view(
model=combobox.model(),
view=combobox.view(),
index=q_model_index,
delay=delay,
)
# Otherwise the click won't get registered.
key_click(combobox.view().viewport(), key="Enter", delay=delay)
else:
warnings.warn(
"Attempted to click on a non-existant combobox. Nothing was "
"performed."
)
def key_sequence_qwidget(control, interaction, delay):
"""Performs simulated typing of a sequence of keys on the given widget
after a delay.
Parameters
----------
control : Qwidget
The Qt widget to be acted on.
interaction : instance of command.KeySequence
The interaction object holding the sequence of key inputs
to be simulated being typed
delay : int
Time delay (in ms) in which each key click in the sequence will be
performed.
Raises
------
Disabled
If the widget is not enabled.
"""
if not control.isEnabled():
raise Disabled("{!r} is disabled.".format(control))
QTest.keyClicks(control, interaction.sequence, delay=delay)
def key_sequence_textbox(control, interaction, delay):
"""Performs simulated typing of a sequence of keys on a widget that is
a textbox. The keys are restricted to values also supported for testing
wx.TextCtrl.
Parameters
----------
control : QWidget
The Qt widget intended to hold text for editing.
e.g. QLineEdit and QTextEdit
interaction : instance of command.KeySequence
The interaction object holding the sequence of key inputs
to be simulated being typed
delay : int
Time delay (in ms) in which each key click in the sequence will be
performed.
"""
for key in interaction.sequence:
check_key_compat(key)
if not control.hasFocus():
key_click(widget=control, key="End", delay=0)
key_sequence_qwidget(control=control, interaction=interaction, delay=delay)
def key_click_qwidget(control, interaction, delay):
"""Performs simulated typing of a key on the given widget after a delay.
Parameters
----------
control : Qwidget
The Qt widget to be acted on.
interaction : instance of command.KeyClick
The interaction object holding the key input
to be simulated being typed
delay : int
Time delay (in ms) in which the key click will be performed.
Raises
------
Disabled
If the widget is not enabled.
"""
if not control.isEnabled():
raise Disabled("{!r} is disabled.".format(control))
key_click(control, interaction.key, delay=delay)
def key_click_qslider(control, interaction, delay):
"""Performs simulated typing of a key on the given slider after a delay.
Only allowed keys are:
"Left", "Right", "Up", "Down", "Page Up", "Page Down"
Also, note that up related keys correspond to an increment on the slider,
and down a decrement.
Parameters
----------
control : QSlider
The Qt slider to be acted on.
interaction : instance of command.KeyClick
The interaction object holding the key input
to be simulated being typed
delay : int
Time delay (in ms) in which the key click will be performed.
Raises
------
ValueError
If the interaction.key is not one of the valid keys.
"""
valid_keys = {"Left", "Right", "Up", "Down", "Page Up", "Page Down"}
if interaction.key not in valid_keys:
raise ValueError(
"Unexpected Key. Supported keys are: {}".format(sorted(valid_keys))
)
else:
key_click(control, interaction.key, delay)