diff --git a/gui.py b/gui.py index 5184589..d9ff5f8 100644 --- a/gui.py +++ b/gui.py @@ -1,4 +1,5 @@ import tkinter as tk +from tkinter import ttk import asyncio import threading from app.fetcher import fetch_game_servers @@ -12,13 +13,26 @@ def __init__(self, root): # Set a custom icon self.root.iconbitmap("img/SFDicon.ico") # Replace with your .ico file path - # Create the Listbox to display server names - self.listbox = tk.Listbox(self.root, width=80, height=15) - self.listbox.pack(fill=tk.BOTH, expand=True) # Make it fill the entire window + # Create the Treeview to display server data with sorting capabilities + self.treeview = ttk.Treeview(self.root, columns=("Game Name", "Game Mode", "Players", "Password", "Version"), show="headings") + self.treeview.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) - # Create a Text widget to display the server details - self.details_text = tk.Text(self.root, height=8, wrap=tk.WORD) - self.details_text.pack(fill=tk.X, padx=10, pady=10) # Fill horizontally, with padding + # Define column headings and configure column widths + self.treeview.heading("Game Name", text="Game Name", command=lambda: self.sort_treeview("Game Name")) + self.treeview.heading("Game Mode", text="Game Mode", command=lambda: self.sort_treeview("Game Mode")) + self.treeview.heading("Players", text="Players", command=lambda: self.sort_treeview("Players")) + self.treeview.heading("Password", text="Password", command=lambda: self.sort_treeview("Password")) + self.treeview.heading("Version", text="Version", command=lambda: self.sort_treeview("Version")) + + self.treeview.column("Game Name", width=100, anchor=tk.W) + self.treeview.column("Game Mode", width=100, anchor=tk.W) + self.treeview.column("Players", width=100, anchor=tk.W) + self.treeview.column("Password", width=100, anchor=tk.W) + self.treeview.column("Version", width=100, anchor=tk.W) + + # Create a frame for the detailed information below the list + self.details_frame = tk.Frame(self.root) + self.details_frame.pack(fill=tk.X, padx=10, pady=10) self.servers = None @@ -28,8 +42,8 @@ def __init__(self, root): # Start the periodic fetch operation self.start_auto_fetch() - # Bind left-click on the listbox to display server details - self.listbox.bind('', self.on_server_select) + # Bind left-click on the Treeview to display server details + self.treeview.bind('', self.on_server_select) def fetch_servers(self): """Fetch servers in a new thread so the UI doesn't freeze.""" @@ -40,49 +54,83 @@ def run_fetch_game_servers(self): asyncio.run(fetch_game_servers(callback=self.update_server_list)) def update_server_list(self, servers): - """Update the Listbox with the server list.""" + """Update the Treeview with the server list.""" self.servers = servers # Store the server list as an attribute for later use - self.listbox.delete(0, tk.END) # Clear the listbox before updating + for item in self.treeview.get_children(): + self.treeview.delete(item) # Clear the existing entries in the Treeview if not servers: - self.listbox.insert(tk.END, "No servers found.") + self.treeview.insert("", "end", values=("No servers found", "", "", "")) else: for server in servers: - self.listbox.insert(tk.END, server.game_name) + self.treeview.insert("", "end", values=( + server.game_name, + server.get_game_mode(), + f"{server.players}/{server.max_players}", + "Yes" if server.has_password else "No", + server.version + )) def on_server_select(self, event): - """Handle left-click on a server in the listbox.""" - selected_index = self.listbox.curselection() # Get the index of the selected item - if selected_index: - # Get the selected server object (using the index) - selected_server = self.servers[selected_index[0]] - - # Show detailed information in the Text widget - server_details = ( - f"Game Name: {selected_server.game_name}\n" - f"Game Mode: {selected_server.get_game_mode()}\n" - f"Address (IPv4): {selected_server.address_ipv4}\n" - #f"Address (IPv6): {selected_server.address_ipv6}\n" - #f"LIP: {selected_server.lip}\n" - f"Port: {selected_server.port}\n" - f"Map Name: {selected_server.map_name}\n" - f"Players: {selected_server.players}/{selected_server.max_players}\n" - f"Bots: {selected_server.bots}\n" - f"Password Protected: {'Yes' if selected_server.has_password else 'No'}\n" - f"Version: {selected_server.version}\n" - #f"Version Number: {selected_server.version_nr}\n" - #f"Application Instance: {selected_server.application_instance}\n" - ) - - # Clear and insert the server details in the Text widget - self.details_text.delete(1.0, tk.END) - self.details_text.insert(tk.END, server_details) + """Handle left-click on a server in the Treeview.""" + selected_item = self.treeview.selection() # Get the selected item + if selected_item: + # Get the corresponding server object for the selected item + selected_server = None + for item in selected_item: + # Find the server object based on the item value (server game name or other unique info) + for server in self.servers: + if server.game_name == self.treeview.item(item)["values"][0]: # Match by game name (or any unique identifier) + selected_server = server + break + + if selected_server: + # Create detailed information for the selected server + server_details = ( + f"Game Name: {selected_server.game_name}\n" + f"Game Mode: {selected_server.get_game_mode()}\n" + f"Address (IPv4): {selected_server.address_ipv4}\n" + f"Port: {selected_server.port}\n" + f"Map Name: {selected_server.map_name}\n" + f"Players: {selected_server.players}/{selected_server.max_players}\n" + f"Bots: {selected_server.bots}\n" + f"Password Protected: {'Yes' if selected_server.has_password else 'No'}\n" + f"Version: {selected_server.version}\n" + ) + + # Clear previous details in the frame + for widget in self.details_frame.winfo_children(): + widget.destroy() + + # Create a Label for the server details and pack it in the frame + details_label = tk.Label(self.details_frame, text=server_details, justify=tk.LEFT) + details_label.pack(fill=tk.X, padx=10, pady=5) + + + def sort_treeview(self, col): + """Sort the Treeview by the selected column.""" + column_index = self.treeview["columns"].index(col) + items = [(self.treeview.item(item)["values"], item) for item in self.treeview.get_children()] + + if col == "Players": + # Sort by the current players (extracted from the format current_players/max_players) + items.sort(key=lambda x: int(x[0][2].split('/')[0]), reverse=True) # Sorting by current players + elif col == "Password": + # Sort by password status (No should come before yes) + items.sort(key=lambda x: x[0][3] == "Yes", reverse=False) # False (No) comes first + else: + # Default sorting for other columns (Game Name, Game Mode, Version) + items.sort(key=lambda x: x[0][column_index]) # Alphabetical sorting for text columns + + # Re-insert sorted items into the Treeview + for i, (_, item) in enumerate(items): + self.treeview.move(item, '', i) def start_auto_fetch(self): """Start a periodic task to fetch game servers automatically.""" self.fetch_servers() # Fetch servers immediately self.root.after(self.fetch_interval, - self.start_auto_fetch) # Set the next fetch after the interval + self.start_auto_fetch) # Set the next fetch after the interval def run_gui(): """Set up the main Tkinter GUI window and start the application."""