pygameui.Text   F
last analyzed

Complexity

Total Complexity 112

Size/Duplication

Total Lines 583
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 438
dl 0
loc 583
rs 2
c 0
b 0
f 0
wmc 112

49 Methods

Rating   Name   Duplication   Size   Complexity  
A TextTestCase.test_move_word_left() 0 12 2
A TextTestCase.test_selection_delete() 0 21 3
A TextTestCase.setUp() 0 35 1
A TextTestCase.test_left() 0 10 2
A TextTestCase.test_right() 0 10 2
A TextTestCase.test_backspace() 0 16 2
A TextTestCase.test_delete() 0 20 3
A TextTestCase.test_selection_return() 0 9 1
A TextTestCase.test_dummy() 0 6 1
A TextTestCase.test_down() 0 10 2
A TextTestCase.test_selection_backspace() 0 9 1
A TextTestCase.test_numpad_input() 0 7 2
A TextTestCase.test_return() 0 12 1
A TextTestCase.test_input() 0 7 2
A TextTestCase.tearDown() 0 2 1
A TextTestCase.test_selection_movement() 0 21 4
A TextTestCase.test_move_word_right() 0 16 3
A TextTestCase.test_up() 0 11 2
A Selection.first() 0 4 1
B Text._processRight() 0 23 7
A Text.onFocusLost() 0 3 1
A Text.processKeyUp() 0 7 2
A Text.__init__() 0 14 1
A Selection.deselect() 0 2 1
A Text._processUp() 0 9 4
A Text._processDelete() 0 8 4
A Text._processBackspace() 0 11 4
A Text._processHome() 0 2 1
A Text._processUnicode() 0 4 1
A Text._deleteSelection() 0 24 2
A Selection.__nonzero__() 0 2 1
A Text.wrapSelect() 0 7 2
A Text._processDown() 0 9 4
A Text.processMWDown() 0 3 2
A Text.attachVScrollbar() 0 8 1
B Text._processLeft() 0 22 6
A Text._processEnd() 0 2 1
A Selection.select() 0 4 2
A Text.onCursorChanged() 0 6 1
A Selection.__init__() 0 3 1
A Text._processReturn() 0 7 1
A Text.wrapDeleteSelection() 0 6 3
A Text.processMWUp() 0 3 2
A Text.onFocusGained() 0 4 1
A Text.onScroll() 0 2 1
A Selection.last() 0 4 1
A Text.draw() 0 3 1
F Text.processKeyDown() 0 45 16
A Text._processNumKeyboard() 0 3 1

How to fix   Complexity   

Complexity

Complex classes like pygameui.Text often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
#
2
#  Copyright 2001 - 2016 Ludek Smid [http://www.ospace.net/]
3
#
4
#  This file is part of Pygame.UI.
5
#
6
#  Pygame.UI is free software; you can redistribute it and/or modify
7
#  it under the terms of the Lesser GNU General Public License as published by
8
#  the Free Software Foundation; either version 2.1 of the License, or
9
#  (at your option) any later version.
10
#
11
#  Pygame.UI is distributed in the hope that it will be useful,
12
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
13
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
#  Lesser GNU General Public License for more details.
15
#
16
#  You should have received a copy of the Lesser GNU General Public License
17
#  along with Pygame.UI; if not, write to the Free Software
18
#  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19
#
20
21
import unittest
22
import Const
23
from WordUtils import splitter
24
from Widget import Widget, registerWidget
25
import pygame.key
26
27
# keys mapping
28
MAPPING = {
29
    pygame.K_KP0: '0', pygame.K_KP1: '1', pygame.K_KP2: '2', pygame.K_KP3: '3', pygame.K_KP4: '4',
30
    pygame.K_KP5: '5', pygame.K_KP6: '6', pygame.K_KP7: '7', pygame.K_KP8: '8', pygame.K_KP9: '9',
31
}
32
33
34
class Selection(object):
35
    """ Object to hold and pre-process information about selected area of the text.
36
    """
37
    def __init__(self):
38
        self._start = None
39
        self._end = None
40
41
    @property
42
    def first(self):
43
        """ Returns coordinates of the beginning of the selection """
44
        return min(self._start, self._end)
45
46
    @property
47
    def last(self):
48
        """ Returns coordinates of the ending of the selection """
49
        return max(self._start, self._end)
50
51
    def select(self, row, column):
52
        if self._start is None:
53
            self._start = (row, column)
54
        self._end = (row, column)
55
56
    def deselect(self):
57
        self._start = self._end = None
58
59
    def __nonzero__(self):
60
        return self._start is not None and self._end is not None
61
62
63
class Text(Widget):
64
    """Text edit widget."""
65
66
    def __init__(self, parent, **kwargs):
67
        Widget.__init__(self, parent)
68
        # data
69
        self.__dict__['text'] = [""]
70
        self.__dict__['offsetRow'] = 0
71
        self.__dict__['cursorRow'] = 0
72
        self.__dict__['cursorColumn'] = 0
73
        self.__dict__['action'] = None
74
        self.__dict__['editable'] = 1
75
        self.__dict__['vertScrollbar'] = None
76
        self.__dict__['selection'] = Selection()
77
        # flags
78
        self.processKWArguments(kwargs)
79
        parent.registerWidget(self)
80
81
    def draw(self, surface):
82
        self.theme.drawText(surface, self)
83
        return self.rect
84
85
    def attachVScrollbar(self, scrollbar):
86
        self.vertScrollbar = scrollbar
87
        scrollbar.subscribeAction("*", self)
88
        scrollbar.action = "onScroll"
89
        scrollbar.slider.min = 0
90
        scrollbar.slider.max = len(self.text) + 100
91
        scrollbar.slider.position = self.offsetRow
92
        scrollbar.slider.shown = 1
93
94
    def onScroll(self, widget, action, data):
95
        self.offsetRow = self.vertScrollbar.slider.position
96
97
    def onCursorChanged(self):
98
        # force redraw
99
        self.cursorColumn += 1
100
        self.cursorColumn -= 1
101
        # super
102
        Widget.onCursorChanged(self)
103
104
    def _deleteSelection(self):
105
        assert self.selection
106
107
        # one-line selection
108
        if self.selection.first[0] == self.selection.last[0]:
109
            textBefore = self.text[self.selection.last[0]][:self.selection.first[1]]
110
            textAfter = self.text[self.selection.last[0]][self.selection.last[1]:]
111
            self.text[self.selection.last[0]] = textBefore + textAfter
112
        else:
113
            # handle multi-line selection
114
            # delete end of selection
115
            self.text[self.selection.last[0]] = self.text[self.selection.last[0]][self.selection.last[1]:]
116
            # delete fully selected rows
117
            del self.text[self.selection.first[0] + 1:self.selection.last[0]]
118
            # delete selection on first row
119
            self.text[self.selection.first[0]] = self.text[self.selection.first[0]][:self.selection.first[1]]
120
            # join the rows that are spanned
121
            self.text[self.selection.first[0]] = self.text[self.selection.first[0]] + self.text[self.selection.first[0] + 1]
122
            del self.text[self.selection.first[0] + 1]
123
        # move cursor to selection begining
124
        self.cursorColumn = self.selection.first[1]
125
        self.cursorRow = self.selection.first[0]
126
        # clear selection
127
        self.selection.deselect()
128
129
    def _processBackspace(self, evt):
130
        if self.selection:
131
            self._deleteSelection()
132
        elif self.cursorColumn > 0:
133
            self.text[self.cursorRow] = self.text[self.cursorRow][:self.cursorColumn - 1] + self.text[self.cursorRow][self.cursorColumn:]
134
            self.cursorColumn -= 1
135
        elif self.cursorRow > 0:
136
            self.cursorColumn = len(self.text[self.cursorRow - 1])
137
            self.text[self.cursorRow - 1] = self.text[self.cursorRow - 1] + self.text[self.cursorRow]
138
            del self.text[self.cursorRow]
139
            self.cursorRow -= 1
140
141
    def _processDelete(self, evt):
142
        if self.selection:
143
            self._deleteSelection()
144
        elif self.cursorColumn < len(self.text[self.cursorRow]):
145
            self.text[self.cursorRow] = self.text[self.cursorRow][:self.cursorColumn] + self.text[self.cursorRow][self.cursorColumn + 1:]
146
        elif self.cursorRow < len(self.text) - 1:
147
            self.text[self.cursorRow] = self.text[self.cursorRow] + self.text[self.cursorRow + 1]
148
            del self.text[self.cursorRow + 1]
149
150
    def _processLeft(self, evt):
151
        if evt.mod & pygame.KMOD_CTRL:
152
            # move one word left
153
            # take words on line
154
            words = splitter(self.text[self.cursorRow][:self.cursorColumn])
155
            if len(words) == 0:
156
                if self.cursorRow == 0:
157
                    # we are on first line, so move cursor to begining of line
158
                    self.cursorColumn = 0
159
                else:
160
                    # move to previous line and try again
161
                    self.cursorRow -= 1
162
                    self.cursorColumn = len(self.text[self.cursorRow])
163
                    self._processLeft(evt)
164
                return
165
            # we must jump to begining of last word
166
            self.cursorColumn = words[-1][1]
167
        elif self.cursorColumn > 0:
168
            self.cursorColumn -= 1
169
        elif self.cursorRow > 0:
170
            self.cursorRow -= 1
171
            self.cursorColumn = len(self.text[self.cursorRow])
172
173
    def _processRight(self, evt):
174
        if evt.mod & pygame.KMOD_CTRL:
175
            # move one word right
176
            # take words on line
177
            words = splitter(self.text[self.cursorRow][self.cursorColumn:])
178
            if len(words) == 0:
179
                if self.cursorRow == len(self.text) - 1:
180
                    # we are on last line, so move cursor to end of line
181
                    self.cursorColumn = len(self.text[self.cursorRow])
182
                else:
183
                    self.cursorRow += 1
184
                    self.cursorColumn = 0
185
                    self._processRight(evt)
186
                return
187
            # we jump to the end of the first word
188
            self.cursorColumn += words[0][2]
189
        elif self.cursorColumn < len(self.text[self.cursorRow]):
190
            self.cursorColumn += 1
191
        elif self.cursorRow < len(self.text):
192
            # move to the next row
193
            if self.cursorRow < len(self.text) - 1:
194
                self.cursorRow += 1
195
                self.cursorColumn = 0
196
197
    def _processUp(self, evt):
198
        if self.cursorRow > 0:
199
            self.cursorRow -= 1
200
            self.cursorColumn = min(self.cursorColumn, len(self.text[self.cursorRow]))
201
        else:
202
            self.cursorColumn = 0
203
204
        if self.vertScrollbar and self.cursorRow - self.offsetRow < 0:
205
            self.vertScrollbar.onButton1(self, "", "")
206
207
    def _processDown(self, evt):
208
        if self.cursorRow < len(self.text) - 1:
209
            self.cursorRow += 1
210
            self.cursorColumn = min(self.cursorColumn, len(self.text[self.cursorRow]))
211
        else:
212
            self.cursorColumn = len(self.text[self.cursorRow])
213
214
        if self.vertScrollbar and self.cursorRow - self.offsetRow >= self.theme.getTextDrawLines(self):
215
            self.vertScrollbar.onButton2(self, "", "")
216
217
    def _processHome(self, evt):
218
        self.cursorColumn = 0
219
220
    def _processEnd(self, evt):
221
        self.cursorColumn = len(self.text[self.cursorRow])
222
223
    def _processReturn(self, evt):
224
        text1 = self.text[self.cursorRow][self.cursorColumn:]
225
        text2 = self.text[self.cursorRow][:self.cursorColumn]
226
        self.text[self.cursorRow] = text1
227
        self.text.insert(self.cursorRow, text2)
228
        self.cursorRow += 1
229
        self.cursorColumn = 0
230
231
    def _processUnicode(self, evt):
232
        char = evt.unicode
233
        self.text[self.cursorRow] = self.text[self.cursorRow][:self.cursorColumn] + char + self.text[self.cursorRow][self.cursorColumn:]
234
        self.cursorColumn += 1
235
236
    def _processNumKeyboard(self, evt):
237
        self.text[self.cursorRow] = self.text[self.cursorRow][:self.cursorColumn] + MAPPING[evt.key] + self.text[self.cursorRow][self.cursorColumn:]
238
        self.cursorColumn += 1
239
240
    def wrapDeleteSelection(self, func, evt, deleteOnly=False):
241
        if self.selection:
242
            self._deleteSelection()
243
            if deleteOnly:
244
                return
245
        func(evt)
246
247
    def wrapSelect(self, func, evt):
248
        if evt.mod & pygame.KMOD_SHIFT:
249
            self.selection.select(self.cursorRow, self.cursorColumn)
250
            func(evt)
251
            self.selection.select(self.cursorRow, self.cursorColumn)
252
        else:
253
            func(evt)
254
255
    def processKeyUp(self, evt):
256
        # consume pygame.K_RETURN (acceptButton on Window will not work)
257
        # can be choosable on construction?
258
        if evt.key == pygame.K_RETURN:
259
            return Const.NoEvent
260
        else:
261
            return Widget.processKeyUp(self, evt)
262
263
    def processKeyDown(self, evt):
264
        if not self.editable:
265
            return Widget.processKeyDown(self, evt)
266
267
        # process keys
268
        if evt.key == pygame.K_BACKSPACE:
269
            self.wrapDeleteSelection(self._processBackspace, evt, deleteOnly=True)
270
271
        elif evt.key == pygame.K_DELETE:
272
            self.wrapDeleteSelection(self._processDelete, evt, deleteOnly=True)
273
274
        elif evt.key == pygame.K_ESCAPE:
275
            self.app.setFocus(None)
276
277
        elif evt.key == pygame.K_LEFT:
278
            self.wrapSelect(self._processLeft, evt)
279
280
        elif evt.key == pygame.K_RIGHT:
281
            self.wrapSelect(self._processRight, evt)
282
283
        elif evt.key == pygame.K_UP:
284
            self.wrapSelect(self._processUp, evt)
285
286
        elif evt.key == pygame.K_DOWN:
287
            self.wrapSelect(self._processDown, evt)
288
289
        elif evt.key == pygame.K_TAB:
290
            pass
291
292
        elif evt.key == pygame.K_HOME:
293
            self.wrapSelect(self._processHome, evt)
294
295
        elif evt.key == pygame.K_END:
296
            self.wrapSelect(self._processEnd, evt)
297
298
        elif evt.key == pygame.K_RETURN:
299
            self.wrapDeleteSelection(self._processReturn, evt)
300
301
        elif hasattr(evt, 'unicode') and evt.unicode:
302
            self.wrapDeleteSelection(self._processUnicode, evt)
303
304
        elif evt.key in MAPPING:
305
            self.wrapDeleteSelection(self._processNumKeyboard, evt)
306
307
        return Widget.processKeyDown(self, Const.NoEvent)
308
309
    def onFocusGained(self):
310
        Widget.onFocusGained(self)
311
        self.cursorRow = len(self.text) - 1
312
        self.cursorColumn = len(self.text[self.cursorRow])
313
314
    def onFocusLost(self):
315
        Widget.onFocusLost(self)
316
        self.processAction(self.action)
317
318
    # redirect mouse wheel events to the scollbar
319
    def processMWUp(self, evt):
320
        if self.vertScrollbar:
321
            return self.vertScrollbar.processMWUp(evt)
322
323
    def processMWDown(self, evt):
324
        if self.vertScrollbar:
325
            return self.vertScrollbar.processMWDown(evt)
326
327
328
registerWidget(Text, 'text')
329
330
331
class TextTestCase(unittest.TestCase):
332
    def setUp(self):
333
        class Evt:
334
            def __init__(self, key, letter="", mod=0):
335
                self.key = key
336
                self.unicode = unicode(letter)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable unicode does not seem to be defined.
Loading history...
337
                self.mod = mod
338
339
        class App:
340
            def __init__(self):
341
                self.theme = "Test"
342
343
        class Parent:
344
            def __init__(self):
345
                self.app = App()
346
347
            def getApp(self):
348
                return self.app
349
350
            def redraw(self, *args, **kwargs):
351
                return
352
353
            def registerWidget(self, *args, **kwargs):
354
                return
355
356
        self.parent = Parent()
357
        self.txt = Text(self.parent)
358
        self.txt.text = ["Zero line", "One line", "Two line", "", "", "Five line"]
359
        self.txt.offsetRow = 0
360
        self.txt.cursorRow = 0
361
        self.txt.cursorColumn = 0
362
        self.txt.action = None
363
        self.txt.editable = 1
364
        self.txt.vertScrollbar = None
365
        self.txt.selection = Selection()
366
        self.Evt = Evt
367
368
    def tearDown(self):
369
        pass
370
371
    def test_dummy(self):
372
        self.txt.cursorColumn = 3
373
        self.txt.cursorRow = 2
374
        self.assertEqual(self.txt.text, ["Zero line", "One line", "Two line", "", "", "Five line"])
375
        self.assertEqual(self.txt.cursorColumn, 3)
376
        self.assertEqual(self.txt.cursorRow, 2)
377
378
    def test_input(self):
379
        self.txt.cursorColumn = 3
380
        self.txt.cursorRow = 2
381
        for letter in " quite expanded":
382
            Text.processKeyDown(self.txt, self.Evt(None, letter=letter))
383
        self.assertEqual(self.txt.text, ["Zero line", "One line", "Two quite expanded line", "", "", "Five line"])
384
        self.assertEqual(self.txt.cursorColumn, 18)
385
386
    def test_numpad_input(self):
387
        self.txt.cursorColumn = 3
388
        self.txt.cursorRow = 2
389
        for key in [pygame.K_KP0, pygame.K_KP4, pygame.K_KP4, pygame.K_KP8]:
390
            Text.processKeyDown(self.txt, self.Evt(key))
391
        self.assertEqual(self.txt.text, ["Zero line", "One line", "Two0448 line", "", "", "Five line"])
392
        self.assertEqual(self.txt.cursorColumn, 7)
393
394
    def test_backspace(self):
395
        evt = self.Evt(pygame.K_BACKSPACE)
396
        self.txt.cursorColumn = 3
397
        self.txt.cursorRow = 2
398
        for x in range(4):
399
            Text.processKeyDown(self.txt, evt)
400
        self.assertEqual(self.txt.text, ["Zero line", "One line line", "", "", "Five line"])
401
        self.assertEqual(self.txt.cursorColumn, 8)
402
        self.assertEqual(self.txt.cursorRow, 1)
403
        # first character
404
        self.txt.cursorColumn = 0
405
        self.txt.cursorRow = 0
406
        Text.processKeyDown(self.txt, evt)
407
        self.assertEqual(self.txt.text, ["Zero line", "One line line", "", "", "Five line"])
408
        self.assertEqual(self.txt.cursorColumn, 0)
409
        self.assertEqual(self.txt.cursorRow, 0)
410
411
    def test_delete(self):
412
        evt = self.Evt(pygame.K_DELETE)
413
        self.txt.cursorColumn = 3
414
        self.txt.cursorRow = 2
415
        for x in range(6):
416
            Text.processKeyDown(self.txt, evt)
417
        self.assertEqual(self.txt.text, ["Zero line", "One line", "Two", "", "Five line"])
418
        self.assertEqual(self.txt.cursorColumn, 3)
419
        self.assertEqual(self.txt.cursorRow, 2)
420
        for x in range(3):
421
            Text.processKeyDown(self.txt, evt)
422
        self.assertEqual(self.txt.text, ["Zero line", "One line", "Twoive line"])
423
        self.assertEqual(self.txt.cursorColumn, 3)
424
        self.assertEqual(self.txt.cursorRow, 2)
425
        # last character
426
        self.txt.cursorColumn = 11
427
        Text.processKeyDown(self.txt, evt)
428
        self.assertEqual(self.txt.text, ["Zero line", "One line", "Twoive line"])
429
        self.assertEqual(self.txt.cursorColumn, 11)
430
        self.assertEqual(self.txt.cursorRow, 2)
431
432
    def test_return(self):
433
        evt = self.Evt(pygame.K_RETURN)
434
        self.txt.cursorColumn = 3
435
        self.txt.cursorRow = 2
436
        Text.processKeyDown(self.txt, evt)
437
        self.assertEqual(self.txt.text, ["Zero line", "One line", "Two", " line", "", "", "Five line"])
438
        self.assertEqual(self.txt.cursorColumn, 0)
439
        self.assertEqual(self.txt.cursorRow, 3)
440
        Text.processKeyDown(self.txt, evt)
441
        self.assertEqual(self.txt.text, ["Zero line", "One line", "Two", "", " line", "", "", "Five line"])
442
        self.assertEqual(self.txt.cursorColumn, 0)
443
        self.assertEqual(self.txt.cursorRow, 4)
444
445
    def test_up(self):
446
        evt = self.Evt(pygame.K_UP)
447
        self.txt.cursorColumn = 3
448
        self.txt.cursorRow = 5
449
        for x in range(3):
450
            Text.processKeyDown(self.txt, evt)
451
        self.assertEqual(self.txt.cursorColumn, 0)
452
        self.assertEqual(self.txt.cursorRow, 2)
453
        self.txt.cursorColumn = 3
454
        Text.processKeyDown(self.txt, evt)
455
        self.assertEqual(self.txt.cursorColumn, 3)
456
457
    def test_down(self):
458
        evt = self.Evt(pygame.K_DOWN)
459
        self.txt.cursorColumn = 3
460
        self.txt.cursorRow = 1
461
        Text.processKeyDown(self.txt, evt)
462
        self.assertEqual(self.txt.cursorColumn, 3)
463
        for x in range(3):
464
            Text.processKeyDown(self.txt, evt)
465
        self.assertEqual(self.txt.cursorColumn, 0)
466
        self.assertEqual(self.txt.cursorRow, 5)
467
468
    def test_left(self):
469
        evt = self.Evt(pygame.K_LEFT)
470
        self.txt.cursorColumn = 3
471
        self.txt.cursorRow = 2
472
        Text.processKeyDown(self.txt, evt)
473
        self.assertEqual(self.txt.cursorColumn, 2)
474
        for x in range(12):
475
            Text.processKeyDown(self.txt, evt)
476
        self.assertEqual(self.txt.cursorColumn, 9)
477
        self.assertEqual(self.txt.cursorRow, 0)
478
479
    def test_right(self):
480
        evt = self.Evt(pygame.K_RIGHT)
481
        self.txt.cursorColumn = 3
482
        self.txt.cursorRow = 2
483
        Text.processKeyDown(self.txt, evt)
484
        self.assertEqual(self.txt.cursorColumn, 4)
485
        for x in range(12):
486
            Text.processKeyDown(self.txt, evt)
487
        self.assertEqual(self.txt.cursorColumn, 5)
488
        self.assertEqual(self.txt.cursorRow, 5)
489
490
    def test_selection_movement(self):
491
        evt = self.Evt(pygame.K_RIGHT, mod=pygame.KMOD_RSHIFT)
492
        self.txt.cursorColumn = 3
493
        self.txt.cursorRow = 2
494
        for x in range(10):
495
            Text.processKeyDown(self.txt, evt)
496
        self.assertEqual(self.txt.text, ["Zero line", "One line", "Two line", "", "", "Five line"])
497
        self.assertEqual(self.txt.selection.first, (2, 3))
498
        self.assertEqual(self.txt.selection.last, (5, 2))
499
500
        evt = self.Evt(pygame.K_LEFT, mod=pygame.KMOD_LSHIFT)
501
        for x in range(3):
502
            Text.processKeyDown(self.txt, evt)
503
        self.assertEqual(self.txt.selection.first, (2, 3))
504
        self.assertEqual(self.txt.selection.last, (4, 0))
505
506
        evt = self.Evt(pygame.K_UP, mod=pygame.KMOD_LSHIFT)
507
        for x in range(3):
508
            Text.processKeyDown(self.txt, evt)
509
        self.assertEqual(self.txt.selection.first, (1, 0))
510
        self.assertEqual(self.txt.selection.last, (2, 3))
511
512
    def test_selection_delete(self):
513
        evt = self.Evt(pygame.K_DOWN, mod=pygame.KMOD_LSHIFT)
514
        self.txt.cursorColumn = 3
515
        self.txt.cursorRow = 0
516
        Text.processKeyDown(self.txt, evt)
517
        Text.processKeyDown(self.txt, self.Evt(pygame.K_DELETE))
518
        self.assertEqual(self.txt.text, ["Zer line", "Two line", "", "", "Five line"])
519
        self.assertIs(self.txt.selection.first, None)
520
        self.assertIs(self.txt.selection.last, None)
521
522
        evt = self.Evt(pygame.K_RIGHT, mod=pygame.KMOD_LSHIFT)
523
        for x in range(3):
524
            Text.processKeyDown(self.txt, evt)
525
        Text.processKeyDown(self.txt, self.Evt(pygame.K_DELETE))
526
        self.assertEqual(self.txt.text, ["Zerne", "Two line", "", "", "Five line"])
527
528
        evt = self.Evt(pygame.K_DOWN, mod=pygame.KMOD_LSHIFT)
529
        for x in range(4):
530
            Text.processKeyDown(self.txt, evt)
531
        Text.processKeyDown(self.txt, self.Evt(pygame.K_DELETE))
532
        self.assertEqual(self.txt.text, ["ZerFive line"])
533
534
    def test_selection_backspace(self):
535
        evt = self.Evt(pygame.K_DOWN, mod=pygame.KMOD_LSHIFT)
536
        self.txt.cursorColumn = 3
537
        self.txt.cursorRow = 0
538
        Text.processKeyDown(self.txt, evt)
539
        Text.processKeyDown(self.txt, self.Evt(pygame.K_BACKSPACE))
540
        self.assertEqual(self.txt.text, ["Zer line", "Two line", "", "", "Five line"])
541
        self.assertIs(self.txt.selection.first, None)
542
        self.assertIs(self.txt.selection.last, None)
543
544
    def test_selection_return(self):
545
        evt = self.Evt(pygame.K_DOWN, mod=pygame.KMOD_LSHIFT)
546
        self.txt.cursorColumn = 3
547
        self.txt.cursorRow = 0
548
        Text.processKeyDown(self.txt, evt)
549
        Text.processKeyDown(self.txt, self.Evt(pygame.K_RETURN))
550
        self.assertEqual(self.txt.text, ["Zer", " line", "Two line", "", "", "Five line"])
551
        self.assertIs(self.txt.selection.first, None)
552
        self.assertIs(self.txt.selection.last, None)
553
554
    def test_move_word_left(self):
555
        evt = self.Evt(pygame.K_LEFT, mod=pygame.KMOD_LCTRL)
556
        self.txt.cursorColumn = 3
557
        self.txt.cursorRow = 5
558
        for x in range(2):
559
            Text.processKeyDown(self.txt, evt)
560
        self.assertEqual(self.txt.text, ["Zero line", "One line", "Two line", "", "", "Five line"])
561
        self.assertEqual(self.txt.cursorColumn, 4)
562
        self.assertEqual(self.txt.cursorRow, 2)
563
        Text.processKeyDown(self.txt, evt)
564
        self.assertEqual(self.txt.cursorColumn, 0)
565
        self.assertEqual(self.txt.cursorRow, 2)
566
567
    def test_move_word_right(self):
568
        evt = self.Evt(pygame.K_RIGHT, mod=pygame.KMOD_RCTRL)
569
        self.txt.cursorColumn = 1
570
        self.txt.cursorRow = 0
571
        Text.processKeyDown(self.txt, evt)
572
        self.assertEqual(self.txt.text, ["Zero line", "One line", "Two line", "", "", "Five line"])
573
        self.assertEqual(self.txt.cursorColumn, 4)
574
        self.assertEqual(self.txt.cursorRow, 0)
575
        for x in range(4):
576
            Text.processKeyDown(self.txt, evt)
577
        self.assertEqual(self.txt.cursorColumn, 3)
578
        self.assertEqual(self.txt.cursorRow, 2)
579
        for x in range(2):
580
            Text.processKeyDown(self.txt, evt)
581
        self.assertEqual(self.txt.cursorColumn, 4)
582
        self.assertEqual(self.txt.cursorRow, 5)
583