Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import os
- import shutil
- import xml.etree.ElementTree as ET
- import tkinter as tk
- from tkinter import messagebox, filedialog, colorchooser
- from PIL import Image, ImageTk
- import subprocess
- from tkinter import ttk
- import sys
- def get_config_path():
- """Gets the path of the config.txt file in the executable's folder."""
- if getattr(sys, 'frozen', False):
- base_dir = os.path.dirname(sys.executable) # If it's packaged
- else:
- base_dir = os.path.dirname(os.path.abspath(__file__)) # If running from the script
- return os.path.join(base_dir, "config.txt")
- class LaunchBoxManager:
- def __init__(self, root):
- self.root = root
- self.root.title("LaunchBox Media Manager")
- self.root.geometry("800x600")
- self.platforms = {}
- self.launchbox_path = None
- self.filters = ["Box - Front", "Clear Logo"]
- self.show_images = False
- self.image_references = {}
- self.last_sorted_column = None
- self.videos_enabled = False
- self.manual_enabled = False
- self.sort_ascending = True
- self.cell_size_text = (100, 50)
- self.cell_size_image = (150, 150)
- self.columns = 0
- self.image_cache_ratio = "150x150"
- self.alternative_path = None
- self.max_games_per_page_imagemode = 100
- self.make_cache = True
- self.current_page = 0
- self.total_pages = 0
- self.filter_active = False
- # Colors
- self.color_media_yes_title = "#0073E6" # Blue
- self.color_media_yes_rom = "#00E673" # Green
- self.color_media_both = "#E67300" # Orange
- self.color_media_no = "#E60073" # Red
- self.color_no_trans = "#C0C0C0" # Gray
- max_retries = 2
- retries = 0
- # Get the path of the config.txt file
- self.config_file = get_config_path()
- # Create a label for the filtering message
- self.filtering_label = tk.Label(self.root, text="Filtering files with all YES...", font=("Arial", 14),
- bg="white")
- # Load configuration on startup
- if not self.load_launchbox_path():
- messagebox.showerror("Error",
- "Could not load LaunchBox configuration. Please verify the path in config.txt.")
- self.root.destroy()
- return
- self.setup_ui()
- def setup_ui(self):
- # Top frame (for platform selection)
- self.top_frame = tk.Frame(self.root)
- self.top_frame.pack(fill="x", padx=10, pady=10)
- # Label and Combobox for selecting the platform
- self.platform_label = tk.Label(self.top_frame, text="Platform:")
- self.platform_label.pack(side="left", padx=(0, 5))
- self.platform_combobox = tk.StringVar()
- self.platform_menu = ttk.Combobox(self.top_frame, textvariable=self.platform_combobox, state="readonly")
- self.platform_menu.pack(side="left", fill="x", expand=True, padx=(0, 10))
- self.platform_combobox.trace("w", self.on_platform_select)
- # Button to load platforms
- self.load_button = tk.Button(self.top_frame, text="Load Platforms", command=self.load_platforms)
- self.load_button.pack(side="left")
- # Frame for Checkbuttons (Image Mode and Hide all YES)
- self.checkbutton_frame = tk.Frame(self.top_frame)
- self.checkbutton_frame.pack(side="left", padx=(10, 0))
- # Checkbutton for image mode
- self.switch_var = tk.BooleanVar()
- self.switch = tk.Checkbutton(self.checkbutton_frame, text="Image Mode", variable=self.switch_var,
- command=self.toggle_view_mode)
- self.switch.pack(anchor="w") # Aligned to the left within the Frame
- # Button to apply the "Hide all YES" filter
- self.hide_all_si_button = tk.Button(self.checkbutton_frame, text="Hide all YES",
- command=self.apply_hide_all_si_filter)
- self.hide_all_si_button.pack(anchor="w") # Aligned to the left within the Frame
- # Button to open settings (on the right)
- self.settings_button = tk.Button(self.top_frame, text="Settings", command=self.open_settings)
- self.settings_button.pack(side="right", padx=(10, 0))
- # Frame for filters and the table
- self.filter_frame = tk.LabelFrame(self.root, text="Filters")
- self.filter_frame.pack(fill="both", expand=True, padx=10, pady=10)
- # Canvas and scrollbars for the table
- self.header_canvas = tk.Canvas(self.filter_frame, borderwidth=1, relief="solid", height=self.cell_size_text[1])
- self.header_canvas.pack(fill="x", side="top")
- self.header_scrollbar_x = tk.Scrollbar(self.filter_frame, orient="horizontal",
- command=lambda *args: self.sync_scroll(*args))
- self.header_scrollbar_x.pack(side="top", fill="x")
- self.header_canvas.config(xscrollcommand=self.header_scrollbar_x.set)
- self.canvas_frame = tk.Frame(self.filter_frame)
- self.canvas_frame.pack(fill="both", expand=True)
- self.canvas = tk.Canvas(self.canvas_frame, borderwidth=1, relief="solid")
- self.canvas.pack(side="left", fill="both", expand=True)
- self.canvas_scrollbar_y = tk.Scrollbar(self.canvas_frame, orient="vertical", command=self.canvas.yview)
- self.canvas_scrollbar_y.pack(side="right", fill="y")
- self.canvas.config(yscrollcommand=self.canvas_scrollbar_y.set)
- self.canvas_scrollbar_x = tk.Scrollbar(self.filter_frame, orient="horizontal",
- command=lambda *args: self.sync_scroll(*args))
- self.canvas_scrollbar_x.pack(side="bottom", fill="x")
- self.canvas.config(xscrollcommand=self.canvas_scrollbar_x.set)
- self.canvas.bind("<Configure>", lambda e: self.sync_scroll())
- # Bind the mouse wheel event to the Canvas
- self.canvas.bind("<MouseWheel>", self.on_mouse_wheel_cells)
- # Frame for pagination and additional buttons
- self.pagination_frame = tk.Frame(self.root)
- self.pagination_frame.pack(fill="x", padx=10, pady=10)
- self.center_frame = tk.Frame(self.pagination_frame)
- self.center_frame.pack()
- # "Previous" button
- self.prev_button = tk.Button(self.center_frame, text="Previous", command=self.prev_page)
- self.prev_button.pack(side="left")
- # Current page label
- self.page_label = tk.Label(self.center_frame, text="Page 1 of 1")
- self.page_label.pack(side="left", padx=10)
- # "Next" button
- self.next_button = tk.Button(self.center_frame, text="Next", command=self.next_page)
- self.next_button.pack(side="left")
- # "Generate all Cache" button
- self.generate_all_cache_button = tk.Button(self.center_frame, text="Generate all Cache",
- command=self.generate_all_cache)
- self.generate_all_cache_button.pack(side="left", padx=(10, 0))
- # "Regenerate cache" button
- self.regenerate_cache_button = tk.Button(self.center_frame, text="Regenerate Cache",
- command=self.regenerate_cache)
- self.regenerate_cache_button.pack(side="left", padx=(10, 0))
- # Hide or show cache buttons based on make_cache
- self.update_cache_buttons_visibility()
- # Create a label for the filtering message on the Canvas
- self.filtering_label = tk.Label(self.canvas, text="Filtering files with all YES...", font=("Arial", 14),
- bg="white", fg="black")
- def get_base_dir(self):
- """Gets the base path depending on whether it's packaged or not."""
- if getattr(sys, 'frozen', False):
- # If packaged, use the executable's directory
- return os.path.dirname(sys.executable)
- else:
- # If not packaged, use the script's path
- return os.path.dirname(os.path.abspath(__file__))
- def get_cache_folder(self):
- """Gets the path of the cache folder next to the executable."""
- base_dir = self.get_base_dir()
- cache_folder = os.path.join(base_dir, "cache")
- os.makedirs(cache_folder, exist_ok=True) # Create the folder if it doesn't exist
- return cache_folder
- def on_mouse_wheel_cells(self, event):
- menu_state = str(self.platform_menu.cget("state")) # Save it as an immutable string
- if menu_state == "readonly":
- delta = -event.delta
- if delta > 0:
- self.canvas.yview_scroll(1, "units")
- else:
- self.canvas.yview_scroll(-1, "units")
- def on_mouse_wheel_platforms(self, event):
- """
- Handles vertical scrolling with the mouse wheel for the platform list.
- """
- menu_state = self.platform_menu.cget("state")
- if menu_state == "normal": # Only scroll if the menu is expanded
- current_index = self.platform_menu.current()
- if event.delta > 0:
- new_index = max(0, current_index - 1) # Scroll up
- else:
- new_index = min(len(self.platform_menu["values"]) - 1, current_index + 1) # Scroll down
- self.platform_menu.current(new_index)
- self.platform_combobox.set(self.platform_menu["values"][new_index])
- self.on_platform_select()
- def sync_scroll(self, *args):
- """
- Synchronizes horizontal scrolling between the header_canvas and the main canvas.
- Does not affect vertical scrolling.
- """
- if len(args) == 2 and args[0] == "moveto":
- fraction = float(args[1])
- self.header_canvas.xview_moveto(fraction)
- self.canvas.xview_moveto(fraction)
- elif len(args) == 2 and args[0] == "scroll":
- amount, units = args[1].split()
- self.header_canvas.xview_scroll(amount, units)
- self.canvas.xview_scroll(amount, units)
- else:
- fraction = self.canvas.xview()[0]
- self.header_canvas.xview_moveto(fraction)
- def apply_hide_all_si_filter(self):
- # Show the filtering message
- self.filtering_label.place(relx=0.5, rely=0.5, anchor="center")
- self.root.update() # Force interface update
- # Apply the filter
- self.filter_active = not self.filter_active # Toggle filter state
- self.update_table_with_filter()
- # Hide the filtering message
- self.filtering_label.place_forget()
- # Get games from the selected platform
- selected_platform = self.platform_combobox.get()
- if not selected_platform:
- return
- xml_path = self.platforms[selected_platform]
- tree = ET.parse(xml_path)
- root = tree.getroot()
- games = root.findall("Game")
- game_dict = {}
- for game in games:
- title_element = game.find("Title")
- title = title_element.text if title_element is not None else None
- app_path_element = game.find("ApplicationPath")
- if app_path_element is not None and app_path_element.text:
- app_filename = os.path.basename(app_path_element.text)
- app_name = os.path.splitext(app_filename)[0]
- else:
- app_name = None
- if title:
- game_dict[title] = [title]
- if app_name and app_name != title:
- game_dict[title].append(app_name)
- # Sort games alphabetically before applying the filter
- sorted_game_dict = dict(sorted(game_dict.items(), key=lambda item: (self.natural_sort_key(item[0]), item[0])))
- # Apply the "Hide all YES" filter
- filtered_game_dict = self.filter_all_si(sorted_game_dict)
- # Update the table with the filtered games
- self.load_filter_file(selected_platform, filtered_game_dict)
- def update_cache_buttons_visibility(self):
- """Updates the visibility of cache buttons based on the value of make_cache."""
- if self.make_cache:
- self.generate_all_cache_button.pack(side="left", padx=(10, 0))
- self.regenerate_cache_button.pack(side="left", padx=(10, 0))
- else:
- self.generate_all_cache_button.pack_forget()
- self.regenerate_cache_button.pack_forget()
- def load_platforms(self):
- self.platform_combobox.set("") # Clear current selection
- self.platforms.clear() # Clear the platforms dictionary
- # Build the path to the platforms folder
- platforms_path = os.path.join(self.launchbox_path, "Data", "Platforms").replace("\\", "/")
- if not os.path.exists(platforms_path):
- messagebox.showerror("Error", f"Platforms folder not found at: {platforms_path}")
- return
- # Get platform names
- platform_names = []
- for filename in os.listdir(platforms_path):
- if filename.endswith(".xml"):
- platform_name = filename[:-4] # Remove the .xml extension
- self.platforms[platform_name] = os.path.join(platforms_path, filename).replace("\\", "/")
- platform_names.append(platform_name)
- if not platform_names:
- messagebox.showerror("Error", "No XML files found in the platforms folder.")
- return
- # Assign values to the Combobox
- self.platform_menu["values"] = platform_names
- self.platform_menu.current(0) # Select the first platform by default
- # Force interface update
- self.platform_menu.update_idletasks()
- # Load data for the selected platform
- self.on_platform_select()
- def hide_all_si(self, game_dict=None):
- if game_dict is None:
- selected_platform = self.platform_combobox.get()
- if not selected_platform:
- return
- xml_path = self.platforms[selected_platform]
- tree = ET.parse(xml_path)
- root = tree.getroot()
- games = root.findall("Game")
- game_dict = {}
- for game in games:
- title_element = game.find("Title")
- title = title_element.text if title_element is not None else None
- app_path_element = game.find("ApplicationPath")
- if app_path_element is not None and app_path_element.text:
- app_filename = os.path.basename(app_path_element.text)
- app_name = os.path.splitext(app_filename)[0]
- else:
- app_name = None
- if title:
- game_dict[title] = [title]
- if app_name and app_name != title:
- game_dict[title].append(app_name)
- # Filter games that have "YES" in all cells
- filtered_game_dict = self.filter_all_si(game_dict)
- # Update the table with the filtered games
- selected_platform = self.platform_combobox.get()
- self.load_filter_file(selected_platform, filtered_game_dict)
- def on_platform_select(self, *args):
- selected_platform = self.platform_combobox.get()
- if not selected_platform:
- return
- try:
- xml_path = self.platforms[selected_platform]
- tree = ET.parse(xml_path)
- root = tree.getroot()
- if root.tag != "LaunchBox":
- messagebox.showerror("Error",
- f"The XML file does not have the expected structure. Root element: {root.tag}")
- return
- games = root.findall("Game")
- game_dict = {}
- for game in games:
- title_element = game.find("Title")
- title = title_element.text if title_element is not None else None
- app_path_element = game.find("ApplicationPath")
- if app_path_element is not None and app_path_element.text:
- app_filename = os.path.basename(app_path_element.text)
- app_name = os.path.splitext(app_filename)[0]
- else:
- app_name = None
- if title:
- game_dict[title] = [title]
- if app_name and app_name != title:
- game_dict[title].append(app_name)
- sorted_game_dict = dict(
- sorted(game_dict.items(), key=lambda item: (self.natural_sort_key(item[0]), item[0])))
- self.load_filter_file(selected_platform, sorted_game_dict)
- except Exception as e:
- messagebox.showerror("Error", f"Could not read the XML file: {e}")
- def generate_all_cache(self):
- selected_platform = self.platform_combobox.get()
- if not selected_platform:
- return
- self.progress_label = tk.Label(self.root, text="Generating Cache...")
- self.progress_label.place(relx=0.5, rely=0.5, anchor="center")
- self.root.update()
- original_page = self.current_page
- for page in range(self.total_pages):
- self.current_page = page
- self.on_platform_select()
- self.progress_label.config(text=f"Generating page {page + 1} of {self.total_pages}")
- self.root.update()
- self.current_page = original_page
- self.on_platform_select()
- self.progress_label.destroy()
- def open_settings(self):
- # Get the path of the config.txt file
- config_file = get_config_path()
- # Create a new window for settings
- settings_window = tk.Toplevel(self.root)
- settings_app = SettingsWindow(settings_window, config_file)
- # Wait for the settings window to close
- self.root.wait_window(settings_window)
- # Reload configuration after saving changes
- self.load_launchbox_path()
- # Update cache buttons visibility
- self.update_cache_buttons_visibility()
- # Update other UI elements if necessary
- self.on_platform_select() # This updates the table with the new values
- def load_launchbox_path(self):
- """Loads configuration from the config.txt file."""
- print(f"Path of config.txt file: {self.config_file}") # Debug
- if not os.path.exists(self.config_file):
- print("Creating config.txt file...") # Debug
- with open(self.config_file, "w") as file:
- file.write("path=\n")
- file.write("filters=Box - Front,Clear Logo\n")
- file.write("image_cache_ratio=150x150\n")
- file.write("alternative_path=\n")
- file.write("image_cell=150x150\n")
- file.write("text_cell=150x50\n")
- file.write("max_games_per_page_imagemode=100\n")
- file.write("color_media_yes_title=#0073E6\n")
- file.write("color_media_yes_rom=#00E673\n")
- file.write("color_media_both=#E67300\n") # Ensure this line is present
- file.write("color_media_no=#E60073\n")
- file.write("color_no_trans=#C0C0C0\n")
- file.write("make_cache=true\n")
- # Load values from the config.txt file
- with open(self.config_file, "r") as file:
- for line in file:
- key, value = line.strip().split('=', 1)
- value = value.strip('"')
- if key == "path":
- self.launchbox_path = value
- elif key == "filters":
- self.filters = [f.strip().strip('"') for f in value.split(',')]
- elif key == "image_cache_ratio":
- self.image_cache_ratio = value
- elif key == "alternative_path":
- self.alternative_path = value
- elif key == "image_cell":
- try:
- width, height = value.split('x')
- self.cell_size_image = (int(width), int(height))
- except ValueError:
- print(
- "Error: The value of image_cell is not in the correct format. Using default values (200x200).")
- self.cell_size_image = (200, 200)
- elif key == "text_cell":
- try:
- width, height = value.split('x')
- self.cell_size_text = (int(width), int(height))
- except ValueError:
- print(
- "Error: The value of text_cell is not in the correct format. Using default values (100x50).")
- self.cell_size_text = (100, 50)
- elif key == "max_games_per_page_imagemode":
- try:
- self.max_games_per_page_imagemode = int(value)
- except ValueError:
- print(
- "Error: The value of max_games_per_page_imagemode is not a valid number. Using default value (20).")
- self.max_games_per_page_imagemode = 20
- elif key == "color_media_yes_title":
- self.color_media_yes_title = value
- elif key == "color_media_yes_rom":
- self.color_media_yes_rom = value
- elif key == "color_media_both": # Ensure this key is present
- self.color_media_both = value
- elif key == "color_media_no":
- self.color_media_no = value
- elif key == "color_no_trans":
- self.color_no_trans = value
- elif key == "make_cache":
- self.make_cache = value.lower() == "true"
- print("Configuration loaded successfully.")
- return True
- def load_platforms(self):
- self.platform_combobox.set("") # Clear current selection
- self.platforms.clear() # Clear the platforms dictionary
- # Build the path to the platforms folder
- platforms_path = os.path.join(self.launchbox_path, "Data", "Platforms").replace("\\", "/")
- if not os.path.exists(platforms_path):
- messagebox.showerror("Error", f"Platforms folder not found at: {platforms_path}")
- return
- # Get platform names
- platform_names = []
- for filename in os.listdir(platforms_path):
- if filename.endswith(".xml"):
- platform_name = filename[:-4] # Remove the .xml extension
- self.platforms[platform_name] = os.path.join(platforms_path, filename).replace("\\", "/")
- platform_names.append(platform_name)
- if not platform_names:
- messagebox.showerror("Error", "No XML files found in the platforms folder.")
- return
- # Assign values to the Combobox
- self.platform_menu["values"] = platform_names
- self.platform_menu.current(0) # Select the first platform by default
- # Force interface update
- self.platform_menu.update_idletasks()
- # Load data for the selected platform
- self.on_platform_select()
- def on_platform_select(self, *args):
- selected_platform = self.platform_combobox.get()
- if not selected_platform:
- return
- try:
- xml_path = self.platforms[selected_platform]
- tree = ET.parse(xml_path)
- root = tree.getroot()
- if root.tag != "LaunchBox":
- messagebox.showerror("Error",
- f"The XML file does not have the expected structure. Root element: {root.tag}")
- return
- games = root.findall("Game")
- game_dict = {}
- for game in games:
- title_element = game.find("Title")
- title = title_element.text if title_element is not None else None
- app_path_element = game.find("ApplicationPath")
- if app_path_element is not None and app_path_element.text:
- app_filename = os.path.basename(app_path_element.text)
- app_name = os.path.splitext(app_filename)[0]
- else:
- app_name = None
- if title:
- game_dict[title] = [title]
- if app_name and app_name != title:
- game_dict[title].append(app_name)
- sorted_game_dict = dict(
- sorted(game_dict.items(), key=lambda item: (self.natural_sort_key(item[0]), item[0])))
- self.load_filter_file(selected_platform, sorted_game_dict)
- except Exception as e:
- messagebox.showerror("Error", f"Could not read the XML file: {e}")
- def load_filter_file(self, platform, game_dict):
- filters_to_use = self.filters[:]
- if self.videos_enabled:
- filters_to_use.append("Videos")
- if self.manual_enabled:
- filters_to_use.append("Manual")
- self.columns = len(filters_to_use) + 1 # Define self.columns
- self.total_pages = (len(game_dict) + self.max_games_per_page_imagemode - 1) // self.max_games_per_page_imagemode
- self.update_pagination_controls()
- start_index = self.current_page * self.max_games_per_page_imagemode
- end_index = start_index + self.max_games_per_page_imagemode
- self.games_to_show = list(game_dict.items())[start_index:end_index] # Save the games being shown
- self.canvas.delete("all")
- self.image_references = {}
- self.draw_table(self.games_to_show, filters_to_use)
- total_width = sum(
- self.cell_size_image[0] if self.show_images else self.cell_size_text[0] for _ in range(self.columns))
- total_height = self.rows * (self.cell_size_image[1] if self.show_images else self.cell_size_text[1])
- self.canvas.config(scrollregion=(0, 0, total_width, total_height))
- def prev_page(self):
- if self.current_page > 0:
- self.current_page -= 1
- self.update_pagination_controls()
- self.update_table_with_filter()
- def next_page(self):
- if self.current_page < self.total_pages - 1:
- self.current_page += 1
- self.update_pagination_controls()
- self.update_table_with_filter()
- def update_pagination_controls(self):
- self.page_label.config(text=f"Page {self.current_page + 1} of {self.total_pages}")
- self.prev_button.config(state="normal" if self.current_page > 0 else "disabled")
- self.next_button.config(state="normal" if self.current_page < self.total_pages - 1 else "disabled")
- def toggle_view_mode(self):
- self.show_images = self.switch_var.get()
- if self.show_images:
- self.header_canvas.config(height=self.cell_size_image[1])
- else:
- self.header_canvas.config(height=self.cell_size_text[1])
- self.on_platform_select()
- def search_image_in_folder(self, folder, search_names, extensions):
- """
- Searches for images in a folder and its subfolders recursively.
- Returns the path of the image if found, or None if not found.
- """
- for root, dirs, files in os.walk(folder):
- for file in files:
- # Check if the file matches any of the names and extensions
- for search_name in search_names:
- for ext in extensions:
- if file.startswith(search_name) and file.endswith(ext):
- return os.path.join(root, file), search_name
- return None, None
- def draw_table(self, games_to_show, filters):
- self.rows = len(games_to_show)
- if self.show_images:
- cell_width, cell_height = self.cell_size_image
- else:
- cell_width, cell_height = self.cell_size_text
- total_width = sum(cell_width for _ in range(self.columns))
- self.header_canvas.config(width=total_width, height=cell_height)
- self.header_canvas.delete("all")
- for col, filter_name in enumerate(["Title"] + filters):
- self.draw_cell(0, col, filter_name, header=True, canvas=self.header_canvas)
- self.header_canvas.tag_bind(f"header_{col}", "<Button-1>", lambda event, col=col: self.on_header_click(col))
- for row, (title, search_names) in enumerate(games_to_show, start=0):
- self.draw_cell(row, 0, title)
- for col, filter_name in enumerate(filters, start=1):
- if filter_name == "Videos":
- # Define main and alternative paths
- main_folder_path = os.path.join(self.launchbox_path, "Videos",
- self.platform_combobox.get()).replace("\\", "/")
- alternative_folder_path = os.path.join(self.alternative_path, self.platform_combobox.get(),
- "Videos").replace("\\",
- "/") if self.alternative_path else None
- file_found = False
- match_type = None
- file_path = None
- # Search in the alternative path first (if it exists)
- if alternative_folder_path and os.path.exists(alternative_folder_path):
- # Search for <Title>-01.mp4
- file_path = os.path.join(alternative_folder_path, f"{search_names[0]}-01.mp4").replace("\\",
- "/")
- if os.path.isfile(file_path):
- file_found = True
- match_type = 'title'
- else:
- # Search for <ApplicationPath>.mp4 (without -01)
- if len(search_names) > 1:
- file_path = os.path.join(alternative_folder_path, f"{search_names[1]}.mp4").replace(
- "\\", "/")
- if os.path.isfile(file_path):
- file_found = True
- match_type = 'rom'
- else:
- # If <Title> and <ApplicationPath> are the same, search for <Title>.mp4 (without -01)
- file_path = os.path.join(alternative_folder_path, f"{search_names[0]}.mp4").replace(
- "\\", "/")
- if os.path.isfile(file_path):
- file_found = True
- match_type = 'rom'
- # If not found in the alternative path, search in the main path
- if not file_found and os.path.exists(main_folder_path):
- # Search for <Title>-01.mp4
- file_path = os.path.join(main_folder_path, f"{search_names[0]}-01.mp4").replace("\\", "/")
- if os.path.isfile(file_path):
- file_found = True
- match_type = 'title'
- else:
- # Search for <ApplicationPath>.mp4 (without -01)
- if len(search_names) > 1:
- file_path = os.path.join(main_folder_path, f"{search_names[1]}.mp4").replace("\\", "/")
- if os.path.isfile(file_path):
- file_found = True
- match_type = 'rom'
- else:
- # If <Title> and <ApplicationPath> are the same, search for <Title>.mp4 (without -01)
- file_path = os.path.join(main_folder_path, f"{search_names[0]}.mp4").replace("\\", "/")
- if os.path.isfile(file_path):
- file_found = True
- match_type = 'rom'
- # Show the result in the table
- if self.show_images:
- if file_found:
- self.draw_cell(row, col, "YES",
- cell_color=self.color_media_yes_title if match_type == 'title' else self.color_media_yes_rom)
- else:
- self.draw_cell(row, col, "NO", cell_color=self.color_media_no)
- else:
- cell_value = "YES (Title)" if match_type == 'title' else "YES (Rom)" if match_type == 'rom' else "NO"
- cell_color = self.color_media_yes_title if match_type == 'title' else self.color_media_yes_rom if match_type == 'rom' else self.color_media_no
- self.draw_cell(row, col, cell_value, cell_color=cell_color, is_text=True)
- elif filter_name == "Manual":
- # Define main and alternative paths
- main_folder_path = os.path.join(self.launchbox_path, "Manuals",
- self.platform_combobox.get()).replace("\\", "/")
- alternative_folder_path = os.path.join(self.alternative_path, self.platform_combobox.get(),
- "Manuals").replace("\\",
- "/") if self.alternative_path else None
- file_found = False
- match_type = None
- file_path = None
- # Search in the alternative path first (if it exists)
- if alternative_folder_path and os.path.exists(alternative_folder_path):
- # Search for <Title>-01.pdf
- file_path = os.path.join(alternative_folder_path, f"{search_names[0]}-01.pdf").replace("\\",
- "/")
- if os.path.isfile(file_path):
- file_found = True
- match_type = 'title'
- else:
- # Search for <ApplicationPath>.pdf (without -01)
- if len(search_names) > 1:
- file_path = os.path.join(alternative_folder_path, f"{search_names[1]}.pdf").replace(
- "\\", "/")
- if os.path.isfile(file_path):
- file_found = True
- match_type = 'rom'
- else:
- # If <Title> and <ApplicationPath> are the same, search for <Title>.pdf (without -01)
- file_path = os.path.join(alternative_folder_path, f"{search_names[0]}.pdf").replace(
- "\\", "/")
- if os.path.isfile(file_path):
- file_found = True
- match_type = 'rom'
- # If not found in the alternative path, search in the main path
- if not file_found and os.path.exists(main_folder_path):
- # Search for <Title>-01.pdf
- file_path = os.path.join(main_folder_path, f"{search_names[0]}-01.pdf").replace("\\", "/")
- if os.path.isfile(file_path):
- file_found = True
- match_type = 'title'
- else:
- # Search for <ApplicationPath>.pdf (without -01)
- if len(search_names) > 1:
- file_path = os.path.join(main_folder_path, f"{search_names[1]}.pdf").replace("\\", "/")
- if os.path.isfile(file_path):
- file_found = True
- match_type = 'rom'
- else:
- # If <Title> and <ApplicationPath> are the same, search for <Title>.pdf (without -01)
- file_path = os.path.join(main_folder_path, f"{search_names[0]}.pdf").replace("\\", "/")
- if os.path.isfile(file_path):
- file_found = True
- match_type = 'rom'
- # Show the result in the table
- if self.show_images:
- if file_found:
- self.draw_cell(row, col, "YES",
- cell_color=self.color_media_yes_title if match_type == 'title' else self.color_media_yes_rom)
- else:
- self.draw_cell(row, col, "NO", cell_color=self.color_media_no)
- else:
- cell_value = "YES (Title)" if match_type == 'title' else "YES (Rom)" if match_type == 'rom' else "NO"
- cell_color = self.color_media_yes_title if match_type == 'title' else self.color_media_yes_rom if match_type == 'rom' else self.color_media_no
- self.draw_cell(row, col, cell_value, cell_color=cell_color, is_text=True)
- else:
- image_folder = os.path.join(self.launchbox_path, "Images", self.platform_combobox.get(),
- filter_name).replace("\\", "/")
- image_found = False
- image_path = None
- match_type = None
- title_found = False
- rom_found = False
- title_path = None
- rom_path = None
- if os.path.exists(image_folder):
- # Search for <Title>-01.ext recursively
- for ext in ["png", "jpg"]:
- for root, dirs, files in os.walk(image_folder):
- for file in files:
- if file.startswith(f"{search_names[0]}-01") and file.endswith(ext):
- title_path = os.path.join(root, file)
- title_found = True
- break
- if title_found:
- break
- if title_found:
- break
- # Search for <ApplicationPath>.ext non-recursively
- if len(search_names) > 1:
- for ext in ["png", "jpg"]:
- rom_path = os.path.join(image_folder, f"{search_names[1]}.{ext}").replace("\\", "/")
- if os.path.isfile(rom_path):
- rom_found = True
- break
- else:
- # If <Title> and <ApplicationPath> are the same, search for <Title>.ext (without -01)
- for ext in ["png", "jpg"]:
- rom_path = os.path.join(image_folder, f"{search_names[0]}.{ext}").replace("\\", "/")
- if os.path.isfile(rom_path):
- rom_found = True
- break
- # Determine the match type
- if title_found and rom_found:
- match_type = 'title/rom'
- image_path = title_path # Use the Title image to display
- elif title_found:
- match_type = 'title'
- image_path = title_path
- elif rom_found:
- match_type = 'rom'
- image_path = rom_path
- if self.show_images:
- if image_path:
- self.draw_image_cell(row, col, image_path, match_type)
- else:
- self.draw_cell(row, col, "NO", cell_color=self.color_media_no)
- else:
- if match_type == 'title/rom':
- cell_value = "YES (Title/Rom)"
- cell_color = self.color_media_both
- elif match_type == 'title':
- cell_value = "YES (Title)"
- cell_color = self.color_media_yes_title
- elif match_type == 'rom':
- cell_value = "YES (Rom)"
- cell_color = self.color_media_yes_rom
- else:
- cell_value = "NO"
- cell_color = self.color_media_no
- self.draw_cell(row, col, cell_value, cell_color=cell_color, is_text=True)
- total_width = sum(
- self.cell_size_image[0] if self.show_images else self.cell_size_text[0] for _ in range(self.columns))
- total_height = len(games_to_show) * (self.cell_size_image[1] if self.show_images else self.cell_size_text[1])
- self.canvas.config(scrollregion=(0, 0, total_width, total_height))
- self.header_canvas.config(scrollregion=(0, 0, total_width, cell_height))
- # Force focus to the Canvas to receive mouse wheel events
- self.canvas.focus_set()
- def on_header_click(self, col):
- # Call the method to sort the data
- self.sort_by_column(col)
- def draw_cell(self, row, col, value, header=False, cell_color=None, is_text=False, canvas=None):
- if canvas is None:
- canvas = self.canvas
- if header:
- if self.show_images:
- cell_width, cell_height = self.cell_size_image
- else:
- cell_width, cell_height = self.cell_size_text
- elif is_text or not self.show_images:
- cell_width, cell_height = self.cell_size_text
- else:
- cell_width, cell_height = self.cell_size_image
- x1 = col * cell_width
- y1 = row * cell_height
- x2 = x1 + cell_width
- y2 = y1 + cell_height
- if value == "YES (Title)":
- fill_color = self.color_media_yes_title
- elif value == "YES (Rom)":
- fill_color = self.color_media_yes_rom
- elif value == "NO":
- fill_color = self.color_media_no
- else:
- fill_color = cell_color or "white"
- # Draw the cell
- canvas.create_rectangle(x1, y1, x2, y2, fill=fill_color, outline="black",
- tags=f"header_{col}" if header else f"cell_{row}_{col}")
- font_style = ("Arial", 10, "bold") if header else ("Arial", 10)
- wrapped_text = self.wrap_text(value, cell_width - 10)
- text_y = y1 + (cell_height - len(wrapped_text) * 12) / 2
- for line in wrapped_text:
- canvas.create_text(x1 + 5, text_y, anchor="w", text=line, font=font_style,
- tags=f"header_{col}" if header else f"cell_{row}_{col}")
- text_y += 12
- def wrap_text(self, text, max_width):
- import textwrap
- return textwrap.wrap(text, width=max_width // 7)
- def draw_image_cell(self, row, col, image_path, match_type):
- try:
- cell_width, cell_height = self.cell_size_image
- if self.make_cache:
- # Load from cache
- cache_folder = os.path.join(self.get_cache_folder(), self.platform_combobox.get(), self.filters[col - 1])
- os.makedirs(cache_folder, exist_ok=True)
- cache_image_path = os.path.join(cache_folder,
- os.path.basename(image_path).replace(".png", ".jpg").replace(".PNG",
- ".jpg"))
- width, height = map(int, self.image_cache_ratio.split("x"))
- if not os.path.exists(cache_image_path):
- try:
- file_size = os.path.getsize(image_path)
- if file_size > 100 * 1024 * 1024:
- raise ValueError(
- f"The file {image_path} is too large ({file_size / (1024 * 1024):.2f} MB)")
- with Image.open(image_path) as image:
- if image.mode in ("P", "1", "L", "LA"):
- image = image.convert("RGBA")
- if image.mode == "RGBA":
- background = Image.new("RGB", image.size, self.color_no_trans)
- background.paste(image, mask=image.split()[-1])
- image = background
- image.thumbnail((width, height))
- image.save(cache_image_path, "JPEG")
- except Exception as e:
- print(f"Error processing image {image_path}: {e}")
- with open("images_with_errors.txt", "a") as error_file:
- error_file.write(f"{image_path}\n")
- self.draw_cell(row, col, "NO", cell_color=self.color_media_no)
- return
- with Image.open(cache_image_path) as image:
- tk_image = ImageTk.PhotoImage(image)
- self.image_references[(row, col)] = tk_image
- else:
- # Load directly from the source
- with Image.open(image_path) as image:
- if image.mode in ("P", "1", "L", "LA"):
- image = image.convert("RGBA")
- if image.mode == "RGBA":
- background = Image.new("RGB", image.size, self.color_no_trans)
- background.paste(image, mask=image.split()[-1])
- image = background
- image.thumbnail((cell_width, cell_height))
- tk_image = ImageTk.PhotoImage(image)
- self.image_references[(row, col)] = tk_image
- x1 = col * self.cell_size_image[0]
- y1 = row * self.cell_size_image[1]
- bg_color = self.color_media_yes_title if match_type == 'title' else self.color_media_yes_rom
- self.canvas.create_rectangle(x1, y1, x1 + cell_width, y1 + cell_height, fill=bg_color, outline="black")
- self.canvas.create_image(x1 + cell_width // 2, y1 + cell_height // 2, anchor="center", image=tk_image)
- except Exception as e:
- print(f"Unexpected error processing image {image_path}: {e}")
- self.draw_cell(row, col, "NO", cell_color=self.color_media_no)
- with open("images_with_errors.txt", "a") as error_file:
- error_file.write(f"{image_path}\n")
- def regenerate_cache(self):
- selected_platform = self.platform_combobox.get()
- if not selected_platform:
- return
- # Show a progress message
- self.progress_label = tk.Label(self.root, text="Regenerating cache...")
- self.progress_label.place(relx=0.5, rely=0.5, anchor="center")
- self.root.update()
- # Get the path of the cache folder
- cache_folder = os.path.join(self.get_cache_folder(), selected_platform)
- # Delete the cache folder if it exists
- if os.path.exists(cache_folder):
- try:
- shutil.rmtree(cache_folder)
- print(f"Cache folder deleted: {cache_folder}")
- except Exception as e:
- print(f"Error deleting cache folder: {e}")
- self.progress_label.destroy()
- return
- # Force cache regeneration
- self.load_platforms() # Reload platforms
- self.platform_combobox.set(selected_platform) # Select the current platform
- self.on_platform_select() # Update the table
- # Hide the progress message
- self.progress_label.destroy()
- def natural_sort_key(self, text):
- import re
- return [int(text) if text.isdigit() else text.lower() for text in re.split(r'(\d+)', text)]
- def sort_by_column(self, col):
- selected_platform = self.platform_combobox.get()
- if not selected_platform:
- return
- xml_path = self.platforms[selected_platform]
- tree = ET.parse(xml_path)
- root = tree.getroot()
- games = root.findall("Game")
- game_dict = {}
- for game in games:
- title_element = game.find("Title")
- title = title_element.text if title_element is not None else None
- app_path_element = game.find("ApplicationPath")
- if app_path_element is not None and app_path_element.text:
- app_filename = os.path.basename(app_path_element.text)
- app_name = os.path.splitext(app_filename)[0]
- else:
- app_name = None
- if title:
- game_dict[title] = [title]
- if app_name and app_name != title:
- game_dict[title].append(app_name)
- # Sort games
- if col == 0:
- # Sort by title
- sorted_game_dict = dict(
- sorted(game_dict.items(), key=lambda item: (self.natural_sort_key(item[0]), item[0])))
- else:
- # Sort by the selected column
- filter_name = self.filters[col - 1]
- sorted_game_dict = dict(
- sorted(game_dict.items(), key=lambda item: self.get_filter_value(item[1], filter_name)))
- # Toggle between ascending and descending order
- if self.last_sorted_column == col:
- self.sort_ascending = not self.sort_ascending
- if not self.sort_ascending:
- sorted_game_dict = dict(reversed(list(sorted_game_dict.items())))
- else:
- self.sort_ascending = True
- self.last_sorted_column = col
- # If the "Hide all YES" filter is active, apply the filter after sorting
- if self.hide_all_si_var.get():
- sorted_game_dict = self.filter_all_si(sorted_game_dict)
- # Update the GUI with the sorted (and filtered if necessary) games
- self.load_filter_file(selected_platform, sorted_game_dict)
- def filter_all_si(self, game_dict):
- """
- Filters games that have "YES" in all cells, based on cell colors.
- """
- filtered_game_dict = {}
- for title, search_names in game_dict.items():
- all_si = True # Assume all cells have "YES" until proven otherwise
- # Check each filter for this game
- for filter_name in self.filters:
- # Get the color of the corresponding cell
- cell_color = self.get_cell_color(title, filter_name, search_names)
- # If the color is not one of the colors indicating "YES", mark as NO
- if cell_color not in [self.color_media_yes_title, self.color_media_yes_rom, self.color_media_both]:
- all_si = False
- break
- # If all cells have "YES", do not add the game to the filtered dictionary
- if not all_si:
- filtered_game_dict[title] = search_names
- return filtered_game_dict
- def get_cell_color(self, title, filter_name, search_names):
- """
- Gets the color of the cell corresponding to the given title and filter.
- """
- if filter_name == "Videos":
- # Logic for videos
- main_folder_path = os.path.join(self.launchbox_path, "Videos", self.platform_combobox.get()).replace("\\",
- "/")
- alternative_folder_path = os.path.join(self.alternative_path, self.platform_combobox.get(),
- "Videos").replace("\\", "/") if self.alternative_path else None
- file_found = False
- match_type = None
- # Search in the alternative path first (if it exists)
- if alternative_folder_path and os.path.exists(alternative_folder_path):
- # Search for <Title>-01.mp4
- file_path = os.path.join(alternative_folder_path, f"{search_names[0]}-01.mp4").replace("\\", "/")
- if os.path.isfile(file_path):
- file_found = True
- match_type = 'title'
- else:
- # Search for <ApplicationPath>.mp4 (without -01)
- if len(search_names) > 1:
- file_path = os.path.join(alternative_folder_path, f"{search_names[1]}.mp4").replace("\\", "/")
- if os.path.isfile(file_path):
- file_found = True
- match_type = 'rom'
- else:
- # If <Title> and <ApplicationPath> are the same, search for <Title>.mp4 (without -01)
- file_path = os.path.join(alternative_folder_path, f"{search_names[0]}.mp4").replace("\\", "/")
- if os.path.isfile(file_path):
- file_found = True
- match_type = 'rom'
- # If not found in the alternative path, search in the main path
- if not file_found and os.path.exists(main_folder_path):
- # Search for <Title>-01.mp4
- file_path = os.path.join(main_folder_path, f"{search_names[0]}-01.mp4").replace("\\", "/")
- if os.path.isfile(file_path):
- file_found = True
- match_type = 'title'
- else:
- # Search for <ApplicationPath>.mp4 (without -01)
- if len(search_names) > 1:
- file_path = os.path.join(main_folder_path, f"{search_names[1]}.mp4").replace("\\", "/")
- if os.path.isfile(file_path):
- file_found = True
- match_type = 'rom'
- else:
- # If <Title> and <ApplicationPath> are the same, search for <Title>.mp4 (without -01)
- file_path = os.path.join(main_folder_path, f"{search_names[0]}.mp4").replace("\\", "/")
- if os.path.isfile(file_path):
- file_found = True
- match_type = 'rom'
- # Determine the color based on the result
- if file_found:
- if match_type == 'title':
- return self.color_media_yes_title
- elif match_type == 'rom':
- return self.color_media_yes_rom
- else:
- return self.color_media_no
- elif filter_name == "Manual":
- # Logic for manuals (similar to videos)
- main_folder_path = os.path.join(self.launchbox_path, "Manuals", self.platform_combobox.get()).replace("\\",
- "/")
- alternative_folder_path = os.path.join(self.alternative_path, self.platform_combobox.get(),
- "Manuals").replace("\\", "/") if self.alternative_path else None
- file_found = False
- match_type = None
- # Search in the alternative path first (if it exists)
- if alternative_folder_path and os.path.exists(alternative_folder_path):
- # Search for <Title>-01.pdf
- file_path = os.path.join(alternative_folder_path, f"{search_names[0]}-01.pdf").replace("\\", "/")
- if os.path.isfile(file_path):
- file_found = True
- match_type = 'title'
- else:
- # Search for <ApplicationPath>.pdf (without -01)
- if len(search_names) > 1:
- file_path = os.path.join(alternative_folder_path, f"{search_names[1]}.pdf").replace("\\", "/")
- if os.path.isfile(file_path):
- file_found = True
- match_type = 'rom'
- else:
- # If <Title> and <ApplicationPath> are the same, search for <Title>.pdf (without -01)
- file_path = os.path.join(alternative_folder_path, f"{search_names[0]}.pdf").replace("\\", "/")
- if os.path.isfile(file_path):
- file_found = True
- match_type = 'rom'
- # If not found in the alternative path, search in the main path
- if not file_found and os.path.exists(main_folder_path):
- # Search for <Title>-01.pdf
- file_path = os.path.join(main_folder_path, f"{search_names[0]}-01.pdf").replace("\\", "/")
- if os.path.isfile(file_path):
- file_found = True
- match_type = 'title'
- else:
- # Search for <ApplicationPath>.pdf (without -01)
- if len(search_names) > 1:
- file_path = os.path.join(main_folder_path, f"{search_names[1]}.pdf").replace("\\", "/")
- if os.path.isfile(file_path):
- file_found = True
- match_type = 'rom'
- else:
- # If <Title> and <ApplicationPath> are the same, search for <Title>.pdf (without -01)
- file_path = os.path.join(main_folder_path, f"{search_names[0]}.pdf").replace("\\", "/")
- if os.path.isfile(file_path):
- file_found = True
- match_type = 'rom'
- # Determine the color based on the result
- if file_found:
- if match_type == 'title':
- return self.color_media_yes_title
- elif match_type == 'rom':
- return self.color_media_yes_rom
- else:
- return self.color_media_no
- else:
- # Logic for images
- image_folder = os.path.join(self.launchbox_path, "Images", self.platform_combobox.get(),
- filter_name).replace("\\", "/")
- image_found = False
- match_type = None
- if os.path.exists(image_folder):
- # Search for <Title>-01.ext recursively
- for ext in ["png", "jpg"]:
- for root, dirs, files in os.walk(image_folder):
- for file in files:
- if file.startswith(f"{search_names[0]}-01") and file.endswith(ext):
- image_found = True
- match_type = 'title'
- break
- if image_found:
- break
- if image_found:
- break
- # Search for <ApplicationPath>.ext non-recursively
- if not image_found and len(search_names) > 1:
- for ext in ["png", "jpg"]:
- image_path = os.path.join(image_folder, f"{search_names[1]}.{ext}").replace("\\", "/")
- if os.path.isfile(image_path):
- image_found = True
- match_type = 'rom'
- break
- # If <Title> and <ApplicationPath> are the same, search for <Title>.ext (without -01)
- if not image_found and len(search_names) == 1:
- for ext in ["png", "jpg"]:
- image_path = os.path.join(image_folder, f"{search_names[0]}.{ext}").replace("\\", "/")
- if os.path.isfile(image_path):
- image_found = True
- match_type = 'rom'
- break
- # Determine the color based on the result
- if image_found:
- if match_type == 'title':
- return self.color_media_yes_title
- elif match_type == 'rom':
- return self.color_media_yes_rom
- elif match_type == 'title/rom':
- return self.color_media_both
- else:
- return self.color_media_no
- def apply_hide_all_si_filter(self):
- # Show the filtering message
- self.filtering_label.place(relx=0.5, rely=0.5, anchor="center")
- self.root.update() # Force interface update
- # Apply the filter
- self.filter_active = not self.filter_active # Toggle filter state
- self.update_table_with_filter()
- # Hide the filtering message
- self.filtering_label.place_forget()
- def update_table_with_filter(self):
- selected_platform = self.platform_combobox.get()
- if not selected_platform:
- return
- xml_path = self.platforms[selected_platform]
- tree = ET.parse(xml_path)
- root = tree.getroot()
- games = root.findall("Game")
- game_dict = {}
- for game in games:
- title_element = game.find("Title")
- title = title_element.text if title_element is not None else None
- app_path_element = game.find("ApplicationPath")
- if app_path_element is not None and app_path_element.text:
- app_filename = os.path.basename(app_path_element.text)
- app_name = os.path.splitext(app_filename)[0]
- else:
- app_name = None
- if title:
- game_dict[title] = [title]
- if app_name and app_name != title:
- game_dict[title].append(app_name)
- # Sort games alphabetically
- sorted_game_dict = dict(sorted(game_dict.items(), key=lambda item: (self.natural_sort_key(item[0]), item[0])))
- # Apply the filter if active
- if self.filter_active:
- sorted_game_dict = self.filter_all_si(sorted_game_dict)
- # Update the table with the (filtered or unfiltered) games
- self.load_filter_file(selected_platform, sorted_game_dict)
- class SettingsWindow:
- def __init__(self, root, config_file):
- self.root = root
- self.config_file = config_file
- self.root.title("Settings")
- # Create a Canvas and Scrollbars
- self.canvas = tk.Canvas(self.root)
- self.scrollbar_y = tk.Scrollbar(self.root, orient="vertical", command=self.canvas.yview)
- self.scrollbar_x = tk.Scrollbar(self.root, orient="horizontal", command=self.canvas.xview)
- # Configure the Canvas to be scrollable
- self.scrollable_frame = tk.Frame(self.canvas)
- self.scrollable_frame.bind(
- "<Configure>",
- lambda e: self.canvas.configure(
- scrollregion=self.canvas.bbox("all")
- )
- )
- self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
- self.canvas.configure(yscrollcommand=self.scrollbar_y.set, xscrollcommand=self.scrollbar_x.set)
- # Configure the mouse wheel event
- self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
- # Place the Canvas and Scrollbars in the window
- self.canvas.grid(row=0, column=0, sticky="nsew")
- self.scrollbar_y.grid(row=0, column=1, sticky="ns")
- self.scrollbar_x.grid(row=1, column=0, sticky="ew")
- # Configure window expansion
- self.root.grid_rowconfigure(0, weight=1)
- self.root.grid_columnconfigure(0, weight=1)
- # Variables to store values
- self.launchbox_path = tk.StringVar(value="C:/LaunchBox")
- self.alternative_path = tk.StringVar(value="D:/LaunchBox")
- self.image_cache_ratio_w = tk.StringVar(value="200")
- self.image_cache_ratio_h = tk.StringVar(value="200")
- self.text_cell_w = tk.StringVar(value="150")
- self.text_cell_h = tk.StringVar(value="50")
- self.image_cell_w = tk.StringVar(value="200")
- self.image_cell_h = tk.StringVar(value="200")
- self.max_games_per_page_imagemode = tk.StringVar(value="200")
- self.color_media_yes_title = tk.StringVar(value="#80FF00")
- self.color_media_yes_rom = tk.StringVar(value="#80AA00")
- self.color_media_both = tk.StringVar(value="#DBDBDB") # Ensure this variable is defined
- self.color_media_no = tk.StringVar(value="#FF0000")
- self.color_no_trans = tk.StringVar(value="#DBDBDB")
- self.selected_filters = ["Box - Front", "Clear Logo"]
- self.make_cache = tk.BooleanVar()
- # Load current values from the config.txt file
- self.load_config()
- # Graphical interface
- self.setup_ui()
- # Adjust window size to the farthest content without additional margin
- self.root.update_idletasks()
- width = self.scrollable_frame.winfo_reqwidth()
- height = self.scrollable_frame.winfo_reqheight()
- self.root.geometry(f"{width}x{height}")
- # Bind the window resize event
- self.root.bind("<Configure>", self._on_window_resize)
- def _on_mousewheel(self, event):
- """Scroll the Canvas with the mouse wheel."""
- if event.delta:
- self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
- def _on_window_resize(self, event):
- """Update the scroll region when the window is resized."""
- self.canvas.configure(scrollregion=self.canvas.bbox("all"))
- def setup_ui(self):
- """Configure the graphical interface elements."""
- main_frame = tk.Frame(self.scrollable_frame)
- main_frame.pack(fill="both", expand=True, padx=10, pady=10)
- # LaunchBox Path
- tk.Label(main_frame, text="LaunchBox Path:").grid(row=0, column=0, sticky="w")
- self.path_entry = tk.Entry(main_frame, textvariable=self.launchbox_path, width=40)
- self.path_entry.grid(row=0, column=1, sticky="w")
- tk.Button(main_frame, text="Browse", command=self.browse_launchbox_path).grid(row=0, column=2)
- # Selected Filters
- tk.Label(main_frame, text="Selected Filters:").grid(row=1, column=0, sticky="w", pady=(10, 0))
- self.filters = [
- "Arcade - Marquee", "Banner", "Advertisement Flyer - Back", "Advertisement Flyer - Front", "Box - Front",
- "Box - Front - Reconstructed", "Fanart - Box - Front", "Box - 3D", "Box - Back",
- "Box - Back - Reconstructed", "Fanart - Box - Back", "Box - Spine", "Box - Full", "Clear Logo",
- "Cart - Front", "Disc", "Fanart - Cart - Front", "Fanart - Disc", "Cart - Back", "Fanart - Cart - Back",
- "Cart - 3D", "Fanart - Background", "Screenshot - Game Over", "Screenshot - Game Select",
- "Screenshot - Game Title", "Screenshot - Gameplay", "Screenshot - High Scores", "Epic Games Background",
- "Epic Games Poster", "Epic Games Screenshot", "GOG Poster", "GOG Screenshot", "Origin Background",
- "Origin Poster", "Origin Screenshot", "Steam Banner", "Steam Poster", "Steam Screenshot",
- "Uplay Background", "Uplay Thumbnail", "Arcade - Cabinet", "Arcade - Circuit Board",
- "Arcade - Control Panel", "Arcade - Controls Information", "Amazon Background", "Amazon Poster",
- "Amazon Screenshot", "Videos", "Manual"
- ]
- self.filter_vars = []
- num_columns = 3 # Number of columns for filters
- max_filters_per_column = (len(self.filters) // num_columns) + (1 if len(self.filters) % num_columns != 0 else 0)
- for i, filter_name in enumerate(self.filters):
- var = tk.BooleanVar(value=(filter_name in self.selected_filters))
- row = 2 + (i % max_filters_per_column)
- column = i // max_filters_per_column
- tk.Checkbutton(main_frame, text=filter_name, variable=var).grid(row=row, column=column, sticky="w")
- self.filter_vars.append(var)
- # Adjust the width of the scrollable_frame to the widest content
- self.scrollable_frame.update_idletasks()
- self.canvas.configure(scrollregion=self.canvas.bbox("all"))
- row_offset = 2 + max_filters_per_column
- # Add a line break
- tk.Label(main_frame, text="").grid(row=row_offset, column=0)
- row_offset += 1
- # Maximum games listed per page
- tk.Label(main_frame, text="Maximum games listed per page:").grid(row=row_offset, column=0, sticky="w")
- self.max_games_entry = tk.Entry(main_frame, textvariable=self.max_games_per_page_imagemode, width=5)
- self.max_games_entry.grid(row=row_offset, column=1, padx=(0, 0))
- row_offset += 1
- # Add a line break
- tk.Label(main_frame, text="").grid(row=row_offset, column=0)
- row_offset += 1
- # Cell size in Text mode
- self.create_size_input(main_frame, "Cell Size in Text Mode", row_offset, self.text_cell_w, self.text_cell_h)
- row_offset += 1
- # Cell size in Image mode
- self.create_size_input(main_frame, "Cell Size in Image Mode", row_offset, self.image_cell_w, self.image_cell_h)
- row_offset += 1
- # Add double line break
- tk.Label(main_frame, text="").grid(row=row_offset, column=0)
- tk.Label(main_frame, text="").grid(row=row_offset + 1, column=0)
- row_offset += 2
- # Alternative Media Path
- tk.Label(main_frame, text="Alternative Media Path (Videos/Manuals):").grid(row=row_offset, column=0, sticky="w")
- self.alt_path_entry = tk.Entry(main_frame, textvariable=self.alternative_path, width=40)
- self.alt_path_entry.grid(row=row_offset, column=1, sticky="w")
- tk.Button(main_frame, text="Browse", command=self.browse_alternative_path).grid(row=row_offset, column=2)
- # Only for Video and Manual, with the format [Platform]/Video/[Game Name].mp4 or [Platform]/Manuals/[Game Name].pdf
- tk.Label(main_frame,
- text="Only for Video and Manual, with the format [Platform]/Videos/[Game Name].mp4 and [Platform]/Manuals/[Game Name].pdf").grid(
- row=row_offset + 1, column=0, columnspan=3, sticky="w")
- row_offset += 2
- # Add double line break
- tk.Label(main_frame, text="").grid(row=row_offset, column=0)
- tk.Label(main_frame, text="").grid(row=row_offset + 1, column=0)
- row_offset += 2
- # Color Selectors
- # Color for Media found with Title
- tk.Label(main_frame, text="Color for Media found with Title:").grid(row=row_offset, column=0, sticky="w")
- self.color_media_yes_title_display = tk.Label(main_frame, textvariable=self.color_media_yes_title, width=10,
- bg=self.color_media_yes_title.get())
- self.color_media_yes_title_display.grid(row=row_offset, column=1, sticky="w", padx=(0, 0))
- self.color_media_yes_title_button = tk.Button(main_frame, text="Select Color",
- command=self.choose_color_media_yes_title)
- self.color_media_yes_title_button.grid(row=row_offset, column=1, sticky="e")
- row_offset += 1
- # Color for Media found with RomName
- tk.Label(main_frame, text="Color for Media found with RomName:").grid(row=row_offset, column=0, sticky="w")
- self.color_media_yes_rom_display = tk.Label(main_frame, textvariable=self.color_media_yes_rom, width=10,
- bg=self.color_media_yes_rom.get())
- self.color_media_yes_rom_display.grid(row=row_offset, column=1, sticky="w", padx=(0, 0))
- self.color_media_yes_rom_button = tk.Button(main_frame, text="Select Color",
- command=self.choose_color_media_yes_rom)
- self.color_media_yes_rom_button.grid(row=row_offset, column=1, sticky="e")
- row_offset += 1
- # Color for Media found with Title/RomName
- tk.Label(main_frame, text="Color for Media found with Both:").grid(row=row_offset, column=0, sticky="w")
- self.color_no_trans_display = tk.Label(main_frame, textvariable=self.color_no_trans, width=10,
- bg=self.color_no_trans.get())
- self.color_no_trans_display.grid(row=row_offset, column=1, sticky="w", padx=(0, 30))
- self.color_no_trans_button = tk.Button(main_frame, text="Select Color", command=self.choose_color_no_trans)
- self.color_no_trans_button.grid(row=row_offset, column=1, sticky="e")
- row_offset += 1
- # Color for Media NOT found
- tk.Label(main_frame, text="Color for Media NOT found:").grid(row=row_offset, column=0, sticky="w")
- self.color_media_no_display = tk.Label(main_frame, textvariable=self.color_media_no, width=10,
- bg=self.color_media_no.get())
- self.color_media_no_display.grid(row=row_offset, column=1, sticky="w", padx=(0, 30))
- self.color_media_no_button = tk.Button(main_frame, text="Select Color", command=self.choose_color_media_no)
- self.color_media_no_button.grid(row=row_offset, column=1, sticky="e")
- row_offset += 1
- # Add double line break
- tk.Label(main_frame, text="").grid(row=row_offset, column=0)
- tk.Label(main_frame, text="").grid(row=row_offset + 1, column=0)
- row_offset += 2
- # Option to make cache
- tk.Checkbutton(main_frame, text="Make Local Cache (Better performance)", variable=self.make_cache, onvalue=True,
- offvalue=False).grid(row=row_offset, column=0, sticky="w")
- row_offset += 1
- # Cache image ratio
- self.create_size_input(main_frame, "Cache Image Ratio", row_offset, self.image_cache_ratio_w,
- self.image_cache_ratio_h)
- row_offset += 1
- # Save Button
- tk.Button(main_frame, text="Save", command=self.save_config).grid(row=row_offset, column=0, columnspan=3,
- pady=(20, 0))
- def create_size_input(self, frame, label_text, row, var_w, var_h):
- """Create a row with a label, two entries, and an 'x' in between, with adjusted spacing."""
- tk.Label(frame, text=label_text).grid(row=row, column=0, sticky="w")
- tk.Entry(frame, textvariable=var_w, width=5).grid(row=row, column=1, padx=(0, 0)) # Remove right padding
- tk.Label(frame, text="x").grid(row=row, column=1, padx=(32, 0)) # Remove padding
- tk.Entry(frame, textvariable=var_h, width=5).grid(row=row, column=1, padx=(80, 0)) # Remove left padding
- def browse_launchbox_path(self):
- folder_path = filedialog.askdirectory(title="Select LaunchBox Folder")
- if folder_path:
- self.launchbox_path.set(folder_path)
- def browse_alternative_path(self):
- folder_path = filedialog.askdirectory(title="Select Alternative Media Folder")
- if folder_path:
- self.alternative_path.set(folder_path)
- def choose_color_media_yes_title(self):
- color_code = colorchooser.askcolor(title="Select a Color")
- if color_code[1]: # Check if a color was selected (color_code[1] is the hex code)
- self.color_media_yes_title.set(color_code[1])
- self.color_media_yes_title_display.config(bg=color_code[1])
- def choose_color_media_yes_rom(self):
- color_code = colorchooser.askcolor(title="Select a Color")
- if color_code[1]: # Check if a color was selected
- self.color_media_yes_rom.set(color_code[1])
- self.color_media_yes_rom_display.config(bg=color_code[1])
- def choose_color_media_no(self):
- color_code = colorchooser.askcolor(title="Select a Color")
- if color_code[1]: # Check if a color was selected
- self.color_media_no.set(color_code[1])
- self.color_media_no_display.config(bg=color_code[1])
- def choose_color_no_trans(self):
- color_code = colorchooser.askcolor(title="Select a Color")
- if color_code[1]: # Check if a color was selected
- self.color_no_trans.set(color_code[1])
- self.color_no_trans_display.config(bg=color_code[1])
- def load_config(self):
- """Loads current values from the config.txt file."""
- print(f"Path of config.txt file: {self.config_file}") # Debug
- if not os.path.exists(self.config_file):
- print("Creating config.txt file...") # Debug
- with open(self.config_file, "w") as file:
- file.write("path=\n")
- file.write("filters=Box - Front,Clear Logo\n")
- file.write("image_cache_ratio=150x150\n")
- file.write("alternative_path=\n")
- file.write("image_cell=150x150\n")
- file.write("text_cell=150x50\n")
- file.write("max_games_per_page_imagemode=100\n")
- file.write("color_media_yes_title=#0073E6\n")
- file.write("color_media_yes_rom=#00E673\n")
- file.write("color_media_both=#E67300\n") # Ensure this line is present
- file.write("color_media_no=#E60073\n")
- file.write("color_no_trans=#C0C0C0\n")
- file.write("make_cache=true\n")
- # Load values from the config.txt file
- with open(self.config_file, "r") as file:
- for line in file:
- key, value = line.strip().split('=', 1)
- value = value.strip('"')
- if key == "path":
- self.launchbox_path.set(value)
- elif key == "filters":
- self.selected_filters = value.split(',')
- elif key == "image_cache_ratio":
- self.image_cache_ratio_w.set(value.split('x')[0])
- self.image_cache_ratio_h.set(value.split('x')[1])
- elif key == "text_cell":
- self.text_cell_w.set(value.split('x')[0])
- self.text_cell_h.set(value.split('x')[1])
- elif key == "image_cell":
- self.image_cell_w.set(value.split('x')[0])
- self.image_cell_h.set(value.split('x')[1])
- elif key == "alternative_path":
- self.alternative_path.set(value)
- elif key == "max_games_per_page_imagemode":
- self.max_games_per_page_imagemode.set(value)
- elif key == "color_media_yes_title":
- self.color_media_yes_title.set(value)
- elif key == "color_media_yes_rom":
- self.color_media_yes_rom.set(value)
- elif key == "color_media_both": # Ensure this key is present
- self.color_media_both.set(value)
- elif key == "color_media_no":
- self.color_media_no.set(value)
- elif key == "color_no_trans":
- self.color_no_trans.set(value)
- elif key == "make_cache":
- self.make_cache.set(value.lower() == "true")
- def save_config(self):
- """Save the selected values to the config.txt file."""
- selected_filters = [self.filters[i] for i, var in enumerate(self.filter_vars) if var.get()]
- with open(self.config_file, "w") as file:
- file.write(f"path={self.launchbox_path.get()}\n")
- file.write(f'filters="{",".join(selected_filters)}"\n') # Corrected line
- file.write(f"image_cache_ratio={self.image_cache_ratio_w.get()}x{self.image_cache_ratio_h.get()}\n")
- file.write(f"text_cell={self.text_cell_w.get()}x{self.text_cell_h.get()}\n")
- file.write(f"image_cell={self.image_cell_w.get()}x{self.image_cell_h.get()}\n")
- file.write(f"alternative_path={self.alternative_path.get()}\n")
- file.write(f"max_games_per_page_imagemode={self.max_games_per_page_imagemode.get()}\n")
- file.write(f"color_media_yes_title={self.color_media_yes_title.get()}\n")
- file.write(f"color_media_yes_rom={self.color_media_yes_rom.get()}\n")
- file.write(f"color_media_no={self.color_media_no.get()}\n")
- file.write(f"color_no_trans={self.color_no_trans.get()}\n")
- file.write(f"make_cache={self.make_cache.get()}\n")
- messagebox.showinfo("Save", "Settings saved successfully.")
- self.root.destroy()
- if __name__ == "__main__":
- try:
- root = tk.Tk()
- app = LaunchBoxManager(root)
- root.mainloop()
- except MemoryError:
- print("Severe memory error. The program must close.")
- except Exception as e:
- print(f"Unexpected error: {e}")
- messagebox.showerror("Error", f"An unexpected error occurred: {e}")
Advertisement
Add Comment
Please, Sign In to add comment