android_data_recovery / gui /main_window.py
meccatronis's picture
Upload gui/main_window.py with huggingface_hub
f635a7b verified
Raw History Blame Contribute Delete
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)