示例#1
0
    def _make_app(self):
        self._jobs_container = HSplit(
            # FIXME: Layout does not accept an empty list of children. We add an
            #        empty Window that doesn't display anything that gets
            #        removed automatically when we rebuild
            #        self._jobs_container.children.
            #        https://github.com/prompt-toolkit/python-prompt-toolkit/issues/1257
            children=[Window()],
            style='class:default',
        )
        self._layout = Layout(self._jobs_container)

        kb = KeyBindings()

        @kb.add('escape')
        @kb.add('c-g')
        @kb.add('c-q')
        @kb.add('c-c')
        def _(event, self=self):
            if self._app.is_running:
                self._exit()

        app = Application(
            layout=self._layout,
            key_bindings=kb,
            style=style.style,
            full_screen=False,
            erase_when_done=False,
            mouse_support=False,
            on_invalidate=self._update_jobs_container,
        )
        # Make escape key work
        app.timeoutlen = 0.1
        app.ttimeoutlen = 0.1
        return app
示例#2
0
    def get_user_comment(self):
        """Modifies the display to add an area to enter a comment for a command

        Creates a BufferControl in a Frame and replaces the toolbar with the Frame

        #bug: the new toolbar is unable to get focus right away; it requires the user to click 
                in the area
        """
        self._savedLayout = self.layout
        self.disabled_bindings = True
        commentControl = BufferControl(
            Buffer(accept_handler=self._set_user_comment), focus_on_click=True)
        user_in_area = Frame(
            Window(
                commentControl,
                height=Dimension(max=1, weight=10000),
                dont_extend_height=True,
            ),
            title="Enter Comment (alt-Enter to submit)",
        )

        self.toolbar = user_in_area
        self.main_view = HSplit([self.body, self.toolbar], padding_char="-")
        self.layout = Layout(self.main_view, focused_element=user_in_area.body)
        self.layout.focus(user_in_area.body)
        self.invalidate()
示例#3
0
def choose_version(assets):
    '''choose a version from assets list'''
    versions = list(map(lambda item: item['version'], assets))
    # print(versions)
    values = list(map(lambda item: (item, item), versions))
    rdo = NewRadioList(values)

    def do_exit(event):
        # get_app().exit()
        event.app.exit(result=rdo.current_value)

    def do_up_down(event):
        print(event)
        pass

    bindings = KeyBindings()
    bindings.add('enter')(do_exit)
    app_bindings = merge_key_bindings([load_key_bindings(), bindings])

    selected = Application(layout=Layout(rdo), key_bindings=app_bindings).run()
    if selected in versions:
        print('your choice is:', end=' ')
        # refer: https://github.com/jonathanslenders/python-prompt-toolkit/blob/master/examples/print-text/ansi.py
        print_formatted_text(ANSI('\x1b[91m{0}'.format(selected)))
        return selected
    else:
        print('canceled')
示例#4
0
    def __init__(self):
        self.lira = LiraApp()
        self.lira.setup()

        self.content = ContentArea(self)
        self.status = StatusBar(self)

        self.menu = SidebarMenu(self)
        self.menu.reset(BooksList(self))

        self.container = HSplit(
            [
                VSplit(
                    [
                        self.menu,
                        self.content,
                    ],
                    padding=Dimension.exact(1),
                    padding_char="│",
                    padding_style=theme["separator"],
                ),
                self.status,
            ],
            padding=Dimension.exact(1),
            padding_char="─",
            padding_style=theme["separator"],
        )
        self.app = Application(
            layout=Layout(self.container),
            key_bindings=self.get_key_bindings(),
            mouse_support=True,
            full_screen=True,
            style=style,
            after_render=self._ready,
        )
示例#5
0
def make_app(sec, width, height, frame=True, gcalender=None, color=False):
    """make auto refresh application class"""
    kb = KeyBindings()

    @kb.add('c-c')
    def _(event):
        event.app.exit()

    def _vsplit(padding, m):
        return VSplit([Window(width=padding, always_hide_cursor=True), m, Window(width=padding, always_hide_cursor=True)])

    if height == 14:
        clock = BigClock(sec=sec, color=color, gcalender=gcalender)
    elif height == 7:
        clock = MinimumClock(sec=sec, color=color, gcalender=gcalender)
    else:
        clock = SmallClock(sec=sec, color=color, gcalender=gcalender)
    padding = to_dimension(D(preferred=0))
    body = Window(content=FormattedTextControl(text=clock.get_clock), width=width, height=height, always_hide_cursor=True)
    if frame:
        body = Frame(body)
    if gcalender is None:
        under_text = Window(height=padding, always_hide_cursor=True)
    else:
        ct = gcalender.get_calender_text_formatted if color else gcalender.get_calender_text
        under_text = Window(content=FormattedTextControl(text=ct), width=gcalender.get_max_length(), height=padding, always_hide_cursor=True)

    # make container app
    root_container = HSplit([Window(height=padding, always_hide_cursor=True), _vsplit(padding, body), _vsplit(padding, under_text)], key_bindings=None)
    layout = Layout(container=root_container)
    return Application(layout=layout, key_bindings=kb, full_screen=True, refresh_interval=.1)
示例#6
0
def show_dialog(
    questions,
    title,
    intro=None,
    summary=False,
    next_text='Next',
    previous_text='Previous',
    cancel_text='Cancel',
    finish_text='Finish',
):
    handlers = [get_instance(q) for q in questions]
    app = Application(
        layout=Layout(
            WizardDialog(
                title,
                handlers,
                intro=intro,
                summary=summary,
                next_text=next_text,
                previous_text=previous_text,
                cancel_text=cancel_text,
                finish_text=finish_text,
            ), ),
        mouse_support=True,
        style=for_dialog(),
        full_screen=True,
    )

    if not app.run():
        return
    answers = {}
    for handler in handlers:
        answers.update(handler.get_answer())
    return answers
示例#7
0
def print_container(
    container: "AnyContainer",
    file: Optional[TextIO] = None,
    style: Optional[BaseStyle] = None,
    include_default_pygments_style: bool = True,
) -> None:
    """
    Print any layout to the output in a non-interactive way.

    Example usage::

        from prompt_toolkit.widgets import Frame, TextArea
        print_container(
            Frame(TextArea(text='Hello world!')))
    """
    if file:
        output = create_output(stdout=file)
    else:
        output = get_app_session().output

    app: Application[None] = Application(
        layout=Layout(container=container),
        output=output,
        # `DummyInput` will cause the application to terminate immediately.
        input=DummyInput(),
        style=_create_merged_style(
            style,
            include_default_pygments_style=include_default_pygments_style),
    )
    try:
        app.run(in_thread=True)
    except EOFError:
        pass
示例#8
0
def build_application():
    layout = Layout(RootController())

    def ensure_focus(_):
        """Ensures that at least one element on the screen is focused"""
        app = get_app()

        # prompt_toolkit's implementation of focusing is retarded
        # so this is the only way I found of 'making it work'

        # when switching screens or something prompt_toolkit doesn't recognize
        # the new focusable elements added to the screen. this will ensure
        # that at least one container/ui is marked as focusable so
        # the screen can be interacted with

        global global__default_target_focus  # preferred element to be focused

        if global__default_target_focus:
            app.layout.focus(global__default_target_focus)
            global__default_target_focus = None  # reset for next render

            app.invalidate()  # trigger re-render
        elif len(app.layout.get_visible_focusable_windows()) == 0:
            focus_first_element()

            app.invalidate()  # trigger re-render

    return Application(layout=layout,
                       key_bindings=merge_key_bindings(
                           [tab_bindings, exit_bindings]),
                       full_screen=True,
                       mouse_support=True,
                       after_render=ensure_focus,
                       style=root_style)
示例#9
0
def main():
    layout = Layout(left_margin=LeftMarginWithLineNumbers(),
                    before_input=DefaultPrompt(text='Before input >> '),
                    after_input=Prompt(' << after input'),
                    top_toolbars=[
                        TextToolbar('This is a top toolbar',
                                    token=Token.TopToolbar1),
                        TextToolbar('This is another top toolbar',
                                    token=Token.TopToolbar2),
                    ],
                    bottom_toolbars=[
                        ArgToolbar(),
                        SearchToolbar(),
                        CompletionsToolbar(),
                        TextToolbar('This is a bottom toolbar',
                                    token=Token.BottomToolbar1),
                        TextToolbar('This is another bottom toolbar',
                                    token=Token.BottomToolbar2),
                    ],
                    show_tildes=True,
                    menus=[CompletionsMenu()])

    cli = CommandLineInterface(layout=layout,
                               style=TestStyle,
                               line=Line(is_multiline=True,
                                         completer=TestCompleter()))

    code_obj = cli.read_input(initial_value=lipsum)
    print('You said: ' + code_obj.text)
示例#10
0
def print_container(
    container: "AnyContainer",
    file: Optional[TextIO] = None,
    style: Optional[BaseStyle] = None,
    include_default_pygments_style: bool = True,
) -> None:
    """
    Print any layout to the output in a non-interactive way.

    Example usage::

        from prompt_toolkit.widgets import Frame, TextArea
        print_container(
            Frame(TextArea(text='Hello world!')))
    """
    if file:
        output = create_output(stdout=file)
    else:
        output = get_app_session().output

    def exit_immediately() -> None:
        # Use `call_from_executor` to exit "soon", so that we still render one
        # initial time, before exiting the application.
        get_event_loop().call_soon(lambda: app.exit())

    app: Application[None] = Application(
        layout=Layout(container=container),
        output=output,
        input=DummyInput(),
        style=_create_merged_style(
            style,
            include_default_pygments_style=include_default_pygments_style),
    )
    app.run(pre_run=exit_immediately, in_thread=True)
示例#11
0
def main():
    style = Style([
        ('terminal not-focused', '#888888'),
        ('title', 'bg:#000044 #ffffff underline'),
    ])

    done_count = [0]  # nonlocal.

    def done():
        done_count[0] += 1
        if done_count[0] == 2:
            application.exit()
        else:
            switch_focus()

    term1 = Terminal(width=D(preferred=80),
                     height=D(preferred=40),
                     style='class:terminal',
                     done_callback=done)

    term2 = Terminal(width=D(preferred=80),
                     height=D(preferred=40),
                     style='class:terminal',
                     done_callback=done)

    kb = KeyBindings()

    @kb.add('c-w')
    def _(event):
        switch_focus()

    def switch_focus():
        " Change focus when Control-W is pressed."
        if application.layout.has_focus(term1):
            application.layout.focus(term2)
        else:
            application.layout.focus(term1)

    application = Application(
        layout=Layout(container=HSplit([
            Window(
                height=1,
                style='class:title',
                content=FormattedTextControl(
                    HTML(
                        ' Press <u fg="#ff8888"><b>Control-W</b></u> to <b>switch focus</b>.'
                    ))),
            VSplit([
                term1,
                Window(style='bg:#aaaaff', width=1),
                term2,
            ]),
        ]),
                      focused_element=term1),
        style=style,
        key_bindings=kb,
        full_screen=True,
        mouse_support=True,
    )
    application.run()
示例#12
0
    def __init__(self):
        self.no_action_dialog = NoActionDialog(
            ok_btn_cb=self.kb_run_action_or_dismiss)
        self.key_bindings = self._init_keybindings()

        tasks = [
            Task('task1, summary',
                 "Longer description",
                 "lightblue",
                 action=OpenOrStart('https://github.com/')),
            Task('task2, summary', "Longer description", "orange"),
            Task('task3, summary', "Longer description", "lightgreen"),
            Task('task4, summary', "Longer description", "darkred"),
            Task('task5, summary', "Longer description", "gray"),
            Task('task6, summary', "Longer description", "darkmagenta"),
        ]
        self.tasklist = TaskList(
            tasks, default_action=lambda t: self.no_action_dialog.show())

        layout = Layout(
            FloatContainer(content=self.tasklist,
                           floats=[Float(self.no_action_dialog)]))

        style = Style([])

        super().__init__(layout,
                         style,
                         key_bindings=self.key_bindings,
                         full_screen=True)
示例#13
0
        def ok_handler():

            # Simplistic check to see if user forgot to enter something for url
            url = len(self.url.text) > len('https://')

            if not all([self.name.text, url, self.method.text]):
                return ErrorDialog(event, title='Input Error',
                                   text='Name, Url, and Method are required.')

            result = server_to_db()

            if result.get('success'):

                root_container.floats.pop()

                # Rather than inserting a new button into, e.g.,
                # hsplit.children, we recreate the layout since we have to
                # pay attention to sort order here
                event.app.layout = Layout(root_container.create())

                # Find the button in the redrawn layout; then focus on it
                buttons = ButtonManager.update_buttons(event.app)
                for button in buttons:
                    if self.name.text == button.content.text()[1][1].strip():
                        event.app.layout.focus(button)
                        break

                select_item(event)

            else:
                # Add/update server returned an error
                ErrorDialog(event, title='Add/edit server error',
                            text=str(result.get('errors')))
def main():
    # Create a big layout of many text areas, then wrap them in a `ScrollablePane`.
    root_container = Frame(
        ScrollablePane(
            HSplit([
                Frame(TextArea(text=f"label-{i}"), width=Dimension())
                for i in range(20)
            ]))
        # ScrollablePane(HSplit([TextArea(text=f"label-{i}") for i in range(20)]))
    )

    layout = Layout(container=root_container)

    # Key bindings.
    kb = KeyBindings()

    @kb.add("c-c")
    def exit(event) -> None:
        get_app().exit()

    kb.add("tab")(focus_next)
    kb.add("s-tab")(focus_previous)

    # Create and run application.
    application = Application(layout=layout, key_bindings=kb, full_screen=True)
    application.run()
示例#15
0
def loop(cmd, history_file):
    from prompt_toolkit import CommandLineInterface, AbortAction
    from prompt_toolkit import Exit
    from prompt_toolkit.layout import Layout
    from prompt_toolkit.line import Line
    from prompt_toolkit.renderer import Output

    cli_line = Line(completer=SQLCompleter(cmd.connection, cmd.lines),
                    history=TruncatedFileHistory(
                        history_file, max_length=MAX_HISTORY_LENGTH))
    layout = Layout(
        before_input=CrashPrompt(cmd.lines),
        menus=[],
        lexer=SqlLexer,
        bottom_toolbars=[],
        show_tildes=False,
    )
    key_binding_factories = _detect_key_bindings()
    cli = CommandLineInterface(style=MonokaiStyle,
                               layout=layout,
                               line=cli_line,
                               key_binding_factories=key_binding_factories)
    output = Output(cli.renderer.stdout)
    global get_num_columns

    def get_num_columns():
        return output.get_size().columns

    try:
        while True:
            doc = cli.read_input(on_exit=AbortAction.RAISE_EXCEPTION)
            cmd.process(doc.text)
    except Exit:  # Quit on Ctrl-D keypress
        cmd.logger.warn(u'Bye!')
        return
示例#16
0
async def show_cli_widget(widget):
    from prompt_toolkit import Application
    from prompt_toolkit.layout import Layout
    app = Application(full_screen=False,
                      layout=Layout(widget),
                      mouse_support=True)
    return await app.run_async()
示例#17
0
文件: base.py 项目: Arrendi/rice
    def __enter__(self):
        # Create UI Application.
        title_toolbar = ConditionalContainer(
            Window(FormattedTextControl(lambda: self.title),
                   height=1,
                   style='class:progressbar,title'),
            filter=Condition(lambda: self.title is not None))

        bottom_toolbar = ConditionalContainer(
            Window(FormattedTextControl(lambda: self.bottom_toolbar,
                                        style='class:bottom-toolbar.text'),
                   style='class:bottom-toolbar',
                   height=1),
            filter=~is_done & renderer_height_is_known
            & Condition(lambda: self.bottom_toolbar is not None))

        def width_for_formatter(formatter):
            return formatter.get_width(progress_bar=self)

        progress_controls = [
            Window(content=_ProgressControl(self, f),
                   width=width_for_formatter(f)) for f in self.formatters
        ]

        self.app = Application(
            min_redraw_interval=.05,
            layout=Layout(
                HSplit([
                    title_toolbar,
                    VSplit(progress_controls,
                           height=lambda: D(preferred=len(self.counters),
                                            max=len(self.counters))),
                    Window(),
                    bottom_toolbar,
                ])),
            style=self.style,
            key_bindings=self.key_bindings,
            output=self.output,
            input=self.input)

        # Run application in different thread.
        def run():
            with _auto_refresh_context(self.app, .3):
                try:
                    self.app.run()
                except Exception as e:
                    traceback.print_exc()
                    print(e)

        self._thread = threading.Thread(target=run)
        self._thread.start()

        # Attach WINCH signal handler in main thread.
        # (Interrupt that we receive during resize events.)
        self._has_sigwinch = hasattr(signal, 'SIGWINCH') and in_main_thread()
        if self._has_sigwinch:
            self._previous_winch_handler = self._loop.add_signal_handler(
                signal.SIGWINCH, self.app.invalidate)

        return self
示例#18
0
    def __init__(self, playback, save_location=None, *args, **kwargs):

        self.mainViewCondition = partial(self.mainView, self)
        self.mainViewCondition = Condition(self.mainViewCondition)
        self.disabled_bindings = False
        bindings = KeyBindings()
        self.init_bindings(bindings)

        super().__init__(full_screen=True,
                         key_bindings=bindings,
                         mouse_support=True,
                         *args,
                         **kwargs)

        self.displayingHelpScreen = (
            False)  # used to toggle between help screen on normal

        if save_location:
            self.save_location = save_location
        else:
            self.save_location = SAVE_LOCATION
        self.playback = playback
        self._savedLayout = Layout(Window())
        self.command_cache = deque([], maxlen=5)

        ##########################################
        ### Setting up views
        ##########################################

        self.old_command_window = FormattedTextControl(text="Output goes here",
                                                       focusable=True)
        self.new_command_window = FormattedTextControl(text="Output goes here",
                                                       focusable=True)

        self.body = Frame(
            HSplit([
                Frame(Window(self.old_command_window)),
                Frame(Window(self.new_command_window)),
            ]))
        self.toolbar = Window(
            FormattedTextControl(text=self.toolbar_text),
            height=Dimension(max=1, weight=10000),
            dont_extend_height=True,
        )

        self.main_view = HSplit([self.body, self.toolbar], padding_char="-")
        self.layout = Layout(self.main_view)
示例#19
0
def make_canvas(_noise, args):

    global noise
    global debug_file
    global root_container

    noise = _noise

    ui = [
        VSplit(
            [
                Frame(
                    title="Delete noise until only signal remains",
                    body=Window(content=BufferControl(buffer=buffer)),
                ),
                Frame(
                    title="Signals",
                    body=Window(width=15, content=subcanvasses_display),
                ),
                Frame(title="Gaps", body=Window(width=10, content=gaps_display)),
            ]
        ),
        VSplit(
            [
                Window(content=FormattedTextControl(text=legend_left)),
                Window(content=FormattedTextControl(text=legend_center)),
                Window(
                    content=FormattedTextControl(text=legend_right),
                    align=WindowAlign.RIGHT,
                ),
            ]
        ),
    ]

    if args.debug:
        debug_file = open(config.runtime.debug_log, "w")
        debug(f"cog started {datetime.now()}")
        ui.append(HorizontalLine())
        ui.append(Window(content=debug_display))

    root_container = HSplit(ui)

    subcanvasses.append(Data(Kind.signal, noise))

    # start with the input noise as the signal
    buffer.text = noise

    kb = KeyBindings()

    @kb.add("c-c")
    def done(event):
        event.app.exit()

    # https://github.com/prompt-toolkit/python-prompt-toolkit/issues/502#issuecomment-466591259
    sys.stdin = sys.stderr
    Application(
        key_bindings=kb, layout=Layout(root_container), editing_mode=EditingMode.VI
    ).run()
示例#20
0
def build_application(container: Container, keys: KeyBindings) -> Application:
    return Application(
        layout=Layout(container),
        key_bindings=keys,
        full_screen=True,
        style=Style([
            ("header", "bg:#005fff fg:black")
        ])
    )
示例#21
0
def main():
    def done():
        application.exit()

    application = Application(
        layout=Layout(container=Terminal(done_callback=done)),
        full_screen=True,
    )
    application.run()
示例#22
0
def set_dummy_app():
    """
    Return a context manager that makes sure that this dummy application is
    active. This is important, because we need an `Application` with
    `is_done=False` flag, otherwise no keys will be processed.
    """
    app = Application(layout=Layout(Window()),
                      output=DummyOutput(),
                      input=create_pipe_input())
    return set_app(app)
示例#23
0
    def removingLayoutFactory(self):  #TODO

        root_container = Box(
            Frame(
                TextArea(
                    text="DELETE layout placeholder",
                    width=40,
                    height=10,
                )), )

        return Layout(container=root_container)
示例#24
0
    def creationLayoutFactory(self):

        root_container = Box(
            Frame(
                TextArea(
                    text="CREATION layout placeholder",
                    width=40,
                    height=10,
                )), )

        return Layout(container=root_container)
示例#25
0
    def editingLayoutFactory(self):  #TODO

        root_container = Box(
            Frame(
                TextArea(
                    text="EDITING layout placeholder",
                    width=40,
                    height=10,
                )), )

        return Layout(container=root_container)
示例#26
0
    def create(self):
        self.display_layout = display_container.create()
        completions = Float(xcursor=True,
                            ycursor=True,
                            content=CompletionsMenu(max_height=16,
                                                    scroll_offset=1))
        self.float_container = FloatContainer(content=self.display_layout,
                                              floats=[completions])

        return Layout(HSplit([self.float_container]),
                      focused_element=self.display_layout)
示例#27
0
    def __init__(self):
        self.style_name = 'trac'
        self.frame = None
        self.argsnlocals = ArgsnLocalsWindow(app=self)
        self.console = ConsoleWindow(app=self, callback=self._gdb_callback)
        self.source = SourceWindow(self)
        self.breakpoints = BreakpointsWindow(self, show=False)
        self.callstack = CallstackWindow(self, show=False)
        self.disassembly = DisassemblyWindow(self, show=False)
        self.registers = RegistersWindow(self, show=False)
        self.threads = ThreadsWindow(self, show=False)
        self.inferiors = ''

        self.col1 = HSplit([
            self.source.get_ui(),
            self.disassembly.get_ui(),
            self.console.get_ui()
        ])

        self.col2 = HSplit([
            self.argsnlocals.get_ui(),
            self.registers.get_ui(),
            self.callstack.get_ui(),
            self.threads.get_ui(),
            self.breakpoints.get_ui()
        ])

        self.container = VSplit([self.col1, self.col2])
        self.layout = Layout(container=self.container,
                             focused_element=self.console.get_ui())

        self.style = style_from_pygments_cls(get_style_by_name(
            self.style_name))

        kb = self._get_key_bindings()

        self.app = PromptApplication(layout=self.layout,
                                     style=self.style,
                                     full_screen=True,
                                     mouse_support=True,
                                     key_bindings=kb)
示例#28
0
def main():
    cli = CommandLineInterface(
        style=AnimalStyle,
        layout=Layout(before_input=DefaultPrompt('Give some animals: '),
                      menus=[CompletionsMenu()]),
        line=Line(completer=AnimalCompleter()),
        create_async_autocompleters=True,
    )

    print('Press tab to complete')
    code_obj = cli.read_input()
    print('You said: ' + code_obj.text)
示例#29
0
 def _build_layout(self) -> Layout:
     self._reset()
     layout = Layout(
         HSplit([
             Window(
                 BufferControl(self._buffer,
                               lexer=SimpleLexer("class:answer")),
                 dont_extend_height=True,
                 get_line_prefix=self._get_prompt,
             )
         ]))
     return layout
示例#30
0
def prompt(
    title: str, options: Sequence[Tuple[RadioListType,
                                        AnyFormattedText]]) -> RadioListType:
    control = RadioList(options_to_html(options))

    application: Application[None] = Application(
        layout=Layout(HSplit([Label(HTML(title)), control])),
        mouse_support=False,
        full_screen=False,
    )

    return cast(RadioListType, application.run())
def test_layout_class():
    c1 = BufferControl()
    c2 = BufferControl()
    c3 = BufferControl()
    win1 = Window(content=c1)
    win2 = Window(content=c2)
    win3 = Window(content=c3)

    layout = Layout(container=VSplit([
        HSplit([
            win1,
            win2
        ]),
        win3
    ]))

    # Listing of windows/controls.
    assert list(layout.find_all_windows()) == [win1, win2, win3]
    assert list(layout.find_all_controls()) == [c1, c2, c3]

    # Focusing something.
    layout.focus(c1)
    assert layout.has_focus(c1)
    assert layout.has_focus(win1)
    assert layout.current_control == c1
    assert layout.previous_control == c1

    layout.focus(c2)
    assert layout.has_focus(c2)
    assert layout.has_focus(win2)
    assert layout.current_control == c2
    assert layout.previous_control == c1

    layout.focus(win3)
    assert layout.has_focus(c3)
    assert layout.has_focus(win3)
    assert layout.current_control == c3
    assert layout.previous_control == c2

    # Pop focus. This should focus the previous control again.
    layout.focus_last()
    assert layout.has_focus(c2)
    assert layout.has_focus(win2)
    assert layout.current_control == c2
    assert layout.previous_control == c1