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