from datetime import date, time
from time import sleep
import unittest
import sys
from unittest.mock import MagicMock, patch
from asciimatics.event import KeyboardEvent, MouseEvent
from asciimatics.exceptions import NextScene, StopApplication, InvalidFields
from asciimatics.scene import Scene
from asciimatics.screen import Screen, Canvas
from asciimatics.widgets import Frame, Layout, Button, Label, TextBox, Text, \
    Divider, RadioButtons, CheckBox, PopUpDialog, ListBox, Widget, MultiColumnListBox, \
    FileBrowser, DatePicker, TimePicker, Background, DropdownList, PopupMenu, \
    _find_min_start, VerticalDivider
from asciimatics.parsers import AsciimaticsParser, AnsiTerminalParser
from asciimatics.strings import ColouredText


class _TestFrame(Frame):
    def __init__(self, screen, has_border=True, can_scroll=True, reduce_cpu=False, label_height=1):
        super().__init__(screen,
                         screen.height,
                         screen.width,
                         name="Test Form",
                         has_border=has_border,
                         can_scroll=can_scroll,
                         hover_focus=True,
                         reduce_cpu=reduce_cpu)
        layout = Layout([1, 18, 1])
        self.add_layout(layout)
        self._reset_button = Button("Reset", self._reset)

        # Test that layout.add_widget returns the widget
        self.label = layout.add_widget(
            Label("Group 1:", height=label_height), 1)

        layout.add_widget(TextBox(5,
                                  label="My First Box:",
                                  name="TA",
                                  on_change=self._on_change), 1)
        layout.add_widget(
            Text(label="Text1:", name="TB", on_change=self._on_change), 1)
        layout.add_widget(
            Text(label="Text2:",
                 name="TC",
                 on_change=self._on_change,
                 validator="^[0-9]*$"), 1)
        layout.add_widget(
            Text(label="Text3:",
                 name="TD",
                 on_change=self._on_change,
                 validator=lambda x: x in ("", "a")), 1)
        layout.add_widget(Divider(height=2), 1)
        layout.add_widget(Label("Group 2:"), 1)
        layout.add_widget(RadioButtons([("Option 1", 1),
                                        ("Option 2", 2),
                                        ("Option 3", 3)],
                                       label="A Longer Selection:",
                                       name="Things",
                                       on_change=self._on_change), 1)
        layout.add_widget(CheckBox("Field 1",
                                   label="A very silly long name for fields:",
                                   name="CA",
                                   on_change=self._on_change), 1)
        layout.add_widget(
            CheckBox("Field 2", name="CB", on_change=self._on_change), 1)
        layout.add_widget(
            CheckBox("Field 3", name="CC", on_change=self._on_change), 1)
        layout.add_widget(Divider(height=3), 1)
        layout2 = Layout([1, 1, 1])
        self.add_layout(layout2)
        layout2.add_widget(self._reset_button, 0)
        layout2.add_widget(Button("View Data", self._view), 1)
        layout2.add_widget(Button("Quit", self._quit), 2)
        layout2.add_widget(Button("One", self._view), 0)
        layout2.add_widget(Button("Two", self._view), 1)
        layout2.add_widget(Button("Three", self._view), 2)
        self.fix()

    def _on_change(self):
        changed = False
        self.save()
        for key, value in self.data.items():
            if isinstance(value, bool):
                if value:
                    changed = True
                    break
            if isinstance(value, int):
                if value != 1:
                    changed = True
                    break
            elif value is None:
                continue
            elif len(value) != 0:
                changed = True
                break
        self._reset_button.disabled = not changed

    def _reset(self):
        self.reset()
        raise NextScene()

    def _view(self):
        # Build result of this form and display it.
        self.save()
        raise NextScene()

    @staticmethod
    def _quit():
        raise StopApplication("User requested exit")


class _TestFrame2(Frame):
    def __init__(self, screen, init_values):
        super().__init__(screen,
                         screen.height,
                         screen.width,
                         data={"selected": "None"},
                         title="Test Frame 2")
        # Create the form for displaying the list of contacts.
        self._list_view = ListBox(
            Widget.FILL_FRAME,
            init_values,
            name="contacts",
            on_change=self._on_pick,
            on_select=self._on_select)
        self._edit_button = Button("Edit", self._edit)
        self._delete_button = Button("Delete", self._delete)
        layout = Layout([100], fill_frame=True)
        self.add_layout(layout)
        layout.add_widget(self._list_view)
        layout.add_widget(Divider(line_char="#"))
        layout2 = Layout([1, 1, 1, 1])
        self.add_layout(layout2)
        layout2.add_widget(Button("Add", self._add), 0)
        layout2.add_widget(self._edit_button, 1)
        layout2.add_widget(self._delete_button, 2)
        layout2.add_widget(Button("Quit", self._quit), 3)
        layout3 = Layout([100])
        self.add_layout(layout3)
        self._info_text = Text(label="Selected:", name="selected")
        self._info_text.disabled = True
        layout3.add_widget(self._info_text)
        self.fix()
        self._on_pick()

    def _on_select(self):
        self._info_text.value = str(self._list_view.value)
        self.save()

    def _on_pick(self):
        self._edit_button.disabled = self._list_view.value is None
        self._delete_button.disabled = self._list_view.value is None

    @staticmethod
    def _add():
        raise NextScene("Add")

    @staticmethod
    def _edit():
        raise NextScene("Edit")

    @staticmethod
    def _delete():
        raise NextScene("Delete")

    @staticmethod
    def _quit():
        raise StopApplication("User pressed quit")


class _TestFrame3(Frame):
    def __init__(self, screen):
        super().__init__(screen, 10, 20,
                         name="Blank",
                         has_shadow=True)
        self.fix()


class _TestFrame4(Frame):
    def __init__(self, screen, file_filter=None):
        super().__init__(
            screen, screen.height, screen.width, has_border=False, can_scroll=False, name="My Form")

        # State tracking for callbacks
        self.selected = None
        self.highlighted = None

        # Simple full-page Widget
        layout = Layout([1], fill_frame=True)
        self.add_layout(layout)
        self.file_list = FileBrowser(Widget.FILL_FRAME,
                                     "/",
                                     name="file_list",
                                     on_select=self.select,
                                     on_change=self.change,
                                     file_filter=file_filter)
        layout.add_widget(self.file_list)
        self.fix()

    def select(self):
        self.selected = self.file_list.value

    def change(self):
        self.highlighted = self.file_list.value


class _TestFrame5(Frame):
    def __init__(self, screen):
        super().__init__(
            screen, screen.height, screen.width, has_border=True, name="My Form")

        # Simple full-page Widget
        layout = Layout([1], fill_frame=True)
        self.add_layout(layout)
        self.date_widget = DatePicker(
            label="Date:", name="date", on_change=self._changed)
        self.date_widget.value = date(2017, 1, 2)
        layout.add_widget(self.date_widget)
        self.time_widget = TimePicker(
            label="Time:", name="time", seconds=True, on_change=self._changed)
        self.time_widget.value = time(12, 0, 59)
        layout.add_widget(self.time_widget)
        self.time_widget2 = TimePicker(
            label="Tim2:", name="time", seconds=False, on_change=self._changed)
        self.time_widget2.value = time(11, 2, 58)
        layout.add_widget(self.time_widget2)
        self.fix()

        # State tracking for widgets
        self.changed = False

    def _changed(self):
        self.changed = True


class _TestFrame6(Frame):
    def __init__(self, screen):
        super().__init__(
            screen, screen.height, screen.width, has_border=True, name="My Form")

        # Simple full-page Widget
        layout = Layout([1], fill_frame=True)
        self.add_layout(layout)
        self.lbl = Label("TstFrm6Lbl", name="tf6_lbl")
        layout.add_widget(self.lbl)
        self.fix()


class TestWidgets(unittest.TestCase):
    def setUp(self):
        self.maxDiff = None
        self.epoch_date = str(date.fromtimestamp(0))

    def _canvas_to_string(self, canvas):
        output = ""
        for y in range(canvas.height):
            for x in range(canvas.width):
                char, _, _, _ = canvas.get_from(x, y)
                output += chr(char)
            output += "\n"
        return output

    def assert_canvas_equals(self, canvas, expected):
        """
        Assert output to canvas is as expected.
        """
        output = self._canvas_to_string(canvas)
        self.assertEqual(output, expected)

    @staticmethod
    def process_keys(form, values, separator=None):
        """
        Inject a set of key events separated by a common key separator.
        """
        for new_value in values:
            if isinstance(new_value, int):
                form.process_event(KeyboardEvent(new_value))
            else:
                for char in new_value:
                    form.process_event(KeyboardEvent(ord(char)))
            if separator:
                form.process_event(KeyboardEvent(separator))

    @staticmethod
    def process_mouse(form, values):
        """
        Inject a set of mouse events.
        """
        for x, y, buttons in values:
            form.process_event(MouseEvent(x, y, buttons))

    def test_form_data(self):
        """
        Check Frame.data works as expected.
        """
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        canvas = Canvas(screen, 10, 40, 0, 0)
        scene = Scene([], -1)
        form = _TestFrame(canvas)
        scene.add_effect(form)

        # Should be empty on construction
        self.assertEqual(form.data, {})

        # Should be blank values after saving.
        form.reset()
        form.save()
        self.assertEqual(
            form.data,
            {
                'CA': False,
                'CB': False,
                'CC': False,
                'TA': [''],
                'TB': '',
                'TC': '',
                'TD': '',
                'Things': 1
            })

    def test_rendering(self):
        """
        Check Frame renders as expected.
        """
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        canvas = Canvas(screen, 10, 40, 0, 0)
        scene = Scene([], -1)
        form = _TestFrame(canvas)
        scene.add_effect(form)
        form.reset()

        # Check initial rendering
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "| Group 1:                             |\n" +
            "| My First                             O\n" +
            "| Box:                                 |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "| Text1:                               |\n" +
            "| Text2:                               |\n" +
            "+--------------------------------------+\n")

        # Check scrolling works.  Should also test label splitting and ellipsis.
        form.move_to(0, 9, 8)
        form.update(1)
        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "| Text3:                               |\n" +
            "|                                      |\n" +
            "| ----------------------------------   |\n" +
            "| Group 2:                             O\n" +
            "| A Longer   (X) Option 1              |\n" +
            "| Selection: ( ) Option 2              |\n" +
            "|            ( ) Option 3              |\n" +
            "| A very...  [ ] Field 1               |\n" +
            "+--------------------------------------+\n")

        # Now check button rendering.
        form.move_to(0, 18, 8)
        form.update(2)
        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "|            [ ] Field 3               |\n" +
            "|                                      |\n" +
            "| ----------------------------------   |\n" +
            "|                                      |\n" +
            "| < Reset >  < View Data > < Quit >    |\n" +
            "|  < One >     < Two >    < Three >    |\n" +
            "|                                      O\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")

    def test_unicode_rendering(self):
        """
        Check Frame renders as expected for unicode environments.
        """
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=True)
        canvas = Canvas(screen, 10, 40, 0, 0)
        scene = Scene([], -1)
        form = _TestFrame(canvas)
        scene.add_effect(form)
        form.reset()

        # Check initial rendering
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "┌──────────────────────────────────────┐\n" +
            "│ Group 1:                             │\n" +
            "│ My First                             █\n" +
            "│ Box:                                 ░\n" +
            "│                                      ░\n" +
            "│                                      ░\n" +
            "│                                      ░\n" +
            "│ Text1:                               ░\n" +
            "│ Text2:                               │\n" +
            "└──────────────────────────────────────┘\n")

        # Check scrolling works.  Should also test label splitting and ellipsis.
        form.move_to(0, 9, 8)
        form.update(1)
        self.assert_canvas_equals(
            canvas,
            "┌──────────────────────────────────────┐\n" +
            "│ Text3:                               │\n" +
            "│                                      ░\n" +
            "│ ──────────────────────────────────   ░\n" +
            "│ Group 2:                             █\n" +
            "│ A Longer   (•) Option 1              ░\n" +
            "│ Selection: ( ) Option 2              ░\n" +
            "│            ( ) Option 3              ░\n" +
            "│ A very...  [ ] Field 1               │\n" +
            "└──────────────────────────────────────┘\n")

        # Now check button rendering.
        form.move_to(0, 18, 8)
        form.update(2)
        self.assert_canvas_equals(
            canvas,
            "┌──────────────────────────────────────┐\n" +
            "│            [ ] Field 3               │\n" +
            "│                                      ░\n" +
            "│ ──────────────────────────────────   ░\n" +
            "│                                      ░\n" +
            "│ < Reset >  < View Data > < Quit >    ░\n" +
            "│  < One >     < Two >    < Three >    ░\n" +
            "│                                      █\n" +
            "│                                      │\n" +
            "└──────────────────────────────────────┘\n")

    def test_no_border(self):
        """
        Check that a Frame with no border works
        """
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        canvas = Canvas(screen, 10, 40, 0, 0)
        scene = Scene([], -1)
        form = _TestFrame(canvas, has_border=False, can_scroll=False)
        scene.add_effect(form)
        form.reset()

        # Check initial rendering
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "  Group 1:                              \n" +
            "  My First                              \n" +
            "  Box:                                  \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "  Text1:                                \n" +
            "  Text2:                                \n" +
            "  Text3:                                \n" +
            "                                        \n")

        # Check scrolling works.  Should also test label splitting and ellipsis.
        form.move_to(0, 10, 10)
        form.update(1)
        self.assert_canvas_equals(
            canvas,
            "  ------------------------------------  \n" +
            "  Group 2:                              \n" +
            "  A Longer    (X) Option 1              \n" +
            "  Selection:  ( ) Option 2              \n" +
            "              ( ) Option 3              \n" +
            "  A very si...[ ] Field 1               \n" +
            "              [ ] Field 2               \n" +
            "              [ ] Field 3               \n" +
            "                                        \n" +
            "  ------------------------------------  \n")

    def test_no_border_can_scroll(self):
        """
        Check that a Frame with scroll bar but without border renders
        """
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        canvas = Canvas(screen, 10, 40, 0, 0)
        scene = Scene([], -1)
        form = _TestFrame(canvas, has_border=False, can_scroll=True)
        scene.add_effect(form)
        form.reset()

        # Check initial rendering
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            " Group 1:                               \n" +
            " My First                              O\n" +
            " Box:                                  |\n" +
            "                                       |\n" +
            "                                       |\n" +
            "                                       |\n" +
            " Text1:                                |\n" +
            " Text2:                                |\n" +
            " Text3:                                |\n" +
            "                                        \n")

    def test_form_input(self):
        """
        Check Frame input works as expected.
        """
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        canvas = Canvas(screen, 10, 40, 0, 0)
        scene = Scene([], -1)
        form = _TestFrame(canvas)
        scene.add_effect(form)
        form.reset()
        self.process_keys(form,
                          ["ABC\nDEF", "GHI", "jkl", "MN", " ", " ", "", " "],
                          Screen.KEY_TAB)
        form.save()
        self.assertEqual(
            form.data,
            {
                'CA': True,
                'CB': False,
                'CC': True,
                'TA': ['ABC', 'DEF'],
                'TB': 'GHI',
                'TC': 'jkl',
                'TD': 'MN',
                'Things': 1
            })

        # Check that forms ignore unrecognised events.
        event = object()
        self.assertEqual(event, form.process_event(event))

        # Check forms don't want global input handling.
        self.assertFalse(form.safe_to_default_unhandled_input)

    def test_textbox_input(self):
        """
        Check TextBox input works as expected.
        """
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        canvas = Canvas(screen, 10, 40, 0, 0)
        scene = Scene([], -1)
        form = _TestFrame(canvas)
        scene.add_effect(form)
        form.reset()

        # Check basic movement keys
        self.process_keys(form,  ["ABC", Screen.KEY_LEFT, "D"])
        form.save()
        self.assertEqual(form.data["TA"], ["ABDC"])
        self.process_keys(form,  [Screen.KEY_RIGHT, Screen.KEY_RIGHT, "E"])
        form.save()
        self.assertEqual(form.data["TA"], ["ABDCE"])
        self.process_keys(form,  ["\nFGH", Screen.KEY_UP, Screen.KEY_UP, "I"])
        form.save()
        self.assertEqual(form.data["TA"], ["ABDICE", "FGH"])
        self.process_keys(form,  [Screen.KEY_DOWN, Screen.KEY_DOWN, "J"])
        form.save()
        self.assertEqual(form.data["TA"], ["ABDICE", "FGHJ"])
        self.process_keys(form,  [Screen.KEY_HOME, "K"])
        form.save()
        self.assertEqual(form.data["TA"], ["ABDICE", "KFGHJ"])
        self.process_keys(form,  [Screen.KEY_END, "L"])
        form.save()
        self.assertEqual(form.data["TA"], ["ABDICE", "KFGHJL"])

        # Backspace - normal and wrapping lines
        self.process_keys(form,  [Screen.KEY_BACK])
        form.save()
        self.assertEqual(form.data["TA"], ["ABDICE", "KFGHJ"])
        self.process_keys(form,  [Screen.KEY_HOME, Screen.KEY_BACK])
        form.save()
        self.assertEqual(form.data["TA"], ["ABDICEKFGHJ"])

        # Check cursor line-wrapping
        self.process_keys(form,  ["\n"])
        form.save()
        self.assertEqual(form.data["TA"], ["ABDICE", "KFGHJ"])
        self.process_keys(form,  [Screen.KEY_LEFT, "M"])
        form.save()
        self.assertEqual(form.data["TA"], ["ABDICEM", "KFGHJ"])
        self.process_keys(form,  [Screen.KEY_RIGHT, "N"])
        form.save()
        self.assertEqual(form.data["TA"], ["ABDICEM", "NKFGHJ"])

        # Delete - normal and wrapping lines and at end of all data.
        self.process_keys(form,  [Screen.KEY_DELETE])
        form.save()
        self.assertEqual(form.data["TA"], ["ABDICEM", "NFGHJ"])
        self.process_keys(form,
                          [Screen.KEY_UP, Screen.KEY_END, Screen.KEY_DELETE])
        form.save()
        self.assertEqual(form.data["TA"], ["ABDICEMNFGHJ"])
        self.process_keys(form, [Screen.KEY_END, Screen.KEY_DELETE])
        form.save()
        self.assertEqual(form.data["TA"], ["ABDICEMNFGHJ"])

        # Check that page up/down work as expected.
        self.process_keys(form,  [Screen.ctrl("m"), Screen.ctrl("m"), Screen.KEY_PAGE_UP, "O"])
        form.save()
        self.assertEqual(form.data["TA"], ["OABDICEMNFGHJ", "", ""])
        self.process_keys(form,  [Screen.KEY_PAGE_DOWN, "P"])
        form.save()
        self.assertEqual(form.data["TA"], ["OABDICEMNFGHJ", "", "P"])

        # Check that the current focus ignores unknown events.
        event = object()
        self.assertEqual(event, form.process_event(event))

    def test_text_input(self):
        """
        Check Text input works as expected.
        """
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        canvas = Canvas(screen, 10, 40, 0, 0)
        scene = Scene([], -1)
        form = _TestFrame(canvas)
        scene.add_effect(form)
        form.reset()

        # Check basic movement keys
        self.process_keys(form,  [Screen.KEY_TAB, "ABC"])
        form.save()
        self.assertEqual(form.data["TB"], "ABC")
        self.process_keys(form,  [Screen.KEY_HOME, "D"])
        form.save()
        self.assertEqual(form.data["TB"], "DABC")
        self.process_keys(form,  [Screen.KEY_END, "E"])
        form.save()
        self.assertEqual(form.data["TB"], "DABCE")
        self.process_keys(form,  [Screen.KEY_LEFT, "F"])
        form.save()
        self.assertEqual(form.data["TB"], "DABCFE")
        self.process_keys(form,  [Screen.KEY_RIGHT, "G"])
        form.save()
        self.assertEqual(form.data["TB"], "DABCFEG")

        # Backspace
        self.process_keys(form,  [Screen.KEY_BACK])
        form.save()
        self.assertEqual(form.data["TB"], "DABCFE")

        # Delete - including at end of data
        self.process_keys(form,  [Screen.KEY_DELETE])
        form.save()
        self.assertEqual(form.data["TB"], "DABCFE")
        self.process_keys(form,  [Screen.KEY_HOME, Screen.KEY_DELETE])
        form.save()
        self.assertEqual(form.data["TB"], "ABCFE")

        # Check that the current focus ignores unknown events.
        event = object()
        self.assertEqual(event, form.process_event(event))

    def test_validation(self):
        """
        Check free-form text validation works as expected.
        """
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        canvas = Canvas(screen, 10, 40, 0, 0)
        scene = Scene([], -1)
        form = _TestFrame(canvas)
        scene.add_effect(form)
        form.reset()

        # Check that save still works with no validation.
        self.process_keys(form,  [Screen.KEY_TAB, Screen.KEY_TAB, "ABC"])
        form.save()
        self.assertEqual(form.data["TC"], "ABC")

        # Check that enforced validation throws exceptions as needed.
        with self.assertRaises(InvalidFields) as cm:
            form.save(validate=True)
        self.assertEqual(cm.exception.fields, ["TC"])

        # Check valid data doesn't throw anything.
        self.process_keys(form,
                          [Screen.KEY_BACK, Screen.KEY_BACK, Screen.KEY_BACK])
        form.save(validate=True)

        # Check functions work as well as regexp strings.
        self.process_keys(form, [Screen.KEY_TAB, "ABC"])
        with self.assertRaises(InvalidFields) as cm:
            form.save(validate=True)
        self.assertEqual(cm.exception.fields, ["TD"])

    def test_checkbox_input(self):
        """
        Check Checkbox input works as expected.
        """
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        canvas = Canvas(screen, 10, 40, 0, 0)
        scene = Scene([], -1)
        form = _TestFrame(canvas)
        scene.add_effect(form)
        form.reset()

        # Check basic selection keys
        self.process_keys(
            form,
            [Screen.KEY_TAB, Screen.KEY_TAB, Screen.KEY_TAB, Screen.KEY_TAB,
             Screen.KEY_TAB, " "])
        form.save()
        self.assertEqual(form.data["CA"], True)
        self.process_keys(form, ["\n"])
        form.save()
        self.assertEqual(form.data["CA"], False)
        self.process_keys(form, ["\r"])
        form.save()
        self.assertEqual(form.data["CA"], True)

        # Check that the current focus ignores unknown events.
        event = object()
        self.assertEqual(event, form.process_event(event))

    def test_radiobutton_input(self):
        """
        Check RadioButton input works as expected.
        """
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        canvas = Canvas(screen, 10, 40, 0, 0)
        scene = Scene([], -1)
        form = _TestFrame(canvas)
        scene.add_effect(form)
        form.reset()

        # Check basic selection keys - including limit checking
        self.process_keys(
            form,
            [Screen.KEY_TAB, Screen.KEY_TAB, Screen.KEY_TAB, Screen.KEY_TAB])
        form.save()
        self.assertEqual(form.data["Things"], 1)
        self.process_keys(form, [Screen.KEY_UP])
        form.save()
        self.assertEqual(form.data["Things"], 1)
        self.process_keys(form, [Screen.KEY_DOWN])
        form.save()
        self.assertEqual(form.data["Things"], 2)
        self.process_keys(form, [Screen.KEY_DOWN])
        form.save()
        self.assertEqual(form.data["Things"], 3)
        self.process_keys(form, [Screen.KEY_DOWN])
        form.save()
        self.assertEqual(form.data["Things"], 3)
        self.process_keys(form, [Screen.KEY_UP])
        form.save()
        self.assertEqual(form.data["Things"], 2)

        # Check that a radio button ignores unknown events.
        event = object()
        self.assertEqual(event, form.process_event(event))

        # Check that setting an unknown value resets to first radio button.
        form.focussed_widget.value = "Nonexistent value"
        form.save()
        self.assertEqual(form.data["Things"], 1)

        # Check mouse clicks change selection
        self.process_mouse(form, [(14, 7, MouseEvent.LEFT_CLICK)])
        form.save()
        self.assertEqual(form.data["Things"], 2)

    def test_mouse_input(self):
        """
        Check mouse input works as expected.
        """
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        canvas = Canvas(screen, 10, 40, 0, 0)
        scene = Scene([], -1)
        form = _TestFrame(canvas)
        scene.add_effect(form)
        form.reset()

        # Check focus moves when clicked on a text or textbox
        self.process_mouse(form, [(29, 7, MouseEvent.LEFT_CLICK)])
        self.assertEqual(form._layouts[form._focus]._live_widget, 2)
        self.process_mouse(form, [(29, 2, MouseEvent.LEFT_CLICK)])
        self.assertEqual(form._layouts[form._focus]._live_widget, 1)

        # Check focus doesn't change on scroll or click outside of widgets
        self.process_mouse(form, [(0, 0, 0)])
        self.assertEqual(form._focus, 0)
        self.assertEqual(form._layouts[form._focus]._live_col, 1)
        self.assertEqual(form._layouts[form._focus]._live_widget, 1)
        self.process_mouse(form, [(39, 7, MouseEvent.LEFT_CLICK)])
        self.assertEqual(form._layouts[form._focus]._live_widget, 1)

        # Check focus moves when clicked on a checkbox or radiobutton
        self.process_mouse(form, [(29, 1, MouseEvent.LEFT_CLICK)])
        self.assertEqual(form._layouts[form._focus]._live_widget, 8)
        # Note that the above changes the Frame start-line.
        self.process_mouse(form, [(29, 5, MouseEvent.LEFT_CLICK)])
        self.assertEqual(form._layouts[form._focus]._live_widget, 11)

        # Check focus moves when hovering over a widget
        self.process_mouse(form, [(39, 7, MouseEvent.LEFT_CLICK), (3, 8, 0)])
        self.assertEqual(form._focus, 1)
        self.assertEqual(form._layouts[form._focus]._live_col, 0)
        self.assertEqual(form._layouts[form._focus]._live_widget, 1)

        # Check button click triggers an event.
        with self.assertRaises(StopApplication):
            self.process_mouse(form, [(30, 7, MouseEvent.LEFT_CLICK)])

        # Check that the current focus ignores unknown events.
        event = object()
        self.assertEqual(event, form.process_event(event))

    def test_frame_focus_widget_property(self):
        """
        Check the frame exposes the focussed widget
        """
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        canvas = Canvas(screen, 10, 40, 0, 0)
        scene = Scene([], -1)
        form = _TestFrame(canvas)
        scene.add_effect(form)
        form.reset()

        # If the Frame loses the focus it must not return a focussed widget.
        form._has_focus = False
        self.assertIsNone(form.focussed_widget)

        # If the Frame focus is undefined, it must not return a focussed widget.
        form._has_focus = True
        form._focus = 9999
        self.assertIsNone(form.focussed_widget)

        # Refixing frame resets focus when needed.
        form._focus = 0
        old_focus = form.focussed_widget
        self.assertIsNotNone(old_focus)
        form._layouts[0].clear_widgets()
        form.fix()
        self.assertNotEqual(form.focussed_widget, old_focus)

        # Moving focus to undefined widget has no effect.
        old_focus = form.focussed_widget
        form.switch_focus(None, 0, 0)
        self.assertEqual(form.focussed_widget, old_focus)

        # Blurring removes focus.
        form.blur()
        self.assertIsNone(form.focussed_widget)

    def test_frame_focus_widget_property_when_frame_focussed(self):
        """
        check the frame exposes nothing when frame is foccused
        """
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        canvas = Canvas(screen, 10, 40, 0, 0)
        scene = Scene([], -1)
        form = _TestFrame(canvas)
        scene.add_effect(form)
        form.reset()

        # A Frame with a valid focus should return the widget in focus.
        layout = form._layouts[form._focus]
        layout._has_focus = True
        form._has_focus = True
        self.assertEqual(layout._columns[layout._live_col][layout._live_widget],
                         form.focussed_widget)

    def test_widget_navigation(self):
        """
        Check widget tab stops work as expected.
        """
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        canvas = Canvas(screen, 10, 40, 0, 0)
        scene = Scene([], -1)
        form = _TestFrame(canvas)
        scene.add_effect(form)
        form.reset()

        # Check default focus at start is first visible widget
        self.assertEqual(form._focus, 0)
        self.assertEqual(form._layouts[form._focus]._live_col, 1)
        self.assertEqual(form._layouts[form._focus]._live_widget, 1)

        # Check BACK_TAB reverses TAB.
        self.process_keys(form, [Screen.KEY_TAB])
        self.assertEqual(form._layouts[form._focus]._live_widget, 2)
        self.process_keys(form, [Screen.KEY_BACK_TAB])
        self.assertEqual(form._layouts[form._focus]._live_widget, 1)

        # Check BACK_TAB/TAB wraps around the form.
        self.process_keys(form, [Screen.KEY_BACK_TAB])
        self.assertEqual(form._focus, 1)
        self.process_keys(form, [Screen.KEY_TAB])
        self.assertEqual(form._focus, 0)

        # Tab out into text fields and check UP/DOWN keys.
        self.process_keys(form, [Screen.KEY_TAB, Screen.KEY_DOWN])
        self.assertEqual(form._layouts[form._focus]._live_widget, 3)
        self.process_keys(form, [Screen.KEY_UP])
        self.assertEqual(form._layouts[form._focus]._live_widget, 2)

        # Tab out into buttons and check LEFT/RIGHT keys.
        self.process_keys(form, [Screen.KEY_TAB, Screen.KEY_TAB, Screen.KEY_TAB,
                                 Screen.KEY_TAB, Screen.KEY_TAB, Screen.KEY_TAB,
                                 Screen.KEY_TAB, Screen.KEY_TAB])
        self.assertEqual(form._focus, 1)
        self.assertEqual(form._layouts[form._focus]._live_col, 1)
        self.assertEqual(form._layouts[form._focus]._live_widget, 0)

        self.process_keys(form, [Screen.KEY_RIGHT])
        self.assertEqual(form._layouts[form._focus]._live_col, 2)
        self.process_keys(form, [Screen.KEY_LEFT])
        self.assertEqual(form._layouts[form._focus]._live_col, 1)
        self.process_keys(form, [Screen.KEY_LEFT])
        # Reset will be disabled, but move down to "One"
        self.assertEqual(form._layouts[form._focus]._live_col, 0)
        self.assertEqual(form._layouts[form._focus]._live_widget, 1)
        self.process_keys(form, [Screen.KEY_RIGHT])
        self.assertEqual(form._layouts[form._focus]._live_col, 1)

        # Check up and down stay in column.
        self.process_keys(form, [Screen.KEY_UP])
        self.assertEqual(form._layouts[form._focus]._live_col, 1)
        self.assertEqual(form._layouts[form._focus]._live_widget, 0)
        self.process_keys(form, [Screen.KEY_DOWN])
        self.assertEqual(form._layouts[form._focus]._live_col, 1)
        self.assertEqual(form._layouts[form._focus]._live_widget, 1)

        # Check up and down find nearest widget across Layouts
        # - Up to checkbox
        self.process_keys(form, [Screen.KEY_UP, Screen.KEY_UP])
        self.assertEqual(form._focus, 0)
        self.assertEqual(form._layouts[form._focus]._live_col, 1)
        self.assertEqual(form._layouts[form._focus]._live_widget, 10)
        # - Down to first button row
        self.process_keys(form, [Screen.KEY_DOWN])
        self.assertEqual(form._focus, 1)
        self.assertEqual(form._layouts[form._focus]._live_col, 1)
        self.assertEqual(form._layouts[form._focus]._live_widget, 0)
        # - Down to wrap to top of form
        self.process_keys(form, [Screen.KEY_DOWN, Screen.KEY_DOWN])
        self.assertEqual(form._focus, 0)
        self.assertEqual(form._layouts[form._focus]._live_col, 1)
        self.assertEqual(form._layouts[form._focus]._live_widget, 1)

    def test_list_box(self):
        """
        Check ListBox widget works as expected.
        """
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        canvas = Canvas(screen, 10, 40, 0, 0)
        scene = Scene([], -1)
        form = _TestFrame2(
            canvas, [("One", 1), ("Two is now quite a bit longer than before", 2)])
        scene.add_effect(form)
        form.reset()

        # Check we have a default value for our list.
        form.save()
        self.assertEqual(form.data, {"selected": "None", "contacts": 1})

        # Check that UP/DOWN change selection.
        self.process_keys(form, [Screen.KEY_DOWN])
        form.save()
        self.assertEqual(form.data, {"selected": "None", "contacts": 2})
        self.process_keys(form, [Screen.KEY_UP])
        form.save()
        self.assertEqual(form.data, {"selected": "None", "contacts": 1})

        # Check that the listbox is rendered correctly.
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "+------------ Test Frame 2 ------------+\n" +
            "|One                                   |\n" +
            "|Two is now quite a bit longer than ...O\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|######################################|\n" +
            "| < Add > < Edit > < Delete < Quit >   |\n" +
            "|Selected: None                        |\n" +
            "+--------------------------------------+\n")

        # Check that mouse input changes selection.
        self.process_mouse(form, [(2, 2, MouseEvent.LEFT_CLICK)])
        form.save()
        self.assertEqual(form.data, {"selected": "None", "contacts": 2})
        self.process_mouse(form, [(2, 1, MouseEvent.LEFT_CLICK)])
        form.save()
        self.assertEqual(form.data, {"selected": "None", "contacts": 1})

        # Check that enter key handles correctly.
        self.process_keys(form, [Screen.ctrl("m")])
        form.save()
        self.assertEqual(form.data, {"selected": "1", "contacts": 1})

        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "+------------ Test Frame 2 ------------+\n" +
            "|One                                   |\n" +
            "|Two is now quite a bit longer than ...O\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|######################################|\n" +
            "| < Add > < Edit > < Delete < Quit >   |\n" +
            "|Selected: 1                           |\n" +
            "+--------------------------------------+\n")

        # Check that mouse double click handles correctly.
        self.process_mouse(form, [(2, 2, MouseEvent.DOUBLE_CLICK)])
        form.save()
        self.assertEqual(form.data, {"selected": "2", "contacts": 2})

        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "+------------ Test Frame 2 ------------+\n" +
            "|One                                   |\n" +
            "|Two is now quite a bit longer than ...O\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|######################################|\n" +
            "| < Add > < Edit > < Delete < Quit >   |\n" +
            "|Selected: 2                           |\n" +
            "+--------------------------------------+\n")

        # Check that the current focus ignores unknown events.
        event = object()
        self.assertEqual(event, form.process_event(event))

    def test_title(self):
        """
        Check Frame titles work as expected.
        """
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        canvas = Canvas(screen, 10, 40, 0, 0)
        scene = Scene([], -1)
        form = _TestFrame2(canvas, [("One", 1), ("Two", 2)])
        scene.add_effect(form)
        form.reset()

        # Check that the title is rendered correctly.
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "+------------ Test Frame 2 ------------+\n" +
            "|One                                   |\n" +
            "|Two                                   O\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|######################################|\n" +
            "| < Add > < Edit > < Delete < Quit >   |\n" +
            "|Selected: None                        |\n" +
            "+--------------------------------------+\n")

        # Check that a new title is rendered correctly.
        form.title = "A New Title!"
        form.update(1)
        self.assert_canvas_equals(
            canvas,
            "+------------ A New Title! ------------+\n" +
            "|One                                   |\n" +
            "|Two                                   O\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|######################################|\n" +
            "| < Add > < Edit > < Delete < Quit >   |\n" +
            "|Selected: None                        |\n" +
            "+--------------------------------------+\n")

        # Note that the actual stored title includes spaces for margins
        self.assertEqual(form.title, " A New Title! ")

    def test_focus_callback(self):
        """
        Check that the _on_focus & _on_blur callbacks work as expected.
        """
        def _on_focus():
            self._did_focus += 1

        def _on_blur():
            self._did_blur += 1

        # Reset state for test
        self._did_blur = 0
        self._did_focus = 0

        # Create a dummy screen
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], -1)
        canvas = Canvas(screen, 2, 40, 0, 0)

        # Create the form we want to test.
        form = Frame(canvas, canvas.height, canvas.width, has_border=False, can_scroll=False)
        scene.add_effect(form)
        layout = Layout([100])
        form.add_layout(layout)
        layout.add_widget(Text("Test"))
        layout2 = Layout([100], fill_frame=True)
        form.add_layout(layout2)
        layout2.add_widget(Text("Test2", on_blur=_on_blur, on_focus=_on_focus))
        form.fix()
        form.reset()

        # Check state after reset
        self.assertEqual(self._did_blur, 1)
        self.assertEqual(self._did_focus, 0)

        # Tab round to move the focus - check it has called the right function.
        self.process_keys(form, [Screen.KEY_TAB])
        self.assertEqual(self._did_blur, 1)
        self.assertEqual(self._did_focus, 1)

        # Now move the focus away with the mouse.
        self.process_mouse(form, [(0, 0, MouseEvent.LEFT_CLICK)])
        self.assertEqual(self._did_blur, 2)
        self.assertEqual(self._did_focus, 1)

        # Now check cursor keys call the right function.
        self.process_keys(form, [Screen.KEY_DOWN])
        self.assertEqual(self._did_blur, 2)
        self.assertEqual(self._did_focus, 2)
        self.process_keys(form, [Screen.KEY_UP])
        self.assertEqual(self._did_blur, 3)
        self.assertEqual(self._did_focus, 2)

    def test_load_callback(self):
        """
        Check that the _on_load callback works as expected.
        """
        def _on_load():
            self._did_load = True

        # Reset state for test
        self._did_load = False

        # Create a dummy screen
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        canvas = Canvas(screen, 2, 40, 0, 0)

        # Create the form we want to test.
        form = Frame(canvas, canvas.height, canvas.width, on_load=_on_load)
        form.fix()
        scene = Scene([form], -1)

        # Check only called on reset.
        self.assertEqual(self._did_load, False)
        scene.reset()
        self.assertEqual(self._did_load, True)

    def test_multi_column_list_box(self):
        """
        Check MultiColumnListBox works as expected.
        """
        # Create a dummy screen.
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], -1)
        canvas = Canvas(screen, 10, 40, 0, 0)

        # Create the form we want to test.
        form = Frame(canvas, canvas.height, canvas.width, has_border=False, can_scroll=False)
        layout = Layout([100], fill_frame=True)
        mc_list = MultiColumnListBox(
            Widget.FILL_FRAME,
            [3, "4", ">4", "<4", "^10%", "100%"],
            [
                (["1", "2", "3", "4", "5", "6"], 1),
                (["11", "222", "333", "444", "555", "6"], 2),
                (["111", "2", "3", "4", "5", "6"], 3),
                (["1", "2", "33333", "4", "5", "6"], 4),
                (["1", "2", "3", "4", "5", "6666666666666666666666"], 5),
            ],
            titles=["A", "B", "C", "D", "E", "F"],
            name="mc_list")
        form.add_layout(layout)
        layout.add_widget(mc_list)
        form.fix()
        scene.add_effect(form)
        form.reset()

        # Check we have a default value for our list.
        form.save()
        self.assertEqual(form.data, {"mc_list": 1})

        # Check that UP/DOWN change selection.
        self.process_keys(form, [Screen.KEY_DOWN])
        form.save()
        self.assertEqual(form.data, {"mc_list": 2})
        self.process_keys(form, [Screen.KEY_UP])
        form.save()
        self.assertEqual(form.data, {"mc_list": 1})

        # Check that PGUP/PGDN change selection.
        self.process_keys(form, [Screen.KEY_PAGE_DOWN])
        form.save()
        self.assertEqual(form.data, {"mc_list": 5})
        self.process_keys(form, [Screen.KEY_PAGE_UP])
        form.save()
        self.assertEqual(form.data, {"mc_list": 1})

        # Check that the widget is rendered correctly.
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "A  B      C D    E  F                   \n" +
            "1  2      3 4    5  6                   \n" +
            "11 222  333 444 555 6                   \n" +
            "1112      3 4    5  6                   \n" +
            "1  2   3... 4    5  6                   \n" +
            "1  2      3 4    5  66666666666666666666\n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n")

        # Check that mouse input changes selection.
        self.process_mouse(form, [(2, 2, MouseEvent.LEFT_CLICK)])
        form.save()
        self.assertEqual(form.data, {"mc_list": 2})
        self.process_mouse(form, [(2, 1, MouseEvent.LEFT_CLICK)])
        form.save()
        self.assertEqual(form.data, {"mc_list": 1})

        # Check that the start_line can be read and set - and enforces good behaviour
        mc_list.start_line = 0
        self.assertEqual(mc_list.start_line, 0)
        mc_list.start_line = len(mc_list.options) - 1
        self.assertEqual(mc_list.start_line, len(mc_list.options) - 1)
        mc_list.start_line = 10000000
        self.assertEqual(mc_list.start_line, len(mc_list.options) - 1)

        # Check that options can be read and set.
        mc_list.options = [(["a", "b", "c", "d", "e", "f"], 0)]
        self.assertEqual(mc_list.options, [(["a", "b", "c", "d", "e", "f"], 0)])
        mc_list.options = []
        self.assertEqual(mc_list.options, [])

        # Check that the form re-renders correctly afterwards.
        form.update(1)
        self.assert_canvas_equals(
            canvas,
            "A  B      C D    E  F                   \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n")

        # Check that the current focus ignores unknown events.
        event = object()
        self.assertEqual(event, form.process_event(event))

        # Check that options retain the current value where possible.
        mc_list.options = [
            (["a", "b", "c", "d", "e", "f"], 0),
            (["b", "b", "c", "d", "e", "f"], 1),
            (["c", "b", "c", "d", "e", "f"], 2),
        ]
        mc_list.value = 1
        mc_list.options = [
            (["a", "b", "c", "d", "e", "f"], 0),
            (["b", "b", "c", "d", "e", "f"], 1),
            (["c", "b", "c", "d", "e", "f"], 2),
            (["d", "b", "c", "d", "e", "f"], 3),
        ]
        self.assertEqual(mc_list.value, 1)
        mc_list.options = [
            (["a", "b", "c", "d", "e", "f"], 0),
            (["c", "b", "c", "d", "e", "f"], 2),
            (["d", "b", "c", "d", "e", "f"], 3),
        ]
        self.assertEqual(mc_list.value, 0)

    def test_multi_column_list_box_delimiter(self):
        """
        Check MultiColumnListBox works as expected with space_delimiter
        """
        # Create a dummy screen.
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], -1)
        canvas = Canvas(screen, 10, 40, 0, 0)

        # Create the form we want to test.
        form = Frame(canvas, canvas.height, canvas.width, has_border=False, can_scroll=False)
        layout = Layout([100], fill_frame=True)
        mc_list = MultiColumnListBox(
            Widget.FILL_FRAME,
            [3, "4", ">4", "<4", "^10%", "100%"],
            [
                (["1", "2", "3", "4", "5", "6"], 1),
                (["11", "222", "333", "444", "555", "6"], 2),
                (["111", "2", "3", "4", "5", "6"], 3),
                (["1", "2", "33333", "4", "5", "6"], 4),
                (["1", "2", "3", "4", "5", "6666666666666666666666"], 5),
            ],
            titles=["A", "B", "C", "D", "E", "F"],
            name="mc_list",
            space_delimiter='|')
        form.add_layout(layout)
        layout.add_widget(mc_list)
        form.fix()
        scene.add_effect(form)
        form.reset()

        # Check that the widget is rendered correctly.
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "A  |B   |   C|D   | E  |F               \n" +
            "1  |2   |   3|4   | 5  |6               \n" +
            "11 |222 | 333|444 |555 |6               \n" +
            "111|2   |   3|4   | 5  |6               \n" +
            "1  |2   |3...|4   | 5  |6               \n" +
            "1  |2   |   3|4   | 5  |6666666666666666\n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n")

    def test_list_box_scrollbar(self):
        """
        Check ListBox scrollbar works.
        """
        # Create a dummy screen.
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], -1)
        canvas = Canvas(screen, 10, 40, 0, 0)

        # Create the form we want to test.
        form = Frame(canvas, canvas.height, canvas.width, has_border=False, can_scroll=False)
        layout = Layout([100], fill_frame=True)
        simple_list = ListBox(
            3,
            [
                ("Some", 1),
                ("Stuff", 2),
                ("To", 3),
                ("See", 4),
            ],
            add_scroll_bar=True,
            name="simple_list")
        form.add_layout(layout)
        layout.add_widget(simple_list)
        form.fix()
        scene.add_effect(form)
        form.reset()

        # Check that the widget is rendered correctly with the scrollbar
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "Some                                   O\n" +
            "Stuff                                  |\n" +
            "To                                     |\n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n")

        # Check that mouse input works
        self.process_mouse(form, [(39, 2, MouseEvent.LEFT_CLICK)])
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "Stuff                                  |\n" +
            "To                                     |\n" +
            "See                                    O\n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n")

        # Check that options can be set and hide scrollbar
        simple_list.options = [("New list", 1)]
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "New list                                \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n")

    def test_multi_column_list_box_scrollbar(self):
        """
        Check MultiColumnListBox scrollbar works.
        """
        # Create a dummy screen.
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], -1)
        canvas = Canvas(screen, 10, 40, 0, 0)

        # Create the form we want to test.
        form = Frame(canvas, canvas.height, canvas.width, has_border=False, can_scroll=False)
        layout = Layout([100], fill_frame=True)
        mc_list = MultiColumnListBox(
            3,
            [3, 0, ">4"],
            [
                (["1", "2", "3"], 1),
                (["11", "222", "333"], 2),
                (["111", "2", "3"], 3),
            ],
            titles=["A", "B", "C"],
            add_scroll_bar=True,
            name="mc_list")
        form.add_layout(layout)
        layout.add_widget(mc_list)
        form.fix()
        scene.add_effect(form)
        form.reset()

        # Check that the widget is rendered correctly with the scrollbar
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "A  B                                  C \n" +
            "1  2                                  3O\n" +
            "11 222                              333|\n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n")

        # Check that search works
        self.process_keys(form, ["111"])
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "A  B                                  C \n" +
            "11 222                              333O\n" +
            "1112                                  3|\n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n")

    def test_disabled_layout(self):
        """
        Check disabled layout cannot be tabstop.
        """
        # Create a dummy screen.
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], -1)
        canvas = Canvas(screen, 10, 40, 0, 0)

        # Create the form we want to test.
        form = Frame(canvas, canvas.height, canvas.width, has_border=False, can_scroll=False)
        layout = Layout([100])
        form.add_layout(layout)
        text_box = TextBox(1, as_string=True)
        text_box.disabled = True
        layout.add_widget(text_box)
        layout2 = Layout([100], fill_frame=True)
        form.add_layout(layout2)
        text_box2 = TextBox(1, as_string=True)
        layout2.add_widget(text_box2)
        form.fix()
        scene.add_effect(form)
        form.reset()

        # Check that input cannot get focus on disabled layout.
        text_box.value = "A test"
        text_box2.value = "Another test"
        self.process_keys(form, ["A", Screen.KEY_TAB, Screen.KEY_UP, "B"])
        form.save()
        self.assertEqual(text_box.value, "A test")
        self.assertEqual(text_box2.value, "Another testAB")

    def test_disabled_text(self):
        """
        Check disabled TextBox can be used for pre-formatted output.
        """
        # Create a dummy screen.
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], -1)
        canvas = Canvas(screen, 10, 40, 0, 0)

        # Create the form we want to test.
        form = Frame(canvas, canvas.height, canvas.width, has_border=False, can_scroll=False)
        layout = Layout([100], fill_frame=True)
        form.add_layout(layout)
        text_box = TextBox(1, as_string=True)
        text_box.disabled = True
        layout.add_widget(text_box)
        form.fix()
        scene.add_effect(form)
        form.reset()

        # Check that input has no effect on the programmed value.
        text_box.value = "A test"
        self.process_keys(form, ["A"])
        form.save()
        self.assertEqual(text_box.value, "A test")

        # Check that we can provide a custom colour.  Since the default palette has no "custom"
        # key, this will throw an exception.
        self.assertEqual(text_box._pick_colours("blah"), form.palette["disabled"])
        with self.assertRaises(KeyError) as cm:
            text_box.custom_colour = "custom"
            text_box._pick_colours("blah")
        self.assertIn("custom", str(cm.exception))

        # Also check the value is returned as set
        self.assertEqual(text_box.custom_colour, "custom")

    def test_line_flow(self):
        """
        Check TextBox line-flow editing works as expected.
        """
        # Create a dummy screen.
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], -1)
        canvas = Canvas(screen, 10, 40, 0, 0)

        # Create the form we want to test.
        form = Frame(canvas, canvas.height, canvas.width, has_border=False, can_scroll=False)
        layout = Layout([100], fill_frame=True)
        form.add_layout(layout)
        text_box = TextBox(5, as_string=True, line_wrap=True)
        layout.add_widget(text_box)
        form.fix()
        scene.add_effect(form)
        form.reset()

        # start with some text that will wrap and check display works.
        text_box.value = "A\nSome very long text that will wrap across multiple lines\nB\n"

        # Check that the pop-up is rendered correctly.
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "A                                       \n" +
            "Some very long text that will wrap acros\n" +
            "s multiple lines                        \n" +
            "B                                       \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n")

        # Check keyboard logic still works and display reflows on demand.
        self.process_mouse(form, [(0, 0, MouseEvent.LEFT_CLICK)])
        self.process_keys(form, [Screen.KEY_DOWN, "A", Screen.KEY_END, "B"])
        form.update(1)
        self.assert_canvas_equals(
            canvas,
            "A                                       \n" +
            "ASome very long text that will wrap acro\n" +
            "ss multiple linesB                      \n" +
            "B                                       \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n")
        self.assertEqual(text_box.value,
                         "A\nASome very long text that will wrap across multiple linesB\nB\n")

        # Check mouse logic still works.
        self.process_mouse(form, [(0, 2, MouseEvent.LEFT_CLICK)])
        self.process_keys(form, ["Z"])
        self.process_mouse(form, [(3, 3, MouseEvent.LEFT_CLICK)])
        self.process_keys(form, ["Y"])
        form.update(1)
        self.assertEqual(text_box.value,
                         "A\nASome very long text that will wrap acroZss multiple linesB\nBY\n")

    def test_pop_up_widget(self):
        """
        Check popup dialog work as expected.
        """
        def test_on_click(selection):
            raise NextScene(str(selection))

        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], -1)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = PopUpDialog(canvas, "Message", ["Yes", "No"], test_on_click)
        scene.add_effect(form)
        form.reset()

        # Check that the pop-up is rendered correctly.
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "                                        \n" +
            "                                        \n" +
            "          +------------------+          \n" +
            "          |Message           |          \n" +
            "          |                  |          \n" +
            "          | < Yes >  < No >  |          \n" +
            "          +------------------+          \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n")

        # Check that mouse input triggers the close function.
        with self.assertRaises(NextScene):
            self.process_mouse(form, [(14, 5, MouseEvent.LEFT_CLICK)])

        # Check that the pop-up swallows all events.
        event = object()
        self.assertIsNone(form.process_event(event))

    def test_pop_up_no_buttons(self):
        """
        Check dialog with nobuttons work as expected.
        """
        def test_on_click(selection):
            raise NextScene(str(selection))

        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], -1)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = PopUpDialog(canvas, "Message", [], test_on_click)
        scene.add_effect(form)
        form.reset()

        # Check that the pop-up is rendered correctly.
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "               +-------+                \n" +
            "               |Message|                \n" +
            "               +-------+                \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n")

    def test_cjk_popup(self):
        """
        Check PopUpDialog widgets work with CJK double-width characters.
        """
        # Apologies to anyone who actually speaks this language!  I just need some double-width
        # glyphs so have re-used the ones from the original bug report.
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=True)
        scene = Scene([], -1)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = PopUpDialog(canvas, "你確定嗎？ 你確定嗎？ 你確定嗎？", ["是", "否"])
        scene.add_effect(form)
        form.reset()

        # Check that the pop-up is rendered correctly.
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "                                        \n" +
            "                                        \n" +
            "       ┌────────────────────────┐       \n" +
            "       │你你確確定定嗎嗎？？ 你你確確定定嗎嗎？？   │       \n" +
            "       │你你確確定定嗎嗎？？              █       \n" +
            "       │                        ░       \n" +
            "       │   < 是是 >      < 否否 >   │       \n" +
            "       └────────────────────────┘       \n" +
            "                                        \n" +
            "                                        \n")

    def test_cjk_forms(self):
        """
        Check form widgets work with CJK characters.
        """
        # Create a dummy screen.
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=True)
        scene = Scene([], -1)
        canvas = Canvas(screen, 10, 40, 0, 0)

        # Create the form we want to test.
        form = Frame(canvas, canvas.height, canvas.width, has_border=False, can_scroll=False)
        layout = Layout([100], fill_frame=True)
        mc_list = MultiColumnListBox(
            4,
            [3, 5, 0],
            [
                (["1", "2", "3"], 1),
                (["你", "確", "定"], 2),
            ],
            titles=["你確定嗎？", "你確定嗎？", "你確定嗎？"])
        text = Text()
        text_box = TextBox(3)
        form.add_layout(layout)
        layout.add_widget(mc_list)
        layout.add_widget(text)
        layout.add_widget(text_box)
        form.fix()
        scene.add_effect(form)
        form.reset()

        # Set some interesting values...
        text.value = "你確定嗎？ 你確定嗎？ 你確定嗎？"
        text_box.value = ["你確定嗎", "？"]

        # Check that the CJK characters render correctly - no really this is correctly aligned!
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "你你 你你確確 你你確確定定嗎嗎？？                      \n" +
            "1  2    3                               \n" +
            "你你 確確   定定                              \n" +
            "                                        \n" +
            "你你確確定定嗎嗎？？ 你你確確定定嗎嗎？？ 你你確確定定嗎嗎？？        \n" +
            "你你確確定定嗎嗎                                \n" +
            "？？                                      \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n")

        # Check that mouse input takes into account the glyph width
        self.process_mouse(form, [(5, 4, MouseEvent.LEFT_CLICK)])
        self.process_keys(form, ["b"])
        self.process_mouse(form, [(2, 4, MouseEvent.LEFT_CLICK)])
        self.process_keys(form, ["p"])
        form.save()
        self.assertEqual(text.value, "你p確b定嗎？ 你確定嗎？ 你確定嗎？")

        self.process_mouse(form, [(2, 5, MouseEvent.LEFT_CLICK)])
        self.process_keys(form, ["p"])
        self.process_mouse(form, [(1, 6, MouseEvent.LEFT_CLICK)])
        self.process_keys(form, ["b"])
        form.save()
        self.assertEqual(text_box.value, ["你p確定嗎", "b？"])

    def test_shadow(self):
        """
        Check Frames support shadows.
        """
        def test_on_click(selection):
            raise NextScene(str(selection))

        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], -1)
        canvas = Canvas(screen, 10, 40, 0, 0)
        for y in range(10):
            canvas.print_at("X" * 40, 0, y)
        form = PopUpDialog(
            canvas, "Message", ["Yes", "No"], test_on_click, has_shadow=True)
        scene.add_effect(form)
        form.reset()

        # Check that the pop-up is rendered correctly.
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n" +
            "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n" +
            "XXXXXXXXXX+------------------+XXXXXXXXXX\n" +
            "XXXXXXXXXX|Message           |XXXXXXXXXX\n" +
            "XXXXXXXXXX|                  |XXXXXXXXXX\n" +
            "XXXXXXXXXX| < Yes >  < No >  |XXXXXXXXXX\n" +
            "XXXXXXXXXX+------------------+XXXXXXXXXX\n" +
            "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n" +
            "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n" +
            "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n")

    def test_clone(self):
        """
        Check Frame cloning works.
        """
        def test_on_click(selection):
            raise NextScene(str(selection))

        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        canvas = Canvas(screen, 10, 40, 0, 0)

        # Check that pop-up dialogs get copied to the new Scene
        scene = Scene([], -1)
        scene2 = Scene([], 10)
        form = PopUpDialog(
            canvas, "Message", ["Yes", "No"], test_on_click, has_shadow=True)
        scene.add_effect(form)
        form.clone(canvas, scene2)
        self.assertEqual(len(scene2.effects), 1)
        self.assertEqual(scene2.effects[0]._text, "Message")
        self.assertEqual(scene2.effects[0]._buttons, ["Yes", "No"])

        # Check that normal Frame data gets copied to the new Scene.
        frame = _TestFrame(canvas)
        frame2 = _TestFrame(canvas)
        scene = Scene([frame], 10)
        scene2 = Scene([frame2], 10)
        frame.data = {"TA": ["something"]}
        frame2.data = {}

        self.assertEqual(frame2.data, {})
        self.assertNotEqual(frame2.data, frame.data)
        frame.clone(canvas, scene2)
        self.assertEqual(frame2.data, frame.data)

    def test_frame_rate(self):
        """
        Check Frame rate limiting works as expected.
        """
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        canvas = Canvas(screen, 10, 40, 0, 0)
        scene = Scene([], -1)
        form = _TestFrame(canvas)
        scene.add_effect(form)
        form.reset()

        # With no special CPU consideration, and a cursor to animate, there
        # should be a 5 frame pause.
        self.assertEqual(form.reduce_cpu, False)
        self.assertEqual(form.frame_update_count, 5)

        # Shift focus away from a text input (to get no cursor animation).
        self.process_keys(form, [Screen.KEY_BACK_TAB])

        # With no special CPU consideration, and no cursors to animate, there
        # should be a (very!) long pause.
        self.assertEqual(form.reduce_cpu, False)
        self.assertEqual(form.frame_update_count, 1000000)

    def test_cpu_saving(self):
        """
        Check Frame rate limiting is even more extreme when in cpu saving mode.
        """
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        canvas = Canvas(screen, 10, 40, 0, 0)
        scene = Scene([], -1)
        form = _TestFrame(canvas, reduce_cpu=True)
        scene.add_effect(form)
        form.reset()

        # In this mode, it shouldn't matter where we are on the Frame - all
        # widgets will basically say they don't need animation.
        self.assertEqual(form.reduce_cpu, True)
        self.assertEqual(form.frame_update_count, 1000000)

        # Shift focus away from a text input, just to be sure.
        self.process_keys(form, [Screen.KEY_BACK_TAB])
        self.assertEqual(form.frame_update_count, 1000000)

    def test_stop_frame(self):
        """
        Check Frames always request no end to the Scene.
        """
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = _TestFrame(canvas, reduce_cpu=True)
        self.assertEqual(form.stop_frame, -1)

    def test_empty_frame(self):
        """
        Check empty Frames still work.
        """
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        canvas = Canvas(screen, 10, 40, 0, 0)
        scene = Scene([], -1)
        form = _TestFrame3(canvas)
        scene.add_effect(form)
        form.reset()

        # Check all keyboard events get swallowed
        self.assertIsNone(form.process_event(KeyboardEvent(ord("A"))))

        # Check Mouse events over the Frame are swallowed and others allowed
        # to bubble down the input stack.
        self.assertIsNone(
            form.process_event(MouseEvent(20, 5, MouseEvent.LEFT_CLICK)))
        self.assertIsNotNone(
            form.process_event(MouseEvent(5, 5, MouseEvent.LEFT_CLICK)))

        # Check form data is empty.
        form.save()
        self.assertEqual(form.data, {})

    def test_label_change(self):
        """
        Check Labels can be dynamically updated.
        """
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = _TestFrame(canvas)
        form.reset()

        # Check initial rendering
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "| Group 1:                             |\n" +
            "| My First                             O\n" +
            "| Box:                                 |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "| Text1:                               |\n" +
            "| Text2:                               |\n" +
            "+--------------------------------------+\n")

        # Check dynamic updates change the rendering.
        form.label.text = "New text here:"
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "| New text here:                       |\n" +
            "| My First                             O\n" +
            "| Box:                                 |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "| Text1:                               |\n" +
            "| Text2:                               |\n" +
            "+--------------------------------------+\n")
        self.assertEqual(form.label.text, "New text here:")

        # And check that values are unaffected (and still not set).
        self.assertEqual(form.label.value, None)

    def test_label_height(self):
        """
        Check Labels can be dynamically updated.
        """
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = _TestFrame(canvas, label_height=2)
        form.reset()

        # Check Label obeys required height
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "| Group 1:                             |\n" +
            "|                                      O\n" +
            "| My First                             |\n" +
            "| Box:                                 |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "| Text1:                               |\n" +
            "+--------------------------------------+\n")

        # Now check wrapping works too...
        form.label.text = "A longer piece of text that should wrap across multiple lines:"
        form.update(1)
        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "| A longer piece of text that should   |\n" +
            "| wrap across multiple lines:          O\n" +
            "| My First                             |\n" +
            "| Box:                                 |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "| Text1:                               |\n" +
            "+--------------------------------------+\n")

    def test_label_alignment(self):
        """
        Check Label alignment works.
        """
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], duration=-1)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = Frame(canvas, canvas.height, canvas.width)
        layout = Layout([1])
        form.add_layout(layout)
        layout.add_widget(Label("Left", align="<"))
        layout.add_widget(Label("Middle", align="^"))
        layout.add_widget(Label("Right", align=">"))
        form.fix()
        scene.add_effect(form)
        scene.reset()

        # Check that the frame is rendered correctly.
        for effect in scene.effects:
            effect.update(0)

        # Check Label obeys required height
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "|Left                                  |\n" +
            "|                Middle                O\n" +
            "|                                 Right|\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")

    def test_label_colours(self):
        """
        Check Label custom colour works.
        """
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], duration=-1)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = Frame(canvas, canvas.height, canvas.width)
        layout = Layout([1])
        form.add_layout(layout)
        label = Label("Some text")
        label.custom_colour = "disabled"
        layout.add_widget(label)
        form.fix()
        scene.add_effect(form)
        scene.reset()

        # Check that the label is rendered in the correct colour palette.
        for effect in scene.effects:
            effect.update(0)
        self.assertEqual(label.custom_colour, "disabled")
        self.assertEqual(canvas.get_from(1, 1), (ord("S"), 0, 1, 4))

        # Cheeky test that Labels always pass on events.
        event = object()
        self.assertEqual(event, label.process_event(event))

    @patch("os.stat")
    @patch("os.listdir")
    def test_file_browser_stat_err(self, mock_list, mock_stat):
        """
        Check FileBrowser widget copes with permissions error on stat.
        """
        # First we need to mock out the file system calls to have a regressible test
        if sys.platform == "win32":
            self.skipTest("File names wrong for windows")

        mock_list.return_value = ["A Directory", "A File", "A Lnk"]
        mock_stat.side_effect = OSError("Fake error")

        # Now set up the Frame ready for testing
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], -1)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = _TestFrame4(canvas)
        scene.add_effect(form)
        form.reset()

        # Check that the Frame is rendered correctly.
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "/                     Size Last modified\n" +
            "|-- A Directory          0    %s\n" % self.epoch_date +
            "|-- A File               0    %s\n" % self.epoch_date +
            "|-- A Lnk                0    %s\n" % self.epoch_date +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n")

    @patch("os.listdir")
    def test_file_browser_list_err(self, mock_list):
        """
        Check FileBrowser widget copes with permissions error on list.
        """
        # First we need to mock out the file system calls to have a regressible test
        if sys.platform == "win32":
            self.skipTest("File names wrong for windows")

        mock_list.side_effect = OSError("Fake error")

        # Now set up the Frame ready for testing
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], -1)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = _TestFrame4(canvas)
        scene.add_effect(form)
        form.reset()

        # Check that the Frame is rendered correctly.
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "/                     Size Last modified\n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n")

    @patch("os.path.exists")
    @patch("os.path.realpath")
    @patch("os.path.islink")
    @patch("os.path.isdir")
    @patch("os.lstat")
    @patch("os.stat")
    @patch("os.listdir")
    def test_file_browser(self, mock_list, mock_stat, mock_lstat, mock_dir, mock_link,
                          mock_real_path, mock_exists):
        """
        Check FileBrowser widget works as expected.
        """
        # First we need to mock out the file system calls to have a regressible test
        if sys.platform == "win32":
            self.skipTest("File names wrong for windows")

        mock_list.return_value = ["A Directory", "A File", "A Lnk", str(b"oo\xcc\x88o\xcc\x88O\xcc\x88.txt", 'utf-8'), "Lnk Directory"]
        mock_result = MagicMock()
        mock_result.st_mtime = 0
        mock_result.st_size = 10000
        mock_stat.return_value = mock_result
        mock_lstat.return_value = mock_result
        mock_dir.side_effect = lambda x: x.endswith("Directory")
        mock_link.side_effect = lambda x: "Lnk" in x
        mock_real_path.return_value = "A Tgt"
        mock_exists.return_value = True

        # Now set up the Frame ready for testing
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], -1)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = _TestFrame4(canvas)
        scene.add_effect(form)
        form.reset()

        # Check we have a default value for our list.
        form.save()
        self.assertIsNone(form.selected)
        self.assertIsNone(form.highlighted)
        self.assertEqual(form.data, {"file_list": None})

        # Check that the Frame is rendered correctly.
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "/                     Size Last modified\n" +
            "|-+ A Directory         9K    %s\n" % self.epoch_date +
            "|-+ Lnk Directo...      9K    %s\n" % self.epoch_date +
            "|-- A File              9K    %s\n" % self.epoch_date +
            "|-- A Lnk -> A Tgt      9K    %s\n" % self.epoch_date +
            "|-- oööÖ.txt            9K    %s\n" % self.epoch_date +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n")

        # Check that mouse inpput changes selection.
        self.process_mouse(form, [(2, 3, MouseEvent.LEFT_CLICK)])
        form.save()
        self.assertEqual(form.data, {"file_list": "/A File"})
        self.assertEqual(form.highlighted, "/A File")
        self.assertIsNone(form.selected)

        # Check that UP/DOWN change selection.
        self.process_keys(form, [Screen.KEY_UP, Screen.KEY_UP])
        form.save()
        self.assertEqual(form.data, {"file_list": "/A Directory"})
        self.assertEqual(form.highlighted, "/A Directory")
        self.assertIsNone(form.selected)

        # Check that enter key handles correctly on directories.
        self.process_keys(form, [Screen.ctrl("m")])
        self.assertEqual(form.highlighted, "/")
        self.assertIsNone(form.selected)
        form.update(1)
        self.assert_canvas_equals(
            canvas,
            "/A Directory          Size Last modified\n" +
            "|-+ ..                                  \n" +
            "|-+ A Directory         9K    %s\n" % self.epoch_date +
            "|-+ Lnk Directo...      9K    %s\n" % self.epoch_date +
            "|-- A File              9K    %s\n" % self.epoch_date +
            "|-- A Lnk -> A Tgt      9K    %s\n" % self.epoch_date +
            "|-- oööÖ.txt            9K    %s\n" % self.epoch_date +
            "                                        \n" +
            "                                        \n" +
            "                                        \n")

        # Check that enter key handles correctly on files.
        self.process_keys(form, [Screen.KEY_DOWN, Screen.KEY_DOWN, Screen.KEY_DOWN, Screen.ctrl("m")])
        self.assertEqual(form.highlighted, "/A Directory/A File")
        self.assertEqual(form.selected, "/A Directory/A File")

    @patch("os.path.exists")
    @patch("os.path.realpath")
    @patch("os.path.islink")
    @patch("os.path.isdir")
    @patch("os.lstat")
    @patch("os.stat")
    @patch("os.listdir")
    def test_file_filter(self, mock_list, mock_stat, mock_lstat, mock_dir, mock_link,
                         mock_real_path, mock_exists):
        """
        Check FileBrowser widget with a file_filter works as expected.
        """
        # First we need to mock out the file system calls to have a regressible test
        if sys.platform == "win32":
            self.skipTest("File names wrong for windows")

        mock_list.return_value = ["A Directory", "A File", "A Lnk", str(b"oo\xcc\x88o\xcc\x88O\xcc\x88.txt", 'utf-8'), "hello.bmp"]
        mock_result = MagicMock()
        mock_result.st_mtime = 0
        mock_result.st_size = 10000
        mock_stat.return_value = mock_result
        mock_lstat.return_value = mock_result
        mock_dir.side_effect = lambda x: x.endswith("Directory")
        mock_link.side_effect = lambda x: "Lnk" in x
        mock_real_path.return_value = "A Tgt"
        mock_exists.return_value = True

        # Now set up the Frame ready for testing
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], -1)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = _TestFrame4(canvas, file_filter=r".*\.bmp$")
        scene.add_effect(form)
        form.reset()

        # Check we have a default value for our list.
        form.save()
        self.assertIsNone(form.selected)
        self.assertIsNone(form.highlighted)
        self.assertEqual(form.data, {"file_list": None})

        # Check that the Frame is rendered correctly.
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "/                     Size Last modified\n" +
            "|-+ A Directory         9K    %s\n" % self.epoch_date +
            "|-- hello.bmp           9K    %s\n" % self.epoch_date +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n")

        # Check that enter key handles correctly on files.
        self.process_keys(form, [Screen.KEY_DOWN, Screen.KEY_DOWN, Screen.ctrl("m")])
        self.assertEqual(form.highlighted, "/hello.bmp")
        self.assertEqual(form.selected, "/hello.bmp")

    def test_date_picker(self):
        """
        Check DatePicker widget works as expected.
        """
        # Now set up the Frame ready for testing
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], duration=-1)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = _TestFrame5(canvas)
        scene.add_effect(form)
        scene.reset()

        # Check that the Frame is rendered correctly.
        for effect in scene.effects:
            effect.update(0)
        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "|Date: 02/Jan/2017                     |\n" +
            "|Time: 12:00:59                        O\n" +
            "|Tim2: 11:02                           |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")

        # Check that enter key brings up edit pop-up
        self.process_keys(scene, [Screen.ctrl("m")])
        self.assertFalse(form.changed)
        for effect in scene.effects:
            effect.update(1)
        self.assert_canvas_equals(
            canvas,
            "+-----|01     2016|--------------------+\n" +
            "|Date:|02/Jan/2017|                    |\n" +
            "|Time:|03 Feb 2018|                    O\n" +
            "|Tim2:+-----------+                    |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")

        # Check that you can't select an invalid date.
        self.process_keys(scene, ["31", "Feb", Screen.ctrl("m")], separator=Screen.KEY_TAB)
        self.assertFalse(form.changed)
        for effect in scene.effects:
            effect.update(2)
        self.assert_canvas_equals(
            canvas,
            "+-----|30 Jan 2016|--------------------+\n" +
            "|Date:|31/Feb/2017|                    |\n" +
            "|Time:|   Mar 2018|                    O\n" +
            "|Tim2:+-----------+                    |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")

        # Check that a valid date updates the value - wait one second to allow search to reset.
        sleep(1)
        self.process_keys(scene, ["15", "Jun", Screen.ctrl("m")], separator=Screen.KEY_TAB)
        self.assertTrue(form.changed)
        for effect in scene.effects:
            effect.update(2)
        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "|Date: 15/Jun/2017                     |\n" +
            "|Time: 12:00:59                        O\n" +
            "|Tim2: 11:02                           |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")
        self.assertEqual(form.date_widget.value, date(2017, 6, 15))

        # Check the mouse works too - pass mouse events to top-level effect
        self.process_mouse(scene.effects[-1], [(10, 1, MouseEvent.LEFT_CLICK)])
        for effect in scene.effects:
            effect.update(3)
        self.assert_canvas_equals(
            canvas,
            "+-----|14 May 2016|--------------------+\n" +
            "|Date:|15/Jun/2017|                    |\n" +
            "|Time:|16 Jul 2018|                    O\n" +
            "|Tim2:+-----------+                    |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")

        self.process_mouse(scene.effects[-1], [(11, 2, MouseEvent.LEFT_CLICK)])
        for effect in scene.effects:
            effect.update(4)
        self.assert_canvas_equals(
            canvas,
            "+-----|14 Jun 2016|--------------------+\n" +
            "|Date:|15/Jul/2017|                    |\n" +
            "|Time:|16 Aug 2018|                    O\n" +
            "|Tim2:+-----------+                    |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")

    def test_time_picker(self):
        """
        Check TimePicker widget works as expected.
        """
        # Now set up the Frame ready for testing
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], duration=-1)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = _TestFrame5(canvas)
        scene.add_effect(form)
        scene.reset()

        # Check that the Frame is rendered correctly.
        for effect in scene.effects:
            effect.update(0)
        self.assertFalse(form.changed)
        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "|Date: 02/Jan/2017                     |\n" +
            "|Time: 12:00:59                        O\n" +
            "|Tim2: 11:02                           |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")

        # Check that enter key brings up edit pop-up
        self.process_keys(scene, [Screen.KEY_DOWN, Screen.ctrl("m")])
        for effect in scene.effects:
            effect.update(1)
        self.assertFalse(form.changed)
        self.assert_canvas_equals(
            canvas,
            "+-----+--------+-----------------------+\n" +
            "|Date:|11    58|17                     |\n" +
            "|Time:|12:00:59|                       O\n" +
            "|Tim2:|13 01   |                       |\n" +
            "|     +--------+                       |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")

        # Check that no match on search changes nothing.
        self.process_keys(scene, ["99"])
        for effect in scene.effects:
            effect.update(1)
        self.assertFalse(form.changed)
        self.assert_canvas_equals(
            canvas,
            "+-----+--------+-----------------------+\n" +
            "|Date:|11    58|17                     |\n" +
            "|Time:|12:00:59|                       O\n" +
            "|Tim2:|13 01   |                       |\n" +
            "|     +--------+                       |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")

        # Check that we can change the time with cursors keys and mouse selection - and click out
        # to exit.
        self.process_mouse(scene, [(7, 1, MouseEvent.LEFT_CLICK)])
        self.process_keys(scene, [Screen.KEY_TAB, Screen.KEY_DOWN, Screen.KEY_TAB, Screen.KEY_UP])
        self.process_mouse(scene, [(10, 10, MouseEvent.LEFT_CLICK)])
        self.assertTrue(form.changed)
        for effect in scene.effects:
            effect.update(2)
        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "|Date: 02/Jan/2017                     |\n" +
            "|Time: 11:01:58                        O\n" +
            "|Tim2: 11:02                           |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")
        self.assertEqual(form.time_widget.value, time(11, 1, 58))

        # Check that small time pops correctly.
        self.process_keys(scene, [Screen.KEY_TAB, Screen.ctrl('m')])
        for effect in scene.effects:
            effect.update(3)
        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "|Date:+-----+/2017                     |\n" +
            "|Time:|10 01|58                        O\n" +
            "|Tim2:|11:02|                          |\n" +
            "|     |12 03|                          |\n" +
            "|     +-----+                          |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")

        # Check the mouse works too
        self.process_mouse(scene, [(30, 3, MouseEvent.LEFT_CLICK), (7, 2, MouseEvent.LEFT_CLICK)])
        for effect in scene.effects:
            effect.update(4)
        self.assert_canvas_equals(
            canvas,
            "+-----+--------+-----------------------+\n" +
            "|Date:|10 00 57|17                     |\n" +
            "|Time:|11:01:58|                       O\n" +
            "|Tim2:|12 02 59|                       |\n" +
            "|     +--------+                       |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")

        self.process_mouse(scene.effects[-1], [(7, 1, MouseEvent.LEFT_CLICK)])
        for effect in scene.effects:
            effect.update(5)
        self.assert_canvas_equals(
            canvas,
            "+-----+--------+-----------------------+\n" +
            "|Date:|09 00 57|17                     |\n" +
            "|Time:|10:01:58|                       O\n" +
            "|Tim2:|11 02 59|                       |\n" +
            "|     +--------+                       |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")

    def test_background(self):
        """
        Check Background widget works as expected.
        """
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], -1)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = Background(canvas, bg=7)
        scene.add_effect(form)
        form.reset()

        # Check that the widget is rendered correctly.
        form.update(0)
        for y in range(canvas.height):
            for x in range(canvas.width):
                char, _, _, bg = canvas.get_from(x, y)
                self.assertEqual(char, ord(" "))
                self.assertEqual(bg, 7)

        # Check properties
        self.assertEqual(form.stop_frame, 0)
        self.assertGreater(form.frame_update_count, 1000)

    def test_dropdown_list(self):
        """
        Check DropdownList widget works as expected.
        """
        def _click():
            self._clicked = True

        # Now set up the Frame ready for testing
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], duration=-1)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = Frame(canvas, canvas.height, canvas.width)
        layout = Layout([100], fill_frame=True)
        form.add_layout(layout)
        layout.add_widget(DropdownList([("Item 1", 1), ("Item 2", 3), ("Item 3", 5)], on_change=_click))
        form.fix()
        scene.add_effect(form)
        scene.reset()
        self._clicked = False

        # Check that the Frame is rendered correctly.
        for effect in scene.effects:
            effect.update(0)
        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "|[ Item 1                             ]|\n" +
            "|                                      O\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")

        # Check it opens as expected
        self.process_mouse(scene, [(7, 1, MouseEvent.LEFT_CLICK)])
        for effect in scene.effects:
            effect.update(1)
        self.assert_canvas_equals(
            canvas,
            "++------------------------------------++\n" +
            "|| Item 1                             ||\n" +
            "||------------------------------------|O\n" +
            "|| Item 1                             ||\n" +
            "|| Item 2                             ||\n" +
            "|| Item 3                             ||\n" +
            "|+------------------------------------+|\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")

        # Check ESC works as expected
        self.process_keys(scene, [Screen.KEY_DOWN, Screen.KEY_ESCAPE])
        for effect in scene.effects:
            effect.update(2)
        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "|[ Item 1                             ]|\n" +
            "|                                      O\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")
        self.assertFalse(self._clicked)

        # Check key selection works as expected
        self.process_keys(scene, [" ", Screen.KEY_DOWN])
        for effect in scene.effects:
            effect.update(3)
        self.assert_canvas_equals(
            canvas,
            "++------------------------------------++\n" +
            "|| Item 2                             ||\n" +
            "||------------------------------------|O\n" +
            "|| Item 1                             ||\n" +
            "|| Item 2                             ||\n" +
            "|| Item 3                             ||\n" +
            "|+------------------------------------+|\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")

        # Check Enter works as expected
        self.process_keys(scene, [Screen.ctrl("m")])
        for effect in scene.effects:
            effect.update(4)
        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "|[ Item 2                             ]|\n" +
            "|                                      O\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")
        self.assertTrue(self._clicked)

    def test_dropdown_list_options(self):
        """
        Check DropdownList widget extra features work.
        """
        # Now set up the Frame ready for testing
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], duration=-1)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = Frame(canvas, canvas.height, canvas.width)
        layout = Layout([100], fill_frame=True)
        form.add_layout(layout)
        layout.add_widget(Divider(draw_line=False, height=7))
        dd_list = DropdownList([(f"Item {i}", i) for i in range(10)])
        layout.add_widget(dd_list)
        form.fix()
        scene.add_effect(form)
        scene.reset()

        # Check that the Frame is rendered correctly.
        for effect in scene.effects:
            effect.update(0)
        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "|                                      |\n" +
            "|                                      O\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|[ Item 0                             ]|\n" +
            "+--------------------------------------+\n")

        # Check it opens as expected
        self.process_mouse(scene, [(7, 8, MouseEvent.LEFT_CLICK)])
        for effect in scene.effects:
            effect.update(1)
        self.assert_canvas_equals(
            canvas,
            "++------------------------------------++\n" +
            "|| Item 0                            O||\n" +
            "|| Item 1                            ||O\n" +
            "|| Item 2                            |||\n" +
            "|| Item 3                            |||\n" +
            "|| Item 4                            |||\n" +
            "|| Item 5                            |||\n" +
            "||------------------------------------||\n" +
            "|| Item 0                             ||\n" +
            "++------------------------------------++\n")

        # Check that options can be read and set.
        dd_list.options = [(["a", "b", "c", "d", "e", "f"], 0)]
        self.assertEqual(dd_list.options, [(["a", "b", "c", "d", "e", "f"], 0)])
        dd_list.options = []
        self.assertEqual(dd_list.options, [])

        # Check that the fit to option width option works
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], duration=-1)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = Frame(canvas, canvas.height, canvas.width)
        layout = Layout([100], fill_frame=True)
        form.add_layout(layout)
        layout.add_widget(Divider(draw_line=False, height=7))
        dd_list = DropdownList([(f"Item {i}", i) for i in range(10)], fit=True)
        layout.add_widget(dd_list)
        form.fix()
        scene.add_effect(form)
        scene.reset()

        # Check that the Frame is rendered correctly.
        for effect in scene.effects:
            effect.update(0)
        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "|                                      |\n" +
            "|                                      O\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|[ Item 0 ]                            |\n" +
            "+--------------------------------------+\n")

        # Check it opens as expected
        self.process_mouse(scene, [(7, 8, MouseEvent.LEFT_CLICK)])
        for effect in scene.effects:
            effect.update(1)
        self.assert_canvas_equals(
            canvas,
            "++--------+----------------------------+\n" +
            "|| Item 0O|                            |\n" +
            "|| Item 1||                            O\n" +
            "|| Item 2||                            |\n" +
            "|| Item 3||                            |\n" +
            "|| Item 4||                            |\n" +
            "|| Item 5||                            |\n" +
            "||--------|                            |\n" +
            "|| Item 0 |                            |\n" +
            "++--------+----------------------------+\n")

    def test_divider(self):
        """
        Check Divider widget sundry features work.
        """
        # Now set up the Frame ready for testing
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], duration=-1)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = Frame(canvas, canvas.height, canvas.width)
        layout = Layout([100], fill_frame=True)
        form.add_layout(layout)
        divider = Divider(draw_line=False, height=7)
        layout.add_widget(divider)
        form.fix()
        scene.add_effect(form)
        form.reset()

        # Check events are ignored
        event = object()
        self.assertEqual(event, divider.process_event(event))

        # Check value is None
        self.assertIsNone(divider.value)

    def test_find_widget(self):
        """
        Check find_widget works as expected.
        """
        # Set up the Frame ready for testing
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], duration=-1)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = _TestFrame5(canvas)
        scene.add_effect(form)
        scene.reset()

        # Can't find a non-existent widget
        self.assertIsNone(form.find_widget("ABLAH"))

        # Can find a defined widget
        self.assertEqual(form.find_widget("date"), form.date_widget)

    def test_password(self):
        """
        Check that we can do password input on Text widgets.
        """
        # Create a dummy screen.
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], -1)
        canvas = Canvas(screen, 2, 40, 0, 0)

        # Create the form we want to test.
        form = Frame(canvas, canvas.height, canvas.width, has_border=False, can_scroll=False)
        layout = Layout([100], fill_frame=True)
        form.add_layout(layout)
        text = Text("Password", hide_char="*")
        layout.add_widget(text)
        form.fix()
        scene.add_effect(form)
        form.reset()

        # Check that input still saves off values as expected
        self.process_keys(form, ["1234"])
        form.save()
        self.assertEqual(text.value, "1234")

        # Check that it is drawn with the obscuring character, though.
        form.update(0)
        self.assert_canvas_equals(
            canvas,
            "Password ****                           \n" +
            "                                        \n")

    def test_change_values(self):
        """
        Check changing Text values resets cursor position.
        """
        # Create a dummy screen.
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], -1)
        canvas = Canvas(screen, 10, 40, 0, 0)

        # Create the form we want to test.
        form = Frame(canvas, canvas.height, canvas.width, has_border=False, can_scroll=False)
        layout = Layout([100], fill_frame=True)
        form.add_layout(layout)
        text = Text()
        layout.add_widget(text)
        form.fix()
        scene.add_effect(form)
        form.reset()

        # Check that input is put at the end of the new text
        text.value = "A test"
        self.process_keys(form, ["A"])
        form.save()
        self.assertEqual(text.value, "A testA")

        # Check that growing longer still puts it at the end.
        text.value = "A longer test"
        self.process_keys(form, ["A"])
        form.save()
        self.assertEqual(text.value, "A longer testA")

    def test_popup_meu(self):
        """
        Check PopupMenu widget works as expected.
        """
        # Simple function to test which item is selected.
        def click(x):
            self.clicked = self.clicked or x

        # Now set up the Frame ready for testing
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], duration=-1)
        canvas = Canvas(screen, 10, 40, 0, 0)

        # Reset menu for test
        self.clicked = 0
        popup = PopupMenu(canvas,
                          [
                              ("First", lambda: click(1)),
                              ("Second", lambda: click(2)),
                              ("Third", lambda: click(4))
                          ],
                          0, 0)
        scene.add_effect(popup)
        scene.reset()

        # Check that the menu is rendered correctly.
        for effect in scene.effects:
            effect.update(0)
        self.assert_canvas_equals(
            canvas,
            "First                                   \n" +
            "Second                                  \n" +
            "Third                                   \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n")

        # Check it handles a selection as expected
        self.process_mouse(scene, [(0, 1, MouseEvent.LEFT_CLICK)])
        self.assertEqual(len(scene.effects), 0)
        self.assertEqual(self.clicked, 2)

        # Check choice of location at bottom right
        self.clicked = 0
        canvas.reset()
        popup = PopupMenu(canvas,
                          [
                              ("First", lambda: click(1)),
                              ("Second", lambda: click(2)),
                              ("Third", lambda: click(4))
                          ],
                          39, 9)
        scene.add_effect(popup)
        scene.reset()

        # Check that the menu is rendered correctly.
        for effect in scene.effects:
            effect.update(0)
        self.assert_canvas_equals(
            canvas,
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                        \n" +
            "                                  First \n" +
            "                                  Second\n" +
            "                                  Third \n")

        # Check it handles a selection as expected
        self.process_mouse(scene, [(39, 7, MouseEvent.LEFT_CLICK)])
        self.assertEqual(len(scene.effects), 0)
        self.assertEqual(self.clicked, 1)

        # Check clicking outside menu dismisses it - wrong X location.
        self.clicked = 0
        canvas.reset()
        popup = PopupMenu(canvas, [("A", lambda: click(1)), ("B", lambda: click(2))], 39, 9)
        scene.add_effect(popup)
        scene.reset()
        self.process_mouse(scene, [(10, 9, MouseEvent.LEFT_CLICK)])
        self.assertEqual(len(scene.effects), 0)
        self.assertEqual(self.clicked, 0)

        # Check clicking outside menu dismisses it - wrong Y location.
        self.clicked = 0
        canvas.reset()
        popup = PopupMenu(canvas, [("A", lambda: click(1)), ("B", lambda: click(2))], 39, 9)
        scene.add_effect(popup)
        scene.reset()
        self.process_mouse(scene, [(39, 1, MouseEvent.LEFT_CLICK)])
        self.assertEqual(len(scene.effects), 0)
        self.assertEqual(self.clicked, 0)

        # Check clicking outside menu dismisses it.
        self.clicked = 0
        canvas.reset()
        popup = PopupMenu(canvas, [("A", lambda: click(1)), ("B", lambda: click(2))], 39, 9)
        scene.add_effect(popup)
        scene.reset()
        self.process_keys(popup, [Screen.KEY_ESCAPE])
        self.assertEqual(len(scene.effects), 0)
        self.assertEqual(self.clicked, 0)

    def test_find_min_start(self):
        """
        Check _find_min_start works as expected.
        """
        # Normal operation will find last <limit> characters.
        self.assertEqual(_find_min_start("ABCDEF", 3), 3)

        # Allow extra space for cursor loses another
        self.assertEqual(_find_min_start("ABCDEF", 3, at_end=True), 4)

    def test_vertical_divider(self):
        """
        Check VerticalDivider widget works as expected.
        """
        # Now set up the Frame ready for testing
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], duration=-1)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = Frame(canvas, canvas.height, canvas.width)
        layout = Layout([5, 1, 26, 1, 5])
        form.add_layout(layout)
        layout.add_widget(Label("A"), 1)
        layout.add_widget(VerticalDivider(), 1)
        layout.add_widget(Label("B"), 1)
        layout.add_widget(TextBox(5), 2)
        divider = VerticalDivider()
        layout.add_widget(divider, 3)
        layout.add_widget(Label("END"), 4)
        form.fix()
        scene.add_effect(form)
        scene.reset()

        # Check that the frame is rendered correctly.
        for effect in scene.effects:
            effect.update(0)
        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "|     A                          |END  |\n" +
            "|     |                          |     O\n" +
            "|     |                          |     |\n" +
            "|     |                          |     |\n" +
            "|     B                          |     |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")

        # Check that a vertcial divider ignores unknown events.
        event = object()
        self.assertEqual(event, divider.process_event(event))

        # Check that it has no value.
        self.assertEqual(divider.value, None)

    def test_value_defaults(self):
        """
        Check Widgets can set default values from code.
        """
        # Now set up the Frame ready for testing
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], duration=-1)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = Frame(canvas, canvas.height, canvas.width)
        layout = Layout([100])
        form.add_layout(layout)

        textbox = TextBox(2, label="TB")
        textbox.value = ["Hello"]
        text = Text(label="B")
        text.value = "World"
        listbox = ListBox(2, [("A", 1), ("B", 2), ("C", 3), ("D", 4)], label="LB")
        listbox.value = 3

        layout.add_widget(textbox)
        layout.add_widget(text)
        layout.add_widget(listbox)
        form.fix()
        scene.add_effect(form)
        scene.reset()

        # Check that the frame is rendered correctly.
        for effect in scene.effects:
            effect.update(0)
        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "|TB Hello                              |\n" +
            "|                                      O\n" +
            "|B  World                              |\n" +
            "|LB C                                  |\n" +
            "|   D                                  |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")

    def test_frame_themes(self):
        """
        Check we can set a colour theme for a Frame.
        """
        # Now set up the Frame ready for testing
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = Frame(canvas, canvas.height, canvas.width)

        # Check colour changes work...
        self.assertEqual(
            form.palette["background"],
            (Screen.COLOUR_WHITE, Screen.A_NORMAL, Screen.COLOUR_BLUE))
        form.set_theme("monochrome")
        self.assertEqual(
            form.palette["background"],
            (Screen.COLOUR_WHITE, Screen.A_NORMAL, Screen.COLOUR_BLACK))

        # Check that a bad theme name has no effect.
        form.set_theme("blah - this doesn't exist")
        self.assertEqual(
            form.palette["background"],
            (Screen.COLOUR_WHITE, Screen.A_NORMAL, Screen.COLOUR_BLACK))

    def test_max_len(self):
        """
        Check that the max_length setting works as expected.
        """
        # Now set up the Frame ready for testing
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], duration=-1)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = Frame(canvas, canvas.height, canvas.width)
        layout = Layout([100])
        form.add_layout(layout)

        # Simple form with a limited length Text field.
        text = Text(label="Text", name="max_len_text", max_length=4)
        layout.add_widget(text)
        form.fix()
        scene.add_effect(form)
        scene.reset()

        # Check it stops accepting text after hitting limit.
        self.process_keys(form, "123456")
        form.save()
        self.assertEqual(form.data["max_len_text"], "1234")

    def test_clear_widgets(self):
        """
        Check that clear_widgets works as expected.
        """
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], duration=-1)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = Frame(canvas, canvas.height, canvas.width)
        layout = Layout([100], fill_frame=True)
        form.add_layout(layout)
        layout.add_widget(Text("Test"))
        form.fix()
        scene.add_effect(form)
        scene.reset()

        # Check that the frame is rendered correctly.
        for effect in scene.effects:
            effect.update(0)
        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "|Test                                  |\n" +
            "|                                      O\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")

        # Check removing widgets clears Frame and safely resets focus.
        self.process_mouse(form, [(0, 0, 0)])
        layout.clear_widgets()
        form.fix()
        self.process_mouse(form, [(0, 0, 0)])
        scene.reset()
        for effect in scene.effects:
            effect.update(1)
        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "|                                      |\n" +
            "|                                      O\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")

        # Check adding another widget now adds it back into the Frame.
        self.process_mouse(form, [(0, 0, 0)])
        layout.add_widget(Text("Another One"))
        form.fix()
        self.process_mouse(form, [(0, 0, 0)])
        scene.reset()
        for effect in scene.effects:
            effect.update(2)
        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "|Another One                           |\n" +
            "|                                      O\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")

    def test_inline_colours(self):
        """
        Check inline colours work as expected.
        """
        # Create a dummy screen.
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=True)
        scene = Scene([], -1)
        canvas = Canvas(screen, 10, 40, 0, 0)

        # Create the form we want to test.
        form = Frame(canvas, canvas.height, canvas.width, has_border=False, can_scroll=False)
        scene.add_effect(form)
        layout = Layout([100], fill_frame=True)
        form.add_layout(layout)
        text_box = TextBox(3, as_string=False, parser=AsciimaticsParser())
        layout.add_widget(text_box)
        listbox = ListBox(2, [("P", 1), ("${9,2}Q", 2), ("R", 3), ("S", 4)], parser=AsciimaticsParser())
        layout.add_widget(listbox)
        mc_list = MultiColumnListBox(
            Widget.FILL_FRAME,
            [3, "100%"],
            [(["1", "\x1B[32m2"], 1)],
            titles=["A", "\x1B[31mB"],
            parser=AnsiTerminalParser())
        layout.add_widget(mc_list)
        form.fix()
        form.reset()

        # Check that the Asciimatics colour parsing worked.
        text_box.value = ["A${1}B"]
        form.update(0)
        self.assertEqual(canvas.get_from(0, 0), (ord("A"), 7, 1, 6))
        self.assertEqual(canvas.get_from(1, 0), (ord("B"), 1, 0, 6))
        self.assertEqual(canvas.get_from(0, 3), (ord("P"), 3, 1, 4))
        self.assertEqual(canvas.get_from(0, 4), (ord("Q"), 9, 2, 4))

        # Check that using ColouredText preserves colour data
        text_box.value = [ColouredText("B${2}A", AsciimaticsParser())]
        form.update(0)
        self.assertEqual(canvas.get_from(0, 0), (ord("B"), 7, 1, 6))
        self.assertEqual(canvas.get_from(1, 0), (ord("A"), 2, 0, 6))

        # Check that keys work on parsed text.
        self.process_keys(form, [Screen.KEY_END, "\n1", Screen.KEY_UP, Screen.KEY_END, Screen.KEY_DELETE])
        form.update(0)
        self.assertEqual(canvas.get_from(0, 0), (ord("B"), 7, 1, 6))
        self.assertEqual(canvas.get_from(1, 0), (ord("A"), 2, 0, 6))
        self.assertEqual(canvas.get_from(2, 0), (ord("1"), 7, 3, 6))

        # Check that the Ansi terminal colour parsing worked.
        self.assertEqual(canvas.get_from(0, 5), (ord("A"), 7, 1, 4))
        self.assertEqual(canvas.get_from(3, 5), (ord("B"), 1, 1, 4))
        self.assertEqual(canvas.get_from(0, 6), (ord("1"), 3, 1, 4))
        self.assertEqual(canvas.get_from(3, 6), (ord("2"), 2, 1, 4))

    def test_list_box_options(self):
        """
        Check setting listbox options works as expected.
        """
        # Create a dummy screen.
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=True)
        canvas = Canvas(screen, 10, 40, 0, 0)

        # Create the form we want to test.
        form = Frame(canvas, canvas.height, canvas.width, has_border=False, can_scroll=False)
        layout = Layout([100], fill_frame=True)
        form.add_layout(layout)
        listbox = ListBox(2, [])
        options = [("P", 1), ("Q", 2), ("R", 3), ("S", 4)]
        listbox.options = options
        layout.add_widget(listbox)

        self.assertEqual(listbox.options, options)

    def test_list_box_color_options(self):
        """
        Check setting listbox options with inline colors works as expected.
        """
        # Create a dummy screen.
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=True)
        canvas = Canvas(screen, 10, 40, 0, 0)

        # Create the form we want to test.
        form = Frame(canvas, canvas.height, canvas.width, has_border=False, can_scroll=False)
        layout = Layout([100], fill_frame=True)
        form.add_layout(layout)
        listbox = ListBox(2, [], parser=AsciimaticsParser())
        options = [("P", 1), ("${9,2}Q", 2), ("R", 3), ("${10,3}S", 4)]
        listbox.options = options
        layout.add_widget(listbox)

        color_options = listbox.options
        self.assertIsInstance(color_options[0][0], ColouredText)
        self.assertEqual(color_options[0][0].raw_text, options[0][0])

        self.assertIsInstance(color_options[1][0], ColouredText)
        self.assertEqual(color_options[1][0].raw_text, options[1][0])

        self.assertIsInstance(color_options[2][0], ColouredText)
        self.assertEqual(color_options[2][0].raw_text, options[2][0])

        self.assertIsInstance(color_options[3][0], ColouredText)
        self.assertEqual(color_options[3][0].raw_text, options[3][0])

    def test_readonly(self):
        """
        Check readonly widgets work as expected.
        """
        # Create a dummy screen.
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], -1)
        canvas = Canvas(screen, 10, 40, 0, 0)

        # Create the form we want to test.
        form = Frame(canvas, canvas.height, canvas.width, has_border=False, can_scroll=False)
        layout = Layout([100], fill_frame=True)
        form.add_layout(layout)
        text_box = TextBox(3, as_string=True, readonly=True)
        layout.add_widget(text_box)
        text = Text(readonly=True)
        layout.add_widget(text)
        form.fix()
        scene.add_effect(form)
        form.reset()

        # Check that entering text has no effect.
        text_box.value = "Untouchable"
        text.value = "You can't touch this..."
        self.process_keys(form, ["1234\r\n", Screen.KEY_BACK, Screen.KEY_DELETE])
        form.save()
        self.assertEqual(text_box.value, "Untouchable")
        self.process_keys(form, [Screen.KEY_TAB, "123", Screen.KEY_BACK, Screen.KEY_DELETE])
        form.save()
        self.assertEqual(text.value, "You can't touch this...")

        # Check property works.
        self.assertTrue(text.readonly)
        self.assertTrue(text_box.readonly)
        text.readonly = False
        text_box.readonly = False
        self.assertFalse(text.readonly)
        self.assertFalse(text_box.readonly)

        # Check that entering text now has an effect.
        self.process_keys(form, [Screen.KEY_TAB, "1234\r\n", Screen.KEY_BACK, Screen.KEY_DELETE])
        form.save()
        self.assertEqual(text_box.value, "Untouchable1234\n")
        self.process_keys(form, [Screen.KEY_TAB, "123", Screen.KEY_BACK, Screen.KEY_DELETE])
        form.save()
        self.assertEqual(text.value, "You can't touch this...12")

    def test_layout_disable(self):
        """
        Check en/disable on layouts work as expected.
        """
        # Create a dummy screen.
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], -1)
        canvas = Canvas(screen, 10, 40, 0, 0)

        # Create the form we want to test.
        form = Frame(canvas, canvas.height, canvas.width, has_border=False, can_scroll=False)
        layout = Layout([1, 1], fill_frame=True)
        form.add_layout(layout)
        for col in range(2):
            layout.add_widget(Label("A field"), col)
            layout.add_widget(Text(), col)
            layout.add_widget(Button("OK", None), col)
        form.fix()
        scene.add_effect(form)
        form.reset()

        def _assert_disabled(cols):
            for i, widgets in enumerate(layout._columns):
                for widget in widgets:
                    self.assertEqual(widget.disabled, i in cols)

        # Check focus moves away from disabled widgets.
        self.assertEqual(layout._live_col, 0)
        self.assertEqual(layout._live_widget, 1)
        layout.disable([0])
        self.assertEqual(layout._live_col, 1)
        self.assertEqual(layout._live_widget, 1)
        layout.enable([1])
        self.assertEqual(layout._live_col, 1)
        self.assertEqual(layout._live_widget, 1)

        # Check that disabling only disables specified columns.
        for col in range(2):
            layout.disable([col])
            _assert_disabled([col])
            layout.enable([col])
            _assert_disabled([])

        # Check that no parameter disables everything
        layout.disable()
        _assert_disabled(range(2))
        layout.enable()
        _assert_disabled([])

    def test_widget_labels(self):
        """
        Check various widgets and labels interact correctly.
        """
        # Create a dummy screen.
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], duration=-1)
        canvas = Canvas(screen, 10, 40, 0, 0)

        # Create the form we want to test.
        self.clicked = False

        def click():
            self.clicked = True

        form = Frame(canvas, canvas.height, canvas.width, has_border=True)
        layout = Layout([80, 20], fill_frame=True)
        form.add_layout(layout)
        layout.add_widget(Button("one", None, label="Buttons"))
        layout.add_widget(Button("two", click))
        listbox = ListBox(1, [("12345678901234567890", 1)], label="List")
        layout.add_widget(listbox)
        text = Text(label="Text")
        layout.add_widget(text)
        form.fix()
        scene.add_effect(form)
        form.reset()

        # Check widgets are displayed correctly
        for effect in scene.effects:
            effect.update(0)
        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "|Buttons < one >                       |\n" +
            "|        < two >                       O\n" +
            "|List    12345678901234567890          |\n" +
            "|Text                                  |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")

        # check specfic bug with Listbox overflow on selection
        self.process_mouse(form, [(20, 3, MouseEvent.LEFT_CLICK)])
        for effect in scene.effects:
            effect.update(1)
        self.assertEqual(canvas.get_from(28, 3), (ord("0"), 7, 1, 6))
        self.assertEqual(canvas.get_from(29, 3), (ord(" "), 7, 1, 6))
        self.assertEqual(canvas.get_from(30, 3), (ord(" "), 7, 1, 6))
        self.assertEqual(canvas.get_from(31, 3), (ord(" "), 7, 2, 4))

        # Check that clicking just outside the button has no effect.
        self.process_mouse(form, [(8, 2, MouseEvent.LEFT_CLICK)])
        self.process_mouse(form, [(16, 2, MouseEvent.LEFT_CLICK)])
        self.assertFalse(self.clicked)

        # Check that clicking just inside the button works.
        self.process_mouse(form, [(9, 2, MouseEvent.LEFT_CLICK)])
        self.assertTrue(self.clicked)
        self.clicked = False
        self.process_mouse(form, [(15, 2, MouseEvent.LEFT_CLICK)])
        self.assertTrue(self.clicked)

        # Check that pressing space on a button works
        self.clicked = False
        self.process_keys(form, [" "])
        self.assertTrue(self.clicked)

    def test_button_name1(self):
        """
        Check Button name can be set in the constructor.
        """
        def _on_click():
            pass

        btn = Button("Run", _on_click, name="btn_run")
        self.assertEqual(btn.name, "btn_run")

    def test_button_text(self):
        """
        Check Button text can be set as an attribute.
        """
        def _on_click():
            pass

        btn = Button("Before", _on_click)
        self.assertEqual(btn.text, "Before")
        btn.text = "After"
        self.assertEqual(btn.text, "After")

    def test_label_name1(self):
        """
        Check Label name can be set in the constructor.
        """
        lbl = Label("Winner", name="my_lbl")
        self.assertEqual(lbl.name, "my_lbl")

    def test_label_name2(self):
        """
        Check Label can be found by its containing Frame
        """
        # Now set up the Frame ready for testing
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], duration=-1)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = _TestFrame6(canvas)
        scene.add_effect(form)
        scene.reset()

        # Check that the Label is found by its name
        # by confirming the label's value
        l = form.find_widget("tf6_lbl")
        self.assertEqual(l.text, "TstFrm6Lbl")

    def test_textbox_hide_cursor(self):
        """
        Check hide cursor works as expected.
        """
        # Now set up the Frame ready for testing
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], duration=-1)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = Frame(canvas, canvas.height, canvas.width)
        layout = Layout([100])
        form.add_layout(layout)
        textbox = TextBox(4, label="TB")
        layout.add_widget(textbox)
        form.fix()
        scene.add_effect(form)
        scene.reset()

        # Check we default to showing the cursor.
        self.assertFalse(textbox.hide_cursor)

        # Check that the cursor is hidden when set off.
        for effect in scene.effects:
            effect.update(0)
        self.assertEqual(canvas.get_from(4, 1),
                         (32, Screen.COLOUR_WHITE, Screen.A_REVERSE, Screen.COLOUR_CYAN))
        textbox.hide_cursor = True
        for effect in scene.effects:
            effect.update(1)
        self.assertEqual(canvas.get_from(4, 1),
                         (32, Screen.COLOUR_WHITE, Screen.A_BOLD, Screen.COLOUR_CYAN))

    def test_textbox_autoscroll(self):
        """
        Check autoscroll works as expected.
        """
        # Now set up the Frame ready for testing
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], duration=-1)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = Frame(canvas, canvas.height, canvas.width)
        layout = Layout([100])
        form.add_layout(layout)
        textbox = TextBox(4, label="TB")
        textbox.value = ["Hello", "World", "Full", "Box"]
        layout.add_widget(textbox)
        form.fix()
        scene.add_effect(form)
        scene.reset()

        # Check that the frame is rendered correctly.
        for effect in scene.effects:
            effect.update(0)
        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "|TB Hello                              |\n" +
            "|   World                              O\n" +
            "|   Full                               |\n" +
            "|   Box                                |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")

        # Check that switching off autoscroll will not move line
        textbox.auto_scroll = False
        textbox.value = ["Hello", "World", "Full", "Box", "New"]
        for effect in scene.effects:
            effect.update(0)
        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "|TB Hello                              |\n" +
            "|   World                              O\n" +
            "|   Full                               |\n" +
            "|   Box                                |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")

        # Check that turning it back on scrolls to bottom.
        textbox.auto_scroll = True
        textbox.value = ["Hello", "World", "Full", "Box", "New"]
        for effect in scene.effects:
            effect.update(0)
        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "|TB World                              |\n" +
            "|   Full                               O\n" +
            "|   Box                                |\n" +
            "|   New                                |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")

    def test_layout_gutters(self):
        """
        Validate that the gutters parameter works for Layouts
        """
        # Now set up the Frame ready for testing
        screen = MagicMock(spec=Screen, colours=8, unicode_aware=False)
        scene = Scene([], duration=-1)
        canvas = Canvas(screen, 10, 40, 0, 0)
        form = Frame(canvas, canvas.height, canvas.width)
        layout = Layout([18, 4, 18], gutter=3)
        form.add_layout(layout)

        #       0
        text = "ABCDEFGHIJKLMNOP"

        box1 = TextBox(1)
        box1.value = [text]
        layout.add_widget(box1, 0)

        box2 = TextBox(1)
        box2.value = [text]
        layout.add_widget(box2, 1)

        box3 = TextBox(1)
        box3.value = [text]
        layout.add_widget(box3, 2)

        form.fix()
        scene.add_effect(form)
        scene.reset()

        # Check that the frame is rendered correctly.
        for effect in scene.effects:
            effect.update(0)

        self.assert_canvas_equals(
            canvas,
            "+--------------------------------------+\n" +
            "|DEFGHIJKLMNOP    OP    DEFGHIJKLMNOP  |\n" +
            "|                                      O\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "|                                      |\n" +
            "+--------------------------------------+\n")


if __name__ == '__main__':
    unittest.main()
