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 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 33from pw_console.widgets import ( 34 create_border, 35 mouse_handlers, 36 to_keybind_indicator, 37) 38 39if TYPE_CHECKING: 40 from pw_console.console_app import ConsoleApp 41 42_LOG = logging.getLogger(__package__) 43 44 45class QuitDialog(ConditionalContainer): 46 """Confirmation quit dialog box.""" 47 48 DIALOG_HEIGHT = 2 49 50 def __init__( 51 self, application: ConsoleApp, on_quit: Callable | None = None 52 ): 53 self.application = application 54 self.show_dialog = False 55 # Tracks the last focused container, to enable restoring focus after 56 # closing the dialog. 57 self.last_focused_pane = None 58 59 self.on_quit_function = ( 60 on_quit if on_quit else self._default_on_quit_function 61 ) 62 63 # Quit keybindings are active when this dialog is in focus 64 key_bindings = KeyBindings() 65 register = self.application.prefs.register_keybinding 66 67 @register('quit-dialog.yes', key_bindings) 68 def _quit(_event: KeyPressEvent) -> None: 69 """Close save as bar.""" 70 self.quit_action() 71 72 @register('quit-dialog.no', key_bindings) 73 def _cancel(_event: KeyPressEvent) -> None: 74 """Close save as bar.""" 75 self.close_dialog() 76 77 self.exit_message = 'Quit? y/n ' 78 79 action_bar_control = FormattedTextControl( 80 self.get_action_fragments, 81 show_cursor=True, 82 focusable=True, 83 key_bindings=key_bindings, 84 # Cursor will appear after the exit_message 85 get_cursor_position=lambda: Point(len(self.exit_message), 0), 86 ) 87 88 action_bar_window = Window( 89 content=action_bar_control, 90 height=QuitDialog.DIALOG_HEIGHT, 91 align=WindowAlign.LEFT, 92 dont_extend_width=False, 93 ) 94 95 super().__init__( 96 create_border( 97 HSplit( 98 [action_bar_window], 99 height=QuitDialog.DIALOG_HEIGHT, 100 style='class:quit-dialog', 101 ), 102 QuitDialog.DIALOG_HEIGHT, 103 border_style='class:quit-dialog-border', 104 left_margin_columns=1, 105 ), 106 filter=Condition(lambda: self.show_dialog), 107 ) 108 109 def focus_self(self): 110 self.application.layout.focus(self) 111 112 def close_dialog(self): 113 """Close this dialog box.""" 114 self.show_dialog = False 115 # Restore original focus if possible. 116 if self.last_focused_pane: 117 self.application.layout.focus(self.last_focused_pane) 118 else: 119 # Fallback to focusing on the main menu. 120 self.application.focus_main_menu() 121 122 def open_dialog(self): 123 self.show_dialog = True 124 self.last_focused_pane = self.application.focused_window() 125 self.focus_self() 126 self.application.redraw_ui() 127 128 def _default_on_quit_function(self): 129 if hasattr(self.application, 'application'): 130 self.application.application.exit() 131 else: 132 sys.exit() 133 134 def quit_action(self): 135 self.on_quit_function() 136 137 def get_action_fragments(self): 138 """Return FormattedText with action buttons.""" 139 140 # Mouse handlers 141 focus = functools.partial(mouse_handlers.on_click, self.focus_self) 142 cancel = functools.partial(mouse_handlers.on_click, self.close_dialog) 143 quit_action = functools.partial( 144 mouse_handlers.on_click, self.quit_action 145 ) 146 147 # Separator should have the focus mouse handler so clicking on any 148 # whitespace focuses the input field. 149 separator_text = ('', ' ', focus) 150 151 # Default button style 152 button_style = 'class:toolbar-button-inactive' 153 154 fragments = [('', self.exit_message), separator_text] 155 fragments.append(('', '\n')) 156 157 # Cancel button 158 fragments.extend( 159 to_keybind_indicator( 160 key='n / Ctrl-c', 161 description='Cancel', 162 mouse_handler=cancel, 163 base_style=button_style, 164 ) 165 ) 166 167 # Two space separator 168 fragments.append(separator_text) 169 170 # Save button 171 fragments.extend( 172 to_keybind_indicator( 173 key='y / Ctrl-d', 174 description='Quit', 175 mouse_handler=quit_action, 176 base_style=button_style, 177 ) 178 ) 179 180 # One space separator 181 fragments.append(('', ' ', focus)) 182 183 return fragments 184