xref: /aosp_15_r20/external/pigweed/pw_console/py/pw_console/log_pane_selection_dialog.py (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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