|
| 1 | +import tkinter as tk |
| 2 | +from tkinter import messagebox, scrolledtext |
| 3 | +from datetime import datetime |
| 4 | +from .os import OSState |
| 5 | + |
| 6 | + |
| 7 | +class AppWindow: |
| 8 | + def __init__(self, parent, title, width=520, height=340, x=40, y=40): |
| 9 | + self.parent = parent |
| 10 | + self.frame = tk.Frame(parent, bg="#3b3d59", bd=2, relief="raised") |
| 11 | + self.frame.place(x=x, y=y, width=width, height=height) |
| 12 | + |
| 13 | + self.title_bar = tk.Frame(self.frame, bg="#44475a", height=32) |
| 14 | + self.title_bar.pack(fill="x") |
| 15 | + |
| 16 | + self.title_label = tk.Label(self.title_bar, text=title, fg="#f8f8f2", bg="#44475a", font=("Segoe UI", 10, "bold")) |
| 17 | + self.title_label.pack(side="left", padx=8) |
| 18 | + |
| 19 | + self.close_btn = tk.Button(self.title_bar, text="✕", bg="#ff5555", fg="#f8f8f2", bd=0, width=3, command=self.close) |
| 20 | + self.close_btn.pack(side="right", padx=4, pady=2) |
| 21 | + |
| 22 | + self.content = tk.Frame(self.frame, bg="#282a36") |
| 23 | + self.content.pack(expand=True, fill="both") |
| 24 | + |
| 25 | + self._drag_data = {"x": 0, "y": 0} |
| 26 | + self.title_bar.bind("<ButtonPress-1>", self._start_drag) |
| 27 | + self.title_bar.bind("<ButtonRelease-1>", self._stop_drag) |
| 28 | + self.title_bar.bind("<B1-Motion>", self._do_drag) |
| 29 | + |
| 30 | + def _start_drag(self, event): |
| 31 | + self._drag_data["x"] = event.x |
| 32 | + self._drag_data["y"] = event.y |
| 33 | + |
| 34 | + def _stop_drag(self, event): |
| 35 | + self._drag_data["x"] = 0 |
| 36 | + self._drag_data["y"] = 0 |
| 37 | + |
| 38 | + def _do_drag(self, event): |
| 39 | + dx = event.x - self._drag_data["x"] |
| 40 | + dy = event.y - self._drag_data["y"] |
| 41 | + x = self.frame.winfo_x() + dx |
| 42 | + y = self.frame.winfo_y() + dy |
| 43 | + self.frame.place(x=x, y=y) |
| 44 | + |
| 45 | + def close(self): |
| 46 | + self.frame.destroy() |
| 47 | + |
| 48 | + |
| 49 | +class PythonOSApp: |
| 50 | + def __init__(self): |
| 51 | + self.root = tk.Tk() |
| 52 | + self.root.title("PythonOS") |
| 53 | + self.root.geometry("1000x650") |
| 54 | + self.root.configure(bg="#1f1f2e") |
| 55 | + self.root.minsize(900, 600) |
| 56 | + |
| 57 | + self.state = OSState() |
| 58 | + self.windows = [] |
| 59 | + self.start_menu = None |
| 60 | + self.lock_screen = None |
| 61 | + |
| 62 | + self._build_ui() |
| 63 | + self._show_welcome() |
| 64 | + self._show_lock_screen() |
| 65 | + self._update_clock() |
| 66 | + |
| 67 | + def _build_ui(self): |
| 68 | + self.top_bar = tk.Frame(self.root, bg="#1f1f2e", height=36) |
| 69 | + self.top_bar.pack(side="top", fill="x") |
| 70 | + |
| 71 | + self.start_btn = tk.Button(self.top_bar, text="Start", command=self._toggle_start_menu, bg="#6272a4", fg="#f8f8f2", bd=0, padx=12, pady=4) |
| 72 | + self.start_btn.pack(side="left", padx=8, pady=4) |
| 73 | + |
| 74 | + self.status_label = tk.Label(self.top_bar, text="Pronto", bg="#1f1f2e", fg="#f8f8f2", font=("Segoe UI", 9)) |
| 75 | + self.status_label.pack(side="left", padx=12) |
| 76 | + |
| 77 | + self.clock_label = tk.Label(self.top_bar, text="00:00", bg="#1f1f2e", fg="#f8f8f2", font=("Segoe UI", 9, "bold")) |
| 78 | + self.clock_label.pack(side="right", padx=12) |
| 79 | + |
| 80 | + self.sidebar = tk.Frame(self.root, bg="#2b2b44", width=180) |
| 81 | + self.sidebar.pack(side="left", fill="y") |
| 82 | + |
| 83 | + self.desktop = tk.Frame(self.root, bg="#282a36") |
| 84 | + self.desktop.pack(side="right", expand=True, fill="both") |
| 85 | + |
| 86 | + title = tk.Label(self.sidebar, text="PythonOS", fg="#f8f8f2", bg="#2b2b44", font=("Segoe UI", 18, "bold")) |
| 87 | + title.pack(pady=20) |
| 88 | + |
| 89 | + self._add_sidebar_button("Terminale", self.open_terminal) |
| 90 | + self._add_sidebar_button("Appunti", self.open_notes) |
| 91 | + self._add_sidebar_button("File Manager", self.open_file_manager) |
| 92 | + self._add_sidebar_button("Impostazioni", self.open_settings) |
| 93 | + self._add_sidebar_button("Blocca schermo", self._show_lock_screen) |
| 94 | + |
| 95 | + self.status_bar = tk.Label(self.root, text="PythonOS mini-OS pronto", bg="#1f1f2e", fg="#f8f8f2", anchor="w") |
| 96 | + self.status_bar.pack(side="bottom", fill="x") |
| 97 | + |
| 98 | + def _add_sidebar_button(self, text, command): |
| 99 | + btn = tk.Button(self.sidebar, text=text, command=command, bg="#6272a4", fg="#f8f8f2", relief="flat") |
| 100 | + btn.pack(fill="x", padx=20, pady=8) |
| 101 | + |
| 102 | + def _show_welcome(self): |
| 103 | + for widget in self.desktop.winfo_children(): |
| 104 | + widget.destroy() |
| 105 | + |
| 106 | + wallpaper = tk.Label(self.desktop, bg="#282a36") |
| 107 | + wallpaper.pack(expand=True, fill="both") |
| 108 | + |
| 109 | + self._create_desktop_icon(wallpaper, "Terminale", self.open_terminal, 60, 80) |
| 110 | + self._create_desktop_icon(wallpaper, "Appunti", self.open_notes, 160, 80) |
| 111 | + self._create_desktop_icon(wallpaper, "File Manager", self.open_file_manager, 260, 80) |
| 112 | + self._create_desktop_icon(wallpaper, "Impostazioni", self.open_settings, 360, 80) |
| 113 | + |
| 114 | + welcome = tk.Label(wallpaper, text=f"Benvenuto, {self.state.user_name}", fg="#f8f8f2", bg="#282a36", font=("Segoe UI", 24, "bold")) |
| 115 | + welcome.place(x=60, y=20) |
| 116 | + |
| 117 | + subtitle = tk.Label(wallpaper, text="Sistema operativo PythonOS - Apri un'app dal desktop o dalla barra laterale.", fg="#f8f8f2", bg="#282a36", font=("Segoe UI", 11)) |
| 118 | + subtitle.place(x=60, y=60) |
| 119 | + |
| 120 | + def _create_desktop_icon(self, parent, text, command, x, y): |
| 121 | + icon = tk.Button(parent, text=text, command=command, bg="#44475a", fg="#f8f8f2", bd=0, width=12, height=3) |
| 122 | + icon.place(x=x, y=y) |
| 123 | + |
| 124 | + def _toggle_start_menu(self): |
| 125 | + if self.start_menu and self.start_menu.winfo_exists(): |
| 126 | + self.start_menu.destroy() |
| 127 | + self.start_menu = None |
| 128 | + return |
| 129 | + |
| 130 | + self.start_menu = tk.Frame(self.root, bg="#3b3d59", bd=2, relief="raised") |
| 131 | + self.start_menu.place(x=10, y=42, width=200, height=220) |
| 132 | + |
| 133 | + apps = [ |
| 134 | + ("Terminale", self.open_terminal), |
| 135 | + ("Appunti", self.open_notes), |
| 136 | + ("File Manager", self.open_file_manager), |
| 137 | + ("Impostazioni", self.open_settings), |
| 138 | + ("Blocca", self._show_lock_screen), |
| 139 | + ] |
| 140 | + for idx, (name, action) in enumerate(apps): |
| 141 | + btn = tk.Button(self.start_menu, text=name, command=lambda action=action: [action(), self._toggle_start_menu()], bg="#6272a4", fg="#f8f8f2", relief="flat") |
| 142 | + btn.pack(fill="x", padx=10, pady=5) |
| 143 | + |
| 144 | + def _show_lock_screen(self): |
| 145 | + if self.lock_screen and self.lock_screen.winfo_exists(): |
| 146 | + return |
| 147 | + |
| 148 | + self.lock_screen = tk.Frame(self.root, bg="#0f101a") |
| 149 | + self.lock_screen.place(relx=0, rely=0, relwidth=1, relheight=1) |
| 150 | + |
| 151 | + label = tk.Label(self.lock_screen, text="Sistema Bloccato", fg="#f8f8f2", bg="#0f101a", font=("Segoe UI", 28, "bold")) |
| 152 | + label.pack(pady=80) |
| 153 | + |
| 154 | + pin_label = tk.Label(self.lock_screen, text="Inserisci PIN per sbloccare", fg="#f8f8f2", bg="#0f101a", font=("Segoe UI", 12)) |
| 155 | + pin_label.pack(pady=10) |
| 156 | + |
| 157 | + self.pin_entry = tk.Entry(self.lock_screen, show="*", width=16, justify="center", font=("Segoe UI", 12)) |
| 158 | + self.pin_entry.pack(pady=10) |
| 159 | + self.pin_entry.focus_set() |
| 160 | + |
| 161 | + unlock_btn = tk.Button(self.lock_screen, text="Sblocca", command=self._unlock_screen, bg="#50fa7b", fg="#282a36", relief="flat", padx=12, pady=6) |
| 162 | + unlock_btn.pack(pady=10) |
| 163 | + |
| 164 | + self.lock_status = tk.Label(self.lock_screen, text="", fg="#ff5555", bg="#0f101a", font=("Segoe UI", 10)) |
| 165 | + self.lock_status.pack(pady=4) |
| 166 | + |
| 167 | + self.root.bind("<Return>", self._unlock_screen_event) |
| 168 | + |
| 169 | + def _unlock_screen_event(self, event): |
| 170 | + if self.lock_screen and self.lock_screen.winfo_exists(): |
| 171 | + self._unlock_screen() |
| 172 | + |
| 173 | + def _unlock_screen(self): |
| 174 | + if self.pin_entry.get() == self.state.lock_code: |
| 175 | + self.lock_screen.destroy() |
| 176 | + self.lock_screen = None |
| 177 | + self.status_bar.config(text="Schermo sbloccato") |
| 178 | + else: |
| 179 | + self.lock_status.config(text="PIN errato. Riprova.") |
| 180 | + self.pin_entry.delete(0, "end") |
| 181 | + |
| 182 | + def open_terminal(self): |
| 183 | + self.status_bar.config(text="Terminale aperto") |
| 184 | + self._open_terminal_window() |
| 185 | + |
| 186 | + def open_notes(self): |
| 187 | + self.status_bar.config(text="Appunti aperti") |
| 188 | + self._open_notes_window() |
| 189 | + |
| 190 | + def open_file_manager(self): |
| 191 | + self.status_bar.config(text="File Manager aperto") |
| 192 | + self._open_file_manager_window() |
| 193 | + |
| 194 | + def open_settings(self): |
| 195 | + self.status_bar.config(text="Impostazioni aperte") |
| 196 | + self._open_settings_window() |
| 197 | + |
| 198 | + def open_about(self): |
| 199 | + messagebox.showinfo( |
| 200 | + "Informazioni su PythonOS", |
| 201 | + "PythonOS è un mini-sistema operativo costruito con Python e Tkinter.\n" |
| 202 | + "Versione: 1.0.0 Beta 1", |
| 203 | + ) |
| 204 | + |
| 205 | + def _open_terminal_window(self): |
| 206 | + window = AppWindow(self.desktop, "Terminale", width=520, height=320, x=120, y=120) |
| 207 | + console = scrolledtext.ScrolledText(window.content, bg="#1e1f2b", fg="#f8f8f2", insertbackground="#f8f8f2") |
| 208 | + console.pack(expand=True, fill="both", padx=10, pady=10) |
| 209 | + console.insert("end", "PythonOS Terminale\n> digita 'help' e premi Invio\n") |
| 210 | + console.bind("<Return>", lambda event: self._handle_terminal_command(console)) |
| 211 | + self.windows.append(window) |
| 212 | + |
| 213 | + def _handle_terminal_command(self, console): |
| 214 | + text = console.get("1.0", "end-1c").strip().splitlines()[-1] |
| 215 | + command = text.replace("> ", "").strip() |
| 216 | + response = self.state.run_command(command) |
| 217 | + console.insert("end", f"\n{response}\n> ") |
| 218 | + console.see("end") |
| 219 | + return "break" |
| 220 | + |
| 221 | + def _open_notes_window(self): |
| 222 | + window = AppWindow(self.desktop, "Appunti", width=520, height=320, x=140, y=130) |
| 223 | + text_area = scrolledtext.ScrolledText(window.content, bg="#1f1f2b", fg="#f8f8f2") |
| 224 | + text_area.pack(expand=True, fill="both", padx=10, pady=10) |
| 225 | + text_area.insert("1.0", self.state.notes) |
| 226 | + |
| 227 | + def save_notes(): |
| 228 | + self.state.notes = text_area.get("1.0", "end-1c") |
| 229 | + self.status_bar.config(text="Appunti salvati") |
| 230 | + |
| 231 | + save_btn = tk.Button(window.content, text="Salva", command=save_notes, bg="#50fa7b", fg="#282a36", relief="flat") |
| 232 | + save_btn.pack(pady=8) |
| 233 | + self.windows.append(window) |
| 234 | + |
| 235 | + def _open_file_manager_window(self): |
| 236 | + window = AppWindow(self.desktop, "File Manager", width=520, height=320, x=160, y=140) |
| 237 | + files_text = tk.Text(window.content, bg="#1e1f2b", fg="#f8f8f2") |
| 238 | + files_text.pack(expand=True, fill="both", padx=10, pady=10) |
| 239 | + files_text.insert("1.0", "\n".join(self.state.get_file_listing())) |
| 240 | + files_text.config(state="disabled") |
| 241 | + self.windows.append(window) |
| 242 | + |
| 243 | + def _open_settings_window(self): |
| 244 | + window = AppWindow(self.desktop, "Impostazioni", width=520, height=340, x=180, y=160) |
| 245 | + |
| 246 | + theme_label = tk.Label(window.content, text="Tema:", fg="#f8f8f2", bg="#282a36", font=("Segoe UI", 10, "bold")) |
| 247 | + theme_label.pack(anchor="w", padx=12, pady=(12, 4)) |
| 248 | + |
| 249 | + theme_frame = tk.Frame(window.content, bg="#282a36") |
| 250 | + theme_frame.pack(anchor="w", padx=12) |
| 251 | + light_btn = tk.Button(theme_frame, text="Chiaro", command=lambda: self._set_theme("light"), bg="#50fa7b", fg="#282a36", relief="flat") |
| 252 | + dark_btn = tk.Button(theme_frame, text="Scuro", command=lambda: self._set_theme("dark"), bg="#6272a4", fg="#f8f8f2", relief="flat") |
| 253 | + light_btn.pack(side="left", padx=4) |
| 254 | + dark_btn.pack(side="left", padx=4) |
| 255 | + |
| 256 | + username_label = tk.Label(window.content, text="Nome utente:", fg="#f8f8f2", bg="#282a36", font=("Segoe UI", 10, "bold")) |
| 257 | + username_label.pack(anchor="w", padx=12, pady=(12, 4)) |
| 258 | + username_entry = tk.Entry(window.content, bg="#1e1f2b", fg="#f8f8f2", insertbackground="#f8f8f2") |
| 259 | + username_entry.insert(0, self.state.user_name) |
| 260 | + username_entry.pack(fill="x", padx=12) |
| 261 | + |
| 262 | + lock_label = tk.Label(window.content, text="PIN blocco schermo:", fg="#f8f8f2", bg="#282a36", font=("Segoe UI", 10, "bold")) |
| 263 | + lock_label.pack(anchor="w", padx=12, pady=(12, 4)) |
| 264 | + lock_entry = tk.Entry(window.content, bg="#1e1f2b", fg="#f8f8f2", insertbackground="#f8f8f2") |
| 265 | + lock_entry.insert(0, self.state.lock_code) |
| 266 | + lock_entry.pack(fill="x", padx=12) |
| 267 | + |
| 268 | + |
| 269 | + def save_settings(): |
| 270 | + self.state.user_name = username_entry.get().strip() or self.state.user_name |
| 271 | + self.state.lock_code = lock_entry.get().strip() or self.state.lock_code |
| 272 | + self.status_bar.config(text="Impostazioni salvate") |
| 273 | + self._show_welcome() |
| 274 | + |
| 275 | + info_label = tk.Button(window.content, text="Informazioni sistema", command=self.open_about, bg="#6272a4", fg="#f8f8f2", relief="flat") |
| 276 | + info_label.pack(pady=12) |
| 277 | + |
| 278 | + save_btn = tk.Button(window.content, text="Salva impostazioni", command=save_settings, bg="#50fa7b", fg="#282a36", relief="flat") |
| 279 | + save_btn.pack(pady=12) |
| 280 | + |
| 281 | + self.windows.append(window) |
| 282 | + |
| 283 | + def _set_theme(self, theme_name): |
| 284 | + self.state.theme = theme_name |
| 285 | + if theme_name == "light": |
| 286 | + self.desktop.configure(bg="#dcdde1") |
| 287 | + self.status_bar.config(bg="#f0f0f0", fg="#1f1f2e") |
| 288 | + self.top_bar.config(bg="#f0f0f0") |
| 289 | + self.status_label.config(bg="#f0f0f0", fg="#1f1f2e") |
| 290 | + self.clock_label.config(bg="#f0f0f0", fg="#1f1f2e") |
| 291 | + else: |
| 292 | + self.desktop.configure(bg="#282a36") |
| 293 | + self.status_bar.config(bg="#1f1f2e", fg="#f8f8f2") |
| 294 | + self.top_bar.config(bg="#1f1f2e") |
| 295 | + self.status_label.config(bg="#1f1f2e", fg="#f8f8f2") |
| 296 | + self.clock_label.config(bg="#1f1f2e", fg="#f8f8f2") |
| 297 | + self.status_bar.config(text=f"Tema impostato su {theme_name}") |
| 298 | + |
| 299 | + def _update_clock(self): |
| 300 | + self.clock_label.config(text=datetime.now().strftime("%H:%M")) |
| 301 | + self.root.after(1000, self._update_clock) |
| 302 | + |
| 303 | + def run(self): |
| 304 | + self.root.mainloop() |
0 commit comments