Dunkler2Engel

LaunchBox - Media Manager + Settings

Mar 4th, 2025
35
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 81.26 KB | None | 0 0
  1. import os
  2. import shutil
  3. import xml.etree.ElementTree as ET
  4. import tkinter as tk
  5. from tkinter import messagebox, filedialog, colorchooser
  6. from PIL import Image, ImageTk
  7. import subprocess
  8. from tkinter import ttk
  9. import sys
  10.  
  11.  
  12. def get_config_path():
  13. """Gets the path of the config.txt file in the executable's folder."""
  14. if getattr(sys, 'frozen', False):
  15. base_dir = os.path.dirname(sys.executable) # If it's packaged
  16. else:
  17. base_dir = os.path.dirname(os.path.abspath(__file__)) # If running from the script
  18. return os.path.join(base_dir, "config.txt")
  19.  
  20.  
  21. class LaunchBoxManager:
  22.  
  23. def __init__(self, root):
  24. self.root = root
  25. self.root.title("LaunchBox Media Manager")
  26. self.root.geometry("800x600")
  27. self.platforms = {}
  28. self.launchbox_path = None
  29. self.filters = ["Box - Front", "Clear Logo"]
  30. self.show_images = False
  31. self.image_references = {}
  32. self.last_sorted_column = None
  33. self.videos_enabled = False
  34. self.manual_enabled = False
  35. self.sort_ascending = True
  36. self.cell_size_text = (100, 50)
  37. self.cell_size_image = (150, 150)
  38. self.columns = 0
  39. self.image_cache_ratio = "150x150"
  40. self.alternative_path = None
  41. self.max_games_per_page_imagemode = 100
  42. self.make_cache = True
  43. self.current_page = 0
  44. self.total_pages = 0
  45. self.filter_active = False
  46.  
  47. # Colors
  48. self.color_media_yes_title = "#0073E6" # Blue
  49. self.color_media_yes_rom = "#00E673" # Green
  50. self.color_media_both = "#E67300" # Orange
  51. self.color_media_no = "#E60073" # Red
  52. self.color_no_trans = "#C0C0C0" # Gray
  53.  
  54. max_retries = 2
  55. retries = 0
  56.  
  57. # Get the path of the config.txt file
  58. self.config_file = get_config_path()
  59.  
  60. # Create a label for the filtering message
  61. self.filtering_label = tk.Label(self.root, text="Filtering files with all YES...", font=("Arial", 14),
  62. bg="white")
  63.  
  64. # Load configuration on startup
  65. if not self.load_launchbox_path():
  66. messagebox.showerror("Error",
  67. "Could not load LaunchBox configuration. Please verify the path in config.txt.")
  68. self.root.destroy()
  69. return
  70.  
  71. self.setup_ui()
  72.  
  73. def setup_ui(self):
  74. # Top frame (for platform selection)
  75. self.top_frame = tk.Frame(self.root)
  76. self.top_frame.pack(fill="x", padx=10, pady=10)
  77.  
  78. # Label and Combobox for selecting the platform
  79. self.platform_label = tk.Label(self.top_frame, text="Platform:")
  80. self.platform_label.pack(side="left", padx=(0, 5))
  81.  
  82. self.platform_combobox = tk.StringVar()
  83. self.platform_menu = ttk.Combobox(self.top_frame, textvariable=self.platform_combobox, state="readonly")
  84. self.platform_menu.pack(side="left", fill="x", expand=True, padx=(0, 10))
  85. self.platform_combobox.trace("w", self.on_platform_select)
  86.  
  87. # Button to load platforms
  88. self.load_button = tk.Button(self.top_frame, text="Load Platforms", command=self.load_platforms)
  89. self.load_button.pack(side="left")
  90.  
  91. # Frame for Checkbuttons (Image Mode and Hide all YES)
  92. self.checkbutton_frame = tk.Frame(self.top_frame)
  93. self.checkbutton_frame.pack(side="left", padx=(10, 0))
  94.  
  95. # Checkbutton for image mode
  96. self.switch_var = tk.BooleanVar()
  97. self.switch = tk.Checkbutton(self.checkbutton_frame, text="Image Mode", variable=self.switch_var,
  98. command=self.toggle_view_mode)
  99. self.switch.pack(anchor="w") # Aligned to the left within the Frame
  100.  
  101. # Button to apply the "Hide all YES" filter
  102. self.hide_all_si_button = tk.Button(self.checkbutton_frame, text="Hide all YES",
  103. command=self.apply_hide_all_si_filter)
  104. self.hide_all_si_button.pack(anchor="w") # Aligned to the left within the Frame
  105.  
  106. # Button to open settings (on the right)
  107. self.settings_button = tk.Button(self.top_frame, text="Settings", command=self.open_settings)
  108. self.settings_button.pack(side="right", padx=(10, 0))
  109.  
  110. # Frame for filters and the table
  111. self.filter_frame = tk.LabelFrame(self.root, text="Filters")
  112. self.filter_frame.pack(fill="both", expand=True, padx=10, pady=10)
  113.  
  114. # Canvas and scrollbars for the table
  115. self.header_canvas = tk.Canvas(self.filter_frame, borderwidth=1, relief="solid", height=self.cell_size_text[1])
  116. self.header_canvas.pack(fill="x", side="top")
  117.  
  118. self.header_scrollbar_x = tk.Scrollbar(self.filter_frame, orient="horizontal",
  119. command=lambda *args: self.sync_scroll(*args))
  120. self.header_scrollbar_x.pack(side="top", fill="x")
  121. self.header_canvas.config(xscrollcommand=self.header_scrollbar_x.set)
  122.  
  123. self.canvas_frame = tk.Frame(self.filter_frame)
  124. self.canvas_frame.pack(fill="both", expand=True)
  125.  
  126. self.canvas = tk.Canvas(self.canvas_frame, borderwidth=1, relief="solid")
  127. self.canvas.pack(side="left", fill="both", expand=True)
  128.  
  129. self.canvas_scrollbar_y = tk.Scrollbar(self.canvas_frame, orient="vertical", command=self.canvas.yview)
  130. self.canvas_scrollbar_y.pack(side="right", fill="y")
  131. self.canvas.config(yscrollcommand=self.canvas_scrollbar_y.set)
  132.  
  133. self.canvas_scrollbar_x = tk.Scrollbar(self.filter_frame, orient="horizontal",
  134. command=lambda *args: self.sync_scroll(*args))
  135. self.canvas_scrollbar_x.pack(side="bottom", fill="x")
  136. self.canvas.config(xscrollcommand=self.canvas_scrollbar_x.set)
  137.  
  138. self.canvas.bind("<Configure>", lambda e: self.sync_scroll())
  139.  
  140. # Bind the mouse wheel event to the Canvas
  141. self.canvas.bind("<MouseWheel>", self.on_mouse_wheel_cells)
  142.  
  143. # Frame for pagination and additional buttons
  144. self.pagination_frame = tk.Frame(self.root)
  145. self.pagination_frame.pack(fill="x", padx=10, pady=10)
  146.  
  147. self.center_frame = tk.Frame(self.pagination_frame)
  148. self.center_frame.pack()
  149.  
  150. # "Previous" button
  151. self.prev_button = tk.Button(self.center_frame, text="Previous", command=self.prev_page)
  152. self.prev_button.pack(side="left")
  153.  
  154. # Current page label
  155. self.page_label = tk.Label(self.center_frame, text="Page 1 of 1")
  156. self.page_label.pack(side="left", padx=10)
  157.  
  158. # "Next" button
  159. self.next_button = tk.Button(self.center_frame, text="Next", command=self.next_page)
  160. self.next_button.pack(side="left")
  161.  
  162. # "Generate all Cache" button
  163. self.generate_all_cache_button = tk.Button(self.center_frame, text="Generate all Cache",
  164. command=self.generate_all_cache)
  165. self.generate_all_cache_button.pack(side="left", padx=(10, 0))
  166.  
  167. # "Regenerate cache" button
  168. self.regenerate_cache_button = tk.Button(self.center_frame, text="Regenerate Cache",
  169. command=self.regenerate_cache)
  170. self.regenerate_cache_button.pack(side="left", padx=(10, 0))
  171.  
  172. # Hide or show cache buttons based on make_cache
  173. self.update_cache_buttons_visibility()
  174.  
  175. # Create a label for the filtering message on the Canvas
  176. self.filtering_label = tk.Label(self.canvas, text="Filtering files with all YES...", font=("Arial", 14),
  177. bg="white", fg="black")
  178.  
  179. def get_base_dir(self):
  180. """Gets the base path depending on whether it's packaged or not."""
  181. if getattr(sys, 'frozen', False):
  182. # If packaged, use the executable's directory
  183. return os.path.dirname(sys.executable)
  184. else:
  185. # If not packaged, use the script's path
  186. return os.path.dirname(os.path.abspath(__file__))
  187.  
  188. def get_cache_folder(self):
  189. """Gets the path of the cache folder next to the executable."""
  190. base_dir = self.get_base_dir()
  191. cache_folder = os.path.join(base_dir, "cache")
  192. os.makedirs(cache_folder, exist_ok=True) # Create the folder if it doesn't exist
  193. return cache_folder
  194.  
  195. def on_mouse_wheel_cells(self, event):
  196. menu_state = str(self.platform_menu.cget("state")) # Save it as an immutable string
  197.  
  198. if menu_state == "readonly":
  199. delta = -event.delta
  200. if delta > 0:
  201. self.canvas.yview_scroll(1, "units")
  202. else:
  203. self.canvas.yview_scroll(-1, "units")
  204.  
  205. def on_mouse_wheel_platforms(self, event):
  206. """
  207. Handles vertical scrolling with the mouse wheel for the platform list.
  208. """
  209. menu_state = self.platform_menu.cget("state")
  210.  
  211. if menu_state == "normal": # Only scroll if the menu is expanded
  212. current_index = self.platform_menu.current()
  213. if event.delta > 0:
  214. new_index = max(0, current_index - 1) # Scroll up
  215. else:
  216. new_index = min(len(self.platform_menu["values"]) - 1, current_index + 1) # Scroll down
  217. self.platform_menu.current(new_index)
  218. self.platform_combobox.set(self.platform_menu["values"][new_index])
  219. self.on_platform_select()
  220.  
  221. def sync_scroll(self, *args):
  222. """
  223. Synchronizes horizontal scrolling between the header_canvas and the main canvas.
  224. Does not affect vertical scrolling.
  225. """
  226. if len(args) == 2 and args[0] == "moveto":
  227. fraction = float(args[1])
  228. self.header_canvas.xview_moveto(fraction)
  229. self.canvas.xview_moveto(fraction)
  230. elif len(args) == 2 and args[0] == "scroll":
  231. amount, units = args[1].split()
  232. self.header_canvas.xview_scroll(amount, units)
  233. self.canvas.xview_scroll(amount, units)
  234. else:
  235. fraction = self.canvas.xview()[0]
  236. self.header_canvas.xview_moveto(fraction)
  237.  
  238. def apply_hide_all_si_filter(self):
  239. # Show the filtering message
  240. self.filtering_label.place(relx=0.5, rely=0.5, anchor="center")
  241. self.root.update() # Force interface update
  242.  
  243. # Apply the filter
  244. self.filter_active = not self.filter_active # Toggle filter state
  245. self.update_table_with_filter()
  246.  
  247. # Hide the filtering message
  248. self.filtering_label.place_forget()
  249. # Get games from the selected platform
  250. selected_platform = self.platform_combobox.get()
  251. if not selected_platform:
  252. return
  253.  
  254. xml_path = self.platforms[selected_platform]
  255. tree = ET.parse(xml_path)
  256. root = tree.getroot()
  257.  
  258. games = root.findall("Game")
  259. game_dict = {}
  260.  
  261. for game in games:
  262. title_element = game.find("Title")
  263. title = title_element.text if title_element is not None else None
  264.  
  265. app_path_element = game.find("ApplicationPath")
  266. if app_path_element is not None and app_path_element.text:
  267. app_filename = os.path.basename(app_path_element.text)
  268. app_name = os.path.splitext(app_filename)[0]
  269. else:
  270. app_name = None
  271.  
  272. if title:
  273. game_dict[title] = [title]
  274. if app_name and app_name != title:
  275. game_dict[title].append(app_name)
  276.  
  277. # Sort games alphabetically before applying the filter
  278. sorted_game_dict = dict(sorted(game_dict.items(), key=lambda item: (self.natural_sort_key(item[0]), item[0])))
  279.  
  280. # Apply the "Hide all YES" filter
  281. filtered_game_dict = self.filter_all_si(sorted_game_dict)
  282.  
  283. # Update the table with the filtered games
  284. self.load_filter_file(selected_platform, filtered_game_dict)
  285.  
  286. def update_cache_buttons_visibility(self):
  287. """Updates the visibility of cache buttons based on the value of make_cache."""
  288. if self.make_cache:
  289. self.generate_all_cache_button.pack(side="left", padx=(10, 0))
  290. self.regenerate_cache_button.pack(side="left", padx=(10, 0))
  291. else:
  292. self.generate_all_cache_button.pack_forget()
  293. self.regenerate_cache_button.pack_forget()
  294.  
  295. def load_platforms(self):
  296. self.platform_combobox.set("") # Clear current selection
  297. self.platforms.clear() # Clear the platforms dictionary
  298.  
  299. # Build the path to the platforms folder
  300. platforms_path = os.path.join(self.launchbox_path, "Data", "Platforms").replace("\\", "/")
  301. if not os.path.exists(platforms_path):
  302. messagebox.showerror("Error", f"Platforms folder not found at: {platforms_path}")
  303. return
  304.  
  305. # Get platform names
  306. platform_names = []
  307. for filename in os.listdir(platforms_path):
  308. if filename.endswith(".xml"):
  309. platform_name = filename[:-4] # Remove the .xml extension
  310. self.platforms[platform_name] = os.path.join(platforms_path, filename).replace("\\", "/")
  311. platform_names.append(platform_name)
  312.  
  313. if not platform_names:
  314. messagebox.showerror("Error", "No XML files found in the platforms folder.")
  315. return
  316.  
  317. # Assign values to the Combobox
  318. self.platform_menu["values"] = platform_names
  319. self.platform_menu.current(0) # Select the first platform by default
  320.  
  321. # Force interface update
  322. self.platform_menu.update_idletasks()
  323.  
  324. # Load data for the selected platform
  325. self.on_platform_select()
  326.  
  327. def hide_all_si(self, game_dict=None):
  328. if game_dict is None:
  329. selected_platform = self.platform_combobox.get()
  330. if not selected_platform:
  331. return
  332.  
  333. xml_path = self.platforms[selected_platform]
  334. tree = ET.parse(xml_path)
  335. root = tree.getroot()
  336.  
  337. games = root.findall("Game")
  338. game_dict = {}
  339.  
  340. for game in games:
  341. title_element = game.find("Title")
  342. title = title_element.text if title_element is not None else None
  343.  
  344. app_path_element = game.find("ApplicationPath")
  345. if app_path_element is not None and app_path_element.text:
  346. app_filename = os.path.basename(app_path_element.text)
  347. app_name = os.path.splitext(app_filename)[0]
  348. else:
  349. app_name = None
  350.  
  351. if title:
  352. game_dict[title] = [title]
  353. if app_name and app_name != title:
  354. game_dict[title].append(app_name)
  355.  
  356. # Filter games that have "YES" in all cells
  357. filtered_game_dict = self.filter_all_si(game_dict)
  358.  
  359. # Update the table with the filtered games
  360. selected_platform = self.platform_combobox.get()
  361. self.load_filter_file(selected_platform, filtered_game_dict)
  362.  
  363. def on_platform_select(self, *args):
  364. selected_platform = self.platform_combobox.get()
  365. if not selected_platform:
  366. return
  367.  
  368. try:
  369. xml_path = self.platforms[selected_platform]
  370. tree = ET.parse(xml_path)
  371. root = tree.getroot()
  372.  
  373. if root.tag != "LaunchBox":
  374. messagebox.showerror("Error",
  375. f"The XML file does not have the expected structure. Root element: {root.tag}")
  376. return
  377.  
  378. games = root.findall("Game")
  379. game_dict = {}
  380.  
  381. for game in games:
  382. title_element = game.find("Title")
  383. title = title_element.text if title_element is not None else None
  384.  
  385. app_path_element = game.find("ApplicationPath")
  386. if app_path_element is not None and app_path_element.text:
  387. app_filename = os.path.basename(app_path_element.text)
  388. app_name = os.path.splitext(app_filename)[0]
  389. else:
  390. app_name = None
  391.  
  392. if title:
  393. game_dict[title] = [title]
  394. if app_name and app_name != title:
  395. game_dict[title].append(app_name)
  396.  
  397. sorted_game_dict = dict(
  398. sorted(game_dict.items(), key=lambda item: (self.natural_sort_key(item[0]), item[0])))
  399. self.load_filter_file(selected_platform, sorted_game_dict)
  400. except Exception as e:
  401. messagebox.showerror("Error", f"Could not read the XML file: {e}")
  402.  
  403. def generate_all_cache(self):
  404. selected_platform = self.platform_combobox.get()
  405. if not selected_platform:
  406. return
  407.  
  408. self.progress_label = tk.Label(self.root, text="Generating Cache...")
  409. self.progress_label.place(relx=0.5, rely=0.5, anchor="center")
  410. self.root.update()
  411.  
  412. original_page = self.current_page
  413.  
  414. for page in range(self.total_pages):
  415. self.current_page = page
  416. self.on_platform_select()
  417. self.progress_label.config(text=f"Generating page {page + 1} of {self.total_pages}")
  418. self.root.update()
  419.  
  420. self.current_page = original_page
  421. self.on_platform_select()
  422. self.progress_label.destroy()
  423.  
  424. def open_settings(self):
  425. # Get the path of the config.txt file
  426. config_file = get_config_path()
  427.  
  428. # Create a new window for settings
  429. settings_window = tk.Toplevel(self.root)
  430. settings_app = SettingsWindow(settings_window, config_file)
  431.  
  432. # Wait for the settings window to close
  433. self.root.wait_window(settings_window)
  434.  
  435. # Reload configuration after saving changes
  436. self.load_launchbox_path()
  437.  
  438. # Update cache buttons visibility
  439. self.update_cache_buttons_visibility()
  440.  
  441. # Update other UI elements if necessary
  442. self.on_platform_select() # This updates the table with the new values
  443.  
  444. def load_launchbox_path(self):
  445. """Loads configuration from the config.txt file."""
  446. print(f"Path of config.txt file: {self.config_file}") # Debug
  447. if not os.path.exists(self.config_file):
  448. print("Creating config.txt file...") # Debug
  449. with open(self.config_file, "w") as file:
  450. file.write("path=\n")
  451. file.write("filters=Box - Front,Clear Logo\n")
  452. file.write("image_cache_ratio=150x150\n")
  453. file.write("alternative_path=\n")
  454. file.write("image_cell=150x150\n")
  455. file.write("text_cell=150x50\n")
  456. file.write("max_games_per_page_imagemode=100\n")
  457. file.write("color_media_yes_title=#0073E6\n")
  458. file.write("color_media_yes_rom=#00E673\n")
  459. file.write("color_media_both=#E67300\n") # Ensure this line is present
  460. file.write("color_media_no=#E60073\n")
  461. file.write("color_no_trans=#C0C0C0\n")
  462. file.write("make_cache=true\n")
  463.  
  464. # Load values from the config.txt file
  465. with open(self.config_file, "r") as file:
  466. for line in file:
  467. key, value = line.strip().split('=', 1)
  468. value = value.strip('"')
  469. if key == "path":
  470. self.launchbox_path = value
  471. elif key == "filters":
  472. self.filters = [f.strip().strip('"') for f in value.split(',')]
  473. elif key == "image_cache_ratio":
  474. self.image_cache_ratio = value
  475. elif key == "alternative_path":
  476. self.alternative_path = value
  477. elif key == "image_cell":
  478. try:
  479. width, height = value.split('x')
  480. self.cell_size_image = (int(width), int(height))
  481. except ValueError:
  482. print(
  483. "Error: The value of image_cell is not in the correct format. Using default values (200x200).")
  484. self.cell_size_image = (200, 200)
  485. elif key == "text_cell":
  486. try:
  487. width, height = value.split('x')
  488. self.cell_size_text = (int(width), int(height))
  489. except ValueError:
  490. print(
  491. "Error: The value of text_cell is not in the correct format. Using default values (100x50).")
  492. self.cell_size_text = (100, 50)
  493. elif key == "max_games_per_page_imagemode":
  494. try:
  495. self.max_games_per_page_imagemode = int(value)
  496. except ValueError:
  497. print(
  498. "Error: The value of max_games_per_page_imagemode is not a valid number. Using default value (20).")
  499. self.max_games_per_page_imagemode = 20
  500. elif key == "color_media_yes_title":
  501. self.color_media_yes_title = value
  502. elif key == "color_media_yes_rom":
  503. self.color_media_yes_rom = value
  504. elif key == "color_media_both": # Ensure this key is present
  505. self.color_media_both = value
  506. elif key == "color_media_no":
  507. self.color_media_no = value
  508. elif key == "color_no_trans":
  509. self.color_no_trans = value
  510. elif key == "make_cache":
  511. self.make_cache = value.lower() == "true"
  512.  
  513. print("Configuration loaded successfully.")
  514. return True
  515.  
  516. def load_platforms(self):
  517. self.platform_combobox.set("") # Clear current selection
  518. self.platforms.clear() # Clear the platforms dictionary
  519.  
  520. # Build the path to the platforms folder
  521. platforms_path = os.path.join(self.launchbox_path, "Data", "Platforms").replace("\\", "/")
  522. if not os.path.exists(platforms_path):
  523. messagebox.showerror("Error", f"Platforms folder not found at: {platforms_path}")
  524. return
  525.  
  526. # Get platform names
  527. platform_names = []
  528. for filename in os.listdir(platforms_path):
  529. if filename.endswith(".xml"):
  530. platform_name = filename[:-4] # Remove the .xml extension
  531. self.platforms[platform_name] = os.path.join(platforms_path, filename).replace("\\", "/")
  532. platform_names.append(platform_name)
  533.  
  534. if not platform_names:
  535. messagebox.showerror("Error", "No XML files found in the platforms folder.")
  536. return
  537.  
  538. # Assign values to the Combobox
  539. self.platform_menu["values"] = platform_names
  540. self.platform_menu.current(0) # Select the first platform by default
  541.  
  542. # Force interface update
  543. self.platform_menu.update_idletasks()
  544.  
  545. # Load data for the selected platform
  546. self.on_platform_select()
  547.  
  548. def on_platform_select(self, *args):
  549. selected_platform = self.platform_combobox.get()
  550. if not selected_platform:
  551. return
  552.  
  553. try:
  554. xml_path = self.platforms[selected_platform]
  555. tree = ET.parse(xml_path)
  556. root = tree.getroot()
  557.  
  558. if root.tag != "LaunchBox":
  559. messagebox.showerror("Error",
  560. f"The XML file does not have the expected structure. Root element: {root.tag}")
  561. return
  562.  
  563. games = root.findall("Game")
  564. game_dict = {}
  565.  
  566. for game in games:
  567. title_element = game.find("Title")
  568. title = title_element.text if title_element is not None else None
  569.  
  570. app_path_element = game.find("ApplicationPath")
  571. if app_path_element is not None and app_path_element.text:
  572. app_filename = os.path.basename(app_path_element.text)
  573. app_name = os.path.splitext(app_filename)[0]
  574. else:
  575. app_name = None
  576.  
  577. if title:
  578. game_dict[title] = [title]
  579. if app_name and app_name != title:
  580. game_dict[title].append(app_name)
  581.  
  582. sorted_game_dict = dict(
  583. sorted(game_dict.items(), key=lambda item: (self.natural_sort_key(item[0]), item[0])))
  584. self.load_filter_file(selected_platform, sorted_game_dict)
  585. except Exception as e:
  586. messagebox.showerror("Error", f"Could not read the XML file: {e}")
  587.  
  588. def load_filter_file(self, platform, game_dict):
  589. filters_to_use = self.filters[:]
  590. if self.videos_enabled:
  591. filters_to_use.append("Videos")
  592. if self.manual_enabled:
  593. filters_to_use.append("Manual")
  594.  
  595. self.columns = len(filters_to_use) + 1 # Define self.columns
  596. self.total_pages = (len(game_dict) + self.max_games_per_page_imagemode - 1) // self.max_games_per_page_imagemode
  597. self.update_pagination_controls()
  598.  
  599. start_index = self.current_page * self.max_games_per_page_imagemode
  600. end_index = start_index + self.max_games_per_page_imagemode
  601. self.games_to_show = list(game_dict.items())[start_index:end_index] # Save the games being shown
  602.  
  603. self.canvas.delete("all")
  604. self.image_references = {}
  605. self.draw_table(self.games_to_show, filters_to_use)
  606.  
  607. total_width = sum(
  608. self.cell_size_image[0] if self.show_images else self.cell_size_text[0] for _ in range(self.columns))
  609. total_height = self.rows * (self.cell_size_image[1] if self.show_images else self.cell_size_text[1])
  610. self.canvas.config(scrollregion=(0, 0, total_width, total_height))
  611.  
  612. def prev_page(self):
  613. if self.current_page > 0:
  614. self.current_page -= 1
  615. self.update_pagination_controls()
  616. self.update_table_with_filter()
  617.  
  618. def next_page(self):
  619. if self.current_page < self.total_pages - 1:
  620. self.current_page += 1
  621. self.update_pagination_controls()
  622. self.update_table_with_filter()
  623.  
  624. def update_pagination_controls(self):
  625. self.page_label.config(text=f"Page {self.current_page + 1} of {self.total_pages}")
  626. self.prev_button.config(state="normal" if self.current_page > 0 else "disabled")
  627. self.next_button.config(state="normal" if self.current_page < self.total_pages - 1 else "disabled")
  628.  
  629. def toggle_view_mode(self):
  630. self.show_images = self.switch_var.get()
  631.  
  632. if self.show_images:
  633. self.header_canvas.config(height=self.cell_size_image[1])
  634. else:
  635. self.header_canvas.config(height=self.cell_size_text[1])
  636.  
  637. self.on_platform_select()
  638.  
  639. def search_image_in_folder(self, folder, search_names, extensions):
  640. """
  641. Searches for images in a folder and its subfolders recursively.
  642. Returns the path of the image if found, or None if not found.
  643. """
  644. for root, dirs, files in os.walk(folder):
  645. for file in files:
  646. # Check if the file matches any of the names and extensions
  647. for search_name in search_names:
  648. for ext in extensions:
  649. if file.startswith(search_name) and file.endswith(ext):
  650. return os.path.join(root, file), search_name
  651. return None, None
  652.  
  653. def draw_table(self, games_to_show, filters):
  654. self.rows = len(games_to_show)
  655.  
  656. if self.show_images:
  657. cell_width, cell_height = self.cell_size_image
  658. else:
  659. cell_width, cell_height = self.cell_size_text
  660.  
  661. total_width = sum(cell_width for _ in range(self.columns))
  662.  
  663. self.header_canvas.config(width=total_width, height=cell_height)
  664. self.header_canvas.delete("all")
  665. for col, filter_name in enumerate(["Title"] + filters):
  666. self.draw_cell(0, col, filter_name, header=True, canvas=self.header_canvas)
  667. self.header_canvas.tag_bind(f"header_{col}", "<Button-1>", lambda event, col=col: self.on_header_click(col))
  668.  
  669. for row, (title, search_names) in enumerate(games_to_show, start=0):
  670. self.draw_cell(row, 0, title)
  671. for col, filter_name in enumerate(filters, start=1):
  672. if filter_name == "Videos":
  673. # Define main and alternative paths
  674. main_folder_path = os.path.join(self.launchbox_path, "Videos",
  675. self.platform_combobox.get()).replace("\\", "/")
  676. alternative_folder_path = os.path.join(self.alternative_path, self.platform_combobox.get(),
  677. "Videos").replace("\\",
  678. "/") if self.alternative_path else None
  679.  
  680. file_found = False
  681. match_type = None
  682. file_path = None
  683.  
  684. # Search in the alternative path first (if it exists)
  685. if alternative_folder_path and os.path.exists(alternative_folder_path):
  686. # Search for <Title>-01.mp4
  687. file_path = os.path.join(alternative_folder_path, f"{search_names[0]}-01.mp4").replace("\\",
  688. "/")
  689. if os.path.isfile(file_path):
  690. file_found = True
  691. match_type = 'title'
  692. else:
  693. # Search for <ApplicationPath>.mp4 (without -01)
  694. if len(search_names) > 1:
  695. file_path = os.path.join(alternative_folder_path, f"{search_names[1]}.mp4").replace(
  696. "\\", "/")
  697. if os.path.isfile(file_path):
  698. file_found = True
  699. match_type = 'rom'
  700. else:
  701. # If <Title> and <ApplicationPath> are the same, search for <Title>.mp4 (without -01)
  702. file_path = os.path.join(alternative_folder_path, f"{search_names[0]}.mp4").replace(
  703. "\\", "/")
  704. if os.path.isfile(file_path):
  705. file_found = True
  706. match_type = 'rom'
  707.  
  708. # If not found in the alternative path, search in the main path
  709. if not file_found and os.path.exists(main_folder_path):
  710. # Search for <Title>-01.mp4
  711. file_path = os.path.join(main_folder_path, f"{search_names[0]}-01.mp4").replace("\\", "/")
  712. if os.path.isfile(file_path):
  713. file_found = True
  714. match_type = 'title'
  715. else:
  716. # Search for <ApplicationPath>.mp4 (without -01)
  717. if len(search_names) > 1:
  718. file_path = os.path.join(main_folder_path, f"{search_names[1]}.mp4").replace("\\", "/")
  719. if os.path.isfile(file_path):
  720. file_found = True
  721. match_type = 'rom'
  722. else:
  723. # If <Title> and <ApplicationPath> are the same, search for <Title>.mp4 (without -01)
  724. file_path = os.path.join(main_folder_path, f"{search_names[0]}.mp4").replace("\\", "/")
  725. if os.path.isfile(file_path):
  726. file_found = True
  727. match_type = 'rom'
  728.  
  729. # Show the result in the table
  730. if self.show_images:
  731. if file_found:
  732. self.draw_cell(row, col, "YES",
  733. cell_color=self.color_media_yes_title if match_type == 'title' else self.color_media_yes_rom)
  734. else:
  735. self.draw_cell(row, col, "NO", cell_color=self.color_media_no)
  736. else:
  737. cell_value = "YES (Title)" if match_type == 'title' else "YES (Rom)" if match_type == 'rom' else "NO"
  738. 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
  739. self.draw_cell(row, col, cell_value, cell_color=cell_color, is_text=True)
  740.  
  741. elif filter_name == "Manual":
  742. # Define main and alternative paths
  743. main_folder_path = os.path.join(self.launchbox_path, "Manuals",
  744. self.platform_combobox.get()).replace("\\", "/")
  745. alternative_folder_path = os.path.join(self.alternative_path, self.platform_combobox.get(),
  746. "Manuals").replace("\\",
  747. "/") if self.alternative_path else None
  748.  
  749. file_found = False
  750. match_type = None
  751. file_path = None
  752.  
  753. # Search in the alternative path first (if it exists)
  754. if alternative_folder_path and os.path.exists(alternative_folder_path):
  755. # Search for <Title>-01.pdf
  756. file_path = os.path.join(alternative_folder_path, f"{search_names[0]}-01.pdf").replace("\\",
  757. "/")
  758. if os.path.isfile(file_path):
  759. file_found = True
  760. match_type = 'title'
  761. else:
  762. # Search for <ApplicationPath>.pdf (without -01)
  763. if len(search_names) > 1:
  764. file_path = os.path.join(alternative_folder_path, f"{search_names[1]}.pdf").replace(
  765. "\\", "/")
  766. if os.path.isfile(file_path):
  767. file_found = True
  768. match_type = 'rom'
  769. else:
  770. # If <Title> and <ApplicationPath> are the same, search for <Title>.pdf (without -01)
  771. file_path = os.path.join(alternative_folder_path, f"{search_names[0]}.pdf").replace(
  772. "\\", "/")
  773. if os.path.isfile(file_path):
  774. file_found = True
  775. match_type = 'rom'
  776.  
  777. # If not found in the alternative path, search in the main path
  778. if not file_found and os.path.exists(main_folder_path):
  779. # Search for <Title>-01.pdf
  780. file_path = os.path.join(main_folder_path, f"{search_names[0]}-01.pdf").replace("\\", "/")
  781. if os.path.isfile(file_path):
  782. file_found = True
  783. match_type = 'title'
  784. else:
  785. # Search for <ApplicationPath>.pdf (without -01)
  786. if len(search_names) > 1:
  787. file_path = os.path.join(main_folder_path, f"{search_names[1]}.pdf").replace("\\", "/")
  788. if os.path.isfile(file_path):
  789. file_found = True
  790. match_type = 'rom'
  791. else:
  792. # If <Title> and <ApplicationPath> are the same, search for <Title>.pdf (without -01)
  793. file_path = os.path.join(main_folder_path, f"{search_names[0]}.pdf").replace("\\", "/")
  794. if os.path.isfile(file_path):
  795. file_found = True
  796. match_type = 'rom'
  797.  
  798. # Show the result in the table
  799. if self.show_images:
  800. if file_found:
  801. self.draw_cell(row, col, "YES",
  802. cell_color=self.color_media_yes_title if match_type == 'title' else self.color_media_yes_rom)
  803. else:
  804. self.draw_cell(row, col, "NO", cell_color=self.color_media_no)
  805. else:
  806. cell_value = "YES (Title)" if match_type == 'title' else "YES (Rom)" if match_type == 'rom' else "NO"
  807. 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
  808. self.draw_cell(row, col, cell_value, cell_color=cell_color, is_text=True)
  809.  
  810. else:
  811. image_folder = os.path.join(self.launchbox_path, "Images", self.platform_combobox.get(),
  812. filter_name).replace("\\", "/")
  813. image_found = False
  814. image_path = None
  815. match_type = None
  816. title_found = False
  817. rom_found = False
  818. title_path = None
  819. rom_path = None
  820.  
  821. if os.path.exists(image_folder):
  822. # Search for <Title>-01.ext recursively
  823. for ext in ["png", "jpg"]:
  824. for root, dirs, files in os.walk(image_folder):
  825. for file in files:
  826. if file.startswith(f"{search_names[0]}-01") and file.endswith(ext):
  827. title_path = os.path.join(root, file)
  828. title_found = True
  829. break
  830. if title_found:
  831. break
  832. if title_found:
  833. break
  834.  
  835. # Search for <ApplicationPath>.ext non-recursively
  836. if len(search_names) > 1:
  837. for ext in ["png", "jpg"]:
  838. rom_path = os.path.join(image_folder, f"{search_names[1]}.{ext}").replace("\\", "/")
  839. if os.path.isfile(rom_path):
  840. rom_found = True
  841. break
  842. else:
  843. # If <Title> and <ApplicationPath> are the same, search for <Title>.ext (without -01)
  844. for ext in ["png", "jpg"]:
  845. rom_path = os.path.join(image_folder, f"{search_names[0]}.{ext}").replace("\\", "/")
  846. if os.path.isfile(rom_path):
  847. rom_found = True
  848. break
  849.  
  850. # Determine the match type
  851. if title_found and rom_found:
  852. match_type = 'title/rom'
  853. image_path = title_path # Use the Title image to display
  854. elif title_found:
  855. match_type = 'title'
  856. image_path = title_path
  857. elif rom_found:
  858. match_type = 'rom'
  859. image_path = rom_path
  860.  
  861. if self.show_images:
  862. if image_path:
  863. self.draw_image_cell(row, col, image_path, match_type)
  864. else:
  865. self.draw_cell(row, col, "NO", cell_color=self.color_media_no)
  866. else:
  867. if match_type == 'title/rom':
  868. cell_value = "YES (Title/Rom)"
  869. cell_color = self.color_media_both
  870. elif match_type == 'title':
  871. cell_value = "YES (Title)"
  872. cell_color = self.color_media_yes_title
  873. elif match_type == 'rom':
  874. cell_value = "YES (Rom)"
  875. cell_color = self.color_media_yes_rom
  876. else:
  877. cell_value = "NO"
  878. cell_color = self.color_media_no
  879. self.draw_cell(row, col, cell_value, cell_color=cell_color, is_text=True)
  880.  
  881. total_width = sum(
  882. self.cell_size_image[0] if self.show_images else self.cell_size_text[0] for _ in range(self.columns))
  883. total_height = len(games_to_show) * (self.cell_size_image[1] if self.show_images else self.cell_size_text[1])
  884. self.canvas.config(scrollregion=(0, 0, total_width, total_height))
  885. self.header_canvas.config(scrollregion=(0, 0, total_width, cell_height))
  886.  
  887. # Force focus to the Canvas to receive mouse wheel events
  888. self.canvas.focus_set()
  889.  
  890. def on_header_click(self, col):
  891. # Call the method to sort the data
  892. self.sort_by_column(col)
  893.  
  894. def draw_cell(self, row, col, value, header=False, cell_color=None, is_text=False, canvas=None):
  895. if canvas is None:
  896. canvas = self.canvas
  897.  
  898. if header:
  899. if self.show_images:
  900. cell_width, cell_height = self.cell_size_image
  901. else:
  902. cell_width, cell_height = self.cell_size_text
  903. elif is_text or not self.show_images:
  904. cell_width, cell_height = self.cell_size_text
  905. else:
  906. cell_width, cell_height = self.cell_size_image
  907.  
  908. x1 = col * cell_width
  909. y1 = row * cell_height
  910. x2 = x1 + cell_width
  911. y2 = y1 + cell_height
  912.  
  913. if value == "YES (Title)":
  914. fill_color = self.color_media_yes_title
  915. elif value == "YES (Rom)":
  916. fill_color = self.color_media_yes_rom
  917. elif value == "NO":
  918. fill_color = self.color_media_no
  919. else:
  920. fill_color = cell_color or "white"
  921.  
  922. # Draw the cell
  923. canvas.create_rectangle(x1, y1, x2, y2, fill=fill_color, outline="black",
  924. tags=f"header_{col}" if header else f"cell_{row}_{col}")
  925.  
  926. font_style = ("Arial", 10, "bold") if header else ("Arial", 10)
  927. wrapped_text = self.wrap_text(value, cell_width - 10)
  928. text_y = y1 + (cell_height - len(wrapped_text) * 12) / 2
  929.  
  930. for line in wrapped_text:
  931. canvas.create_text(x1 + 5, text_y, anchor="w", text=line, font=font_style,
  932. tags=f"header_{col}" if header else f"cell_{row}_{col}")
  933. text_y += 12
  934.  
  935. def wrap_text(self, text, max_width):
  936. import textwrap
  937. return textwrap.wrap(text, width=max_width // 7)
  938.  
  939. def draw_image_cell(self, row, col, image_path, match_type):
  940. try:
  941. cell_width, cell_height = self.cell_size_image
  942.  
  943. if self.make_cache:
  944. # Load from cache
  945. cache_folder = os.path.join(self.get_cache_folder(), self.platform_combobox.get(), self.filters[col - 1])
  946. os.makedirs(cache_folder, exist_ok=True)
  947.  
  948. cache_image_path = os.path.join(cache_folder,
  949. os.path.basename(image_path).replace(".png", ".jpg").replace(".PNG",
  950. ".jpg"))
  951.  
  952. width, height = map(int, self.image_cache_ratio.split("x"))
  953.  
  954. if not os.path.exists(cache_image_path):
  955. try:
  956. file_size = os.path.getsize(image_path)
  957. if file_size > 100 * 1024 * 1024:
  958. raise ValueError(
  959. f"The file {image_path} is too large ({file_size / (1024 * 1024):.2f} MB)")
  960.  
  961. with Image.open(image_path) as image:
  962. if image.mode in ("P", "1", "L", "LA"):
  963. image = image.convert("RGBA")
  964.  
  965. if image.mode == "RGBA":
  966. background = Image.new("RGB", image.size, self.color_no_trans)
  967. background.paste(image, mask=image.split()[-1])
  968. image = background
  969.  
  970. image.thumbnail((width, height))
  971. image.save(cache_image_path, "JPEG")
  972.  
  973. except Exception as e:
  974. print(f"Error processing image {image_path}: {e}")
  975. with open("images_with_errors.txt", "a") as error_file:
  976. error_file.write(f"{image_path}\n")
  977. self.draw_cell(row, col, "NO", cell_color=self.color_media_no)
  978. return
  979.  
  980. with Image.open(cache_image_path) as image:
  981. tk_image = ImageTk.PhotoImage(image)
  982. self.image_references[(row, col)] = tk_image
  983.  
  984. else:
  985. # Load directly from the source
  986. with Image.open(image_path) as image:
  987. if image.mode in ("P", "1", "L", "LA"):
  988. image = image.convert("RGBA")
  989.  
  990. if image.mode == "RGBA":
  991. background = Image.new("RGB", image.size, self.color_no_trans)
  992. background.paste(image, mask=image.split()[-1])
  993. image = background
  994.  
  995. image.thumbnail((cell_width, cell_height))
  996. tk_image = ImageTk.PhotoImage(image)
  997. self.image_references[(row, col)] = tk_image
  998.  
  999. x1 = col * self.cell_size_image[0]
  1000. y1 = row * self.cell_size_image[1]
  1001.  
  1002. bg_color = self.color_media_yes_title if match_type == 'title' else self.color_media_yes_rom
  1003. self.canvas.create_rectangle(x1, y1, x1 + cell_width, y1 + cell_height, fill=bg_color, outline="black")
  1004. self.canvas.create_image(x1 + cell_width // 2, y1 + cell_height // 2, anchor="center", image=tk_image)
  1005. except Exception as e:
  1006. print(f"Unexpected error processing image {image_path}: {e}")
  1007. self.draw_cell(row, col, "NO", cell_color=self.color_media_no)
  1008. with open("images_with_errors.txt", "a") as error_file:
  1009. error_file.write(f"{image_path}\n")
  1010.  
  1011. def regenerate_cache(self):
  1012. selected_platform = self.platform_combobox.get()
  1013. if not selected_platform:
  1014. return
  1015.  
  1016. # Show a progress message
  1017. self.progress_label = tk.Label(self.root, text="Regenerating cache...")
  1018. self.progress_label.place(relx=0.5, rely=0.5, anchor="center")
  1019. self.root.update()
  1020.  
  1021. # Get the path of the cache folder
  1022. cache_folder = os.path.join(self.get_cache_folder(), selected_platform)
  1023.  
  1024. # Delete the cache folder if it exists
  1025. if os.path.exists(cache_folder):
  1026. try:
  1027. shutil.rmtree(cache_folder)
  1028. print(f"Cache folder deleted: {cache_folder}")
  1029. except Exception as e:
  1030. print(f"Error deleting cache folder: {e}")
  1031. self.progress_label.destroy()
  1032. return
  1033.  
  1034. # Force cache regeneration
  1035. self.load_platforms() # Reload platforms
  1036. self.platform_combobox.set(selected_platform) # Select the current platform
  1037. self.on_platform_select() # Update the table
  1038.  
  1039. # Hide the progress message
  1040. self.progress_label.destroy()
  1041.  
  1042. def natural_sort_key(self, text):
  1043. import re
  1044. return [int(text) if text.isdigit() else text.lower() for text in re.split(r'(\d+)', text)]
  1045.  
  1046. def sort_by_column(self, col):
  1047. selected_platform = self.platform_combobox.get()
  1048. if not selected_platform:
  1049. return
  1050.  
  1051. xml_path = self.platforms[selected_platform]
  1052. tree = ET.parse(xml_path)
  1053. root = tree.getroot()
  1054.  
  1055. games = root.findall("Game")
  1056. game_dict = {}
  1057.  
  1058. for game in games:
  1059. title_element = game.find("Title")
  1060. title = title_element.text if title_element is not None else None
  1061.  
  1062. app_path_element = game.find("ApplicationPath")
  1063. if app_path_element is not None and app_path_element.text:
  1064. app_filename = os.path.basename(app_path_element.text)
  1065. app_name = os.path.splitext(app_filename)[0]
  1066. else:
  1067. app_name = None
  1068.  
  1069. if title:
  1070. game_dict[title] = [title]
  1071. if app_name and app_name != title:
  1072. game_dict[title].append(app_name)
  1073.  
  1074. # Sort games
  1075. if col == 0:
  1076. # Sort by title
  1077. sorted_game_dict = dict(
  1078. sorted(game_dict.items(), key=lambda item: (self.natural_sort_key(item[0]), item[0])))
  1079. else:
  1080. # Sort by the selected column
  1081. filter_name = self.filters[col - 1]
  1082. sorted_game_dict = dict(
  1083. sorted(game_dict.items(), key=lambda item: self.get_filter_value(item[1], filter_name)))
  1084.  
  1085. # Toggle between ascending and descending order
  1086. if self.last_sorted_column == col:
  1087. self.sort_ascending = not self.sort_ascending
  1088. if not self.sort_ascending:
  1089. sorted_game_dict = dict(reversed(list(sorted_game_dict.items())))
  1090. else:
  1091. self.sort_ascending = True
  1092.  
  1093. self.last_sorted_column = col
  1094.  
  1095. # If the "Hide all YES" filter is active, apply the filter after sorting
  1096. if self.hide_all_si_var.get():
  1097. sorted_game_dict = self.filter_all_si(sorted_game_dict)
  1098.  
  1099. # Update the GUI with the sorted (and filtered if necessary) games
  1100. self.load_filter_file(selected_platform, sorted_game_dict)
  1101.  
  1102. def filter_all_si(self, game_dict):
  1103. """
  1104. Filters games that have "YES" in all cells, based on cell colors.
  1105. """
  1106. filtered_game_dict = {}
  1107. for title, search_names in game_dict.items():
  1108. all_si = True # Assume all cells have "YES" until proven otherwise
  1109.  
  1110. # Check each filter for this game
  1111. for filter_name in self.filters:
  1112. # Get the color of the corresponding cell
  1113. cell_color = self.get_cell_color(title, filter_name, search_names)
  1114.  
  1115. # If the color is not one of the colors indicating "YES", mark as NO
  1116. if cell_color not in [self.color_media_yes_title, self.color_media_yes_rom, self.color_media_both]:
  1117. all_si = False
  1118. break
  1119.  
  1120. # If all cells have "YES", do not add the game to the filtered dictionary
  1121. if not all_si:
  1122. filtered_game_dict[title] = search_names
  1123.  
  1124. return filtered_game_dict
  1125.  
  1126. def get_cell_color(self, title, filter_name, search_names):
  1127. """
  1128. Gets the color of the cell corresponding to the given title and filter.
  1129. """
  1130. if filter_name == "Videos":
  1131. # Logic for videos
  1132. main_folder_path = os.path.join(self.launchbox_path, "Videos", self.platform_combobox.get()).replace("\\",
  1133. "/")
  1134. alternative_folder_path = os.path.join(self.alternative_path, self.platform_combobox.get(),
  1135. "Videos").replace("\\", "/") if self.alternative_path else None
  1136.  
  1137. file_found = False
  1138. match_type = None
  1139.  
  1140. # Search in the alternative path first (if it exists)
  1141. if alternative_folder_path and os.path.exists(alternative_folder_path):
  1142. # Search for <Title>-01.mp4
  1143. file_path = os.path.join(alternative_folder_path, f"{search_names[0]}-01.mp4").replace("\\", "/")
  1144. if os.path.isfile(file_path):
  1145. file_found = True
  1146. match_type = 'title'
  1147. else:
  1148. # Search for <ApplicationPath>.mp4 (without -01)
  1149. if len(search_names) > 1:
  1150. file_path = os.path.join(alternative_folder_path, f"{search_names[1]}.mp4").replace("\\", "/")
  1151. if os.path.isfile(file_path):
  1152. file_found = True
  1153. match_type = 'rom'
  1154. else:
  1155. # If <Title> and <ApplicationPath> are the same, search for <Title>.mp4 (without -01)
  1156. file_path = os.path.join(alternative_folder_path, f"{search_names[0]}.mp4").replace("\\", "/")
  1157. if os.path.isfile(file_path):
  1158. file_found = True
  1159. match_type = 'rom'
  1160.  
  1161. # If not found in the alternative path, search in the main path
  1162. if not file_found and os.path.exists(main_folder_path):
  1163. # Search for <Title>-01.mp4
  1164. file_path = os.path.join(main_folder_path, f"{search_names[0]}-01.mp4").replace("\\", "/")
  1165. if os.path.isfile(file_path):
  1166. file_found = True
  1167. match_type = 'title'
  1168. else:
  1169. # Search for <ApplicationPath>.mp4 (without -01)
  1170. if len(search_names) > 1:
  1171. file_path = os.path.join(main_folder_path, f"{search_names[1]}.mp4").replace("\\", "/")
  1172. if os.path.isfile(file_path):
  1173. file_found = True
  1174. match_type = 'rom'
  1175. else:
  1176. # If <Title> and <ApplicationPath> are the same, search for <Title>.mp4 (without -01)
  1177. file_path = os.path.join(main_folder_path, f"{search_names[0]}.mp4").replace("\\", "/")
  1178. if os.path.isfile(file_path):
  1179. file_found = True
  1180. match_type = 'rom'
  1181.  
  1182. # Determine the color based on the result
  1183. if file_found:
  1184. if match_type == 'title':
  1185. return self.color_media_yes_title
  1186. elif match_type == 'rom':
  1187. return self.color_media_yes_rom
  1188. else:
  1189. return self.color_media_no
  1190.  
  1191. elif filter_name == "Manual":
  1192. # Logic for manuals (similar to videos)
  1193. main_folder_path = os.path.join(self.launchbox_path, "Manuals", self.platform_combobox.get()).replace("\\",
  1194. "/")
  1195. alternative_folder_path = os.path.join(self.alternative_path, self.platform_combobox.get(),
  1196. "Manuals").replace("\\", "/") if self.alternative_path else None
  1197.  
  1198. file_found = False
  1199. match_type = None
  1200.  
  1201. # Search in the alternative path first (if it exists)
  1202. if alternative_folder_path and os.path.exists(alternative_folder_path):
  1203. # Search for <Title>-01.pdf
  1204. file_path = os.path.join(alternative_folder_path, f"{search_names[0]}-01.pdf").replace("\\", "/")
  1205. if os.path.isfile(file_path):
  1206. file_found = True
  1207. match_type = 'title'
  1208. else:
  1209. # Search for <ApplicationPath>.pdf (without -01)
  1210. if len(search_names) > 1:
  1211. file_path = os.path.join(alternative_folder_path, f"{search_names[1]}.pdf").replace("\\", "/")
  1212. if os.path.isfile(file_path):
  1213. file_found = True
  1214. match_type = 'rom'
  1215. else:
  1216. # If <Title> and <ApplicationPath> are the same, search for <Title>.pdf (without -01)
  1217. file_path = os.path.join(alternative_folder_path, f"{search_names[0]}.pdf").replace("\\", "/")
  1218. if os.path.isfile(file_path):
  1219. file_found = True
  1220. match_type = 'rom'
  1221.  
  1222. # If not found in the alternative path, search in the main path
  1223. if not file_found and os.path.exists(main_folder_path):
  1224. # Search for <Title>-01.pdf
  1225. file_path = os.path.join(main_folder_path, f"{search_names[0]}-01.pdf").replace("\\", "/")
  1226. if os.path.isfile(file_path):
  1227. file_found = True
  1228. match_type = 'title'
  1229. else:
  1230. # Search for <ApplicationPath>.pdf (without -01)
  1231. if len(search_names) > 1:
  1232. file_path = os.path.join(main_folder_path, f"{search_names[1]}.pdf").replace("\\", "/")
  1233. if os.path.isfile(file_path):
  1234. file_found = True
  1235. match_type = 'rom'
  1236. else:
  1237. # If <Title> and <ApplicationPath> are the same, search for <Title>.pdf (without -01)
  1238. file_path = os.path.join(main_folder_path, f"{search_names[0]}.pdf").replace("\\", "/")
  1239. if os.path.isfile(file_path):
  1240. file_found = True
  1241. match_type = 'rom'
  1242.  
  1243. # Determine the color based on the result
  1244. if file_found:
  1245. if match_type == 'title':
  1246. return self.color_media_yes_title
  1247. elif match_type == 'rom':
  1248. return self.color_media_yes_rom
  1249. else:
  1250. return self.color_media_no
  1251.  
  1252. else:
  1253. # Logic for images
  1254. image_folder = os.path.join(self.launchbox_path, "Images", self.platform_combobox.get(),
  1255. filter_name).replace("\\", "/")
  1256. image_found = False
  1257. match_type = None
  1258.  
  1259. if os.path.exists(image_folder):
  1260. # Search for <Title>-01.ext recursively
  1261. for ext in ["png", "jpg"]:
  1262. for root, dirs, files in os.walk(image_folder):
  1263. for file in files:
  1264. if file.startswith(f"{search_names[0]}-01") and file.endswith(ext):
  1265. image_found = True
  1266. match_type = 'title'
  1267. break
  1268. if image_found:
  1269. break
  1270. if image_found:
  1271. break
  1272.  
  1273. # Search for <ApplicationPath>.ext non-recursively
  1274. if not image_found and len(search_names) > 1:
  1275. for ext in ["png", "jpg"]:
  1276. image_path = os.path.join(image_folder, f"{search_names[1]}.{ext}").replace("\\", "/")
  1277. if os.path.isfile(image_path):
  1278. image_found = True
  1279. match_type = 'rom'
  1280. break
  1281.  
  1282. # If <Title> and <ApplicationPath> are the same, search for <Title>.ext (without -01)
  1283. if not image_found and len(search_names) == 1:
  1284. for ext in ["png", "jpg"]:
  1285. image_path = os.path.join(image_folder, f"{search_names[0]}.{ext}").replace("\\", "/")
  1286. if os.path.isfile(image_path):
  1287. image_found = True
  1288. match_type = 'rom'
  1289. break
  1290.  
  1291. # Determine the color based on the result
  1292. if image_found:
  1293. if match_type == 'title':
  1294. return self.color_media_yes_title
  1295. elif match_type == 'rom':
  1296. return self.color_media_yes_rom
  1297. elif match_type == 'title/rom':
  1298. return self.color_media_both
  1299. else:
  1300. return self.color_media_no
  1301.  
  1302. def apply_hide_all_si_filter(self):
  1303. # Show the filtering message
  1304. self.filtering_label.place(relx=0.5, rely=0.5, anchor="center")
  1305. self.root.update() # Force interface update
  1306.  
  1307. # Apply the filter
  1308. self.filter_active = not self.filter_active # Toggle filter state
  1309. self.update_table_with_filter()
  1310.  
  1311. # Hide the filtering message
  1312. self.filtering_label.place_forget()
  1313.  
  1314. def update_table_with_filter(self):
  1315. selected_platform = self.platform_combobox.get()
  1316. if not selected_platform:
  1317. return
  1318.  
  1319. xml_path = self.platforms[selected_platform]
  1320. tree = ET.parse(xml_path)
  1321. root = tree.getroot()
  1322.  
  1323. games = root.findall("Game")
  1324. game_dict = {}
  1325.  
  1326. for game in games:
  1327. title_element = game.find("Title")
  1328. title = title_element.text if title_element is not None else None
  1329.  
  1330. app_path_element = game.find("ApplicationPath")
  1331. if app_path_element is not None and app_path_element.text:
  1332. app_filename = os.path.basename(app_path_element.text)
  1333. app_name = os.path.splitext(app_filename)[0]
  1334. else:
  1335. app_name = None
  1336.  
  1337. if title:
  1338. game_dict[title] = [title]
  1339. if app_name and app_name != title:
  1340. game_dict[title].append(app_name)
  1341.  
  1342. # Sort games alphabetically
  1343. sorted_game_dict = dict(sorted(game_dict.items(), key=lambda item: (self.natural_sort_key(item[0]), item[0])))
  1344.  
  1345. # Apply the filter if active
  1346. if self.filter_active:
  1347. sorted_game_dict = self.filter_all_si(sorted_game_dict)
  1348.  
  1349. # Update the table with the (filtered or unfiltered) games
  1350. self.load_filter_file(selected_platform, sorted_game_dict)
  1351.  
  1352.  
  1353. class SettingsWindow:
  1354. def __init__(self, root, config_file):
  1355. self.root = root
  1356. self.config_file = config_file
  1357. self.root.title("Settings")
  1358.  
  1359. # Create a Canvas and Scrollbars
  1360. self.canvas = tk.Canvas(self.root)
  1361. self.scrollbar_y = tk.Scrollbar(self.root, orient="vertical", command=self.canvas.yview)
  1362. self.scrollbar_x = tk.Scrollbar(self.root, orient="horizontal", command=self.canvas.xview)
  1363.  
  1364. # Configure the Canvas to be scrollable
  1365. self.scrollable_frame = tk.Frame(self.canvas)
  1366. self.scrollable_frame.bind(
  1367. "<Configure>",
  1368. lambda e: self.canvas.configure(
  1369. scrollregion=self.canvas.bbox("all")
  1370. )
  1371. )
  1372.  
  1373. self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
  1374. self.canvas.configure(yscrollcommand=self.scrollbar_y.set, xscrollcommand=self.scrollbar_x.set)
  1375.  
  1376. # Configure the mouse wheel event
  1377. self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
  1378.  
  1379. # Place the Canvas and Scrollbars in the window
  1380. self.canvas.grid(row=0, column=0, sticky="nsew")
  1381. self.scrollbar_y.grid(row=0, column=1, sticky="ns")
  1382. self.scrollbar_x.grid(row=1, column=0, sticky="ew")
  1383.  
  1384. # Configure window expansion
  1385. self.root.grid_rowconfigure(0, weight=1)
  1386. self.root.grid_columnconfigure(0, weight=1)
  1387.  
  1388. # Variables to store values
  1389. self.launchbox_path = tk.StringVar(value="C:/LaunchBox")
  1390. self.alternative_path = tk.StringVar(value="D:/LaunchBox")
  1391. self.image_cache_ratio_w = tk.StringVar(value="200")
  1392. self.image_cache_ratio_h = tk.StringVar(value="200")
  1393. self.text_cell_w = tk.StringVar(value="150")
  1394. self.text_cell_h = tk.StringVar(value="50")
  1395. self.image_cell_w = tk.StringVar(value="200")
  1396. self.image_cell_h = tk.StringVar(value="200")
  1397. self.max_games_per_page_imagemode = tk.StringVar(value="200")
  1398. self.color_media_yes_title = tk.StringVar(value="#80FF00")
  1399. self.color_media_yes_rom = tk.StringVar(value="#80AA00")
  1400. self.color_media_both = tk.StringVar(value="#DBDBDB") # Ensure this variable is defined
  1401. self.color_media_no = tk.StringVar(value="#FF0000")
  1402. self.color_no_trans = tk.StringVar(value="#DBDBDB")
  1403. self.selected_filters = ["Box - Front", "Clear Logo"]
  1404. self.make_cache = tk.BooleanVar()
  1405.  
  1406. # Load current values from the config.txt file
  1407. self.load_config()
  1408.  
  1409. # Graphical interface
  1410. self.setup_ui()
  1411.  
  1412. # Adjust window size to the farthest content without additional margin
  1413. self.root.update_idletasks()
  1414. width = self.scrollable_frame.winfo_reqwidth()
  1415. height = self.scrollable_frame.winfo_reqheight()
  1416. self.root.geometry(f"{width}x{height}")
  1417.  
  1418. # Bind the window resize event
  1419. self.root.bind("<Configure>", self._on_window_resize)
  1420.  
  1421. def _on_mousewheel(self, event):
  1422. """Scroll the Canvas with the mouse wheel."""
  1423. if event.delta:
  1424. self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
  1425.  
  1426. def _on_window_resize(self, event):
  1427. """Update the scroll region when the window is resized."""
  1428. self.canvas.configure(scrollregion=self.canvas.bbox("all"))
  1429.  
  1430. def setup_ui(self):
  1431. """Configure the graphical interface elements."""
  1432. main_frame = tk.Frame(self.scrollable_frame)
  1433. main_frame.pack(fill="both", expand=True, padx=10, pady=10)
  1434.  
  1435. # LaunchBox Path
  1436. tk.Label(main_frame, text="LaunchBox Path:").grid(row=0, column=0, sticky="w")
  1437. self.path_entry = tk.Entry(main_frame, textvariable=self.launchbox_path, width=40)
  1438. self.path_entry.grid(row=0, column=1, sticky="w")
  1439. tk.Button(main_frame, text="Browse", command=self.browse_launchbox_path).grid(row=0, column=2)
  1440.  
  1441. # Selected Filters
  1442. tk.Label(main_frame, text="Selected Filters:").grid(row=1, column=0, sticky="w", pady=(10, 0))
  1443. self.filters = [
  1444. "Arcade - Marquee", "Banner", "Advertisement Flyer - Back", "Advertisement Flyer - Front", "Box - Front",
  1445. "Box - Front - Reconstructed", "Fanart - Box - Front", "Box - 3D", "Box - Back",
  1446. "Box - Back - Reconstructed", "Fanart - Box - Back", "Box - Spine", "Box - Full", "Clear Logo",
  1447. "Cart - Front", "Disc", "Fanart - Cart - Front", "Fanart - Disc", "Cart - Back", "Fanart - Cart - Back",
  1448. "Cart - 3D", "Fanart - Background", "Screenshot - Game Over", "Screenshot - Game Select",
  1449. "Screenshot - Game Title", "Screenshot - Gameplay", "Screenshot - High Scores", "Epic Games Background",
  1450. "Epic Games Poster", "Epic Games Screenshot", "GOG Poster", "GOG Screenshot", "Origin Background",
  1451. "Origin Poster", "Origin Screenshot", "Steam Banner", "Steam Poster", "Steam Screenshot",
  1452. "Uplay Background", "Uplay Thumbnail", "Arcade - Cabinet", "Arcade - Circuit Board",
  1453. "Arcade - Control Panel", "Arcade - Controls Information", "Amazon Background", "Amazon Poster",
  1454. "Amazon Screenshot", "Videos", "Manual"
  1455. ]
  1456.  
  1457. self.filter_vars = []
  1458. num_columns = 3 # Number of columns for filters
  1459. max_filters_per_column = (len(self.filters) // num_columns) + (1 if len(self.filters) % num_columns != 0 else 0)
  1460. for i, filter_name in enumerate(self.filters):
  1461. var = tk.BooleanVar(value=(filter_name in self.selected_filters))
  1462. row = 2 + (i % max_filters_per_column)
  1463. column = i // max_filters_per_column
  1464. tk.Checkbutton(main_frame, text=filter_name, variable=var).grid(row=row, column=column, sticky="w")
  1465. self.filter_vars.append(var)
  1466.  
  1467. # Adjust the width of the scrollable_frame to the widest content
  1468. self.scrollable_frame.update_idletasks()
  1469. self.canvas.configure(scrollregion=self.canvas.bbox("all"))
  1470.  
  1471. row_offset = 2 + max_filters_per_column
  1472.  
  1473. # Add a line break
  1474. tk.Label(main_frame, text="").grid(row=row_offset, column=0)
  1475. row_offset += 1
  1476.  
  1477. # Maximum games listed per page
  1478. tk.Label(main_frame, text="Maximum games listed per page:").grid(row=row_offset, column=0, sticky="w")
  1479. self.max_games_entry = tk.Entry(main_frame, textvariable=self.max_games_per_page_imagemode, width=5)
  1480. self.max_games_entry.grid(row=row_offset, column=1, padx=(0, 0))
  1481. row_offset += 1
  1482.  
  1483. # Add a line break
  1484. tk.Label(main_frame, text="").grid(row=row_offset, column=0)
  1485. row_offset += 1
  1486.  
  1487. # Cell size in Text mode
  1488. self.create_size_input(main_frame, "Cell Size in Text Mode", row_offset, self.text_cell_w, self.text_cell_h)
  1489. row_offset += 1
  1490.  
  1491. # Cell size in Image mode
  1492. self.create_size_input(main_frame, "Cell Size in Image Mode", row_offset, self.image_cell_w, self.image_cell_h)
  1493. row_offset += 1
  1494.  
  1495. # Add double line break
  1496. tk.Label(main_frame, text="").grid(row=row_offset, column=0)
  1497. tk.Label(main_frame, text="").grid(row=row_offset + 1, column=0)
  1498. row_offset += 2
  1499.  
  1500. # Alternative Media Path
  1501. tk.Label(main_frame, text="Alternative Media Path (Videos/Manuals):").grid(row=row_offset, column=0, sticky="w")
  1502. self.alt_path_entry = tk.Entry(main_frame, textvariable=self.alternative_path, width=40)
  1503. self.alt_path_entry.grid(row=row_offset, column=1, sticky="w")
  1504. tk.Button(main_frame, text="Browse", command=self.browse_alternative_path).grid(row=row_offset, column=2)
  1505. # Only for Video and Manual, with the format [Platform]/Video/[Game Name].mp4 or [Platform]/Manuals/[Game Name].pdf
  1506. tk.Label(main_frame,
  1507. text="Only for Video and Manual, with the format [Platform]/Videos/[Game Name].mp4 and [Platform]/Manuals/[Game Name].pdf").grid(
  1508. row=row_offset + 1, column=0, columnspan=3, sticky="w")
  1509. row_offset += 2
  1510.  
  1511. # Add double line break
  1512. tk.Label(main_frame, text="").grid(row=row_offset, column=0)
  1513. tk.Label(main_frame, text="").grid(row=row_offset + 1, column=0)
  1514. row_offset += 2
  1515.  
  1516. # Color Selectors
  1517. # Color for Media found with Title
  1518. tk.Label(main_frame, text="Color for Media found with Title:").grid(row=row_offset, column=0, sticky="w")
  1519. self.color_media_yes_title_display = tk.Label(main_frame, textvariable=self.color_media_yes_title, width=10,
  1520. bg=self.color_media_yes_title.get())
  1521. self.color_media_yes_title_display.grid(row=row_offset, column=1, sticky="w", padx=(0, 0))
  1522. self.color_media_yes_title_button = tk.Button(main_frame, text="Select Color",
  1523. command=self.choose_color_media_yes_title)
  1524. self.color_media_yes_title_button.grid(row=row_offset, column=1, sticky="e")
  1525. row_offset += 1
  1526. # Color for Media found with RomName
  1527. tk.Label(main_frame, text="Color for Media found with RomName:").grid(row=row_offset, column=0, sticky="w")
  1528. self.color_media_yes_rom_display = tk.Label(main_frame, textvariable=self.color_media_yes_rom, width=10,
  1529. bg=self.color_media_yes_rom.get())
  1530. self.color_media_yes_rom_display.grid(row=row_offset, column=1, sticky="w", padx=(0, 0))
  1531. self.color_media_yes_rom_button = tk.Button(main_frame, text="Select Color",
  1532. command=self.choose_color_media_yes_rom)
  1533. self.color_media_yes_rom_button.grid(row=row_offset, column=1, sticky="e")
  1534. row_offset += 1
  1535.  
  1536. # Color for Media found with Title/RomName
  1537. tk.Label(main_frame, text="Color for Media found with Both:").grid(row=row_offset, column=0, sticky="w")
  1538. self.color_no_trans_display = tk.Label(main_frame, textvariable=self.color_no_trans, width=10,
  1539. bg=self.color_no_trans.get())
  1540. self.color_no_trans_display.grid(row=row_offset, column=1, sticky="w", padx=(0, 30))
  1541. self.color_no_trans_button = tk.Button(main_frame, text="Select Color", command=self.choose_color_no_trans)
  1542. self.color_no_trans_button.grid(row=row_offset, column=1, sticky="e")
  1543. row_offset += 1
  1544.  
  1545. # Color for Media NOT found
  1546. tk.Label(main_frame, text="Color for Media NOT found:").grid(row=row_offset, column=0, sticky="w")
  1547. self.color_media_no_display = tk.Label(main_frame, textvariable=self.color_media_no, width=10,
  1548. bg=self.color_media_no.get())
  1549. self.color_media_no_display.grid(row=row_offset, column=1, sticky="w", padx=(0, 30))
  1550. self.color_media_no_button = tk.Button(main_frame, text="Select Color", command=self.choose_color_media_no)
  1551. self.color_media_no_button.grid(row=row_offset, column=1, sticky="e")
  1552. row_offset += 1
  1553.  
  1554. # Add double line break
  1555. tk.Label(main_frame, text="").grid(row=row_offset, column=0)
  1556. tk.Label(main_frame, text="").grid(row=row_offset + 1, column=0)
  1557. row_offset += 2
  1558.  
  1559. # Option to make cache
  1560. tk.Checkbutton(main_frame, text="Make Local Cache (Better performance)", variable=self.make_cache, onvalue=True,
  1561. offvalue=False).grid(row=row_offset, column=0, sticky="w")
  1562. row_offset += 1
  1563.  
  1564. # Cache image ratio
  1565. self.create_size_input(main_frame, "Cache Image Ratio", row_offset, self.image_cache_ratio_w,
  1566. self.image_cache_ratio_h)
  1567. row_offset += 1
  1568.  
  1569. # Save Button
  1570. tk.Button(main_frame, text="Save", command=self.save_config).grid(row=row_offset, column=0, columnspan=3,
  1571. pady=(20, 0))
  1572.  
  1573. def create_size_input(self, frame, label_text, row, var_w, var_h):
  1574. """Create a row with a label, two entries, and an 'x' in between, with adjusted spacing."""
  1575. tk.Label(frame, text=label_text).grid(row=row, column=0, sticky="w")
  1576. tk.Entry(frame, textvariable=var_w, width=5).grid(row=row, column=1, padx=(0, 0)) # Remove right padding
  1577. tk.Label(frame, text="x").grid(row=row, column=1, padx=(32, 0)) # Remove padding
  1578. tk.Entry(frame, textvariable=var_h, width=5).grid(row=row, column=1, padx=(80, 0)) # Remove left padding
  1579.  
  1580. def browse_launchbox_path(self):
  1581. folder_path = filedialog.askdirectory(title="Select LaunchBox Folder")
  1582. if folder_path:
  1583. self.launchbox_path.set(folder_path)
  1584.  
  1585. def browse_alternative_path(self):
  1586. folder_path = filedialog.askdirectory(title="Select Alternative Media Folder")
  1587. if folder_path:
  1588. self.alternative_path.set(folder_path)
  1589.  
  1590. def choose_color_media_yes_title(self):
  1591. color_code = colorchooser.askcolor(title="Select a Color")
  1592. if color_code[1]: # Check if a color was selected (color_code[1] is the hex code)
  1593. self.color_media_yes_title.set(color_code[1])
  1594. self.color_media_yes_title_display.config(bg=color_code[1])
  1595.  
  1596. def choose_color_media_yes_rom(self):
  1597. color_code = colorchooser.askcolor(title="Select a Color")
  1598. if color_code[1]: # Check if a color was selected
  1599. self.color_media_yes_rom.set(color_code[1])
  1600. self.color_media_yes_rom_display.config(bg=color_code[1])
  1601.  
  1602. def choose_color_media_no(self):
  1603. color_code = colorchooser.askcolor(title="Select a Color")
  1604. if color_code[1]: # Check if a color was selected
  1605. self.color_media_no.set(color_code[1])
  1606. self.color_media_no_display.config(bg=color_code[1])
  1607.  
  1608. def choose_color_no_trans(self):
  1609. color_code = colorchooser.askcolor(title="Select a Color")
  1610. if color_code[1]: # Check if a color was selected
  1611. self.color_no_trans.set(color_code[1])
  1612. self.color_no_trans_display.config(bg=color_code[1])
  1613.  
  1614. def load_config(self):
  1615. """Loads current values from the config.txt file."""
  1616. print(f"Path of config.txt file: {self.config_file}") # Debug
  1617. if not os.path.exists(self.config_file):
  1618. print("Creating config.txt file...") # Debug
  1619. with open(self.config_file, "w") as file:
  1620. file.write("path=\n")
  1621. file.write("filters=Box - Front,Clear Logo\n")
  1622. file.write("image_cache_ratio=150x150\n")
  1623. file.write("alternative_path=\n")
  1624. file.write("image_cell=150x150\n")
  1625. file.write("text_cell=150x50\n")
  1626. file.write("max_games_per_page_imagemode=100\n")
  1627. file.write("color_media_yes_title=#0073E6\n")
  1628. file.write("color_media_yes_rom=#00E673\n")
  1629. file.write("color_media_both=#E67300\n") # Ensure this line is present
  1630. file.write("color_media_no=#E60073\n")
  1631. file.write("color_no_trans=#C0C0C0\n")
  1632. file.write("make_cache=true\n")
  1633.  
  1634. # Load values from the config.txt file
  1635. with open(self.config_file, "r") as file:
  1636. for line in file:
  1637. key, value = line.strip().split('=', 1)
  1638. value = value.strip('"')
  1639. if key == "path":
  1640. self.launchbox_path.set(value)
  1641. elif key == "filters":
  1642. self.selected_filters = value.split(',')
  1643. elif key == "image_cache_ratio":
  1644. self.image_cache_ratio_w.set(value.split('x')[0])
  1645. self.image_cache_ratio_h.set(value.split('x')[1])
  1646. elif key == "text_cell":
  1647. self.text_cell_w.set(value.split('x')[0])
  1648. self.text_cell_h.set(value.split('x')[1])
  1649. elif key == "image_cell":
  1650. self.image_cell_w.set(value.split('x')[0])
  1651. self.image_cell_h.set(value.split('x')[1])
  1652. elif key == "alternative_path":
  1653. self.alternative_path.set(value)
  1654. elif key == "max_games_per_page_imagemode":
  1655. self.max_games_per_page_imagemode.set(value)
  1656. elif key == "color_media_yes_title":
  1657. self.color_media_yes_title.set(value)
  1658. elif key == "color_media_yes_rom":
  1659. self.color_media_yes_rom.set(value)
  1660. elif key == "color_media_both": # Ensure this key is present
  1661. self.color_media_both.set(value)
  1662. elif key == "color_media_no":
  1663. self.color_media_no.set(value)
  1664. elif key == "color_no_trans":
  1665. self.color_no_trans.set(value)
  1666. elif key == "make_cache":
  1667. self.make_cache.set(value.lower() == "true")
  1668.  
  1669. def save_config(self):
  1670. """Save the selected values to the config.txt file."""
  1671. selected_filters = [self.filters[i] for i, var in enumerate(self.filter_vars) if var.get()]
  1672. with open(self.config_file, "w") as file:
  1673. file.write(f"path={self.launchbox_path.get()}\n")
  1674. file.write(f'filters="{",".join(selected_filters)}"\n') # Corrected line
  1675. file.write(f"image_cache_ratio={self.image_cache_ratio_w.get()}x{self.image_cache_ratio_h.get()}\n")
  1676. file.write(f"text_cell={self.text_cell_w.get()}x{self.text_cell_h.get()}\n")
  1677. file.write(f"image_cell={self.image_cell_w.get()}x{self.image_cell_h.get()}\n")
  1678. file.write(f"alternative_path={self.alternative_path.get()}\n")
  1679. file.write(f"max_games_per_page_imagemode={self.max_games_per_page_imagemode.get()}\n")
  1680. file.write(f"color_media_yes_title={self.color_media_yes_title.get()}\n")
  1681. file.write(f"color_media_yes_rom={self.color_media_yes_rom.get()}\n")
  1682. file.write(f"color_media_no={self.color_media_no.get()}\n")
  1683. file.write(f"color_no_trans={self.color_no_trans.get()}\n")
  1684. file.write(f"make_cache={self.make_cache.get()}\n")
  1685. messagebox.showinfo("Save", "Settings saved successfully.")
  1686. self.root.destroy()
  1687.  
  1688. if __name__ == "__main__":
  1689. try:
  1690. root = tk.Tk()
  1691. app = LaunchBoxManager(root)
  1692. root.mainloop()
  1693. except MemoryError:
  1694. print("Severe memory error. The program must close.")
  1695. except Exception as e:
  1696. print(f"Unexpected error: {e}")
  1697. messagebox.showerror("Error", f"An unexpected error occurred: {e}")
Advertisement
Add Comment
Please, Sign In to add comment