from PyQt6.QtWidgets import QApplication, QMainWindow, QLabel from PyQt6.QtGui import QResizeEvent, QCloseEvent from PyQt6.QtCore import QSize, QEvent from app.core.main_manager import MainManager, NotificationType from app.ui.widgets.tabs_widget import TabsWidget, MenuDirection, ButtonPosition, BorderSide, TabSide, TextPosition from app.ui.windows.settings_window import SettingsWindow from app.ui.windows.suggestion_window import SuggestionWindow from app.ui.windows.activation_window import ActivationWindow from app.ui.windows.chat_window import ChatWindow import app.utils.paths as paths, shutil from typing import Optional class MainWindow(QMainWindow): def __init__(self) -> None: super().__init__() self.main_manager: MainManager = MainManager.get_instance() self.language_manager = self.main_manager.get_language_manager() self.theme_manager = self.main_manager.get_theme_manager() self.settings_manager = self.main_manager.get_settings_manager() self.observer_manager = self.main_manager.get_observer_manager() self.observer_manager.subscribe(NotificationType.THEME, self.update_theme) self.observer_manager.subscribe(NotificationType.LANGUAGE, self.update_language) self.is_maximizing: bool = False # Initialiser les attributs de taille AVANT setup_ui app: Optional[QApplication] = QApplication.instance() size: QSize = app.primaryScreen().size() self.settings_manager.minScreenSize = min(size.height(), size.width()) # Initialiser les tailles par défaut window_size: dict = self.settings_manager.get_window_size() self.current_size: QSize = QSize(window_size["width"], window_size["height"]) self.previous_size: QSize = QSize(window_size["width"], window_size["height"]) # Configuration des tailles de police de référence self.base_width = 1200 # Largeur de référence (taille par défaut) self.base_height = 700 # Hauteur de référence (taille par défaut) self.base_tab_height = 70 # Hauteur de base du tab menu # Cache pour stocker les font-sizes de base de chaque widget self._base_font_sizes = {} self._font_sizes_extracted = False # Flag pour savoir si on a déjà extrait les tailles # UI elements self.side_menu: TabsWidget self.settings_window: SettingsWindow self.suggestion_window: SuggestionWindow self.setMinimumSize(600, 450) # Initialiser l'UI immédiatement (sera fait pendant le splash) self.setup_ui() # Différer l'application des paramètres de fenêtre jusqu'à l'affichage réel self._window_state_applied = False def showEvent(self, event): """Applique les paramètres de fenêtre lors du premier affichage""" super().showEvent(event) if not self._window_state_applied: self.apply_saved_window_state() self._window_state_applied = True def apply_saved_window_state(self) -> None: """Apply saved window size and maximized state""" window_size: dict = self.settings_manager.get_window_size() self.current_size = QSize(window_size["width"], window_size["height"]) self.previous_size = QSize(window_size["width"], window_size["height"]) self.resize(self.current_size) if self.settings_manager.get_maximized(): self.is_maximizing = True self.showMaximized() def changeEvent(self, event: QEvent) -> None: """Handle window state changes""" super().changeEvent(event) if event.type() == event.Type.WindowStateChange: if self.isMaximized(): # On vient de maximiser self.is_maximizing = False else: # On vient de dé-maximiser, restaurer la taille précédente if hasattr(self, 'previous_size'): self.current_size = self.previous_size self.settings_manager.set_maximized(self.isMaximized()) def resizeEvent(self, a0: QResizeEvent) -> None: # Ne pas sauvegarder la taille si on est en train de maximiser if not self.isMaximized() and not self.is_maximizing: self.previous_size = self.current_size self.current_size = self.size() # Ajuster dynamiquement les font-sizes avec un ratio self.adjust_all_font_sizes() def adjust_all_font_sizes(self): """Ajuste dynamiquement les font-sizes de tous les éléments avec un ratio proportionnel""" # Calculer le ratio basé sur la largeur ET la hauteur actuelle current_width = self.width() current_height = self.height() # Calculer les ratios séparément width_ratio = current_width / self.base_width height_ratio = current_height / self.base_height # Utiliser la moyenne des deux ratios pour un scaling plus naturel # Ou utiliser le minimum pour éviter le débordement ratio = min(width_ratio,height_ratio) * 1.5 # Limiter le ratio pour éviter des tailles extrêmes ratio = max(0.5, min(ratio, 2.0)) # Entre 50% et 200% # Récupérer tous les widgets des tabs all_widgets = [] if hasattr(self, 'side_menu'): all_widgets = self.side_menu.widgets # Extraire les tailles de base une seule fois if not self._font_sizes_extracted: self._extract_base_font_sizes(all_widgets) self._font_sizes_extracted = True # Parcourir tous les widgets et ajuster leurs tailles for widget in all_widgets: if widget: self._adjust_widget_font_sizes(widget, ratio) def _extract_base_font_sizes(self, widgets): """Extrait les tailles de police de base de tous les widgets une seule fois""" from PyQt6.QtWidgets import QPushButton, QLineEdit, QTextEdit, QComboBox widget_types = [QLabel, QPushButton, QLineEdit, QTextEdit, QComboBox] # Extraire les tailles des boutons d'onglets du side menu if hasattr(self, 'side_menu') and hasattr(self.side_menu, 'buttons'): for button in self.side_menu.buttons: if button: widget_id = id(button) current_style = button.styleSheet() base_size = self._extract_font_size_from_style(current_style) if base_size is None: base_size = 14 # Taille par défaut self._base_font_sizes[widget_id] = base_size for widget in widgets: if not widget: continue for widget_type in widget_types: for child in widget.findChildren(widget_type): widget_id = id(child) # Ignorer les widgets avec un objectName (généralement stylisés spécifiquement) if child.objectName() != "": continue # Extraire la taille de police depuis le stylesheet current_style = child.styleSheet() base_size = self._extract_font_size_from_style(current_style) # Si pas trouvé dans le style, utiliser la taille par défaut if base_size is None: base_size = 14 # Taille par défaut # Stocker la taille de base self._base_font_sizes[widget_id] = base_size def _extract_font_size_from_style(self, style: str) -> Optional[int]: """Extrait la taille de police depuis un stylesheet""" import re # Chercher "font-size: XXpx" match = re.search(r'font-size:\s*(\d+)px', style) if match: return int(match.group(1)) return None def _adjust_widget_font_sizes(self, widget, ratio): """Ajuste les font-sizes de tous les éléments d'un widget avec un ratio proportionnel""" from PyQt6.QtWidgets import QPushButton, QLineEdit, QTextEdit, QComboBox import re # Ajuster les boutons d'onglets du side menu if hasattr(self, 'side_menu') and hasattr(self.side_menu, 'buttons'): for button in self.side_menu.buttons: if button: widget_id = id(button) if widget_id in self._base_font_sizes: base_size = self._base_font_sizes[widget_id] new_size = max(8, int(base_size * ratio)) current_style = button.styleSheet() style_without_font = re.sub(r'font-size:\s*\d+px;?', '', current_style) style_without_font = re.sub(r';+', ';', style_without_font) style_without_font = style_without_font.strip() if style_without_font and not style_without_font.endswith(';'): style_without_font += ';' new_style = f"{style_without_font} font-size: {new_size}px;" button.setStyleSheet(new_style) widget_types = [QLabel, QPushButton, QLineEdit, QTextEdit, QComboBox] for widget_type in widget_types: for child in widget.findChildren(widget_type): widget_id = id(child) # Récupérer la taille de base if widget_id not in self._base_font_sizes: continue # Pas de taille de base, ignorer base_size = self._base_font_sizes[widget_id] # Calculer la nouvelle taille avec le ratio new_size = max(8, int(base_size * ratio)) # Minimum 8px # Appliquer le style en préservant les autres propriétés current_style = child.styleSheet() # Retirer l'ancienne font-size style_without_font = re.sub(r'font-size:\s*\d+px;?', '', current_style) # Nettoyer les points-virgules multiples style_without_font = re.sub(r';+', ';', style_without_font) style_without_font = style_without_font.strip() # Ajouter la nouvelle font-size if style_without_font and not style_without_font.endswith(';'): style_without_font += ';' new_style = f"{style_without_font} font-size: {new_size}px;" child.setStyleSheet(new_style) def closeEvent(self, event: QCloseEvent) -> None: """Handle application close event""" if hasattr(self, 'chat_window'): self.chat_window.cleanup() super().closeEvent(event) # si la difference de taille est plus grande que 10 pixels, enregistrer previoussize if abs(self.current_size.width() - self.previous_size.width()) > 10 or abs(self.current_size.height() - self.previous_size.height()) > 10: self.current_size = self.previous_size self.settings_manager.set_window_size( self.current_size.width(), self.current_size.height() ) try: shutil.rmtree(paths.get_user_temp(self.settings_manager.get_config("app_name"))) except Exception: pass def setup_ui(self) -> None: self.side_menu = TabsWidget(self, MenuDirection.VERTICAL, 160, self._on_tab_changed, 4, BorderSide.LEFT, TabSide.LEFT) self.chat_window = ChatWindow(self) if self.settings_manager.get_config("enable_licensing"): self.activation_window = ActivationWindow(self) self.side_menu.add_widget(self.activation_window, self.language_manager.get_text("tab_licensing"), paths.get_asset_svg_path("license"), position=ButtonPosition.START, text_position=TextPosition.RIGHT) self.settings_window = SettingsWindow(self) self.side_menu.add_widget(self.settings_window, self.language_manager.get_text("tab_settings"), paths.get_asset_svg_path("settings"), position=ButtonPosition.START, text_position=TextPosition.RIGHT) self.suggestion_window = SuggestionWindow(self) self.side_menu.add_widget(self.suggestion_window, self.language_manager.get_text("tab_suggestions"), paths.get_asset_svg_path("suggestion"), position=ButtonPosition.START, text_position=TextPosition.RIGHT) self.side_menu.add_widget(self.chat_window, self.language_manager.get_text("tab_chat"), paths.get_asset_svg_path("chat"), position=ButtonPosition.START, text_position=TextPosition.RIGHT) bl = self.side_menu.button_layout for i in reversed(range(bl.count())): item = bl.itemAt(i) if item.spacerItem(): bl.removeItem(item) self._chat_sidebar = self.chat_window.get_sidebar_widget() bl.addWidget(self._chat_sidebar, 1) self.side_menu.switch_to_tab(len(self.side_menu.buttons) - 1) self.setCentralWidget(self.side_menu) self._update_session_visibility(len(self.side_menu.buttons) - 1) def _on_tab_changed(self, index: int): self._update_session_visibility(index) def _update_session_visibility(self, index: int): if not hasattr(self, 'side_menu') or len(self.side_menu.buttons) < 1: return chat_idx = len(self.side_menu.buttons) - 1 session_active = self.side_menu.buttons[chat_idx].isChecked() self.chat_window.chat_list.setVisible(session_active) self.chat_window.new_chat_btn.setVisible(session_active) def get_tab_widget(self): """Retourne le widget TabsWidget pour permettre le changement d'onglet""" return self.side_menu def update_theme(self) -> None: self.setStyleSheet(self.theme_manager.get_sheet()) def update_language(self) -> None: num_buttons = len(self.side_menu.buttons) if num_buttons >= 4: self.side_menu.update_button_text(0, self.language_manager.get_text("tab_licensing")) self.side_menu.update_button_text(1, self.language_manager.get_text("tab_settings")) self.side_menu.update_button_text(2, self.language_manager.get_text("tab_suggestions")) self.side_menu.update_button_text(3, self.language_manager.get_text("tab_chat")) elif num_buttons >= 3: self.side_menu.update_button_text(0, self.language_manager.get_text("tab_settings")) self.side_menu.update_button_text(1, self.language_manager.get_text("tab_suggestions")) self.side_menu.update_button_text(2, self.language_manager.get_text("tab_chat"))