""" 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", "
Versão 1.0.0
" "Software de recuperação de dados para dispositivos Android.
" "Recursos:
" "Desenvolvido com:
" "