# Copyright (C) 2025 James Brotosky, Brandon Wickline # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . import logging from textual.containers import Horizontal, Vertical from textual.message import Message from textual.widget import Widget from textual.widgets import Button, Footer, Header, Static logger = logging.getLogger(__name__) class ResultsDisplay(Widget): """Widget for displaying operation results in a two-column layout.""" CSS = """ ResultsDisplay { height: 100%; } #results_screen { height: 100%; } #results_title { text-align: center; margin: 1 0; text-style: bold; } #results_layout { height: 1fr; margin: 1 0; } #left_column, #right_column { width: 1fr; height: 100%; border: solid green; padding: 1; } #right_column { border: solid red; } #success_label, #failure_label { text-style: bold; margin-bottom: 1; } #success_results, #failure_results { height: 1fr; overflow-y: auto; background: $surface; border: round $primary; padding: 1; } .copy_button { margin-top: 1; width: 100%; } """ class CopySuccess(Message): """Posted when success results are copied.""" pass class CopyFailure(Message): """Posted when failure results are copied.""" pass class GoBack(Message): """Posted when back button is pressed.""" pass def __init__( self, operation: str, successful_results: str, unsuccessful_results: str ) -> None: super().__init__() self.operation = operation self.successful_results = successful_results self.unsuccessful_results = unsuccessful_results def compose(self): with Vertical(id="results_screen"): yield Header(show_clock=True, icon="⚙️") # Title title = Static(f"{self.operation} - Results", id="results_title") yield title # Two-column layout with Horizontal(id="results_layout"): # Left Column - Success with Vertical(id="left_column"): yield Static("✅ Successful", id="success_label") yield Static(self.successful_results, id="success_results") yield Button( "Copy Success List", id="copy_success", classes="copy_button", ) # Right Column - Failure with Vertical(id="right_column"): yield Static("❌ Failed", id="failure_label") yield Static(self.unsuccessful_results, id="failure_results") yield Button( "Copy Failure List", id="copy_failure", classes="copy_button", ) yield Footer() def on_button_pressed(self, event: Button.Pressed) -> None: btn_id = event.button.id if btn_id == "copy_success": success_widget = self.query_one("#success_results", Static) try: import pyperclip pyperclip.copy(str(success_widget.renderable)) self.app.notify( "Success list copied to clipboard!", severity="information", timeout=2, ) self.post_message(self.CopySuccess()) except ImportError: self.app.notify( "❌ pyperclip not installed. Run: pip install pyperclip", severity="warning", ) except Exception as e: self.app.notify(f"¢ Failed to copy: {str(e)}", severity="error") event.stop() elif btn_id == "copy_failure": failure_widget = self.query_one("#failure_results", Static) try: import pyperclip pyperclip.copy(str(failure_widget.renderable)) self.app.notify( "Failure list copied to clipboard!", severity="information", timeout=2, ) self.post_message(self.CopyFailure()) except ImportError: self.app.notify( "❌ pyperclip not installed. Run: pip install pyperclip", severity="warning", ) except Exception as e: self.app.notify(f"❌ Failed to copy: {str(e)}", severity="error") event.stop()