1# -*- coding: utf-8 -*-
2from __future__ import division
3from __future__ import absolute_import
4from __future__ import print_function
5from __future__ import unicode_literals
6from datetime import date, time
7from time import sleep
8from mock import patch
9from builtins import ord
10from builtins import chr
11from builtins import str
12import unittest
13import sys
14from mock.mock import MagicMock
15from asciimatics.event import KeyboardEvent, MouseEvent
16from asciimatics.exceptions import NextScene, StopApplication, InvalidFields
17from asciimatics.scene import Scene
18from asciimatics.screen import Screen, Canvas
19from asciimatics.widgets import Frame, Layout, Button, Label, TextBox, Text, \
20    Divider, RadioButtons, CheckBox, PopUpDialog, ListBox, Widget, MultiColumnListBox, \
21    FileBrowser, DatePicker, TimePicker, Background, DropdownList, PopupMenu, \
22    _find_min_start, VerticalDivider
23from asciimatics.parsers import AsciimaticsParser, AnsiTerminalParser
24from asciimatics.strings import ColouredText
25
26
27class TestFrame(Frame):
28    def __init__(self, screen, has_border=True, reduce_cpu=False, label_height=1):
29        super(TestFrame, self).__init__(screen,
30                                        screen.height,
31                                        screen.width,
32                                        name="Test Form",
33                                        has_border=has_border,
34                                        hover_focus=True,
35                                        reduce_cpu=reduce_cpu)
36        layout = Layout([1, 18, 1])
37        self.add_layout(layout)
38        self._reset_button = Button("Reset", self._reset)
39        self.label = Label("Group 1:", height=label_height)
40        layout.add_widget(self.label, 1)
41        layout.add_widget(TextBox(5,
42                                  label="My First Box:",
43                                  name="TA",
44                                  on_change=self._on_change), 1)
45        layout.add_widget(
46            Text(label="Text1:", name="TB", on_change=self._on_change), 1)
47        layout.add_widget(
48            Text(label="Text2:",
49                 name="TC",
50                 on_change=self._on_change,
51                 validator="^[0-9]*$"), 1)
52        layout.add_widget(
53            Text(label="Text3:",
54                 name="TD",
55                 on_change=self._on_change,
56                 validator=lambda x: x in ("", "a")), 1)
57        layout.add_widget(Divider(height=2), 1)
58        layout.add_widget(Label("Group 2:"), 1)
59        layout.add_widget(RadioButtons([("Option 1", 1),
60                                        ("Option 2", 2),
61                                        ("Option 3", 3)],
62                                       label="A Longer Selection:",
63                                       name="Things",
64                                       on_change=self._on_change), 1)
65        layout.add_widget(CheckBox("Field 1",
66                                   label="A very silly long name for fields:",
67                                   name="CA",
68                                   on_change=self._on_change), 1)
69        layout.add_widget(
70            CheckBox("Field 2", name="CB", on_change=self._on_change), 1)
71        layout.add_widget(
72            CheckBox("Field 3", name="CC", on_change=self._on_change), 1)
73        layout.add_widget(Divider(height=3), 1)
74        layout2 = Layout([1, 1, 1])
75        self.add_layout(layout2)
76        layout2.add_widget(self._reset_button, 0)
77        layout2.add_widget(Button("View Data", self._view), 1)
78        layout2.add_widget(Button("Quit", self._quit), 2)
79        layout2.add_widget(Button("One", self._view), 0)
80        layout2.add_widget(Button("Two", self._view), 1)
81        layout2.add_widget(Button("Three", self._view), 2)
82        self.fix()
83
84    def _on_change(self):
85        changed = False
86        self.save()
87        for key, value in self.data.items():
88            if isinstance(value, bool):
89                if value:
90                    changed = True
91                    break
92            if isinstance(value, int):
93                if value != 1:
94                    changed = True
95                    break
96            elif value is None:
97                continue
98            elif len(value) != 0:
99                changed = True
100                break
101        self._reset_button.disabled = not changed
102
103    def _reset(self):
104        self.reset()
105        raise NextScene()
106
107    def _view(self):
108        # Build result of this form and display it.
109        self.save()
110        raise NextScene()
111
112    @staticmethod
113    def _quit():
114        raise StopApplication("User requested exit")
115
116
117class TestFrame2(Frame):
118    def __init__(self, screen, init_values):
119        super(TestFrame2, self).__init__(screen,
120                                         screen.height,
121                                         screen.width,
122                                         data={"selected": "None"},
123                                         title="Test Frame 2")
124        # Create the form for displaying the list of contacts.
125        self._list_view = ListBox(
126            Widget.FILL_FRAME,
127            init_values,
128            name="contacts",
129            on_change=self._on_pick,
130            on_select=self._on_select)
131        self._edit_button = Button("Edit", self._edit)
132        self._delete_button = Button("Delete", self._delete)
133        layout = Layout([100], fill_frame=True)
134        self.add_layout(layout)
135        layout.add_widget(self._list_view)
136        layout.add_widget(Divider(line_char="#"))
137        layout2 = Layout([1, 1, 1, 1])
138        self.add_layout(layout2)
139        layout2.add_widget(Button("Add", self._add), 0)
140        layout2.add_widget(self._edit_button, 1)
141        layout2.add_widget(self._delete_button, 2)
142        layout2.add_widget(Button("Quit", self._quit), 3)
143        layout3 = Layout([100])
144        self.add_layout(layout3)
145        self._info_text = Text(label="Selected:", name="selected")
146        self._info_text.disabled = True
147        layout3.add_widget(self._info_text)
148        self.fix()
149        self._on_pick()
150
151    def _on_select(self):
152        self._info_text.value = str(self._list_view.value)
153        self.save()
154
155    def _on_pick(self):
156        self._edit_button.disabled = self._list_view.value is None
157        self._delete_button.disabled = self._list_view.value is None
158
159    @staticmethod
160    def _add():
161        raise NextScene("Add")
162
163    @staticmethod
164    def _edit():
165        raise NextScene("Edit")
166
167    @staticmethod
168    def _delete():
169        raise NextScene("Delete")
170
171    @staticmethod
172    def _quit():
173        raise StopApplication("User pressed quit")
174
175
176class TestFrame3(Frame):
177    def __init__(self, screen):
178        super(TestFrame3, self).__init__(screen, 10, 20,
179                                         name="Blank",
180                                         has_shadow=True)
181        self.fix()
182
183
184class TestFrame4(Frame):
185    def __init__(self, screen, file_filter=None):
186        super(TestFrame4, self).__init__(
187            screen, screen.height, screen.width, has_border=False, name="My Form")
188
189        # State tracking for callbacks
190        self.selected = None
191        self.highlighted = None
192
193        # Simple full-page Widget
194        layout = Layout([1], fill_frame=True)
195        self.add_layout(layout)
196        self.file_list = FileBrowser(Widget.FILL_FRAME,
197                                     "/",
198                                     name="file_list",
199                                     on_select=self.select,
200                                     on_change=self.change,
201                                     file_filter=file_filter)
202        layout.add_widget(self.file_list)
203        self.fix()
204
205    def select(self):
206        self.selected = self.file_list.value
207
208    def change(self):
209        self.highlighted = self.file_list.value
210
211
212class TestFrame5(Frame):
213    def __init__(self, screen):
214        super(TestFrame5, self).__init__(
215            screen, screen.height, screen.width, has_border=True, name="My Form")
216
217        # Simple full-page Widget
218        layout = Layout([1], fill_frame=True)
219        self.add_layout(layout)
220        self.date_widget = DatePicker(
221            label="Date:", name="date", year_range=range(1999, 2020), on_change=self._changed)
222        self.date_widget.value = date(2017, 1, 2)
223        layout.add_widget(self.date_widget)
224        self.time_widget = TimePicker(
225            label="Time:", name="time", seconds=True, on_change=self._changed)
226        self.time_widget.value = time(12, 0, 59)
227        layout.add_widget(self.time_widget)
228        self.fix()
229
230        # State tracking for widgets
231        self.changed = False
232
233    def _changed(self):
234        self.changed = True
235
236
237class TestFrame6(Frame):
238    def __init__(self, screen):
239        super(TestFrame6, self).__init__(
240            screen, screen.height, screen.width, has_border=True, name="My Form")
241
242        # Simple full-page Widget
243        layout = Layout([1], fill_frame=True)
244        self.add_layout(layout)
245        self.lbl = Label("TstFrm6Lbl", name="tf6_lbl")
246        layout.add_widget(self.lbl)
247        self.fix()
248
249
250class TestWidgets(unittest.TestCase):
251    def setUp(self):
252        self.maxDiff = None
253
254    def assert_canvas_equals(self, canvas, expected):
255        """
256        Assert output to canvas is as expected.
257        """
258        output = ""
259        for y in range(canvas.height):
260            for x in range(canvas.width):
261                char, _, _, _ = canvas.get_from(x, y)
262                output += chr(char)
263            output += "\n"
264        self.assertEqual(output, expected)
265
266    @staticmethod
267    def process_keys(form, values, separator=None):
268        """
269        Inject a set of key events separated by a common key separator.
270        """
271        for new_value in values:
272            if isinstance(new_value, int):
273                form.process_event(KeyboardEvent(new_value))
274            else:
275                for char in new_value:
276                    form.process_event(KeyboardEvent(ord(char)))
277            if separator:
278                form.process_event(KeyboardEvent(separator))
279
280    @staticmethod
281    def process_mouse(form, values):
282        """
283        Inject a set of mouse events.
284        """
285        for x, y, buttons in values:
286            form.process_event(MouseEvent(x, y, buttons))
287
288    def test_form_data(self):
289        """
290        Check Frame.data works as expected.
291        """
292        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
293        canvas = Canvas(screen, 10, 40, 0, 0)
294        form = TestFrame(canvas)
295
296        # Should be empty on construction
297        self.assertEqual(form.data, {})
298
299        # Should be blank values after saving.
300        form.reset()
301        form.save()
302        self.assertEqual(
303            form.data,
304            {
305                'CA': False,
306                'CB': False,
307                'CC': False,
308                'TA': [''],
309                'TB': '',
310                'TC': '',
311                'TD': '',
312                'Things': 1
313            })
314
315    def test_rendering(self):
316        """
317        Check Frame renders as expected.
318        """
319        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
320        canvas = Canvas(screen, 10, 40, 0, 0)
321        form = TestFrame(canvas)
322        form.reset()
323
324        # Check initial rendering
325        form.update(0)
326        self.assert_canvas_equals(
327            canvas,
328            "+--------------------------------------+\n" +
329            "| Group 1:                             |\n" +
330            "| My First                             O\n" +
331            "| Box:                                 |\n" +
332            "|                                      |\n" +
333            "|                                      |\n" +
334            "|                                      |\n" +
335            "| Text1:                               |\n" +
336            "| Text2:                               |\n" +
337            "+--------------------------------------+\n")
338
339        # Check scrolling works.  Should also test label splitting and ellipsis.
340        form.move_to(0, 9, 8)
341        form.update(1)
342        self.assert_canvas_equals(
343            canvas,
344            "+--------------------------------------+\n" +
345            "| Text3:                               |\n" +
346            "|                                      |\n" +
347            "| ----------------------------------   |\n" +
348            "| Group 2:                             O\n" +
349            "| A Longer   (X) Option 1              |\n" +
350            "| Selection: ( ) Option 2              |\n" +
351            "|            ( ) Option 3              |\n" +
352            "| A very...  [ ] Field 1               |\n" +
353            "+--------------------------------------+\n")
354
355        # Now check button rendering.
356        form.move_to(0, 18, 8)
357        form.update(2)
358        self.assert_canvas_equals(
359            canvas,
360            "+--------------------------------------+\n" +
361            "|            [ ] Field 3               |\n" +
362            "|                                      |\n" +
363            "| ----------------------------------   |\n" +
364            "|                                      |\n" +
365            "| < Reset >  < View Data > < Quit >    |\n" +
366            "|  < One >     < Two >    < Three >    |\n" +
367            "|                                      O\n" +
368            "|                                      |\n" +
369            "+--------------------------------------+\n")
370
371    def test_unicode_rendering(self):
372        """
373        Check Frame renders as expected for unicode environments.
374        """
375        screen = MagicMock(spec=Screen, colours=8, unicode_aware=True)
376        canvas = Canvas(screen, 10, 40, 0, 0)
377        form = TestFrame(canvas)
378        form.reset()
379
380        # Check initial rendering
381        form.update(0)
382        self.assert_canvas_equals(
383            canvas,
384            "┌──────────────────────────────────────┐\n" +
385            "│ Group 1:                             │\n" +
386            "│ My First                             █\n" +
387            "│ Box:                                 ░\n" +
388            "│                                      ░\n" +
389            "│                                      ░\n" +
390            "│                                      ░\n" +
391            "│ Text1:                               ░\n" +
392            "│ Text2:                               │\n" +
393            "└──────────────────────────────────────┘\n")
394
395        # Check scrolling works.  Should also test label splitting and ellipsis.
396        form.move_to(0, 9, 8)
397        form.update(1)
398        self.assert_canvas_equals(
399            canvas,
400            "┌──────────────────────────────────────┐\n" +
401            "│ Text3:                               │\n" +
402            "│                                      ░\n" +
403            "│ ──────────────────────────────────   ░\n" +
404            "│ Group 2:                             █\n" +
405            "│ A Longer   (•) Option 1              ░\n" +
406            "│ Selection: ( ) Option 2              ░\n" +
407            "│            ( ) Option 3              ░\n" +
408            "│ A very...  [ ] Field 1               │\n" +
409            "└──────────────────────────────────────┘\n")
410
411        # Now check button rendering.
412        form.move_to(0, 18, 8)
413        form.update(2)
414        self.assert_canvas_equals(
415            canvas,
416            "┌──────────────────────────────────────┐\n" +
417            "│            [ ] Field 3               │\n" +
418            "│                                      ░\n" +
419            "│ ──────────────────────────────────   ░\n" +
420            "│                                      ░\n" +
421            "│ < Reset >  < View Data > < Quit >    ░\n" +
422            "│  < One >     < Two >    < Three >    ░\n" +
423            "│                                      █\n" +
424            "│                                      │\n" +
425            "└──────────────────────────────────────┘\n")
426
427    def test_no_border(self):
428        """
429        Check that a Frame with no border works
430        """
431        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
432        canvas = Canvas(screen, 10, 40, 0, 0)
433        form = TestFrame(canvas, has_border=False)
434        form.reset()
435
436        # Check initial rendering
437        form.update(0)
438        self.assert_canvas_equals(
439            canvas,
440            "  Group 1:                              \n" +
441            "  My First                              \n" +
442            "  Box:                                  \n" +
443            "                                        \n" +
444            "                                        \n" +
445            "                                        \n" +
446            "  Text1:                                \n" +
447            "  Text2:                                \n" +
448            "  Text3:                                \n" +
449            "                                        \n")
450
451        # Check scrolling works.  Should also test label splitting and ellipsis.
452        form.move_to(0, 10, 10)
453        form.update(1)
454        self.assert_canvas_equals(
455            canvas,
456            "  ------------------------------------  \n" +
457            "  Group 2:                              \n" +
458            "  A Longer    (X) Option 1              \n" +
459            "  Selection:  ( ) Option 2              \n" +
460            "              ( ) Option 3              \n" +
461            "  A very si...[ ] Field 1               \n" +
462            "              [ ] Field 2               \n" +
463            "              [ ] Field 3               \n" +
464            "                                        \n" +
465            "  ------------------------------------  \n")
466
467    def test_form_input(self):
468        """
469        Check Frame input works as expected.
470        """
471        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
472        canvas = Canvas(screen, 10, 40, 0, 0)
473        form = TestFrame(canvas)
474        form.reset()
475        self.process_keys(form,
476                          ["ABC\nDEF", "GHI", "jkl", "MN", " ", " ", "", " "],
477                          Screen.KEY_TAB)
478        form.save()
479        self.assertEqual(
480            form.data,
481            {
482                'CA': True,
483                'CB': False,
484                'CC': True,
485                'TA': ['ABC', 'DEF'],
486                'TB': 'GHI',
487                'TC': 'jkl',
488                'TD': 'MN',
489                'Things': 1
490            })
491
492        # Check that forms ignore unrecognised events.
493        event = object()
494        self.assertEqual(event, form.process_event(event))
495
496        # Check forms don't want global input handling.
497        self.assertFalse(form.safe_to_default_unhandled_input)
498
499    def test_textbox_input(self):
500        """
501        Check TextBox input works as expected.
502        """
503        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
504        canvas = Canvas(screen, 10, 40, 0, 0)
505        form = TestFrame(canvas)
506        form.reset()
507
508        # Check basic movement keys
509        self.process_keys(form,  ["ABC", Screen.KEY_LEFT, "D"])
510        form.save()
511        self.assertEqual(form.data["TA"], ["ABDC"])
512        self.process_keys(form,  [Screen.KEY_RIGHT, Screen.KEY_RIGHT, "E"])
513        form.save()
514        self.assertEqual(form.data["TA"], ["ABDCE"])
515        self.process_keys(form,  ["\nFGH", Screen.KEY_UP, Screen.KEY_UP, "I"])
516        form.save()
517        self.assertEqual(form.data["TA"], ["ABDICE", "FGH"])
518        self.process_keys(form,  [Screen.KEY_DOWN, Screen.KEY_DOWN, "J"])
519        form.save()
520        self.assertEqual(form.data["TA"], ["ABDICE", "FGHJ"])
521        self.process_keys(form,  [Screen.KEY_HOME, "K"])
522        form.save()
523        self.assertEqual(form.data["TA"], ["ABDICE", "KFGHJ"])
524        self.process_keys(form,  [Screen.KEY_END, "L"])
525        form.save()
526        self.assertEqual(form.data["TA"], ["ABDICE", "KFGHJL"])
527
528        # Backspace - normal and wrapping lines
529        self.process_keys(form,  [Screen.KEY_BACK])
530        form.save()
531        self.assertEqual(form.data["TA"], ["ABDICE", "KFGHJ"])
532        self.process_keys(form,  [Screen.KEY_HOME, Screen.KEY_BACK])
533        form.save()
534        self.assertEqual(form.data["TA"], ["ABDICEKFGHJ"])
535
536        # Check cursor line-wrapping
537        self.process_keys(form,  ["\n"])
538        form.save()
539        self.assertEqual(form.data["TA"], ["ABDICE", "KFGHJ"])
540        self.process_keys(form,  [Screen.KEY_LEFT, "M"])
541        form.save()
542        self.assertEqual(form.data["TA"], ["ABDICEM", "KFGHJ"])
543        self.process_keys(form,  [Screen.KEY_RIGHT, "N"])
544        form.save()
545        self.assertEqual(form.data["TA"], ["ABDICEM", "NKFGHJ"])
546
547        # Delete - normal and wrapping lines and at end of all data.
548        self.process_keys(form,  [Screen.KEY_DELETE])
549        form.save()
550        self.assertEqual(form.data["TA"], ["ABDICEM", "NFGHJ"])
551        self.process_keys(form,
552                          [Screen.KEY_UP, Screen.KEY_END, Screen.KEY_DELETE])
553        form.save()
554        self.assertEqual(form.data["TA"], ["ABDICEMNFGHJ"])
555        self.process_keys(form, [Screen.KEY_END, Screen.KEY_DELETE])
556        form.save()
557        self.assertEqual(form.data["TA"], ["ABDICEMNFGHJ"])
558
559        # Check that page up/down work as expected.
560        self.process_keys(form,  [Screen.ctrl("m"), Screen.ctrl("m"), Screen.KEY_PAGE_UP, "O"])
561        form.save()
562        self.assertEqual(form.data["TA"], ["OABDICEMNFGHJ", "", ""])
563        self.process_keys(form,  [Screen.KEY_PAGE_DOWN, "P"])
564        form.save()
565        self.assertEqual(form.data["TA"], ["OABDICEMNFGHJ", "", "P"])
566
567        # Check that the current focus ignores unknown events.
568        event = object()
569        self.assertEqual(event, form.process_event(event))
570
571    def test_text_input(self):
572        """
573        Check Text input works as expected.
574        """
575        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
576        canvas = Canvas(screen, 10, 40, 0, 0)
577        form = TestFrame(canvas)
578        form.reset()
579
580        # Check basic movement keys
581        self.process_keys(form,  [Screen.KEY_TAB, "ABC"])
582        form.save()
583        self.assertEqual(form.data["TB"], "ABC")
584        self.process_keys(form,  [Screen.KEY_HOME, "D"])
585        form.save()
586        self.assertEqual(form.data["TB"], "DABC")
587        self.process_keys(form,  [Screen.KEY_END, "E"])
588        form.save()
589        self.assertEqual(form.data["TB"], "DABCE")
590        self.process_keys(form,  [Screen.KEY_LEFT, "F"])
591        form.save()
592        self.assertEqual(form.data["TB"], "DABCFE")
593        self.process_keys(form,  [Screen.KEY_RIGHT, "G"])
594        form.save()
595        self.assertEqual(form.data["TB"], "DABCFEG")
596
597        # Backspace
598        self.process_keys(form,  [Screen.KEY_BACK])
599        form.save()
600        self.assertEqual(form.data["TB"], "DABCFE")
601
602        # Delete - including at end of data
603        self.process_keys(form,  [Screen.KEY_DELETE])
604        form.save()
605        self.assertEqual(form.data["TB"], "DABCFE")
606        self.process_keys(form,  [Screen.KEY_HOME, Screen.KEY_DELETE])
607        form.save()
608        self.assertEqual(form.data["TB"], "ABCFE")
609
610        # Check that the current focus ignores unknown events.
611        event = object()
612        self.assertEqual(event, form.process_event(event))
613
614    def test_validation(self):
615        """
616        Check free-form text validation works as expected.
617        """
618        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
619        canvas = Canvas(screen, 10, 40, 0, 0)
620        form = TestFrame(canvas)
621        form.reset()
622
623        # Check that save still works with no validation.
624        self.process_keys(form,  [Screen.KEY_TAB, Screen.KEY_TAB, "ABC"])
625        form.save()
626        self.assertEqual(form.data["TC"], "ABC")
627
628        # Check that enforced validation throws exceptions as needed.
629        with self.assertRaises(InvalidFields) as cm:
630            form.save(validate=True)
631        self.assertEqual(cm.exception.fields, ["TC"])
632
633        # Check valid data doesn't throw anything.
634        self.process_keys(form,
635                          [Screen.KEY_BACK, Screen.KEY_BACK, Screen.KEY_BACK])
636        form.save(validate=True)
637
638        # Check functions work as well as regexp strings.
639        self.process_keys(form, [Screen.KEY_TAB, "ABC"])
640        with self.assertRaises(InvalidFields) as cm:
641            form.save(validate=True)
642        self.assertEqual(cm.exception.fields, ["TD"])
643
644    def test_checkbox_input(self):
645        """
646        Check Checkbox input works as expected.
647        """
648        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
649        canvas = Canvas(screen, 10, 40, 0, 0)
650        form = TestFrame(canvas)
651        form.reset()
652
653        # Check basic selection keys
654        self.process_keys(
655            form,
656            [Screen.KEY_TAB, Screen.KEY_TAB, Screen.KEY_TAB, Screen.KEY_TAB,
657             Screen.KEY_TAB, " "])
658        form.save()
659        self.assertEqual(form.data["CA"], True)
660        self.process_keys(form, ["\n"])
661        form.save()
662        self.assertEqual(form.data["CA"], False)
663        self.process_keys(form, ["\r"])
664        form.save()
665        self.assertEqual(form.data["CA"], True)
666
667        # Check that the current focus ignores unknown events.
668        event = object()
669        self.assertEqual(event, form.process_event(event))
670
671    def test_radiobutton_input(self):
672        """
673        Check RadioButton input works as expected.
674        """
675        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
676        canvas = Canvas(screen, 10, 40, 0, 0)
677        form = TestFrame(canvas)
678        form.reset()
679
680        # Check basic selection keys - including limit checking
681        self.process_keys(
682            form,
683            [Screen.KEY_TAB, Screen.KEY_TAB, Screen.KEY_TAB, Screen.KEY_TAB])
684        form.save()
685        self.assertEqual(form.data["Things"], 1)
686        self.process_keys(form, [Screen.KEY_UP])
687        form.save()
688        self.assertEqual(form.data["Things"], 1)
689        self.process_keys(form, [Screen.KEY_DOWN])
690        form.save()
691        self.assertEqual(form.data["Things"], 2)
692        self.process_keys(form, [Screen.KEY_DOWN])
693        form.save()
694        self.assertEqual(form.data["Things"], 3)
695        self.process_keys(form, [Screen.KEY_DOWN])
696        form.save()
697        self.assertEqual(form.data["Things"], 3)
698        self.process_keys(form, [Screen.KEY_UP])
699        form.save()
700        self.assertEqual(form.data["Things"], 2)
701
702        # Check that a radio button ignores unknown events.
703        event = object()
704        self.assertEqual(event, form.process_event(event))
705
706        # Check that setting an unknown value resets to first radio button.
707        form.focussed_widget.value = "Nonexistent value"
708        form.save()
709        self.assertEqual(form.data["Things"], 1)
710
711    def test_mouse_input(self):
712        """
713        Check mouse input works as expected.
714        """
715        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
716        scene = MagicMock(spec=Scene)
717        canvas = Canvas(screen, 10, 40, 0, 0)
718        form = TestFrame(canvas)
719        form.register_scene(scene)
720        form.reset()
721
722        # Check focus moves when clicked on a text or textbox
723        self.process_mouse(form, [(29, 7, MouseEvent.LEFT_CLICK)])
724        self.assertEqual(form._layouts[form._focus]._live_widget, 2)
725        self.process_mouse(form, [(29, 2, MouseEvent.LEFT_CLICK)])
726        self.assertEqual(form._layouts[form._focus]._live_widget, 1)
727
728        # Check focus doesn't change on scroll or click outside of widgets
729        self.process_mouse(form, [(0, 0, 0)])
730        self.assertEqual(form._focus, 0)
731        self.assertEqual(form._layouts[form._focus]._live_col, 1)
732        self.assertEqual(form._layouts[form._focus]._live_widget, 1)
733        self.process_mouse(form, [(39, 7, MouseEvent.LEFT_CLICK)])
734        self.assertEqual(form._layouts[form._focus]._live_widget, 1)
735
736        # Check focus moves when clicked on a checkbox or radiobutton
737        self.process_mouse(form, [(29, 1, MouseEvent.LEFT_CLICK)])
738        self.assertEqual(form._layouts[form._focus]._live_widget, 8)
739        # Note that the above changes the Frame start-line.
740        self.process_mouse(form, [(29, 5, MouseEvent.LEFT_CLICK)])
741        self.assertEqual(form._layouts[form._focus]._live_widget, 11)
742
743        # Check focus moves when hovering over a widget
744        self.process_mouse(form, [(39, 7, MouseEvent.LEFT_CLICK), (3, 8, 0)])
745        self.assertEqual(form._focus, 1)
746        self.assertEqual(form._layouts[form._focus]._live_col, 0)
747        self.assertEqual(form._layouts[form._focus]._live_widget, 1)
748
749        # Check button click triggers an event.
750        with self.assertRaises(StopApplication):
751            self.process_mouse(form, [(30, 7, MouseEvent.LEFT_CLICK)])
752
753        # Check that the current focus ignores unknown events.
754        event = object()
755        self.assertEqual(event, form.process_event(event))
756
757
758    def test_frame_focus_widget_property(self):
759        """
760        Check the frame exposes the focussed widget
761        """
762        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
763        canvas = Canvas(screen, 10, 40, 0, 0)
764        form = TestFrame(canvas)
765        form.reset()
766
767        # If the Frame loses the focus it must not return a focussed widget.
768        form._has_focus = False
769        self.assertIsNone(form.focussed_widget)
770
771        # If the Frame focus is undefined, it must not return a focussed widget.
772        form._has_focus = True
773        form._focus = 9999
774        self.assertIsNone(form.focussed_widget)
775
776    def test_frame_focus_widget_property_when_frame_focussed(self):
777        """
778        check the frame exposes nothing when frame is foccused
779        """
780        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
781        canvas = Canvas(screen, 10, 40, 0, 0)
782        form = TestFrame(canvas)
783        form.reset()
784
785        # A Frame with a valid focus should return the widget in focus.
786        layout = form._layouts[form._focus]
787        layout._has_focus = True
788        form._has_focus = True
789        self.assertEqual(layout._columns[layout._live_col][layout._live_widget],
790                         form.focussed_widget)
791
792    def test_widget_navigation(self):
793        """
794        Check widget tab stops work as expected.
795        """
796        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
797        canvas = Canvas(screen, 10, 40, 0, 0)
798        form = TestFrame(canvas)
799        form.reset()
800
801        # Check default focus at start is first visible widget
802        self.assertEqual(form._focus, 0)
803        self.assertEqual(form._layouts[form._focus]._live_col, 1)
804        self.assertEqual(form._layouts[form._focus]._live_widget, 1)
805
806        # Check BACK_TAB reverses TAB.
807        self.process_keys(form, [Screen.KEY_TAB])
808        self.assertEqual(form._layouts[form._focus]._live_widget, 2)
809        self.process_keys(form, [Screen.KEY_BACK_TAB])
810        self.assertEqual(form._layouts[form._focus]._live_widget, 1)
811
812        # Check BACK_TAB/TAB wraps around the form.
813        self.process_keys(form, [Screen.KEY_BACK_TAB])
814        self.assertEqual(form._focus, 1)
815        self.process_keys(form, [Screen.KEY_TAB])
816        self.assertEqual(form._focus, 0)
817
818        # Tab out into text fields and check UP/DOWN keys.
819        self.process_keys(form, [Screen.KEY_TAB, Screen.KEY_DOWN])
820        self.assertEqual(form._layouts[form._focus]._live_widget, 3)
821        self.process_keys(form, [Screen.KEY_UP])
822        self.assertEqual(form._layouts[form._focus]._live_widget, 2)
823
824        # Tab out into buttons and check LEFT/RIGHT keys.
825        self.process_keys(form, [Screen.KEY_TAB, Screen.KEY_TAB, Screen.KEY_TAB,
826                                 Screen.KEY_TAB, Screen.KEY_TAB, Screen.KEY_TAB,
827                                 Screen.KEY_TAB, Screen.KEY_TAB])
828        self.assertEqual(form._focus, 1)
829        self.assertEqual(form._layouts[form._focus]._live_col, 1)
830        self.assertEqual(form._layouts[form._focus]._live_widget, 0)
831
832        self.process_keys(form, [Screen.KEY_RIGHT])
833        self.assertEqual(form._layouts[form._focus]._live_col, 2)
834        self.process_keys(form, [Screen.KEY_LEFT])
835        self.assertEqual(form._layouts[form._focus]._live_col, 1)
836        self.process_keys(form, [Screen.KEY_LEFT])
837        # Reset will be disabled, but move down to "One"
838        self.assertEqual(form._layouts[form._focus]._live_col, 0)
839        self.assertEqual(form._layouts[form._focus]._live_widget, 1)
840        self.process_keys(form, [Screen.KEY_RIGHT])
841        self.assertEqual(form._layouts[form._focus]._live_col, 1)
842
843        # Check up and down stay in column.
844        self.process_keys(form, [Screen.KEY_UP])
845        self.assertEqual(form._layouts[form._focus]._live_col, 1)
846        self.assertEqual(form._layouts[form._focus]._live_widget, 0)
847        self.process_keys(form, [Screen.KEY_DOWN])
848        self.assertEqual(form._layouts[form._focus]._live_col, 1)
849        self.assertEqual(form._layouts[form._focus]._live_widget, 1)
850
851        # Check up and down find nearest widget across Layouts
852        # - Up to checkbox
853        self.process_keys(form, [Screen.KEY_UP, Screen.KEY_UP])
854        self.assertEqual(form._focus, 0)
855        self.assertEqual(form._layouts[form._focus]._live_col, 1)
856        self.assertEqual(form._layouts[form._focus]._live_widget, 10)
857        # - Down to first button row
858        self.process_keys(form, [Screen.KEY_DOWN])
859        self.assertEqual(form._focus, 1)
860        self.assertEqual(form._layouts[form._focus]._live_col, 1)
861        self.assertEqual(form._layouts[form._focus]._live_widget, 0)
862        # - Down to wrap to top of form
863        self.process_keys(form, [Screen.KEY_DOWN, Screen.KEY_DOWN])
864        self.assertEqual(form._focus, 0)
865        self.assertEqual(form._layouts[form._focus]._live_col, 1)
866        self.assertEqual(form._layouts[form._focus]._live_widget, 1)
867
868    def test_list_box(self):
869        """
870        Check ListBox widget works as expected.
871        """
872        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
873        scene = MagicMock(spec=Scene)
874        canvas = Canvas(screen, 10, 40, 0, 0)
875        form = TestFrame2(
876            canvas, [("One", 1), ("Two is now quite a bit longer than before", 2)])
877        form.register_scene(scene)
878        form.reset()
879
880        # Check we have a default value for our list.
881        form.save()
882        self.assertEqual(form.data, {"selected": "None", "contacts": 1})
883
884        # Check that UP/DOWN change selection.
885        self.process_keys(form, [Screen.KEY_DOWN])
886        form.save()
887        self.assertEqual(form.data, {"selected": "None", "contacts": 2})
888        self.process_keys(form, [Screen.KEY_UP])
889        form.save()
890        self.assertEqual(form.data, {"selected": "None", "contacts": 1})
891
892        # Check that the listbox is rendered correctly.
893        form.update(0)
894        self.assert_canvas_equals(
895            canvas,
896            "+------------ Test Frame 2 ------------+\n" +
897            "|One                                   |\n" +
898            "|Two is now quite a bit longer than ...O\n" +
899            "|                                      |\n" +
900            "|                                      |\n" +
901            "|                                      |\n" +
902            "|######################################|\n" +
903            "| < Add > < Edit > < Delete < Quit >   |\n" +
904            "|Selected: None                        |\n" +
905            "+--------------------------------------+\n")
906
907        # Check that mouse input changes selection.
908        self.process_mouse(form, [(2, 2, MouseEvent.LEFT_CLICK)])
909        form.save()
910        self.assertEqual(form.data, {"selected": "None", "contacts": 2})
911        self.process_mouse(form, [(2, 1, MouseEvent.LEFT_CLICK)])
912        form.save()
913        self.assertEqual(form.data, {"selected": "None", "contacts": 1})
914
915        # Check that enter key handles correctly.
916        self.process_keys(form, [Screen.ctrl("m")])
917        form.save()
918        self.assertEqual(form.data, {"selected": "1", "contacts": 1})
919
920        form.update(0)
921        self.assert_canvas_equals(
922            canvas,
923            "+------------ Test Frame 2 ------------+\n" +
924            "|One                                   |\n" +
925            "|Two is now quite a bit longer than ...O\n" +
926            "|                                      |\n" +
927            "|                                      |\n" +
928            "|                                      |\n" +
929            "|######################################|\n" +
930            "| < Add > < Edit > < Delete < Quit >   |\n" +
931            "|Selected: 1                           |\n" +
932            "+--------------------------------------+\n")
933
934        # Check that mouse double click handles correctly.
935        self.process_mouse(form, [(2, 2, MouseEvent.DOUBLE_CLICK)])
936        form.save()
937        self.assertEqual(form.data, {"selected": "2", "contacts": 2})
938
939        form.update(0)
940        self.assert_canvas_equals(
941            canvas,
942            "+------------ Test Frame 2 ------------+\n" +
943            "|One                                   |\n" +
944            "|Two is now quite a bit longer than ...O\n" +
945            "|                                      |\n" +
946            "|                                      |\n" +
947            "|                                      |\n" +
948            "|######################################|\n" +
949            "| < Add > < Edit > < Delete < Quit >   |\n" +
950            "|Selected: 2                           |\n" +
951            "+--------------------------------------+\n")
952
953        # Check that the current focus ignores unknown events.
954        event = object()
955        self.assertEqual(event, form.process_event(event))
956
957    def test_title(self):
958        """
959        Check Frame titles work as expected.
960        """
961        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
962        scene = MagicMock(spec=Scene)
963        canvas = Canvas(screen, 10, 40, 0, 0)
964        form = TestFrame2(canvas, [("One", 1), ("Two", 2)])
965        form.register_scene(scene)
966        form.reset()
967
968        # Check that the title is rendered correctly.
969        form.update(0)
970        self.assert_canvas_equals(
971            canvas,
972            "+------------ Test Frame 2 ------------+\n" +
973            "|One                                   |\n" +
974            "|Two                                   O\n" +
975            "|                                      |\n" +
976            "|                                      |\n" +
977            "|                                      |\n" +
978            "|######################################|\n" +
979            "| < Add > < Edit > < Delete < Quit >   |\n" +
980            "|Selected: None                        |\n" +
981            "+--------------------------------------+\n")
982
983        # Check that a new title is rendered correctly.
984        form.title = "A New Title!"
985        form.update(1)
986        self.assert_canvas_equals(
987            canvas,
988            "+------------ A New Title! ------------+\n" +
989            "|One                                   |\n" +
990            "|Two                                   O\n" +
991            "|                                      |\n" +
992            "|                                      |\n" +
993            "|                                      |\n" +
994            "|######################################|\n" +
995            "| < Add > < Edit > < Delete < Quit >   |\n" +
996            "|Selected: None                        |\n" +
997            "+--------------------------------------+\n")
998
999        # Note that the actual stored title includes spaces for margins
1000        self.assertEqual(form.title, " A New Title! ")
1001
1002    def test_focus_callback(self):
1003        """
1004        Check that the _on_focus & _on_blur callbacks work as expected.
1005        """
1006        def _on_focus():
1007            self._did_focus = True
1008
1009        def _on_blur():
1010            self._did_blur = True
1011
1012        # Create a dummy screen
1013        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
1014        scene = MagicMock(spec=Scene)
1015        canvas = Canvas(screen, 2, 40, 0, 0)
1016
1017        # Create the form we want to test.
1018        form = Frame(canvas, canvas.height, canvas.width, has_border=False)
1019        layout = Layout([100], fill_frame=True)
1020        form.add_layout(layout)
1021        layout.add_widget(Text("Test"))
1022        layout.add_widget(Text("Test2", on_blur=_on_blur, on_focus=_on_focus))
1023        form.fix()
1024        form.register_scene(scene)
1025        form.reset()
1026
1027        # Reset state for test
1028        self._did_blur = False
1029        self._did_focus = False
1030
1031        # Tab round to move the focus - check it has called the right function.
1032        self.process_keys(form, [Screen.KEY_TAB])
1033        self.assertEqual(self._did_blur, False)
1034        self.assertEqual(self._did_focus, True)
1035
1036        # Reset the state and Now move the focus away with the mouse.
1037        self._did_focus = False
1038        self.process_mouse(form, [(0, 0, MouseEvent.LEFT_CLICK)])
1039        self.assertEqual(self._did_blur, True)
1040        self.assertEqual(self._did_focus, False)
1041
1042    def test_load_callback(self):
1043        """
1044        Check that the _on_load callback works as expected.
1045        """
1046        def _on_load():
1047            self._did_load = True
1048
1049        # Reset state for test
1050        self._did_load = False
1051
1052        # Create a dummy screen
1053        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
1054        canvas = Canvas(screen, 2, 40, 0, 0)
1055
1056        # Create the form we want to test.
1057        form = Frame(canvas, canvas.height, canvas.width, on_load=_on_load)
1058        form.fix()
1059        scene = Scene([form], -1)
1060
1061        # Check only called on reset.
1062        self.assertEqual(self._did_load, False)
1063        scene.reset()
1064        self.assertEqual(self._did_load, True)
1065
1066    def test_multi_column_list_box(self):
1067        """
1068        Check MultiColumnListBox works as expected.
1069        """
1070        # Create a dummy screen.
1071        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
1072        scene = MagicMock(spec=Scene)
1073        canvas = Canvas(screen, 10, 40, 0, 0)
1074
1075        # Create the form we want to test.
1076        form = Frame(canvas, canvas.height, canvas.width, has_border=False)
1077        layout = Layout([100], fill_frame=True)
1078        mc_list = MultiColumnListBox(
1079            Widget.FILL_FRAME,
1080            [3, "4", ">4", "<4", "^10%", "100%"],
1081            [
1082                (["1", "2", "3", "4", "5", "6"], 1),
1083                (["11", "222", "333", "444", "555", "6"], 2),
1084                (["111", "2", "3", "4", "5", "6"], 3),
1085                (["1", "2", "33333", "4", "5", "6"], 4),
1086                (["1", "2", "3", "4", "5", "6666666666666666666666"], 5),
1087            ],
1088            titles=["A", "B", "C", "D", "E", "F"],
1089            name="mc_list")
1090        form.add_layout(layout)
1091        layout.add_widget(mc_list)
1092        form.fix()
1093        form.register_scene(scene)
1094        form.reset()
1095
1096        # Check we have a default value for our list.
1097        form.save()
1098        self.assertEqual(form.data, {"mc_list": 1})
1099
1100        # Check that UP/DOWN change selection.
1101        self.process_keys(form, [Screen.KEY_DOWN])
1102        form.save()
1103        self.assertEqual(form.data, {"mc_list": 2})
1104        self.process_keys(form, [Screen.KEY_UP])
1105        form.save()
1106        self.assertEqual(form.data, {"mc_list": 1})
1107
1108        # Check that PGUP/PGDN change selection.
1109        self.process_keys(form, [Screen.KEY_PAGE_DOWN])
1110        form.save()
1111        self.assertEqual(form.data, {"mc_list": 5})
1112        self.process_keys(form, [Screen.KEY_PAGE_UP])
1113        form.save()
1114        self.assertEqual(form.data, {"mc_list": 1})
1115
1116        # Check that the widget is rendered correctly.
1117        form.update(0)
1118        self.assert_canvas_equals(
1119            canvas,
1120            "A  B      C D    E  F                   \n" +
1121            "1  2      3 4    5  6                   \n" +
1122            "11 222  333 444 555 6                   \n" +
1123            "1112      3 4    5  6                   \n" +
1124            "1  2   3... 4    5  6                   \n" +
1125            "1  2      3 4    5  66666666666666666666\n" +
1126            "                                        \n" +
1127            "                                        \n" +
1128            "                                        \n" +
1129            "                                        \n")
1130
1131        # Check that mouse input changes selection.
1132        self.process_mouse(form, [(2, 2, MouseEvent.LEFT_CLICK)])
1133        form.save()
1134        self.assertEqual(form.data, {"mc_list": 2})
1135        self.process_mouse(form, [(2, 1, MouseEvent.LEFT_CLICK)])
1136        form.save()
1137        self.assertEqual(form.data, {"mc_list": 1})
1138
1139        # Check that the start_line can be read and set - and enforces good behaviour
1140        mc_list.start_line = 0
1141        self.assertEqual(mc_list.start_line, 0)
1142        mc_list.start_line = len(mc_list.options) - 1
1143        self.assertEqual(mc_list.start_line, len(mc_list.options) - 1)
1144        mc_list.start_line = 10000000
1145        self.assertEqual(mc_list.start_line, len(mc_list.options) - 1)
1146
1147        # Check that options can be read and set.
1148        mc_list.options = [(["a", "b", "c", "d", "e", "f"], 0)]
1149        self.assertEqual(mc_list.options, [(["a", "b", "c", "d", "e", "f"], 0)])
1150        mc_list.options = []
1151        self.assertEqual(mc_list.options, [])
1152
1153        # Check that the form re-renders correctly afterwards.
1154        form.update(1)
1155        self.assert_canvas_equals(
1156            canvas,
1157            "A  B      C D    E  F                   \n" +
1158            "                                        \n" +
1159            "                                        \n" +
1160            "                                        \n" +
1161            "                                        \n" +
1162            "                                        \n" +
1163            "                                        \n" +
1164            "                                        \n" +
1165            "                                        \n" +
1166            "                                        \n")
1167
1168        # Check that the current focus ignores unknown events.
1169        event = object()
1170        self.assertEqual(event, form.process_event(event))
1171
1172        # Check that options retain the current value where possible.
1173        mc_list.options = [
1174            (["a", "b", "c", "d", "e", "f"], 0),
1175            (["b", "b", "c", "d", "e", "f"], 1),
1176            (["c", "b", "c", "d", "e", "f"], 2),
1177        ]
1178        mc_list.value = 1
1179        mc_list.options = [
1180            (["a", "b", "c", "d", "e", "f"], 0),
1181            (["b", "b", "c", "d", "e", "f"], 1),
1182            (["c", "b", "c", "d", "e", "f"], 2),
1183            (["d", "b", "c", "d", "e", "f"], 3),
1184        ]
1185        self.assertEqual(mc_list.value, 1)
1186        mc_list.options = [
1187            (["a", "b", "c", "d", "e", "f"], 0),
1188            (["c", "b", "c", "d", "e", "f"], 2),
1189            (["d", "b", "c", "d", "e", "f"], 3),
1190        ]
1191        self.assertEqual(mc_list.value, 0)
1192
1193
1194    def test_multi_column_list_box_delimiter(self):
1195        """
1196        Check MultiColumnListBox works as expected with space_delimiter
1197        """
1198        # Create a dummy screen.
1199        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
1200        scene = MagicMock(spec=Scene)
1201        canvas = Canvas(screen, 10, 40, 0, 0)
1202
1203        # Create the form we want to test.
1204        form = Frame(canvas, canvas.height, canvas.width, has_border=False)
1205        layout = Layout([100], fill_frame=True)
1206        mc_list = MultiColumnListBox(
1207            Widget.FILL_FRAME,
1208            [3, "4", ">4", "<4", "^10%", "100%"],
1209            [
1210                (["1", "2", "3", "4", "5", "6"], 1),
1211                (["11", "222", "333", "444", "555", "6"], 2),
1212                (["111", "2", "3", "4", "5", "6"], 3),
1213                (["1", "2", "33333", "4", "5", "6"], 4),
1214                (["1", "2", "3", "4", "5", "6666666666666666666666"], 5),
1215            ],
1216            titles=["A", "B", "C", "D", "E", "F"],
1217            name="mc_list",
1218            space_delimiter='|')
1219        form.add_layout(layout)
1220        layout.add_widget(mc_list)
1221        form.fix()
1222        form.register_scene(scene)
1223        form.reset()
1224
1225        # Check that the widget is rendered correctly.
1226        form.update(0)
1227        self.assert_canvas_equals(
1228            canvas,
1229            "A  |B   |   C|D   | E  |F               \n" +
1230            "1  |2   |   3|4   | 5  |6               \n" +
1231            "11 |222 | 333|444 |555 |6               \n" +
1232            "111|2   |   3|4   | 5  |6               \n" +
1233            "1  |2   |3...|4   | 5  |6               \n" +
1234            "1  |2   |   3|4   | 5  |6666666666666666\n" +
1235            "                                        \n" +
1236            "                                        \n" +
1237            "                                        \n" +
1238            "                                        \n")
1239
1240    def test_list_box_scrollbar(self):
1241        """
1242        Check ListBox scrollbar works.
1243        """
1244        # Create a dummy screen.
1245        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
1246        scene = MagicMock(spec=Scene)
1247        canvas = Canvas(screen, 10, 40, 0, 0)
1248
1249        # Create the form we want to test.
1250        form = Frame(canvas, canvas.height, canvas.width, has_border=False)
1251        layout = Layout([100], fill_frame=True)
1252        simple_list = ListBox(
1253            3,
1254            [
1255                ("Some", 1),
1256                ("Stuff", 2),
1257                ("To", 3),
1258                ("See", 4),
1259            ],
1260            add_scroll_bar=True,
1261            name="simple_list")
1262        form.add_layout(layout)
1263        layout.add_widget(simple_list)
1264        form.fix()
1265        form.register_scene(scene)
1266        form.reset()
1267
1268        # Check that the widget is rendered correctly with the scrollbar
1269        form.update(0)
1270        self.assert_canvas_equals(
1271            canvas,
1272            "Some                                   O\n" +
1273            "Stuff                                  |\n" +
1274            "To                                     |\n" +
1275            "                                        \n" +
1276            "                                        \n" +
1277            "                                        \n" +
1278            "                                        \n" +
1279            "                                        \n" +
1280            "                                        \n" +
1281            "                                        \n")
1282
1283        # Check that mouse input works
1284        self.process_mouse(form, [(39, 2, MouseEvent.LEFT_CLICK)])
1285        form.update(0)
1286        self.assert_canvas_equals(
1287            canvas,
1288            "Stuff                                  |\n" +
1289            "To                                     |\n" +
1290            "See                                    O\n" +
1291            "                                        \n" +
1292            "                                        \n" +
1293            "                                        \n" +
1294            "                                        \n" +
1295            "                                        \n" +
1296            "                                        \n" +
1297            "                                        \n")
1298
1299        # Check that options can be set and hide scrollbar
1300        simple_list.options = [("New list", 1)]
1301        form.update(0)
1302        self.assert_canvas_equals(
1303            canvas,
1304            "New list                                \n" +
1305            "                                        \n" +
1306            "                                        \n" +
1307            "                                        \n" +
1308            "                                        \n" +
1309            "                                        \n" +
1310            "                                        \n" +
1311            "                                        \n" +
1312            "                                        \n" +
1313            "                                        \n")
1314
1315    def test_multi_column_list_box_scrollbar(self):
1316        """
1317        Check MultiColumnListBox scrollbar works.
1318        """
1319        # Create a dummy screen.
1320        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
1321        scene = MagicMock(spec=Scene)
1322        canvas = Canvas(screen, 10, 40, 0, 0)
1323
1324        # Create the form we want to test.
1325        form = Frame(canvas, canvas.height, canvas.width, has_border=False)
1326        layout = Layout([100], fill_frame=True)
1327        mc_list = MultiColumnListBox(
1328            3,
1329            [3, 0, ">4"],
1330            [
1331                (["1", "2", "3"], 1),
1332                (["11", "222", "333"], 2),
1333                (["111", "2", "3"], 3),
1334            ],
1335            titles=["A", "B", "C"],
1336            add_scroll_bar=True,
1337            name="mc_list")
1338        form.add_layout(layout)
1339        layout.add_widget(mc_list)
1340        form.fix()
1341        form.register_scene(scene)
1342        form.reset()
1343
1344        # Check that the widget is rendered correctly with the scrollbar
1345        form.update(0)
1346        self.assert_canvas_equals(
1347            canvas,
1348            "A  B                                  C \n" +
1349            "1  2                                  3O\n" +
1350            "11 222                              333|\n" +
1351            "                                        \n" +
1352            "                                        \n" +
1353            "                                        \n" +
1354            "                                        \n" +
1355            "                                        \n" +
1356            "                                        \n" +
1357            "                                        \n")
1358
1359    def test_disabled_text(self):
1360        """
1361        Check disabled TextBox can be used for pre-formatted output.
1362        """
1363        # Create a dummy screen.
1364        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
1365        scene = MagicMock(spec=Scene)
1366        canvas = Canvas(screen, 10, 40, 0, 0)
1367
1368        # Create the form we want to test.
1369        form = Frame(canvas, canvas.height, canvas.width, has_border=False)
1370        layout = Layout([100], fill_frame=True)
1371        form.add_layout(layout)
1372        text_box = TextBox(1, as_string=True)
1373        text_box.disabled = True
1374        layout.add_widget(text_box)
1375        form.fix()
1376        form.register_scene(scene)
1377        form.reset()
1378
1379        # Check that input has no effect on the programmed value.
1380        text_box.value = "A test"
1381        self.process_keys(form, ["A"])
1382        form.save()
1383        self.assertEqual(text_box.value, "A test")
1384
1385        # Check that we can provide a custom colour.  Since the default palette has no "custom"
1386        # key, this will throw an exception.
1387        self.assertEqual(text_box._pick_colours("blah"), form.palette["disabled"])
1388        with self.assertRaises(KeyError) as cm:
1389            text_box.custom_colour = "custom"
1390            text_box._pick_colours("blah")
1391        self.assertIn("custom", str(cm.exception))
1392
1393        # Also check the value is returned as set
1394        self.assertEqual(text_box.custom_colour, "custom")
1395
1396    def test_line_flow(self):
1397        """
1398        Check TextBox line-flow editing works as expected.
1399        """
1400        # Create a dummy screen.
1401        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
1402        scene = MagicMock(spec=Scene)
1403        canvas = Canvas(screen, 10, 40, 0, 0)
1404
1405        # Create the form we want to test.
1406        form = Frame(canvas, canvas.height, canvas.width, has_border=False)
1407        layout = Layout([100], fill_frame=True)
1408        form.add_layout(layout)
1409        text_box = TextBox(5, as_string=True, line_wrap=True)
1410        layout.add_widget(text_box)
1411        form.fix()
1412        form.register_scene(scene)
1413        form.reset()
1414
1415        # start with some text that will wrap and check display works.
1416        text_box.value = "A\nSome very long text that will wrap across multiple lines\nB\n"
1417
1418        # Check that the pop-up is rendered correctly.
1419        form.update(0)
1420        self.assert_canvas_equals(
1421            canvas,
1422            "A                                       \n" +
1423            "Some very long text that will wrap acros\n" +
1424            "s multiple lines                        \n" +
1425            "B                                       \n" +
1426            "                                        \n" +
1427            "                                        \n" +
1428            "                                        \n" +
1429            "                                        \n" +
1430            "                                        \n" +
1431            "                                        \n")
1432
1433        # Check keyboard logic still works and display reflows on demand.
1434        self.process_mouse(form, [(0, 0, MouseEvent.LEFT_CLICK)])
1435        self.process_keys(form, [Screen.KEY_DOWN, "A", Screen.KEY_END, "B"])
1436        form.update(1)
1437        self.assert_canvas_equals(
1438            canvas,
1439            "A                                       \n" +
1440            "ASome very long text that will wrap acro\n" +
1441            "ss multiple linesB                      \n" +
1442            "B                                       \n" +
1443            "                                        \n" +
1444            "                                        \n" +
1445            "                                        \n" +
1446            "                                        \n" +
1447            "                                        \n" +
1448            "                                        \n")
1449        self.assertEqual(text_box.value,
1450                         "A\nASome very long text that will wrap across multiple linesB\nB\n")
1451
1452        # Check mouse logic still works.
1453        self.process_mouse(form, [(0, 2, MouseEvent.LEFT_CLICK)])
1454        self.process_keys(form, ["Z"])
1455        self.process_mouse(form, [(3, 3, MouseEvent.LEFT_CLICK)])
1456        self.process_keys(form, ["Y"])
1457        form.update(1)
1458        self.assertEqual(text_box.value,
1459                         "A\nASome very long text that will wrap acroZss multiple linesB\nBY\n")
1460
1461    def test_pop_up_widget(self):
1462        """
1463        Check popup dialog work as expected.
1464        """
1465        def test_on_click(selection):
1466            raise NextScene(str(selection))
1467
1468        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
1469        scene = MagicMock(spec=Scene)
1470        canvas = Canvas(screen, 10, 40, 0, 0)
1471        form = PopUpDialog(canvas, "Message", ["Yes", "No"], test_on_click)
1472        form.register_scene(scene)
1473        form.reset()
1474
1475        # Check that the pop-up is rendered correctly.
1476        form.update(0)
1477        self.assert_canvas_equals(
1478            canvas,
1479            "                                        \n" +
1480            "                                        \n" +
1481            "          +------------------+          \n" +
1482            "          |Message           |          \n" +
1483            "          |                  |          \n" +
1484            "          | < Yes >  < No >  |          \n" +
1485            "          +------------------+          \n" +
1486            "                                        \n" +
1487            "                                        \n" +
1488            "                                        \n")
1489
1490        # Check that mouse input triggers the close function.
1491        with self.assertRaises(NextScene):
1492            self.process_mouse(form, [(14, 5, MouseEvent.LEFT_CLICK)])
1493
1494        # Check that the pop-up swallows all events.
1495        event = object()
1496        self.assertIsNone(form.process_event(event))
1497
1498    def test_pop_up_no_buttons(self):
1499        """
1500        Check dialog with nobuttons work as expected.
1501        """
1502        def test_on_click(selection):
1503            raise NextScene(str(selection))
1504
1505        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
1506        scene = MagicMock(spec=Scene)
1507        canvas = Canvas(screen, 10, 40, 0, 0)
1508        form = PopUpDialog(canvas, "Message", [], test_on_click)
1509        form.register_scene(scene)
1510        form.reset()
1511
1512        # Check that the pop-up is rendered correctly.
1513        form.update(0)
1514        self.assert_canvas_equals(
1515            canvas,
1516            "                                        \n" +
1517            "                                        \n" +
1518            "                                        \n" +
1519            "               +-------+                \n" +
1520            "               |Message|                \n" +
1521            "               +-------+                \n" +
1522            "                                        \n" +
1523            "                                        \n" +
1524            "                                        \n" +
1525            "                                        \n")
1526
1527    def test_cjk_popup(self):
1528        """
1529        Check PopUpDialog widgets work with CJK double-width characters.
1530        """
1531        # Apologies to anyone who actually speaks this language!  I just need some double-width
1532        # glyphs so have re-used the ones from the original bug report.
1533        screen = MagicMock(spec=Screen, colours=8, unicode_aware=True)
1534        scene = MagicMock(spec=Scene)
1535        canvas = Canvas(screen, 10, 40, 0, 0)
1536        form = PopUpDialog(canvas, u"你確定嗎? 你確定嗎? 你確定嗎?", [u"是", u"否"])
1537        form.register_scene(scene)
1538        form.reset()
1539
1540        # Check that the pop-up is rendered correctly.
1541        form.update(0)
1542        self.assert_canvas_equals(
1543            canvas,
1544            "                                        \n" +
1545            "                                        \n" +
1546            "       ┌────────────────────────┐       \n" +
1547            "       │你你確確定定嗎嗎?? 你你確確定定嗎嗎??   │       \n" +
1548            "       │你你確確定定嗎嗎??              █       \n" +
1549            "       │                        ░       \n" +
1550            "       │   < 是是 >      < 否否 >   │       \n" +
1551            "       └────────────────────────┘       \n" +
1552            "                                        \n" +
1553            "                                        \n")
1554
1555    def test_cjk_forms(self):
1556        """
1557        Check form widgets work with CJK characters.
1558        """
1559        # Create a dummy screen.
1560        screen = MagicMock(spec=Screen, colours=8, unicode_aware=True)
1561        scene = MagicMock(spec=Scene)
1562        canvas = Canvas(screen, 10, 40, 0, 0)
1563
1564        # Create the form we want to test.
1565        form = Frame(canvas, canvas.height, canvas.width, has_border=False)
1566        layout = Layout([100], fill_frame=True)
1567        mc_list = MultiColumnListBox(
1568            4,
1569            [3, 5, 0],
1570            [
1571                (["1", "2", "3"], 1),
1572                ([u"你", u"確", u"定"], 2),
1573            ],
1574            titles=[u"你確定嗎?", u"你確定嗎?", u"你確定嗎?"])
1575        text = Text()
1576        text_box = TextBox(3)
1577        form.add_layout(layout)
1578        layout.add_widget(mc_list)
1579        layout.add_widget(text)
1580        layout.add_widget(text_box)
1581        form.fix()
1582        form.register_scene(scene)
1583        form.reset()
1584
1585        # Set some interesting values...
1586        text.value = u"你確定嗎? 你確定嗎? 你確定嗎?"
1587        text_box.value = [u"你確定嗎", u"?"]
1588
1589        # Check that the CJK characters render correctly - no really this is correctly aligned!
1590        form.update(0)
1591        self.assert_canvas_equals(
1592            canvas,
1593            u"你你 你你確確 你你確確定定嗎嗎??                      \n" +
1594            u"1  2    3                               \n" +
1595            u"你你 確確   定定                              \n" +
1596            u"                                        \n" +
1597            u"你你確確定定嗎嗎?? 你你確確定定嗎嗎?? 你你確確定定嗎嗎??        \n" +
1598            u"你你確確定定嗎嗎                                \n" +
1599            u"??                                      \n" +
1600            u"                                        \n" +
1601            u"                                        \n" +
1602            u"                                        \n")
1603
1604        # Check that mouse input takes into account the glyph width
1605        self.process_mouse(form, [(5, 4, MouseEvent.LEFT_CLICK)])
1606        self.process_keys(form, ["b"])
1607        self.process_mouse(form, [(2, 4, MouseEvent.LEFT_CLICK)])
1608        self.process_keys(form, ["p"])
1609        form.save()
1610        self.assertEqual(text.value, u"你p確b定嗎? 你確定嗎? 你確定嗎?")
1611
1612        self.process_mouse(form, [(2, 5, MouseEvent.LEFT_CLICK)])
1613        self.process_keys(form, ["p"])
1614        self.process_mouse(form, [(1, 6, MouseEvent.LEFT_CLICK)])
1615        self.process_keys(form, ["b"])
1616        form.save()
1617        self.assertEqual(text_box.value, [u"你p確定嗎", u"b?"])
1618
1619    def test_shadow(self):
1620        """
1621        Check Frames support shadows.
1622        """
1623        def test_on_click(selection):
1624            raise NextScene(str(selection))
1625
1626        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
1627        scene = MagicMock(spec=Scene)
1628        canvas = Canvas(screen, 10, 40, 0, 0)
1629        for y in range(10):
1630            canvas.print_at("X" * 40, 0, y)
1631        form = PopUpDialog(
1632            canvas, "Message", ["Yes", "No"], test_on_click, has_shadow=True)
1633        form.register_scene(scene)
1634        form.reset()
1635
1636        # Check that the pop-up is rendered correctly.
1637        form.update(0)
1638        self.assert_canvas_equals(
1639            canvas,
1640            "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n" +
1641            "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n" +
1642            "XXXXXXXXXX+------------------+XXXXXXXXXX\n" +
1643            "XXXXXXXXXX|Message           |XXXXXXXXXX\n" +
1644            "XXXXXXXXXX|                  |XXXXXXXXXX\n" +
1645            "XXXXXXXXXX| < Yes >  < No >  |XXXXXXXXXX\n" +
1646            "XXXXXXXXXX+------------------+XXXXXXXXXX\n" +
1647            "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n" +
1648            "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n" +
1649            "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n")
1650
1651    def test_clone(self):
1652        """
1653        Check Frame cloning works.
1654        """
1655        def test_on_click(selection):
1656            raise NextScene(str(selection))
1657
1658        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
1659        scene = MagicMock(spec=Scene)
1660        scene2 = Scene([], 10)
1661        canvas = Canvas(screen, 10, 40, 0, 0)
1662
1663        # Check that pop-up dialogs get copied to the new Scene
1664        form = PopUpDialog(
1665            canvas, "Message", ["Yes", "No"], test_on_click, has_shadow=True)
1666        form.register_scene(scene)
1667        form.clone(canvas, scene2)
1668        self.assertEqual(len(scene2.effects), 1)
1669        self.assertEqual(scene2.effects[0]._text, "Message")
1670        self.assertEqual(scene2.effects[0]._buttons, ["Yes", "No"])
1671
1672        # Check that normal Frame data gets copied to the new Scene.
1673        frame = TestFrame(canvas)
1674        frame2 = TestFrame(canvas)
1675        scene2 = Scene([frame2], 10)
1676        frame.register_scene(scene)
1677        frame2.register_scene(scene)
1678        frame.data = {"TA": ["something"]}
1679        frame2.data = {}
1680
1681        self.assertEqual(frame2.data, {})
1682        self.assertNotEqual(frame2.data, frame.data)
1683        frame.clone(canvas, scene2)
1684        self.assertEqual(frame2.data, frame.data)
1685
1686    def test_frame_rate(self):
1687        """
1688        Check Frame rate limiting works as expected.
1689        """
1690        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
1691        canvas = Canvas(screen, 10, 40, 0, 0)
1692        form = TestFrame(canvas)
1693        form.reset()
1694
1695        # With no special CPU consideration, and a cursor to animate, there
1696        # should be a 5 frame pause.
1697        self.assertEqual(form.reduce_cpu, False)
1698        self.assertEqual(form.frame_update_count, 5)
1699
1700        # Shift focus away from a text input (to get no cursor animation).
1701        self.process_keys(form, [Screen.KEY_BACK_TAB])
1702
1703        # With no special CPU consideration, and no cursors to animate, there
1704        # should be a (very!) long pause.
1705        self.assertEqual(form.reduce_cpu, False)
1706        self.assertEqual(form.frame_update_count, 1000000)
1707
1708    def test_cpu_saving(self):
1709        """
1710        Check Frame rate limiting is even more extreme when in cpu saving mode.
1711        """
1712        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
1713        canvas = Canvas(screen, 10, 40, 0, 0)
1714        form = TestFrame(canvas, reduce_cpu=True)
1715        form.reset()
1716
1717        # In this mode, it shouldn't matter where we are on the Frame - all
1718        # widgets will basically say they don't need animation.
1719        self.assertEqual(form.reduce_cpu, True)
1720        self.assertEqual(form.frame_update_count, 1000000)
1721
1722        # Shift focus away from a text input, just to be sure.
1723        self.process_keys(form, [Screen.KEY_BACK_TAB])
1724        self.assertEqual(form.frame_update_count, 1000000)
1725
1726    def test_stop_frame(self):
1727        """
1728        Check Frames always request no end to the Scene.
1729        """
1730        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
1731        canvas = Canvas(screen, 10, 40, 0, 0)
1732        form = TestFrame(canvas, reduce_cpu=True)
1733        self.assertEqual(form.stop_frame, -1)
1734
1735    def test_empty_frame(self):
1736        """
1737        Check empty Frames still work.
1738        """
1739        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
1740        canvas = Canvas(screen, 10, 40, 0, 0)
1741        scene = MagicMock(spec=Scene)
1742        form = TestFrame3(canvas)
1743        form.register_scene(scene)
1744        form.reset()
1745
1746        # Check all keyboard events get swallowed
1747        self.assertIsNone(form.process_event(KeyboardEvent(ord("A"))))
1748
1749        # Check Mouse events over the Frame are swallowed and others allowed
1750        # to bubble down the input stack.
1751        self.assertIsNone(
1752            form.process_event(MouseEvent(20, 5, MouseEvent.LEFT_CLICK)))
1753        self.assertIsNotNone(
1754            form.process_event(MouseEvent(5, 5, MouseEvent.LEFT_CLICK)))
1755
1756        # Check form data is empty.
1757        form.save()
1758        self.assertEqual(form.data, {})
1759
1760    def test_label_change(self):
1761        """
1762        Check Labels can be dynamically updated.
1763        """
1764        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
1765        canvas = Canvas(screen, 10, 40, 0, 0)
1766        form = TestFrame(canvas)
1767        form.reset()
1768
1769        # Check initial rendering
1770        form.update(0)
1771        self.assert_canvas_equals(
1772            canvas,
1773            "+--------------------------------------+\n" +
1774            "| Group 1:                             |\n" +
1775            "| My First                             O\n" +
1776            "| Box:                                 |\n" +
1777            "|                                      |\n" +
1778            "|                                      |\n" +
1779            "|                                      |\n" +
1780            "| Text1:                               |\n" +
1781            "| Text2:                               |\n" +
1782            "+--------------------------------------+\n")
1783
1784        # Check dynamic updates change the rendering.
1785        form.label.text = "New text here:"
1786        form.update(0)
1787        self.assert_canvas_equals(
1788            canvas,
1789            "+--------------------------------------+\n" +
1790            "| New text here:                       |\n" +
1791            "| My First                             O\n" +
1792            "| Box:                                 |\n" +
1793            "|                                      |\n" +
1794            "|                                      |\n" +
1795            "|                                      |\n" +
1796            "| Text1:                               |\n" +
1797            "| Text2:                               |\n" +
1798            "+--------------------------------------+\n")
1799        self.assertEqual(form.label.text, "New text here:")
1800
1801        # And check that values are unaffected (and still not set).
1802        self.assertEqual(form.label.value, None)
1803
1804    def test_label_height(self):
1805        """
1806        Check Labels can be dynamically updated.
1807        """
1808        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
1809        canvas = Canvas(screen, 10, 40, 0, 0)
1810        form = TestFrame(canvas, label_height=2)
1811        form.reset()
1812
1813        # Check Label obeys required height
1814        form.update(0)
1815        self.assert_canvas_equals(
1816            canvas,
1817            "+--------------------------------------+\n" +
1818            "| Group 1:                             |\n" +
1819            "|                                      O\n" +
1820            "| My First                             |\n" +
1821            "| Box:                                 |\n" +
1822            "|                                      |\n" +
1823            "|                                      |\n" +
1824            "|                                      |\n" +
1825            "| Text1:                               |\n" +
1826            "+--------------------------------------+\n")
1827
1828        # Now check wrapping works too...
1829        form.label.text = "A longer piece of text that should wrap across multiple lines:"
1830        form.update(1)
1831        self.assert_canvas_equals(
1832            canvas,
1833            "+--------------------------------------+\n" +
1834            "| A longer piece of text that should   |\n" +
1835            "| wrap across multiple lines:          O\n" +
1836            "| My First                             |\n" +
1837            "| Box:                                 |\n" +
1838            "|                                      |\n" +
1839            "|                                      |\n" +
1840            "|                                      |\n" +
1841            "| Text1:                               |\n" +
1842            "+--------------------------------------+\n")
1843
1844    def test_label_alignment(self):
1845        """
1846        Check Label alignment works.
1847        """
1848        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
1849        scene = Scene([], duration=-1)
1850        canvas = Canvas(screen, 10, 40, 0, 0)
1851        form = Frame(canvas, canvas.height, canvas.width)
1852        layout = Layout([1])
1853        form.add_layout(layout)
1854        layout.add_widget(Label("Left", align="<"))
1855        layout.add_widget(Label("Middle", align="^"))
1856        layout.add_widget(Label("Right", align=">"))
1857        form.fix()
1858        form.register_scene(scene)
1859        scene.add_effect(form)
1860        scene.reset()
1861
1862        # Check that the frame is rendered correctly.
1863        for effect in scene.effects:
1864            effect.update(0)
1865
1866        # Check Label obeys required height
1867        form.update(0)
1868        self.assert_canvas_equals(
1869            canvas,
1870            "+--------------------------------------+\n" +
1871            "|Left                                  |\n" +
1872            "|                Middle                O\n" +
1873            "|                                 Right|\n" +
1874            "|                                      |\n" +
1875            "|                                      |\n" +
1876            "|                                      |\n" +
1877            "|                                      |\n" +
1878            "|                                      |\n" +
1879            "+--------------------------------------+\n")
1880
1881    def test_label_colours(self):
1882        """
1883        Check Label custom colour works.
1884        """
1885        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
1886        scene = Scene([], duration=-1)
1887        canvas = Canvas(screen, 10, 40, 0, 0)
1888        form = Frame(canvas, canvas.height, canvas.width)
1889        layout = Layout([1])
1890        form.add_layout(layout)
1891        label = Label("Some text")
1892        label.custom_colour = "disabled"
1893        layout.add_widget(label)
1894        form.fix()
1895        form.register_scene(scene)
1896        scene.add_effect(form)
1897        scene.reset()
1898
1899        # Check that the label is rendered in the correct colour palette.
1900        for effect in scene.effects:
1901            effect.update(0)
1902        self.assertEqual(label.custom_colour, "disabled")
1903        self.assertEqual(canvas.get_from(1, 1), (ord("S"), 0, 1, 4))
1904
1905        # Cheeky test that Labels always pass on events.
1906        event = object()
1907        self.assertEqual(event, label.process_event(event))
1908
1909    @patch("os.stat")
1910    @patch("os.listdir")
1911    def test_file_browser_stat_err(self, mock_list, mock_stat):
1912        """
1913        Check FileBrowser widget copes with permissions error on stat.
1914        """
1915        # First we need to mock out the file system calls to have a regressible test
1916        if sys.platform == "win32":
1917            self.skipTest("File names wrong for windows")
1918
1919        mock_list.return_value = ["A Directory", "A File", "A Lnk"]
1920        mock_stat.side_effect = OSError("Fake error")
1921
1922        # Now set up the Frame ready for testing
1923        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
1924        scene = MagicMock(spec=Scene)
1925        canvas = Canvas(screen, 10, 40, 0, 0)
1926        form = TestFrame4(canvas)
1927        form.register_scene(scene)
1928        form.reset()
1929
1930        # Check that the Frame is rendered correctly.
1931        form.update(0)
1932        self.assert_canvas_equals(
1933            canvas,
1934            "/                     Size Last modified\n" +
1935            "|-- A Directory          0    1970-01-01\n" +
1936            "|-- A File               0    1970-01-01\n" +
1937            "|-- A Lnk                0    1970-01-01\n" +
1938            "                                        \n" +
1939            "                                        \n" +
1940            "                                        \n" +
1941            "                                        \n" +
1942            "                                        \n" +
1943            "                                        \n")
1944
1945    @patch("os.listdir")
1946    def test_file_browser_list_err(self, mock_list):
1947        """
1948        Check FileBrowser widget copes with permissions error on list.
1949        """
1950        # First we need to mock out the file system calls to have a regressible test
1951        if sys.platform == "win32":
1952            self.skipTest("File names wrong for windows")
1953
1954        mock_list.side_effect = OSError("Fake error")
1955
1956        # Now set up the Frame ready for testing
1957        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
1958        scene = MagicMock(spec=Scene)
1959        canvas = Canvas(screen, 10, 40, 0, 0)
1960        form = TestFrame4(canvas)
1961        form.register_scene(scene)
1962        form.reset()
1963
1964        # Check that the Frame is rendered correctly.
1965        form.update(0)
1966        self.assert_canvas_equals(
1967            canvas,
1968            "/                     Size Last modified\n" +
1969            "                                        \n" +
1970            "                                        \n" +
1971            "                                        \n" +
1972            "                                        \n" +
1973            "                                        \n" +
1974            "                                        \n" +
1975            "                                        \n" +
1976            "                                        \n" +
1977            "                                        \n")
1978
1979    @patch("os.path.exists")
1980    @patch("os.path.realpath")
1981    @patch("os.path.islink")
1982    @patch("os.path.isdir")
1983    @patch("os.lstat")
1984    @patch("os.stat")
1985    @patch("os.listdir")
1986    def test_file_browser(self, mock_list, mock_stat, mock_lstat, mock_dir, mock_link,
1987                          mock_real_path, mock_exists):
1988        """
1989        Check FileBrowser widget works as expected.
1990        """
1991        # First we need to mock out the file system calls to have a regressible test
1992        if sys.platform == "win32":
1993            self.skipTest("File names wrong for windows")
1994
1995        mock_list.return_value = ["A Directory", "A File", "A Lnk", str(b"oo\xcc\x88o\xcc\x88O\xcc\x88.txt", 'utf-8'), "Lnk Directory"]
1996        mock_result = MagicMock()
1997        mock_result.st_mtime = 0
1998        mock_result.st_size = 10000
1999        mock_stat.return_value = mock_result
2000        mock_lstat.return_value = mock_result
2001        mock_dir.side_effect = lambda x: x.endswith("Directory")
2002        mock_link.side_effect = lambda x: "Lnk" in x
2003        mock_real_path.return_value = "A Tgt"
2004        mock_exists.return_value = True
2005
2006        # Now set up the Frame ready for testing
2007        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
2008        scene = MagicMock(spec=Scene)
2009        canvas = Canvas(screen, 10, 40, 0, 0)
2010        form = TestFrame4(canvas)
2011        form.register_scene(scene)
2012        form.reset()
2013
2014        # Check we have a default value for our list.
2015        form.save()
2016        self.assertIsNone(form.selected)
2017        self.assertIsNone(form.highlighted)
2018        self.assertEqual(form.data, {"file_list": None})
2019
2020        # Check that the Frame is rendered correctly.
2021        form.update(0)
2022        self.assert_canvas_equals(
2023            canvas,
2024            "/                     Size Last modified\n" +
2025            "|-+ A Directory         9K    1970-01-01\n" +
2026            "|-+ Lnk Directo...      9K    1970-01-01\n" +
2027            "|-- A File              9K    1970-01-01\n" +
2028            "|-- A Lnk -> A Tgt      9K    1970-01-01\n" +
2029            "|-- oööÖ.txt            9K    1970-01-01\n" +
2030            "                                        \n" +
2031            "                                        \n" +
2032            "                                        \n" +
2033            "                                        \n")
2034
2035        # Check that mouse inpput changes selection.
2036        self.process_mouse(form, [(2, 3, MouseEvent.LEFT_CLICK)])
2037        form.save()
2038        self.assertEqual(form.data, {"file_list": "/A File"})
2039        self.assertEqual(form.highlighted, "/A File")
2040        self.assertIsNone(form.selected)
2041
2042        # Check that UP/DOWN change selection.
2043        self.process_keys(form, [Screen.KEY_UP, Screen.KEY_UP])
2044        form.save()
2045        self.assertEqual(form.data, {"file_list": "/A Directory"})
2046        self.assertEqual(form.highlighted, "/A Directory")
2047        self.assertIsNone(form.selected)
2048
2049        # Check that enter key handles correctly on directories.
2050        self.process_keys(form, [Screen.ctrl("m")])
2051        self.assertEqual(form.highlighted, "/")
2052        self.assertIsNone(form.selected)
2053        form.update(1)
2054        self.assert_canvas_equals(
2055            canvas,
2056            "/A Directory          Size Last modified\n" +
2057            "|-+ ..                                  \n" +
2058            "|-+ A Directory         9K    1970-01-01\n" +
2059            "|-+ Lnk Directo...      9K    1970-01-01\n" +
2060            "|-- A File              9K    1970-01-01\n" +
2061            "|-- A Lnk -> A Tgt      9K    1970-01-01\n" +
2062            "|-- oööÖ.txt            9K    1970-01-01\n" +
2063            "                                        \n" +
2064            "                                        \n" +
2065            "                                        \n")
2066
2067        # Check that enter key handles correctly on files.
2068        self.process_keys(form, [Screen.KEY_DOWN, Screen.KEY_DOWN, Screen.KEY_DOWN, Screen.ctrl("m")])
2069        self.assertEqual(form.highlighted, "/A Directory/A File")
2070        self.assertEqual(form.selected, "/A Directory/A File")
2071
2072    @patch("os.path.exists")
2073    @patch("os.path.realpath")
2074    @patch("os.path.islink")
2075    @patch("os.path.isdir")
2076    @patch("os.lstat")
2077    @patch("os.stat")
2078    @patch("os.listdir")
2079    def test_file_filter(self, mock_list, mock_stat, mock_lstat, mock_dir, mock_link,
2080                          mock_real_path, mock_exists):
2081        """
2082        Check FileBrowser widget with a file_filter works as expected.
2083        """
2084        # First we need to mock out the file system calls to have a regressible test
2085        if sys.platform == "win32":
2086            self.skipTest("File names wrong for windows")
2087
2088        mock_list.return_value = ["A Directory", "A File", "A Lnk", str(b"oo\xcc\x88o\xcc\x88O\xcc\x88.txt", 'utf-8'), "hello.bmp"]
2089        mock_result = MagicMock()
2090        mock_result.st_mtime = 0
2091        mock_result.st_size = 10000
2092        mock_stat.return_value = mock_result
2093        mock_lstat.return_value = mock_result
2094        mock_dir.side_effect = lambda x: x.endswith("Directory")
2095        mock_link.side_effect = lambda x: "Lnk" in x
2096        mock_real_path.return_value = "A Tgt"
2097        mock_exists.return_value = True
2098
2099        # Now set up the Frame ready for testing
2100        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
2101        scene = MagicMock(spec=Scene)
2102        canvas = Canvas(screen, 10, 40, 0, 0)
2103        form = TestFrame4(canvas, file_filter=r".*\.bmp$")
2104        form.register_scene(scene)
2105        form.reset()
2106
2107        # Check we have a default value for our list.
2108        form.save()
2109        self.assertIsNone(form.selected)
2110        self.assertIsNone(form.highlighted)
2111        self.assertEqual(form.data, {"file_list": None})
2112
2113        # Check that the Frame is rendered correctly.
2114        form.update(0)
2115        self.assert_canvas_equals(
2116            canvas,
2117            "/                     Size Last modified\n" +
2118            "|-+ A Directory         9K    1970-01-01\n" +
2119            "|-- hello.bmp           9K    1970-01-01\n" +
2120            "                                        \n" +
2121            "                                        \n" +
2122            "                                        \n" +
2123            "                                        \n" +
2124            "                                        \n" +
2125            "                                        \n" +
2126            "                                        \n")
2127
2128        # Check that enter key handles correctly on files.
2129        self.process_keys(form, [Screen.KEY_DOWN, Screen.KEY_DOWN, Screen.ctrl("m")])
2130        self.assertEqual(form.highlighted, "/hello.bmp")
2131        self.assertEqual(form.selected, "/hello.bmp")
2132
2133    def test_date_picker(self):
2134        """
2135        Check DatePicker widget works as expected.
2136        """
2137        # Now set up the Frame ready for testing
2138        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
2139        scene = Scene([], duration=-1)
2140        canvas = Canvas(screen, 10, 40, 0, 0)
2141        form = TestFrame5(canvas)
2142        scene.add_effect(form)
2143        scene.reset()
2144
2145        # Check that the Frame is rendered correctly.
2146        for effect in scene.effects:
2147            effect.update(0)
2148        self.assert_canvas_equals(
2149            canvas,
2150            "+--------------------------------------+\n" +
2151            "|Date: 02/Jan/2017                     |\n" +
2152            "|Time: 12:00:59                        O\n" +
2153            "|                                      |\n" +
2154            "|                                      |\n" +
2155            "|                                      |\n" +
2156            "|                                      |\n" +
2157            "|                                      |\n" +
2158            "|                                      |\n" +
2159            "+--------------------------------------+\n")
2160
2161        # Check that enter key brings up edit pop-up
2162        self.process_keys(scene, [Screen.ctrl("m")])
2163        self.assertFalse(form.changed)
2164        for effect in scene.effects:
2165            effect.update(1)
2166        self.assert_canvas_equals(
2167            canvas,
2168            "+-----|01     2016|--------------------+\n" +
2169            "|Date:|02/Jan/2017|                    |\n" +
2170            "|Time:|03 Feb 2018|                    O\n" +
2171            "|     +-----------+                    |\n" +
2172            "|                                      |\n" +
2173            "|                                      |\n" +
2174            "|                                      |\n" +
2175            "|                                      |\n" +
2176            "|                                      |\n" +
2177            "+--------------------------------------+\n")
2178
2179        # Check that you can't select an invalid date.
2180        self.process_keys(scene, ["31", "Feb", Screen.ctrl("m")], separator=Screen.KEY_TAB)
2181        self.assertFalse(form.changed)
2182        for effect in scene.effects:
2183            effect.update(2)
2184        self.assert_canvas_equals(
2185            canvas,
2186            "+-----|30 Jan 2016|--------------------+\n" +
2187            "|Date:|31/Feb/2017|                    |\n" +
2188            "|Time:|   Mar 2018|                    O\n" +
2189            "|     +-----------+                    |\n" +
2190            "|                                      |\n" +
2191            "|                                      |\n" +
2192            "|                                      |\n" +
2193            "|                                      |\n" +
2194            "|                                      |\n" +
2195            "+--------------------------------------+\n")
2196
2197        # Check that a valid date updates the value - wait one second to allow search to reset.
2198        sleep(1)
2199        self.process_keys(scene, ["15", "Jun", Screen.ctrl("m")], separator=Screen.KEY_TAB)
2200        self.assertTrue(form.changed)
2201        for effect in scene.effects:
2202            effect.update(2)
2203        self.assert_canvas_equals(
2204            canvas,
2205            "+--------------------------------------+\n" +
2206            "|Date: 15/Jun/2017                     |\n" +
2207            "|Time: 12:00:59                        O\n" +
2208            "|                                      |\n" +
2209            "|                                      |\n" +
2210            "|                                      |\n" +
2211            "|                                      |\n" +
2212            "|                                      |\n" +
2213            "|                                      |\n" +
2214            "+--------------------------------------+\n")
2215        self.assertEquals(form.date_widget.value, date(2017, 6, 15))
2216
2217        # Check the mouse works too - pass mouse events to top-level effect
2218        self.process_mouse(scene.effects[-1], [(10, 1, MouseEvent.LEFT_CLICK)])
2219        for effect in scene.effects:
2220            effect.update(3)
2221        self.assert_canvas_equals(
2222            canvas,
2223            "+-----|14 May 2016|--------------------+\n" +
2224            "|Date:|15/Jun/2017|                    |\n" +
2225            "|Time:|16 Jul 2018|                    O\n" +
2226            "|     +-----------+                    |\n" +
2227            "|                                      |\n" +
2228            "|                                      |\n" +
2229            "|                                      |\n" +
2230            "|                                      |\n" +
2231            "|                                      |\n" +
2232            "+--------------------------------------+\n")
2233
2234        self.process_mouse(scene.effects[-1], [(11, 2, MouseEvent.LEFT_CLICK)])
2235        for effect in scene.effects:
2236            effect.update(4)
2237        self.assert_canvas_equals(
2238            canvas,
2239            "+-----|14 Jun 2016|--------------------+\n" +
2240            "|Date:|15/Jul/2017|                    |\n" +
2241            "|Time:|16 Aug 2018|                    O\n" +
2242            "|     +-----------+                    |\n" +
2243            "|                                      |\n" +
2244            "|                                      |\n" +
2245            "|                                      |\n" +
2246            "|                                      |\n" +
2247            "|                                      |\n" +
2248            "+--------------------------------------+\n")
2249
2250    def test_time_picker(self):
2251        """
2252        Check TimePicker widget works as expected.
2253        """
2254        # Now set up the Frame ready for testing
2255        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
2256        scene = Scene([], duration=-1)
2257        canvas = Canvas(screen, 10, 40, 0, 0)
2258        form = TestFrame5(canvas)
2259        scene.add_effect(form)
2260        scene.reset()
2261
2262        # Check that the Frame is rendered correctly.
2263        for effect in scene.effects:
2264            effect.update(0)
2265        self.assertFalse(form.changed)
2266        self.assert_canvas_equals(
2267            canvas,
2268            "+--------------------------------------+\n" +
2269            "|Date: 02/Jan/2017                     |\n" +
2270            "|Time: 12:00:59                        O\n" +
2271            "|                                      |\n" +
2272            "|                                      |\n" +
2273            "|                                      |\n" +
2274            "|                                      |\n" +
2275            "|                                      |\n" +
2276            "|                                      |\n" +
2277            "+--------------------------------------+\n")
2278
2279        # Check that enter key brings up edit pop-up
2280        self.process_keys(scene, [Screen.KEY_DOWN, Screen.ctrl("m")])
2281        for effect in scene.effects:
2282            effect.update(1)
2283        self.assertFalse(form.changed)
2284        self.assert_canvas_equals(
2285            canvas,
2286            "+-----+--------+-----------------------+\n" +
2287            "|Date:|11    58|17                     |\n" +
2288            "|Time:|12:00:59|                       O\n" +
2289            "|     |13 01   |                       |\n" +
2290            "|     +--------+                       |\n" +
2291            "|                                      |\n" +
2292            "|                                      |\n" +
2293            "|                                      |\n" +
2294            "|                                      |\n" +
2295            "+--------------------------------------+\n")
2296
2297        # Check that we can change the time with cursors keys and mouse selection - and click out
2298        # to exit.
2299        self.process_mouse(scene, [(7, 1, MouseEvent.LEFT_CLICK)])
2300        self.process_keys(scene, [Screen.KEY_TAB, Screen.KEY_DOWN, Screen.KEY_TAB, Screen.KEY_UP])
2301        self.process_mouse(scene, [(10, 10, MouseEvent.LEFT_CLICK)])
2302        self.assertTrue(form.changed)
2303        for effect in scene.effects:
2304            effect.update(2)
2305        self.assert_canvas_equals(
2306            canvas,
2307            "+--------------------------------------+\n" +
2308            "|Date: 02/Jan/2017                     |\n" +
2309            "|Time: 11:01:58                        O\n" +
2310            "|                                      |\n" +
2311            "|                                      |\n" +
2312            "|                                      |\n" +
2313            "|                                      |\n" +
2314            "|                                      |\n" +
2315            "|                                      |\n" +
2316            "+--------------------------------------+\n")
2317        self.assertEquals(form.time_widget.value, time(11, 1, 58))
2318
2319        # Check the mouse works too - pass mouse events to top-level effect
2320        self.process_mouse(scene.effects[-1], [(7, 2, MouseEvent.LEFT_CLICK)])
2321        for effect in scene.effects:
2322            effect.update(3)
2323        self.assert_canvas_equals(
2324            canvas,
2325            "+-----+--------+-----------------------+\n" +
2326            "|Date:|10 00 57|17                     |\n" +
2327            "|Time:|11:01:58|                       O\n" +
2328            "|     |12 02 59|                       |\n" +
2329            "|     +--------+                       |\n" +
2330            "|                                      |\n" +
2331            "|                                      |\n" +
2332            "|                                      |\n" +
2333            "|                                      |\n" +
2334            "+--------------------------------------+\n")
2335
2336        self.process_mouse(scene.effects[-1], [(7, 1, MouseEvent.LEFT_CLICK)])
2337        for effect in scene.effects:
2338            effect.update(4)
2339        self.assert_canvas_equals(
2340            canvas,
2341            "+-----+--------+-----------------------+\n" +
2342            "|Date:|09 00 57|17                     |\n" +
2343            "|Time:|10:01:58|                       O\n" +
2344            "|     |11 02 59|                       |\n" +
2345            "|     +--------+                       |\n" +
2346            "|                                      |\n" +
2347            "|                                      |\n" +
2348            "|                                      |\n" +
2349            "|                                      |\n" +
2350            "+--------------------------------------+\n")
2351
2352    def test_background(self):
2353        """
2354        Check Background widget works as expected.
2355        """
2356        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
2357        scene = MagicMock(spec=Scene)
2358        canvas = Canvas(screen, 10, 40, 0, 0)
2359        form = Background(canvas, bg=7)
2360        form.register_scene(scene)
2361        form.reset()
2362
2363        # Check that the widget is rendered correctly.
2364        form.update(0)
2365        for y in range(canvas.height):
2366            for x in range(canvas.width):
2367                char, _, _, bg = canvas.get_from(x, y)
2368                self.assertEquals(char, ord(" "))
2369                self.assertEquals(bg, 7)
2370
2371        # Check properties
2372        self.assertEquals(form.stop_frame, 0)
2373        self.assertGreater(form.frame_update_count, 1000)
2374
2375    def test_dropdown_list(self):
2376        """
2377        Check DropdownList widget works as expected.
2378        """
2379        # Now set up the Frame ready for testing
2380        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
2381        scene = Scene([], duration=-1)
2382        canvas = Canvas(screen, 10, 40, 0, 0)
2383        form = Frame(canvas, canvas.height, canvas.width)
2384        layout = Layout([100], fill_frame=True)
2385        form.add_layout(layout)
2386        layout.add_widget(DropdownList([("Item 1", 1), ("Item 2", 3), ("Item 3", 5)]))
2387        form.fix()
2388        form.register_scene(scene)
2389        scene.add_effect(form)
2390        scene.reset()
2391
2392        # Check that the Frame is rendered correctly.
2393        for effect in scene.effects:
2394            effect.update(0)
2395        self.assert_canvas_equals(
2396            canvas,
2397            "+--------------------------------------+\n" +
2398            "|[Item 1                              ]|\n" +
2399            "|                                      O\n" +
2400            "|                                      |\n" +
2401            "|                                      |\n" +
2402            "|                                      |\n" +
2403            "|                                      |\n" +
2404            "|                                      |\n" +
2405            "|                                      |\n" +
2406            "+--------------------------------------+\n")
2407
2408        # Check it opens as expected
2409        self.process_mouse(scene, [(7, 1, MouseEvent.LEFT_CLICK)])
2410        for effect in scene.effects:
2411            effect.update(1)
2412        self.assert_canvas_equals(
2413            canvas,
2414            "++------------------------------------++\n" +
2415            "||Item 1                              ||\n" +
2416            "||------------------------------------|O\n" +
2417            "||Item 1                              ||\n" +
2418            "||Item 2                              ||\n" +
2419            "||Item 3                              ||\n" +
2420            "|+------------------------------------+|\n" +
2421            "|                                      |\n" +
2422            "|                                      |\n" +
2423            "+--------------------------------------+\n")
2424
2425        # Check ESC works as expected
2426        self.process_keys(scene, [Screen.KEY_DOWN, Screen.KEY_ESCAPE])
2427        for effect in scene.effects:
2428            effect.update(2)
2429        self.assert_canvas_equals(
2430            canvas,
2431            "+--------------------------------------+\n" +
2432            "|[Item 1                              ]|\n" +
2433            "|                                      O\n" +
2434            "|                                      |\n" +
2435            "|                                      |\n" +
2436            "|                                      |\n" +
2437            "|                                      |\n" +
2438            "|                                      |\n" +
2439            "|                                      |\n" +
2440            "+--------------------------------------+\n")
2441
2442        # Check key selection works as expected
2443        self.process_keys(scene, [" ", Screen.KEY_DOWN])
2444        for effect in scene.effects:
2445            effect.update(3)
2446        self.assert_canvas_equals(
2447            canvas,
2448            "++------------------------------------++\n" +
2449            "||Item 2                              ||\n" +
2450            "||------------------------------------|O\n" +
2451            "||Item 1                              ||\n" +
2452            "||Item 2                              ||\n" +
2453            "||Item 3                              ||\n" +
2454            "|+------------------------------------+|\n" +
2455            "|                                      |\n" +
2456            "|                                      |\n" +
2457            "+--------------------------------------+\n")
2458
2459        # Check Enter works as expected
2460        self.process_keys(scene, [Screen.ctrl("m")])
2461        for effect in scene.effects:
2462            effect.update(4)
2463        self.assert_canvas_equals(
2464            canvas,
2465            "+--------------------------------------+\n" +
2466            "|[Item 2                              ]|\n" +
2467            "|                                      O\n" +
2468            "|                                      |\n" +
2469            "|                                      |\n" +
2470            "|                                      |\n" +
2471            "|                                      |\n" +
2472            "|                                      |\n" +
2473            "|                                      |\n" +
2474            "+--------------------------------------+\n")
2475
2476    def test_dropdown_list_options(self):
2477        """
2478        Check DropdownList widget extra features work.
2479        """
2480        # Now set up the Frame ready for testing
2481        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
2482        scene = Scene([], duration=-1)
2483        canvas = Canvas(screen, 10, 40, 0, 0)
2484        form = Frame(canvas, canvas.height, canvas.width)
2485        layout = Layout([100], fill_frame=True)
2486        form.add_layout(layout)
2487        layout.add_widget(Divider(draw_line=False, height=7))
2488        dd_list = DropdownList([("Item {}".format(i), i) for i in range(10)])
2489        layout.add_widget(dd_list)
2490        form.fix()
2491        form.register_scene(scene)
2492        scene.add_effect(form)
2493        scene.reset()
2494
2495        # Check that the Frame is rendered correctly.
2496        for effect in scene.effects:
2497            effect.update(0)
2498        self.assert_canvas_equals(
2499            canvas,
2500            "+--------------------------------------+\n" +
2501            "|                                      |\n" +
2502            "|                                      O\n" +
2503            "|                                      |\n" +
2504            "|                                      |\n" +
2505            "|                                      |\n" +
2506            "|                                      |\n" +
2507            "|                                      |\n" +
2508            "|[Item 0                              ]|\n" +
2509            "+--------------------------------------+\n")
2510
2511        # Check it opens as expected
2512        self.process_mouse(scene, [(7, 8, MouseEvent.LEFT_CLICK)])
2513        for effect in scene.effects:
2514            effect.update(1)
2515        self.assert_canvas_equals(
2516            canvas,
2517            "++------------------------------------++\n" +
2518            "||Item 0                             O||\n" +
2519            "||Item 1                             ||O\n" +
2520            "||Item 2                             |||\n" +
2521            "||Item 3                             |||\n" +
2522            "||Item 4                             |||\n" +
2523            "||Item 5                             |||\n" +
2524            "||------------------------------------||\n" +
2525            "||Item 0                              ||\n" +
2526            "++------------------------------------++\n")
2527
2528        # Check that options can be read and set.
2529        dd_list.options = [(["a", "b", "c", "d", "e", "f"], 0)]
2530        self.assertEqual(dd_list.options, [(["a", "b", "c", "d", "e", "f"], 0)])
2531        dd_list.options = []
2532        self.assertEqual(dd_list.options, [])
2533
2534    def test_divider(self):
2535        """
2536        Check Divider widget sundry features work.
2537        """
2538        # Now set up the Frame ready for testing
2539        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
2540        scene = Scene([], duration=-1)
2541        canvas = Canvas(screen, 10, 40, 0, 0)
2542        form = Frame(canvas, canvas.height, canvas.width)
2543        layout = Layout([100], fill_frame=True)
2544        form.add_layout(layout)
2545        divider = Divider(draw_line=False, height=7)
2546        layout.add_widget(divider)
2547        form.fix()
2548        form.register_scene(scene)
2549        form.reset()
2550
2551        # Check events are ignored
2552        event = object()
2553        self.assertEqual(event, divider.process_event(event))
2554
2555        # Check value is None
2556        self.assertIsNone(divider.value)
2557
2558    def test_find_widget(self):
2559        """
2560        Check find_widget works as expected.
2561        """
2562        # Set up the Frame ready for testing
2563        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
2564        scene = Scene([], duration=-1)
2565        canvas = Canvas(screen, 10, 40, 0, 0)
2566        form = TestFrame5(canvas)
2567        scene.add_effect(form)
2568        scene.reset()
2569
2570        # Can't find a non-existent widget
2571        self.assertIsNone(form.find_widget("ABLAH"))
2572
2573        # Can find a defined widget
2574        self.assertEquals(form.find_widget("date"), form.date_widget)
2575
2576    def test_password(self):
2577        """
2578        Check that we can do password input on Text widgets.
2579        """
2580        # Create a dummy screen.
2581        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
2582        scene = MagicMock(spec=Scene)
2583        canvas = Canvas(screen, 2, 40, 0, 0)
2584
2585        # Create the form we want to test.
2586        form = Frame(canvas, canvas.height, canvas.width, has_border=False)
2587        layout = Layout([100], fill_frame=True)
2588        form.add_layout(layout)
2589        text = Text("Password", hide_char="*")
2590        layout.add_widget(text)
2591        form.fix()
2592        form.register_scene(scene)
2593        form.reset()
2594
2595        # Check that input still saves off values as expected
2596        self.process_keys(form, ["1234"])
2597        form.save()
2598        self.assertEqual(text.value, "1234")
2599
2600        # Check that it is drawn with the obscuring character, though.
2601        form.update(0)
2602        self.assert_canvas_equals(
2603            canvas,
2604            "Password ****                           \n" +
2605            "                                        \n")
2606
2607    def test_change_values(self):
2608        """
2609        Check changing Text values resets cursor position.
2610        """
2611        # Create a dummy screen.
2612        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
2613        scene = MagicMock(spec=Scene)
2614        canvas = Canvas(screen, 10, 40, 0, 0)
2615
2616        # Create the form we want to test.
2617        form = Frame(canvas, canvas.height, canvas.width, has_border=False)
2618        layout = Layout([100], fill_frame=True)
2619        form.add_layout(layout)
2620        text = Text()
2621        layout.add_widget(text)
2622        form.fix()
2623        form.register_scene(scene)
2624        form.reset()
2625
2626        # Check that input is put at the end of the new text
2627        text.value = "A test"
2628        self.process_keys(form, ["A"])
2629        form.save()
2630        self.assertEqual(text.value, "A testA")
2631
2632        # Check that growing longer still puts it at the end.
2633        text.value = "A longer test"
2634        self.process_keys(form, ["A"])
2635        form.save()
2636        self.assertEqual(text.value, "A longer testA")
2637
2638    def test_popup_meu(self):
2639        """
2640        Check PopupMenu widget works as expected.
2641        """
2642        # Simple function to test which item is selected.
2643        def click(x):
2644            self.clicked = self.clicked or x
2645
2646        # Now set up the Frame ready for testing
2647        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
2648        scene = Scene([], duration=-1)
2649        canvas = Canvas(screen, 10, 40, 0, 0)
2650
2651        # Reset menu for test
2652        self.clicked = 0
2653        popup = PopupMenu(canvas,
2654                          [
2655                              ("First", lambda: click(1)),
2656                              ("Second", lambda: click(2)),
2657                              ("Third", lambda: click(4))
2658                          ],
2659                          0, 0)
2660        popup.register_scene(scene)
2661        scene.add_effect(popup)
2662        scene.reset()
2663
2664        # Check that the menu is rendered correctly.
2665        for effect in scene.effects:
2666            effect.update(0)
2667        self.assert_canvas_equals(
2668            canvas,
2669            "First                                   \n" +
2670            "Second                                  \n" +
2671            "Third                                   \n" +
2672            "                                        \n" +
2673            "                                        \n" +
2674            "                                        \n" +
2675            "                                        \n" +
2676            "                                        \n" +
2677            "                                        \n" +
2678            "                                        \n")
2679
2680        # Check it handles a selection as expected
2681        self.process_mouse(scene, [(0, 1, MouseEvent.LEFT_CLICK)])
2682        self.assertEquals(len(scene.effects), 0)
2683        self.assertEquals(self.clicked, 2)
2684
2685        # Check choice of location at bottom right
2686        self.clicked = 0
2687        canvas.reset()
2688        popup = PopupMenu(canvas,
2689                          [
2690                              ("First", lambda: click(1)),
2691                              ("Second", lambda: click(2)),
2692                              ("Third", lambda: click(4))
2693                          ],
2694                          39, 9)
2695        popup.register_scene(scene)
2696        scene.add_effect(popup)
2697        scene.reset()
2698
2699        # Check that the menu is rendered correctly.
2700        for effect in scene.effects:
2701            effect.update(0)
2702        self.assert_canvas_equals(
2703            canvas,
2704            "                                        \n" +
2705            "                                        \n" +
2706            "                                        \n" +
2707            "                                        \n" +
2708            "                                        \n" +
2709            "                                        \n" +
2710            "                                        \n" +
2711            "                                  First \n" +
2712            "                                  Second\n" +
2713            "                                  Third \n")
2714
2715        # Check it handles a selection as expected
2716        self.process_mouse(scene, [(39, 7, MouseEvent.LEFT_CLICK)])
2717        self.assertEquals(len(scene.effects), 0)
2718        self.assertEquals(self.clicked, 1)
2719
2720        # Check clicking outside menu dismisses it - wrong X location.
2721        self.clicked = 0
2722        canvas.reset()
2723        popup = PopupMenu(canvas, [("A", lambda: click(1)), ("B", lambda: click(2))], 39, 9)
2724        popup.register_scene(scene)
2725        scene.add_effect(popup)
2726        scene.reset()
2727        self.process_mouse(scene, [(10, 9, MouseEvent.LEFT_CLICK)])
2728        self.assertEquals(len(scene.effects), 0)
2729        self.assertEquals(self.clicked, 0)
2730
2731        # Check clicking outside menu dismisses it - wrong Y location.
2732        self.clicked = 0
2733        canvas.reset()
2734        popup = PopupMenu(canvas, [("A", lambda: click(1)), ("B", lambda: click(2))], 39, 9)
2735        popup.register_scene(scene)
2736        scene.add_effect(popup)
2737        scene.reset()
2738        self.process_mouse(scene, [(39, 1, MouseEvent.LEFT_CLICK)])
2739        self.assertEquals(len(scene.effects), 0)
2740        self.assertEquals(self.clicked, 0)
2741
2742        # Check clicking outside menu dismisses it.
2743        self.clicked = 0
2744        canvas.reset()
2745        popup = PopupMenu(canvas, [("A", lambda: click(1)), ("B", lambda: click(2))], 39, 9)
2746        popup.register_scene(scene)
2747        scene.add_effect(popup)
2748        scene.reset()
2749        self.process_keys(popup, [Screen.KEY_ESCAPE])
2750        self.assertEquals(len(scene.effects), 0)
2751        self.assertEquals(self.clicked, 0)
2752
2753    def test_find_min_start(self):
2754        """
2755        Check _find_min_start works as expected.
2756        """
2757        # Normal operation will find last <limit> characters.
2758        self.assertEqual(_find_min_start("ABCDEF", 3), 3)
2759
2760        # Allow extra space for cursor loses another
2761        self.assertEqual(_find_min_start("ABCDEF", 3, at_end=True), 4)
2762
2763
2764    def test_vertical_divider(self):
2765        """
2766        Check VerticalDivider widget works as expected.
2767        """
2768        # Now set up the Frame ready for testing
2769        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
2770        scene = Scene([], duration=-1)
2771        canvas = Canvas(screen, 10, 40, 0, 0)
2772        form = Frame(canvas, canvas.height, canvas.width)
2773        layout = Layout([5, 1, 26, 1, 5])
2774        form.add_layout(layout)
2775        layout.add_widget(Label("A"), 1)
2776        layout.add_widget(VerticalDivider(), 1)
2777        layout.add_widget(Label("B"), 1)
2778        layout.add_widget(TextBox(5), 2)
2779        divider = VerticalDivider()
2780        layout.add_widget(divider, 3)
2781        layout.add_widget(Label("END"), 4)
2782        form.fix()
2783        form.register_scene(scene)
2784        scene.add_effect(form)
2785        scene.reset()
2786
2787        # Check that the frame is rendered correctly.
2788        for effect in scene.effects:
2789            effect.update(0)
2790        self.assert_canvas_equals(
2791            canvas,
2792            "+--------------------------------------+\n" +
2793            "|     A                          |END  |\n" +
2794            "|     |                          |     O\n" +
2795            "|     |                          |     |\n" +
2796            "|     |                          |     |\n" +
2797            "|     B                          |     |\n" +
2798            "|                                      |\n" +
2799            "|                                      |\n" +
2800            "|                                      |\n" +
2801            "+--------------------------------------+\n")
2802
2803        # Check that a vertcial divider ignores unknown events.
2804        event = object()
2805        self.assertEqual(event, divider.process_event(event))
2806
2807        # Check that it has no value.
2808        self.assertEqual(divider.value, None)
2809
2810    def test_value_defaults(self):
2811        """
2812        Check Widgets can set default values from code.
2813        """
2814        # Now set up the Frame ready for testing
2815        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
2816        scene = Scene([], duration=-1)
2817        canvas = Canvas(screen, 10, 40, 0, 0)
2818        form = Frame(canvas, canvas.height, canvas.width)
2819        layout = Layout([100])
2820        form.add_layout(layout)
2821
2822        textbox = TextBox(2, label="TB")
2823        textbox.value = ["Hello"]
2824        text = Text(label="B")
2825        text.value = "World"
2826        listbox = ListBox(2, [("A", 1), ("B", 2), ("C", 3), ("D", 4)], label="LB")
2827        listbox.value = 3
2828
2829        layout.add_widget(textbox)
2830        layout.add_widget(text)
2831        layout.add_widget(listbox)
2832        form.fix()
2833        form.register_scene(scene)
2834        scene.add_effect(form)
2835        scene.reset()
2836
2837        # Check that the frame is rendered correctly.
2838        for effect in scene.effects:
2839            effect.update(0)
2840        self.assert_canvas_equals(
2841            canvas,
2842            "+--------------------------------------+\n" +
2843            "|TB Hello                              |\n" +
2844            "|                                      O\n" +
2845            "|B  World                              |\n" +
2846            "|LB C                                  |\n" +
2847            "|   D                                  |\n" +
2848            "|                                      |\n" +
2849            "|                                      |\n" +
2850            "|                                      |\n" +
2851            "+--------------------------------------+\n")
2852
2853    def test_frame_themes(self):
2854        """
2855        Check we can set a colour theme for a Frame.
2856        """
2857        # Now set up the Frame ready for testing
2858        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
2859        scene = Scene([], duration=-1)
2860        canvas = Canvas(screen, 10, 40, 0, 0)
2861        form = Frame(canvas, canvas.height, canvas.width)
2862
2863        # Check colour changes work...
2864        self.assertEqual(
2865            form.palette["background"],
2866            (Screen.COLOUR_WHITE, Screen.A_NORMAL, Screen.COLOUR_BLUE))
2867        form.set_theme("monochrome")
2868        self.assertEqual(
2869            form.palette["background"],
2870            (Screen.COLOUR_WHITE, Screen.A_NORMAL, Screen.COLOUR_BLACK))
2871
2872        # Check that a bad theme name has no effect.
2873        form.set_theme("blah - this doesn't exist")
2874        self.assertEqual(
2875            form.palette["background"],
2876            (Screen.COLOUR_WHITE, Screen.A_NORMAL, Screen.COLOUR_BLACK))
2877
2878    def test_max_len(self):
2879        """
2880        Check that the max_length setting works as expected.
2881        """
2882        # Now set up the Frame ready for testing
2883        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
2884        scene = Scene([], duration=-1)
2885        canvas = Canvas(screen, 10, 40, 0, 0)
2886        form = Frame(canvas, canvas.height, canvas.width)
2887        layout = Layout([100])
2888        form.add_layout(layout)
2889
2890        # Simple form with a limited length Text field.
2891        text = Text(label="Text", name="max_len_text", max_length=4)
2892        layout.add_widget(text)
2893        form.fix()
2894        form.register_scene(scene)
2895        scene.add_effect(form)
2896        scene.reset()
2897
2898        # Check it stops accepting text after hitting limit.
2899        self.process_keys(form, "123456")
2900        form.save()
2901        self.assertEqual(form.data["max_len_text"], "1234")
2902
2903    def test_clear_widgets(self):
2904        """
2905        Check that clear_widgets works as expected.
2906        """
2907        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
2908        scene = Scene([], duration=-1)
2909        canvas = Canvas(screen, 10, 40, 0, 0)
2910        form = Frame(canvas, canvas.height, canvas.width)
2911        layout = Layout([100], fill_frame=True)
2912        form.add_layout(layout)
2913        layout.add_widget(Text("Test"))
2914        form.fix()
2915        form.register_scene(scene)
2916        scene.add_effect(form)
2917        scene.reset()
2918
2919        # Check that the frame is rendered correctly.
2920        for effect in scene.effects:
2921            effect.update(0)
2922        self.assert_canvas_equals(
2923            canvas,
2924            "+--------------------------------------+\n" +
2925            "|Test                                  |\n" +
2926            "|                                      O\n" +
2927            "|                                      |\n" +
2928            "|                                      |\n" +
2929            "|                                      |\n" +
2930            "|                                      |\n" +
2931            "|                                      |\n" +
2932            "|                                      |\n" +
2933            "+--------------------------------------+\n")
2934
2935        # Check removing widgets clears Frame.
2936        layout.clear_widgets()
2937        form.fix()
2938        scene.reset()
2939        for effect in scene.effects:
2940            effect.update(1)
2941        self.assert_canvas_equals(
2942            canvas,
2943            "+--------------------------------------+\n" +
2944            "|                                      |\n" +
2945            "|                                      O\n" +
2946            "|                                      |\n" +
2947            "|                                      |\n" +
2948            "|                                      |\n" +
2949            "|                                      |\n" +
2950            "|                                      |\n" +
2951            "|                                      |\n" +
2952            "+--------------------------------------+\n")
2953
2954        # Check adding another widget now adds it back into the Frame.
2955        layout.add_widget(Text("Another One"))
2956        form.fix()
2957        scene.reset()
2958        for effect in scene.effects:
2959            effect.update(2)
2960        self.assert_canvas_equals(
2961            canvas,
2962            "+--------------------------------------+\n" +
2963            "|Another One                           |\n" +
2964            "|                                      O\n" +
2965            "|                                      |\n" +
2966            "|                                      |\n" +
2967            "|                                      |\n" +
2968            "|                                      |\n" +
2969            "|                                      |\n" +
2970            "|                                      |\n" +
2971            "+--------------------------------------+\n")
2972
2973
2974    def test_inline_colours(self):
2975        """
2976        Check inline colours work as expected.
2977        """
2978        # Create a dummy screen.
2979        screen = MagicMock(spec=Screen, colours=8, unicode_aware=True)
2980        scene = MagicMock(spec=Scene)
2981        canvas = Canvas(screen, 10, 40, 0, 0)
2982
2983        # Create the form we want to test.
2984        form = Frame(canvas, canvas.height, canvas.width, has_border=False)
2985        layout = Layout([100], fill_frame=True)
2986        form.add_layout(layout)
2987        text_box = TextBox(3, as_string=True, parser=AsciimaticsParser())
2988        layout.add_widget(text_box)
2989        listbox = ListBox(2, [("P", 1), ("${9,2}Q", 2), ("R", 3), ("S", 4)], parser=AsciimaticsParser())
2990        layout.add_widget(listbox)
2991        mc_list = MultiColumnListBox(
2992            Widget.FILL_FRAME,
2993            [3, "100%"],
2994            [(["1", "\x1B[32m2"], 1)],
2995            titles=["A", "B"],
2996            parser=AnsiTerminalParser())
2997        layout.add_widget(mc_list)
2998        form.fix()
2999        form.register_scene(scene)
3000        form.reset()
3001
3002        # Check that the Asciimatics colour parsing worked.
3003        text_box.value = "A${1}B"
3004        form.update(0)
3005        self.assertEqual(canvas.get_from(0, 0), (ord("A"), 7, 2, 4))
3006        self.assertEqual(canvas.get_from(1, 0), (ord("B"), 1, 0, 4))
3007        self.assertEqual(canvas.get_from(0, 3), (ord("P"), 3, 1, 4))
3008        self.assertEqual(canvas.get_from(0, 4), (ord("Q"), 9, 2, 4))
3009
3010        # Check that the Ansi terminal colour parsing worked.
3011        self.assertEqual(canvas.get_from(0, 5), (ord("A"), 7, 1, 4))
3012        self.assertEqual(canvas.get_from(3, 5), (ord("B"), 7, 1, 4))
3013        self.assertEqual(canvas.get_from(0, 6), (ord("1"), 3, 1, 4))
3014        self.assertEqual(canvas.get_from(3, 6), (ord("2"), 2, 1, 4))
3015
3016    def test_list_box_options(self):
3017        """
3018        Check setting listbox options works as expected.
3019        """
3020        # Create a dummy screen.
3021        screen = MagicMock(spec=Screen, colours=8, unicode_aware=True)
3022        scene = MagicMock(spec=Scene)
3023        canvas = Canvas(screen, 10, 40, 0, 0)
3024
3025        # Create the form we want to test.
3026        form = Frame(canvas, canvas.height, canvas.width, has_border=False)
3027        layout = Layout([100], fill_frame=True)
3028        form.add_layout(layout)
3029        listbox = ListBox(2, [])
3030        options = [("P", 1), ("Q", 2), ("R", 3), ("S", 4)]
3031        listbox.options = options
3032        layout.add_widget(listbox)
3033
3034        self.assertEquals(listbox.options, options)
3035
3036    def test_list_box_color_options(self):
3037        """
3038        Check setting listbox options with inline colors works as expected.
3039        """
3040        # Create a dummy screen.
3041        screen = MagicMock(spec=Screen, colours=8, unicode_aware=True)
3042        scene = MagicMock(spec=Scene)
3043        canvas = Canvas(screen, 10, 40, 0, 0)
3044
3045        # Create the form we want to test.
3046        form = Frame(canvas, canvas.height, canvas.width, has_border=False)
3047        layout = Layout([100], fill_frame=True)
3048        form.add_layout(layout)
3049        listbox = ListBox(2, [], parser=AsciimaticsParser())
3050        options = [("P", 1), ("${9,2}Q", 2), ("R", 3), ("${10,3}S", 4)]
3051        listbox.options = options
3052        layout.add_widget(listbox)
3053
3054        color_options = listbox.options
3055        self.assertIsInstance(color_options[0][0], ColouredText)
3056        self.assertEquals(color_options[0][0].raw_text, options[0][0])
3057
3058        self.assertIsInstance(color_options[1][0], ColouredText)
3059        self.assertEquals(color_options[1][0].raw_text, options[1][0])
3060
3061        self.assertIsInstance(color_options[2][0], ColouredText)
3062        self.assertEquals(color_options[2][0].raw_text, options[2][0])
3063
3064        self.assertIsInstance(color_options[3][0], ColouredText)
3065        self.assertEquals(color_options[3][0].raw_text, options[3][0])
3066
3067    def test_readonly(self):
3068        """
3069        Check readonly widgets work as expected.
3070        """
3071        # Create a dummy screen.
3072        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
3073        scene = MagicMock(spec=Scene)
3074        canvas = Canvas(screen, 10, 40, 0, 0)
3075
3076        # Create the form we want to test.
3077        form = Frame(canvas, canvas.height, canvas.width, has_border=False)
3078        layout = Layout([100], fill_frame=True)
3079        form.add_layout(layout)
3080        text_box = TextBox(3, as_string=True, readonly=True)
3081        layout.add_widget(text_box)
3082        text = Text(readonly=True)
3083        layout.add_widget(text)
3084        form.fix()
3085        form.register_scene(scene)
3086        form.reset()
3087
3088        # Check that entering text has no effect.
3089        text_box.value = "Untouchable"
3090        text.value = "You can't touch this..."
3091        self.process_keys(form, ["1234\r\n", Screen.KEY_BACK, Screen.KEY_DELETE])
3092        form.save()
3093        self.assertEqual(text_box.value, "Untouchable")
3094        self.process_keys(form, [Screen.KEY_TAB, "123", Screen.KEY_BACK, Screen.KEY_DELETE])
3095        form.save()
3096        self.assertEqual(text.value, "You can't touch this...")
3097
3098        # Check property works.
3099        self.assertTrue(text.readonly)
3100        self.assertTrue(text_box.readonly)
3101        text.readonly = False
3102        text_box.readonly = False
3103        self.assertFalse(text.readonly)
3104        self.assertFalse(text_box.readonly)
3105
3106        # Check that entering text now has an effect.
3107        self.process_keys(form, [Screen.KEY_TAB, "1234\r\n", Screen.KEY_BACK, Screen.KEY_DELETE])
3108        form.save()
3109        self.assertEqual(text_box.value, "Untouchable1234\n")
3110        self.process_keys(form, [Screen.KEY_TAB, "123", Screen.KEY_BACK, Screen.KEY_DELETE])
3111        form.save()
3112        self.assertEqual(text.value, "You can't touch this...12")
3113
3114    def test_layout_disable(self):
3115        """
3116        Check en/disable on layouts work as expected.
3117        """
3118        # Create a dummy screen.
3119        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
3120        scene = MagicMock(spec=Scene)
3121        canvas = Canvas(screen, 10, 40, 0, 0)
3122
3123        # Create the form we want to test.
3124        form = Frame(canvas, canvas.height, canvas.width, has_border=False)
3125        layout = Layout([1,1], fill_frame=True)
3126        form.add_layout(layout)
3127        for col in range(2):
3128            layout.add_widget(Label("A field"), col)
3129            layout.add_widget(Text(), col)
3130            layout.add_widget(Button("OK", None), col)
3131        form.fix()
3132        form.register_scene(scene)
3133        form.reset()
3134
3135        def _assert_disabled(cols):
3136            for i, widgets in enumerate(layout._columns):
3137                for widget in widgets:
3138                    self.assertEqual(widget.disabled, i in cols)
3139
3140        # Check focus moves away from disabled widgets.
3141        self.assertEqual(layout._live_col, 0)
3142        self.assertEqual(layout._live_widget, 1)
3143        layout.disable([0])
3144        self.assertEqual(layout._live_col, 1)
3145        self.assertEqual(layout._live_widget, 1)
3146        layout.enable([1])
3147        self.assertEqual(layout._live_col, 1)
3148        self.assertEqual(layout._live_widget, 1)
3149
3150        # Check that disabling only disables specified columns.
3151        for col in range(2):
3152            layout.disable([col])
3153            _assert_disabled([col])
3154            layout.enable([col])
3155            _assert_disabled([])
3156
3157        # Check that no parameter disables everything
3158        layout.disable()
3159        _assert_disabled(range(2))
3160        layout.enable()
3161        _assert_disabled([])
3162
3163    def test_widget_labels(self):
3164        """
3165        Check various widgets and labels interact correctly.
3166        """
3167        # Create a dummy screen.
3168        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
3169        scene = Scene([], duration=-1)
3170        canvas = Canvas(screen, 10, 40, 0, 0)
3171
3172        # Create the form we want to test.
3173        self.clicked = False
3174
3175        def click():
3176            self.clicked = True
3177
3178        form = Frame(canvas, canvas.height, canvas.width, has_border=True)
3179        layout = Layout([80, 20], fill_frame=True)
3180        form.add_layout(layout)
3181        layout.add_widget(Button("one", None, label="Buttons"))
3182        layout.add_widget(Button("two", click))
3183        listbox = ListBox(1, [("12345678901234567890", 1)], label="List")
3184        layout.add_widget(listbox)
3185        text = Text(label="Text")
3186        layout.add_widget(text)
3187        form.fix()
3188        form.register_scene(scene)
3189        scene.add_effect(form)
3190        form.reset()
3191
3192        # Check widgets are displayed correctly
3193        for effect in scene.effects:
3194            effect.update(0)
3195        self.assert_canvas_equals(
3196            canvas,
3197            "+--------------------------------------+\n" +
3198            "|Buttons < one >                       |\n" +
3199            "|        < two >                       O\n" +
3200            "|List    12345678901234567890          |\n" +
3201            "|Text                                  |\n" +
3202            "|                                      |\n" +
3203            "|                                      |\n" +
3204            "|                                      |\n" +
3205            "|                                      |\n" +
3206            "+--------------------------------------+\n")
3207
3208        # check specfic bug with Listbox overflow on selection
3209        self.process_mouse(form, [(20, 3, MouseEvent.LEFT_CLICK)])
3210        for effect in scene.effects:
3211            effect.update(1)
3212        self.assertEqual(canvas.get_from(28, 3), (ord("0"), 7, 1, 6))
3213        self.assertEqual(canvas.get_from(29, 3), (ord(" "), 7, 1, 6))
3214        self.assertEqual(canvas.get_from(30, 3), (ord(" "), 7, 1, 6))
3215        self.assertEqual(canvas.get_from(31, 3), (ord(" "), 7, 2, 4))
3216
3217        # Check that clicking just outside the button has no effect.
3218        self.process_mouse(form, [(8, 2, MouseEvent.LEFT_CLICK)])
3219        self.process_mouse(form, [(16, 2, MouseEvent.LEFT_CLICK)])
3220        self.assertFalse(self.clicked)
3221
3222        # Check that clicking just inside the button works.
3223        self.process_mouse(form, [(9, 2, MouseEvent.LEFT_CLICK)])
3224        self.assertTrue(self.clicked)
3225        self.clicked = False
3226        self.process_mouse(form, [(15, 2, MouseEvent.LEFT_CLICK)])
3227        self.assertTrue(self.clicked)
3228
3229
3230    def test_button_name1(self):
3231        """
3232        Check Button name can be set in the constructor.
3233        """
3234        def _on_click():
3235            pass
3236
3237        btn = Button("Run", _on_click, name="btn_run")
3238
3239
3240    def test_button_text(self):
3241        """
3242        Check Button text can be set as an attribute.
3243        """
3244        def _on_click():
3245            pass
3246
3247        btn = Button("Before", _on_click)
3248        self.assertEqual(btn.text, "Before")
3249        btn.text = "After"
3250        self.assertEqual(btn.text, "After")
3251
3252
3253    def test_label_name1(self):
3254        """
3255        Check Label name can be set in the constructor.
3256        """
3257        lbl = Label("Winner", name="my_lbl")
3258
3259
3260    def test_label_name2(self):
3261        """
3262        Check Label can be found by its containing Frame
3263        """
3264        # Now set up the Frame ready for testing
3265        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
3266        scene = Scene([], duration=-1)
3267        canvas = Canvas(screen, 10, 40, 0, 0)
3268        form = TestFrame6(canvas)
3269        scene.add_effect(form)
3270        scene.reset()
3271
3272        # Check that the Label is found by its name
3273        # by confirming the label's value
3274        l = form.find_widget("tf6_lbl")
3275        self.assertEqual(l.text, "TstFrm6Lbl")
3276
3277
3278if __name__ == '__main__':
3279    unittest.main()
3280