Download gui/main_window.py from meccatronis/android_data_recovery: direct link, hf CLI and curl.
- Browser
- Download file 17.1 kB
-
https://huggingface.co/meccatronis/android_data_recovery/resolve/main/gui/main_window.py
- Command line
-
hf download hf://meccatronis/android_data_recovery/gui/main_window.py
-
curl -L -o main_window.py https://huggingface.co/meccatronis/android_data_recovery/resolve/main/gui/main_window.py
17.1 kB
| """ | |
| Main Window Module | |
| ================== | |
| Main application window for Android Data Recovery. | |
| """ | |
| import os | |
| import logging | |
| from PyQt6.QtWidgets import ( # type: ignore | |
| QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QTabWidget, | |
| QStatusBar, QMenuBar, QMenu, QMessageBox, QFileDialog, | |
| QSplitter, QFrame, QLabel | |
| ) | |
| from PyQt6.QtCore import Qt, pyqtSignal, QSettings # type: ignore | |
| from PyQt6.QtGui import QAction, QIcon, QFont # type: ignore | |
| from .styles import DARK_THEME, LIGHT_THEME, get_icon | |
| from .device_panel import DevicePanel | |
| from .scan_panel import ScanPanel | |
| from .recovery_panel import RecoveryPanel | |
| from .preview_panel import PreviewPanel | |
| logger = logging.getLogger(__name__) | |
| class MainWindow(QMainWindow): | |
| """Main application window.""" | |
| def __init__(self, adb_manager=None, parent=None): | |
| super().__init__(parent) | |
| self.adb_manager = adb_manager | |
| self.current_theme = "dark" | |
| self.settings = QSettings("AndroidDataRecovery", "MainWindow") | |
| # Initialize UI | |
| self._setup_ui() | |
| self._setup_menu_bar() | |
| self._setup_status_bar() | |
| self._apply_theme() | |
| self._restore_settings() | |
| logger.info("Main window initialized") | |
| def _setup_ui(self): | |
| """Setup the user interface.""" | |
| self.setWindowTitle("Android Data Recovery") | |
| self.setMinimumSize(1200, 800) | |
| # Central widget | |
| central_widget = QWidget() | |
| self.setCentralWidget(central_widget) | |
| main_layout = QVBoxLayout(central_widget) | |
| main_layout.setContentsMargins(0, 0, 0, 0) | |
| main_layout.setSpacing(0) | |
| # Header | |
| header = self._create_header() | |
| main_layout.addWidget(header) | |
| # Main content area with splitter | |
| splitter = QSplitter(Qt.Orientation.Horizontal) | |
| # Left panel - Device connection | |
| self.device_panel = DevicePanel(self.adb_manager) | |
| self.device_panel.setMaximumWidth(400) | |
| self.device_panel.setMinimumWidth(300) | |
| splitter.addWidget(self.device_panel) | |
| # Right panel - Tab widget for different functions | |
| self.tab_widget = QTabWidget() | |
| self.tab_widget.setTabPosition(QTabWidget.TabPosition.North) | |
| # Create panels | |
| self.scan_panel = ScanPanel(self.adb_manager) | |
| self.recovery_panel = RecoveryPanel(self.adb_manager) | |
| self.preview_panel = PreviewPanel() | |
| # Add panels to tab widget | |
| self.tab_widget.addTab(self.scan_panel, f"{get_icon('scan')} Scan") | |
| self.tab_widget.addTab(self.recovery_panel, f"{get_icon('recover')} Recuperação") | |
| self.tab_widget.addTab(self.preview_panel, f"{get_icon('file')} Visualização") | |
| splitter.addWidget(self.tab_widget) | |
| # Set splitter proportions | |
| splitter.setStretchFactor(0, 0) | |
| splitter.setStretchFactor(1, 1) | |
| splitter.setSizes([350, 850]) | |
| main_layout.addWidget(splitter) | |
| # Connect device panel signals | |
| self.device_panel.device_connected.connect(self._on_device_connected) | |
| self.device_panel.device_disconnected.connect(self._on_device_disconnected) | |
| # Connect scan panel signals | |
| self.scan_panel.files_selected.connect(self._on_files_selected) | |
| # Connect recovery panel signals | |
| self.recovery_panel.recovery_finished.connect(self._on_recovery_finished) | |
| def _create_header(self) -> QFrame: | |
| """Create the application header.""" | |
| header = QFrame() | |
| header.setObjectName("header") | |
| header.setStyleSheet(""" | |
| QFrame#header { | |
| background-color: #2d2d2d; | |
| border-bottom: 2px solid #424242; | |
| padding: 15px; | |
| } | |
| """) | |
| layout = QHBoxLayout(header) | |
| layout.setContentsMargins(20, 10, 20, 10) | |
| # App icon and title | |
| icon_label = QLabel(get_icon('android')) | |
| icon_label.setFont(QFont('Segoe UI Emoji', 32)) | |
| layout.addWidget(icon_label) | |
| title_layout = QVBoxLayout() | |
| title_layout.setSpacing(5) | |
| title_label = QLabel("Android Data Recovery") | |
| title_label.setObjectName("appTitle") | |
| title_label.setStyleSheet(""" | |
| QLabel#appTitle { | |
| font-size: 24px; | |
| font-weight: bold; | |
| color: #4fc3f7; | |
| } | |
| """) | |
| title_layout.addWidget(title_label) | |
| subtitle_label = QLabel("Recuperação de dados para dispositivos Android") | |
| subtitle_label.setObjectName("appSubtitle") | |
| subtitle_label.setStyleSheet(""" | |
| QLabel#appSubtitle { | |
| font-size: 12px; | |
| color: #b0b0b0; | |
| } | |
| """) | |
| title_layout.addWidget(subtitle_label) | |
| layout.addLayout(title_layout) | |
| layout.addStretch() | |
| # Version info | |
| version_label = QLabel("v1.0.0") | |
| version_label.setStyleSheet("color: #757575; font-size: 11px;") | |
| layout.addWidget(version_label) | |
| return header | |
| def _setup_menu_bar(self): | |
| """Setup the menu bar.""" | |
| menubar = self.menuBar() | |
| # File menu | |
| file_menu = menubar.addMenu("&Arquivo") | |
| # New scan action | |
| new_scan_action = QAction(f"{get_icon('scan')} Novo Scan", self) | |
| new_scan_action.setShortcut("Ctrl+N") | |
| new_scan_action.triggered.connect(self._new_scan) | |
| file_menu.addAction(new_scan_action) | |
| file_menu.addSeparator() | |
| # Open recovered data | |
| open_action = QAction(f"{get_icon('folder')} Abrir Dados Recuperados", self) | |
| open_action.setShortcut("Ctrl+O") | |
| open_action.triggered.connect(self._open_recovered_data) | |
| file_menu.addAction(open_action) | |
| file_menu.addSeparator() | |
| # Export action | |
| export_action = QAction(f"{get_icon('export')} Exportar Relatório", self) | |
| export_action.setShortcut("Ctrl+E") | |
| export_action.triggered.connect(self._export_report) | |
| file_menu.addAction(export_action) | |
| file_menu.addSeparator() | |
| # Exit action | |
| exit_action = QAction(f"{get_icon('exit')} Sair", self) | |
| exit_action.setShortcut("Ctrl+Q") | |
| exit_action.triggered.connect(self.close) | |
| file_menu.addAction(exit_action) | |
| # View menu | |
| view_menu = menubar.addMenu("&Visualizar") | |
| # Theme submenu | |
| theme_menu = view_menu.addMenu("Tema") | |
| dark_theme_action = QAction("Escuro", self) | |
| dark_theme_action.setCheckable(True) | |
| dark_theme_action.setChecked(True) | |
| dark_theme_action.triggered.connect(lambda: self._set_theme("dark")) | |
| theme_menu.addAction(dark_theme_action) | |
| light_theme_action = QAction("Claro", self) | |
| light_theme_action.setCheckable(True) | |
| light_theme_action.triggered.connect(lambda: self._set_theme("light")) | |
| theme_menu.addAction(light_theme_action) | |
| view_menu.addSeparator() | |
| # Refresh action | |
| refresh_action = QAction(f"{get_icon('refresh')} Atualizar", self) | |
| refresh_action.setShortcut("F5") | |
| refresh_action.triggered.connect(self._refresh) | |
| view_menu.addAction(refresh_action) | |
| # Tools menu | |
| tools_menu = menubar.addMenu("&Ferramentas") | |
| # ADB tools | |
| adb_menu = tools_menu.addMenu("ADB") | |
| start_server_action = QAction("Iniciar Servidor ADB", self) | |
| start_server_action.triggered.connect(self._start_adb_server) | |
| adb_menu.addAction(start_server_action) | |
| stop_server_action = QAction("Parar Servidor ADB", self) | |
| stop_server_action.triggered.connect(self._stop_adb_server) | |
| adb_menu.addAction(stop_server_action) | |
| restart_server_action = QAction("Reiniciar Servidor ADB", self) | |
| restart_server_action.triggered.connect(self._restart_adb_server) | |
| adb_menu.addAction(restart_server_action) | |
| tools_menu.addSeparator() | |
| # Settings action | |
| settings_action = QAction(f"{get_icon('settings')} Configurações", self) | |
| settings_action.setShortcut("Ctrl+,") | |
| settings_action.triggered.connect(self._show_settings) | |
| tools_menu.addAction(settings_action) | |
| # Help menu | |
| help_menu = menubar.addMenu("&Ajuda") | |
| # Documentation | |
| docs_action = QAction(f"{get_icon('help')} Documentação", self) | |
| docs_action.setShortcut("F1") | |
| docs_action.triggered.connect(self._show_documentation) | |
| help_menu.addAction(docs_action) | |
| help_menu.addSeparator() | |
| # About | |
| about_action = QAction(f"{get_icon('info')} Sobre", self) | |
| about_action.triggered.connect(self._show_about) | |
| help_menu.addAction(about_action) | |
| def _setup_status_bar(self): | |
| """Setup the status bar.""" | |
| self.status_bar = QStatusBar() | |
| self.setStatusBar(self.status_bar) | |
| # Status message | |
| self.status_label = QLabel("Pronto") | |
| self.status_bar.addWidget(self.status_label) | |
| # Device status | |
| self.device_status_label = QLabel("Nenhum dispositivo conectado") | |
| self.status_bar.addPermanentWidget(self.device_status_label) | |
| def _apply_theme(self): | |
| """Apply the current theme.""" | |
| if self.current_theme == "dark": | |
| self.setStyleSheet(DARK_THEME) | |
| else: | |
| self.setStyleSheet(LIGHT_THEME) | |
| def _set_theme(self, theme: str): | |
| """Set the application theme.""" | |
| self.current_theme = theme | |
| self._apply_theme() | |
| self.settings.setValue("theme", theme) | |
| self.status_label.setText(f"Tema alterado para {theme}") | |
| def _restore_settings(self): | |
| """Restore window settings.""" | |
| # Restore theme | |
| theme = self.settings.value("theme", "dark") | |
| self._set_theme(theme) | |
| # Restore window geometry | |
| geometry = self.settings.value("geometry") | |
| if geometry: | |
| self.restoreGeometry(geometry) | |
| # Restore window state | |
| state = self.settings.value("windowState") | |
| if state: | |
| self.restoreState(state) | |
| def closeEvent(self, event): | |
| """Handle window close event.""" | |
| # Save settings | |
| self.settings.setValue("geometry", self.saveGeometry()) | |
| self.settings.setValue("windowState", self.saveState()) | |
| # Stop ADB server if running | |
| if self.adb_manager: | |
| self.adb_manager.stop_server() | |
| logger.info("Application closing") | |
| event.accept() | |
| def _on_device_connected(self, serial: str): | |
| """Handle device connection.""" | |
| self.device_status_label.setText(f"Conectado: {serial}") | |
| self.status_label.setText(f"Dispositivo conectado: {serial}") | |
| logger.info(f"Device connected: {serial}") | |
| def _on_device_disconnected(self): | |
| """Handle device disconnection.""" | |
| self.device_status_label.setText("Nenhum dispositivo conectado") | |
| self.status_label.setText("Dispositivo desconectado") | |
| logger.info("Device disconnected") | |
| def _on_files_selected(self, files): | |
| """Handle files selected from scan panel.""" | |
| # Switch to recovery tab | |
| self.tab_widget.setCurrentIndex(1) | |
| # Set files to recover | |
| self.recovery_panel.set_files_to_recover(files) | |
| self.status_label.setText(f"{len(files)} arquivo(s) selecionado(s) para recuperação") | |
| logger.info(f"Files selected for recovery: {len(files)}") | |
| def _on_recovery_finished(self, result): | |
| """Handle recovery completion.""" | |
| self.status_label.setText( | |
| f"Recuperação concluída: {result.recovered_files}/{result.total_files} arquivos" | |
| ) | |
| logger.info(f"Recovery finished: {result.recovered_files}/{result.total_files} files") | |
| def _new_scan(self): | |
| """Start a new scan.""" | |
| if not self.device_panel.is_connected(): | |
| QMessageBox.warning( | |
| self, | |
| "Aviso", | |
| "Por favor, conecte um dispositivo primeiro." | |
| ) | |
| return | |
| # Switch to scan tab | |
| self.tab_widget.setCurrentIndex(0) | |
| # Trigger scan on scan panel | |
| self.scan_panel._start_scan() | |
| def _open_recovered_data(self): | |
| """Open recovered data directory.""" | |
| default_dir = "./recovered_data" | |
| if os.path.exists(default_dir): | |
| os.startfile(default_dir) if os.name == 'nt' else os.system(f'xdg-open "{default_dir}"') | |
| else: | |
| QMessageBox.information( | |
| self, | |
| "Info", | |
| "Nenhum dado recuperado encontrado." | |
| ) | |
| def _export_report(self): | |
| """Export recovery report.""" | |
| # TODO: Implement report export | |
| QMessageBox.information( | |
| self, | |
| "Info", | |
| "Funcionalidade de exportação será implementada." | |
| ) | |
| def _refresh(self): | |
| """Refresh the application.""" | |
| self.device_panel.refresh_devices() | |
| self.status_label.setText("Atualizado") | |
| def _start_adb_server(self): | |
| """Start ADB server.""" | |
| if self.adb_manager and self.adb_manager.start_server(): | |
| self.status_label.setText("Servidor ADB iniciado") | |
| QMessageBox.information(self, "Sucesso", "Servidor ADB iniciado com sucesso") | |
| else: | |
| QMessageBox.warning(self, "Erro", "Falha ao iniciar servidor ADB") | |
| def _stop_adb_server(self): | |
| """Stop ADB server.""" | |
| if self.adb_manager and self.adb_manager.stop_server(): | |
| self.status_label.setText("Servidor ADB parado") | |
| QMessageBox.information(self, "Sucesso", "Servidor ADB parado com sucesso") | |
| else: | |
| QMessageBox.warning(self, "Erro", "Falha ao parar servidor ADB") | |
| def _restart_adb_server(self): | |
| """Restart ADB server.""" | |
| if self.adb_manager and self.adb_manager.restart_server(): | |
| self.status_label.setText("Servidor ADB reiniciado") | |
| QMessageBox.information(self, "Sucesso", "Servidor ADB reiniciado com sucesso") | |
| else: | |
| QMessageBox.warning(self, "Erro", "Falha ao reiniciar servidor ADB") | |
| def _show_settings(self): | |
| """Show settings dialog.""" | |
| # TODO: Implement settings dialog | |
| QMessageBox.information( | |
| self, | |
| "Info", | |
| "Configurações serão implementadas em uma janela dedicada." | |
| ) | |
| def _show_documentation(self): | |
| """Show documentation.""" | |
| # TODO: Implement documentation viewer | |
| QMessageBox.information( | |
| self, | |
| "Documentação", | |
| "Android Data Recovery v1.0.0\n\n" | |
| "Este software permite recuperar dados de dispositivos Android usando ADB.\n\n" | |
| "Funcionalidades:\n" | |
| "- Scan de arquivos existentes e deletados\n" | |
| "- Recuperação de contatos, mensagens e chamadas\n" | |
| "- Recuperação de mídia (fotos, vídeos, áudio)\n" | |
| "- Recuperação de dados do WhatsApp\n\n" | |
| "Requisitos:\n" | |
| "- ADB instalado no sistema\n" | |
| "- Depuração USB ativada no dispositivo\n" | |
| "- Root para acesso completo aos dados" | |
| ) | |
| def _show_about(self): | |
| """Show about dialog.""" | |
| QMessageBox.about( | |
| self, | |
| "Sobre Android Data Recovery", | |
| "<h3>Android Data Recovery</h3>" | |
| "<p>Versão 1.0.0</p>" | |
| "<p>Software de recuperação de dados para dispositivos Android.</p>" | |
| "<p><b>Recursos:</b></p>" | |
| "<ul>" | |
| "<li>Scan de arquivos existentes e deletados</li>" | |
| "<li>Recuperação de contatos, mensagens e chamadas</li>" | |
| "<li>Recuperação de mídia (fotos, vídeos, áudio)</li>" | |
| "<li>Recuperação de dados do WhatsApp</li>" | |
| "</ul>" | |
| "<p><b>Desenvolvido com:</b></p>" | |
| "<ul>" | |
| "<li>Python 3.8+</li>" | |
| "<li>PyQt6</li>" | |
| "<li>ADB (Android Debug Bridge)</li>" | |
| "</ul>" | |
| ) | |
| def add_tab(self, widget, title: str): | |
| """Add a tab to the main tab widget.""" | |
| self.tab_widget.addTab(widget, title) | |
| def get_current_tab(self): | |
| """Get the currently active tab widget.""" | |
| return self.tab_widget.currentWidget() | |
| def set_status_message(self, message: str): | |
| """Set the status bar message.""" | |
| self.status_label.setText(message) | |