• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2022 The Pigweed Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4# use this file except in compliance with the License. You may obtain a copy of
5# the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations under
13# the License.
14"""LogPane Info Toolbar classes."""
15
16from __future__ import annotations
17import functools
18import logging
19import sys
20from typing import Optional, Callable, TYPE_CHECKING
21
22from prompt_toolkit.data_structures import Point
23from prompt_toolkit.key_binding import KeyBindings, KeyPressEvent
24from prompt_toolkit.filters import Condition
25from prompt_toolkit.layout import (
26    ConditionalContainer,
27    FormattedTextControl,
28    HSplit,
29    Window,
30    WindowAlign,
31)
32
33import pw_console.widgets.border
34import pw_console.widgets.checkbox
35import pw_console.widgets.mouse_handlers
36
37if TYPE_CHECKING:
38    from pw_console.console_app import ConsoleApp
39
40_LOG = logging.getLogger(__package__)
41
42
43class QuitDialog(ConditionalContainer):
44    """Confirmation quit dialog box."""
45
46    DIALOG_HEIGHT = 2
47
48    def __init__(self,
49                 application: ConsoleApp,
50                 on_quit: Optional[Callable] = None):
51        self.application = application
52        self.show_dialog = False
53        # Tracks the last focused container, to enable restoring focus after
54        # closing the dialog.
55        self.last_focused_pane = None
56
57        self.on_quit_function = (on_quit if on_quit else
58                                 self._default_on_quit_function)
59
60        # Quit keybindings are active when this dialog is in focus
61        key_bindings = KeyBindings()
62        register = self.application.prefs.register_keybinding
63
64        @register('quit-dialog.yes', key_bindings)
65        def _quit(_event: KeyPressEvent) -> None:
66            """Close save as bar."""
67            self.quit_action()
68
69        @register('quit-dialog.no', key_bindings)
70        def _cancel(_event: KeyPressEvent) -> None:
71            """Close save as bar."""
72            self.close_dialog()
73
74        self.exit_message = 'Quit? y/n '
75
76        action_bar_control = FormattedTextControl(
77            self.get_action_fragments,
78            show_cursor=True,
79            focusable=True,
80            key_bindings=key_bindings,
81            # Cursor will appear after the exit_message
82            get_cursor_position=lambda: Point(len(self.exit_message), 0),
83        )
84
85        action_bar_window = Window(content=action_bar_control,
86                                   height=QuitDialog.DIALOG_HEIGHT,
87                                   align=WindowAlign.LEFT,
88                                   dont_extend_width=False)
89
90        super().__init__(
91            pw_console.widgets.border.create_border(
92                HSplit(
93                    [action_bar_window],
94                    height=QuitDialog.DIALOG_HEIGHT,
95                    style='class:quit-dialog',
96                ),
97                QuitDialog.DIALOG_HEIGHT,
98                border_style='class:quit-dialog-border',
99                left_margin_columns=1,
100            ),
101            filter=Condition(lambda: self.show_dialog),
102        )
103
104    def focus_self(self):
105        self.application.layout.focus(self)
106
107    def close_dialog(self):
108        """Close this dialog box."""
109        self.show_dialog = False
110        # Restore original focus if possible.
111        if self.last_focused_pane:
112            self.application.layout.focus(self.last_focused_pane)
113        else:
114            # Fallback to focusing on the main menu.
115            self.application.focus_main_menu()
116
117    def open_dialog(self):
118        self.show_dialog = True
119        self.last_focused_pane = self.application.focused_window()
120        self.focus_self()
121        self.application.redraw_ui()
122
123    def _default_on_quit_function(self):
124        if hasattr(self.application, 'application'):
125            self.application.application.exit()
126        else:
127            sys.exit()
128
129    def quit_action(self):
130        self.on_quit_function()
131
132    def get_action_fragments(self):
133        """Return FormattedText with action buttons."""
134
135        # Mouse handlers
136        focus = functools.partial(pw_console.widgets.mouse_handlers.on_click,
137                                  self.focus_self)
138        cancel = functools.partial(pw_console.widgets.mouse_handlers.on_click,
139                                   self.close_dialog)
140        quit_action = functools.partial(
141            pw_console.widgets.mouse_handlers.on_click, self.quit_action)
142
143        # Separator should have the focus mouse handler so clicking on any
144        # whitespace focuses the input field.
145        separator_text = ('', '  ', focus)
146
147        # Default button style
148        button_style = 'class:toolbar-button-inactive'
149
150        fragments = [('', self.exit_message), separator_text]
151        fragments.append(('', '\n'))
152
153        # Cancel button
154        fragments.extend(
155            pw_console.widgets.checkbox.to_keybind_indicator(
156                key='n / Ctrl-c',
157                description='Cancel',
158                mouse_handler=cancel,
159                base_style=button_style,
160            ))
161
162        # Two space separator
163        fragments.append(separator_text)
164
165        # Save button
166        fragments.extend(
167            pw_console.widgets.checkbox.to_keybind_indicator(
168                key='y / Ctrl-d',
169                description='Quit',
170                mouse_handler=quit_action,
171                base_style=button_style,
172            ))
173
174        # One space separator
175        fragments.append(('', ' ', focus))
176
177        return fragments
178