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"""Dialog box for log selection functions.""" 15 16from __future__ import annotations 17import functools 18from typing import TYPE_CHECKING 19 20from prompt_toolkit.filters import Condition 21from prompt_toolkit.layout import ( 22 ConditionalContainer, 23 FormattedTextControl, 24 Window, 25 WindowAlign, 26) 27 28from pw_console.widgets import ( 29 create_border, 30 mouse_handlers, 31 to_checkbox_with_keybind_indicator, 32 to_keybind_indicator, 33) 34 35if TYPE_CHECKING: 36 from pw_console.log_pane import LogPane 37 38 39class LogPaneSelectionDialog(ConditionalContainer): 40 """Dialog box for showing log selection functions. 41 42 Displays number of lines selected, buttons for copying to the clipboar or 43 saving to a file, and buttons to select all or cancel (clear) the 44 selection.""" 45 46 # Height of the dialog box contens in lines of text. 47 DIALOG_HEIGHT = 3 48 49 def __init__(self, log_pane: LogPane): 50 self.log_pane = log_pane 51 self.log_view = log_pane.log_view 52 53 self._markdown_flag: bool = False 54 self._table_flag: bool = True 55 56 selection_bar_control = FormattedTextControl(self.get_fragments) 57 selection_bar_window = Window( 58 content=selection_bar_control, 59 height=1, 60 align=WindowAlign.LEFT, 61 dont_extend_width=False, 62 style='class:selection-dialog', 63 ) 64 65 super().__init__( 66 create_border( 67 selection_bar_window, 68 (LogPaneSelectionDialog.DIALOG_HEIGHT - 1), 69 border_style='class:selection-dialog-border', 70 base_style='class:selection-dialog-default-fg', 71 top=False, 72 right=False, 73 ), 74 filter=Condition(lambda: self.log_view.visual_select_mode), 75 ) 76 77 def focus_log_pane(self): 78 self.log_pane.application.focus_on_container(self.log_pane) 79 80 def _toggle_markdown_flag(self) -> None: 81 self._markdown_flag = not self._markdown_flag 82 83 def _toggle_table_flag(self) -> None: 84 self._table_flag = not self._table_flag 85 86 def _select_all(self) -> None: 87 self.log_view.visual_select_all() 88 89 def _select_none(self) -> None: 90 self.log_view.clear_visual_selection() 91 92 def _copy_selection(self) -> None: 93 if self.log_view.export_logs( 94 to_clipboard=True, 95 use_table_formatting=self._table_flag, 96 selected_lines_only=True, 97 add_markdown_fence=self._markdown_flag, 98 ): 99 self._select_none() 100 101 def _saveas_file(self) -> None: 102 self.log_pane.start_saveas( 103 table_format=self._table_flag, selected_lines_only=True 104 ) 105 106 def get_fragments(self): 107 """Return formatted text tuples for both rows of the selection 108 dialog.""" 109 110 focus = functools.partial(mouse_handlers.on_click, self.focus_log_pane) 111 112 one_space = ('', ' ', focus) 113 two_spaces = ('', ' ', focus) 114 select_all = functools.partial( 115 mouse_handlers.on_click, self._select_all 116 ) 117 select_none = functools.partial( 118 mouse_handlers.on_click, self._select_none 119 ) 120 121 copy_selection = functools.partial( 122 mouse_handlers.on_click, self._copy_selection 123 ) 124 saveas_file = functools.partial( 125 mouse_handlers.on_click, self._saveas_file 126 ) 127 toggle_markdown = functools.partial( 128 mouse_handlers.on_click, 129 self._toggle_markdown_flag, 130 ) 131 toggle_table = functools.partial( 132 mouse_handlers.on_click, self._toggle_table_flag 133 ) 134 135 button_style = 'class:toolbar-button-inactive' 136 137 # First row of text 138 fragments = [ 139 ( 140 'class:selection-dialog-title', 141 ' {} Selected '.format( 142 self.log_view.visual_selected_log_count() 143 ), 144 focus, 145 ), 146 one_space, 147 ('class:selection-dialog-default-fg', 'Format: ', focus), 148 ] 149 150 # Table and Markdown options 151 fragments.extend( 152 to_checkbox_with_keybind_indicator( 153 self._table_flag, 154 key='', 155 description='Table', 156 mouse_handler=toggle_table, 157 base_style='class:selection-dialog-default-bg', 158 ) 159 ) 160 161 fragments.extend( 162 to_checkbox_with_keybind_indicator( 163 self._markdown_flag, 164 key='', 165 description='Markdown', 166 mouse_handler=toggle_markdown, 167 base_style='class:selection-dialog-default-bg', 168 ) 169 ) 170 171 # Line break 172 fragments.append(('', '\n')) 173 174 # Second row of text 175 fragments.append(one_space) 176 177 fragments.extend( 178 to_keybind_indicator( 179 key='Ctrl-c', 180 description='Cancel', 181 mouse_handler=select_none, 182 base_style=button_style, 183 ) 184 ) 185 fragments.append(two_spaces) 186 187 fragments.extend( 188 to_keybind_indicator( 189 key='Ctrl-a', 190 description='Select All', 191 mouse_handler=select_all, 192 base_style=button_style, 193 ) 194 ) 195 fragments.append(two_spaces) 196 197 fragments.append(one_space) 198 fragments.extend( 199 to_keybind_indicator( 200 key='', 201 description='Save as File', 202 mouse_handler=saveas_file, 203 base_style=button_style, 204 ) 205 ) 206 fragments.append(two_spaces) 207 208 fragments.extend( 209 to_keybind_indicator( 210 key='', 211 description='Copy', 212 mouse_handler=copy_selection, 213 base_style=button_style, 214 ) 215 ) 216 fragments.append(one_space) 217 218 return fragments 219