# Annexe : Noyau - window.py

## Rôle et utilité
Puisque GrimOS démarre directement sur un serveur graphique nu (sans GNOME ni XFCE), il n'existe **aucune notion de fenêtre** au niveau du système d'exploitation. C'est le fichier `core/window.py` qui invente cette notion. Il crée une fausse fenêtre (un rectangle) contenant une barre de titre, des boutons de contrôle, et la capacité d'être déplacée à la souris.

## Implémentation technique

*   **Composition Tkinter** : La classe `Window` hérite d'un `tk.Frame`. Elle est composée de deux parties : un `title_bar` (le bandeau supérieur coloré contenant le titre et la croix rouge) et un `content_frame` (la zone vide où l'application aura le droit de dessiner).
*   **Mathématiques du Déplacement (Drag & Drop)** : C'est la mécanique la plus complexe du fichier. Le script "écoute" le clic gauche de la souris sur la barre de titre (`<ButtonPress-1>`) pour mémoriser les coordonnées initiales `(x, y)`. Ensuite, lorsqu'il détecte le mouvement (`<B1-Motion>`), il calcule la différence (le delta) et met à jour instantanément la position du composant global sur l'écran (`place(x=..., y=...)`), créant l'illusion parfaite d'une fenêtre qui glisse sous la souris.
*   **Superposition (Z-Index)** : Lorsque l'utilisateur clique sur une fenêtre, le gestionnaire appelle `self.tkraise()`. C'est l'instruction qui ordonne au moteur graphique de placer ce rectangle par-dessus tous les autres, simulant la "mise au premier plan".

## Pistes de modification

*   **Redimensionnement (Resize)** : Actuellement, les fenêtres ont une taille fixe ou calculée. Ajouter des bords réactifs (des minuscules cadres invisibles de 2 pixels sur les bords) écoutant le déplacement de la souris permettrait de coder une fonctionnalité de redimensionnement manuel.
*   **Animations d'ouverture** : Au lieu d'apparaître brutalement, on pourrait utiliser la fonction `after` pour créer une mini-animation de 0.2 secondes, faisant passer la fenêtre d'une taille nulle à sa taille réelle, renforçant l'impression de fluidité du système.


## Code Source

```python
import tkinter as tk

class Window(tk.Frame):
    def __init__(self, parent, desktop, app_config, title="Fenêtre", width=400, height=300, x=50, y=50, is_maximized=False, filepath=None):
        super().__init__(parent, highlightbackground="black", highlightthickness=1)
        self.parent = parent
        self.desktop = desktop
        self.app_config = app_config
        self.filepath = filepath
        
        if filepath:
            import os
            self.title = os.path.basename(filepath)
        else:
            self.title = title
        
        # Bounding box constraints
        parent_w = parent.winfo_width()
        if parent_w <= 1:
            parent_w = parent.winfo_screenwidth()
            
        parent_h = parent.winfo_height()
        if parent_h <= 1:
            parent_h = parent.winfo_screenheight()
        
        if width > parent_w: width = parent_w
        if height > parent_h - 40: height = parent_h - 40
        if x < 0: x = 0
        if y < 0: y = 0
        if x + width > parent_w: x = parent_w - width
        if y + 30 > parent_h: y = parent_h - 60
        
        self.is_maximized = False
        self.saved_geometry = {"x": x, "y": y, "width": width, "height": height}
        
        # Récupération du thème
        import sys, os
        sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
        from core.theme import THEMES
        theme_name = self.desktop.settings.get("theme", "GrimOS")
        self.theme_data = THEMES.get(theme_name, THEMES["GrimOS"])
        
        # Application de la bordure
        self.config(highlightbackground=self.theme_data.get("window_border", "black"))
        
        self.place(x=x, y=y, width=width, height=height)
        
        # Title bar
        self.title_bar = tk.Frame(self, bg=self.theme_data.get("title_bg", "darkblue"), relief="raised", bd=1)
        self.title_bar.pack(fill="x")
        
        self.title_label = tk.Label(self.title_bar, text=self.title, bg=self.theme_data.get("title_bg", "darkblue"), fg=self.theme_data.get("title_fg", "white"), font=("Arial", 10, "bold"))
        self.title_label.pack(side="left", padx=5)
        
        self.close_btn = tk.Button(self.title_bar, text="X", bg="red", fg="white", command=self.close, bd=0, padx=5)
        self.close_btn.pack(side="right")
        
        self.max_btn = tk.Button(self.title_bar, text="[ ]", bg="gray", fg="white", command=self.toggle_maximize, bd=0, padx=5)
        self.max_btn.pack(side="right", padx=2)
        
        self.min_btn = tk.Button(self.title_bar, text="_", bg="gray", fg="white", command=self.minimize, bd=0, padx=5)
        self.min_btn.pack(side="right", padx=2)
        
        # Content area
        self.content = tk.Frame(self, bg="white")
        self.content.pack(fill="both", expand=True)
        
        # Resize grip
        self.sizegrip = tk.Label(self, text="◢", fg="gray", bg="lightgray", cursor="bottom_right_corner")
        self.sizegrip.place(relx=1.0, rely=1.0, anchor="se")

        def on_destroy(event):
            if str(event.widget) == str(self):
                self.desktop.remove_window(self)
        self.bind("<Destroy>", on_destroy, add="+")
        
        # Bindings
        self.title_bar.bind("<ButtonPress-1>", self.start_drag)
        self.title_label.bind("<ButtonPress-1>", self.start_drag)
        self.title_bar.bind("<B1-Motion>", self.do_drag)
        self.title_label.bind("<B1-Motion>", self.do_drag)
        
        self.sizegrip.bind("<ButtonPress-1>", self.start_resize)
        self.sizegrip.bind("<B1-Motion>", self.do_resize)
        
        self._drag_data = {"x": 0, "y": 0}
        self._resize_data = {"width": 0, "height": 0, "x": 0, "y": 0}
        
        if is_maximized:
            self.toggle_maximize()
            
        self.desktop.register_window(self)
        self.lift()

    def start_drag(self, event):
        if self.is_maximized:
            return
        self.lift()
        self._drag_data["x"] = event.x
        self._drag_data["y"] = event.y

    def do_drag(self, event):
        if self.is_maximized:
            return
        x = self.winfo_x() - self._drag_data["x"] + event.x
        y = self.winfo_y() - self._drag_data["y"] + event.y
        self.place(x=x, y=y)
        self.update_saved_geometry()

    def start_resize(self, event):
        if self.is_maximized:
            return
        self.lift()
        self._resize_data["x"] = event.x_root
        self._resize_data["y"] = event.y_root
        self._resize_data["width"] = self.winfo_width()
        self._resize_data["height"] = self.winfo_height()

    def do_resize(self, event):
        if self.is_maximized:
            return
        dx = event.x_root - self._resize_data["x"]
        dy = event.y_root - self._resize_data["y"]
        new_width = max(150, self._resize_data["width"] + dx)
        new_height = max(100, self._resize_data["height"] + dy)
        self.place(width=new_width, height=new_height)
        self.update_saved_geometry()

    def toggle_maximize(self):
        if not self.is_maximized:
            self.update_saved_geometry()
            self.config(highlightthickness=0)
            self.place(x=0, y=0, width=self.parent.winfo_width(), height=self.parent.winfo_height())
            self.is_maximized = True
            self.max_btn.config(text="[R]")
        else:
            self.config(highlightthickness=1)
            self.place(x=self.saved_geometry["x"], y=self.saved_geometry["y"], 
                       width=self.saved_geometry["width"], height=self.saved_geometry["height"])
            self.is_maximized = False
            self.max_btn.config(text="[ ]")
            
    def update_saved_geometry(self):
        # Prevent layout artifacts from propagating empty dimensions during creation
        if self.winfo_width() > 10:
            self.saved_geometry = {
                "x": self.winfo_x(),
                "y": self.winfo_y(),
                "width": self.winfo_width(),
                "height": self.winfo_height()
            }

    def minimize(self):
        self.update_saved_geometry()
        self.place_forget()
        self.desktop.update_taskbar_btn(self)

    def restore(self):
        if self.is_maximized:
            self.config(highlightthickness=0)
            self.place(x=0, y=0, width=self.parent.winfo_width(), height=self.parent.winfo_height())
        else:
            self.config(highlightthickness=1)
            self.place(x=self.saved_geometry["x"], y=self.saved_geometry["y"], 
                       width=self.saved_geometry["width"], height=self.saved_geometry["height"])
        self.lift()
        self.desktop.update_taskbar_btn(self)

    def close(self):
        if hasattr(self, 'on_close_callback') and callable(self.on_close_callback):
            if self.on_close_callback() is False:
                return
                
        self.update_saved_geometry()
        app_module = self.app_config.get("module")
        if app_module:
            try:
                import json
                import os
                config_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'config', 'applications.json')
                with open(config_path, 'r', encoding='utf-8') as f:
                    apps = json.load(f)
                
                updated = False
                for app in apps:
                    if app.get("module") == app_module:
                        app["width"] = self.saved_geometry["width"]
                        app["height"] = self.saved_geometry["height"]
                        app["x"] = self.saved_geometry["x"]
                        app["y"] = self.saved_geometry["y"]
                        updated = True
                        break
                        
                if updated:
                    with open(config_path, 'w', encoding='utf-8') as f:
                        json.dump(apps, f, indent=4)
                        
                    for app in self.desktop.apps:
                        if app.get("module") == app_module:
                            app["width"] = self.saved_geometry["width"]
                            app["height"] = self.saved_geometry["height"]
                            app["x"] = self.saved_geometry["x"]
                            app["y"] = self.saved_geometry["y"]
                            break
            except Exception as e:
                print(f"Erreur de sauvegarde de géométrie: {e}")

        self.desktop.remove_window(self)
        self.destroy()
```
