3434import webbrowser
3535import requests
3636import babel
37+ from typing import Any
3738from datetime import datetime , timezone
3839from library .smartmonitor_compile import compile_theme_file
39- from library .smartmonitor_classic_theme_convert import convert_classic_theme_to_smartmonitor_project
40+ from library .smartmonitor_classic_theme_convert import (
41+ convert_classic_theme_to_smartmonitor_project ,
42+ find_classic_theme_files ,
43+ )
4044from library .smartmonitor_ui import encode_ui_file , parse_theme_bundle , resolve_theme_path
4145
4246try :
@@ -530,6 +534,23 @@ def compile_and_import_classic_theme(theme_name: str, classic_theme_path: str):
530534 return target_img , result
531535
532536
537+ def compile_and_import_classic_theme_batch (classic_root : str ) -> tuple [list [str ], list [str ], list [tuple [str , Any ]]]:
538+ imported : list [str ] = []
539+ failed : list [str ] = []
540+ results : list [tuple [str , Any ]] = []
541+
542+ for theme_yaml in find_classic_theme_files (classic_root ):
543+ theme_name = sanitize_smartmonitor_theme_name (theme_yaml .parent .name )
544+ try :
545+ _ , result = compile_and_import_classic_theme (theme_name , str (theme_yaml ))
546+ imported .append (theme_name )
547+ results .append ((theme_name , result ))
548+ except Exception as exc :
549+ failed .append (f"{ theme_yaml .parent .name } : { exc } " )
550+
551+ return imported , failed , results
552+
553+
533554def create_smartmonitor_ui_project (project_dir : str , project_name : str ):
534555 project_path = Path (project_dir )
535556 images_dir = project_path / "images"
@@ -1013,12 +1034,24 @@ def __init__(self):
10131034 "A linked editable .ui project is saved for later editing." ,
10141035 )
10151036
1037+ self .classic_batch_btn = ttk .Button (
1038+ self .window ,
1039+ text = "Classic batch" ,
1040+ command = lambda : self .on_smartmonitor_classic_batch_convert_click (),
1041+ )
1042+ self .classic_batch_btn .place (x = 480 , y = 635 , height = 42 , width = 110 )
1043+ self .classic_batch_tooltip = ToolTip (
1044+ self .classic_batch_btn ,
1045+ msg = "Convert every classic theme.yaml found under a folder into\n "
1046+ "SmartMonitor themes and import them into the GUI library." ,
1047+ )
1048+
10161049 self .new_ui_btn = ttk .Button (
10171050 self .window ,
10181051 text = "New UI" ,
10191052 command = lambda : self .on_new_ui_project_click (),
10201053 )
1021- self .new_ui_btn .place (x = 480 , y = 635 , height = 42 , width = 100 )
1054+ self .new_ui_btn .place (x = 600 , y = 635 , height = 42 , width = 100 )
10221055 self .new_ui_tooltip = ToolTip (
10231056 self .new_ui_btn ,
10241057 msg = "Create a new starter SmartMonitor UI project with background,\n "
@@ -1052,7 +1085,7 @@ def __init__(self):
10521085 text = "Force upload" ,
10531086 command = lambda : self .on_force_upload_click (),
10541087 )
1055- self .force_upload_btn .place (x = 590 , y = 635 , height = 42 , width = 110 )
1088+ self .force_upload_btn .place (x = 710 , y = 635 , height = 42 , width = 110 )
10561089 self .force_upload_tooltip = ToolTip (
10571090 self .force_upload_btn ,
10581091 msg = "Upload the currently selected SmartMonitor .dat theme immediately,\n "
@@ -1064,7 +1097,7 @@ def __init__(self):
10641097 text = "Doctor" ,
10651098 command = lambda : self .on_doctor_click (),
10661099 )
1067- self .doctor_btn .place (x = 710 , y = 635 , height = 42 , width = 90 )
1100+ self .doctor_btn .place (x = 830 , y = 635 , height = 42 , width = 80 )
10681101 self .doctor_tooltip = ToolTip (
10691102 self .doctor_btn ,
10701103 msg = "Run first-launch diagnostics: Python packages, config, themes,\n "
@@ -1107,6 +1140,7 @@ def refresh_theme_selector(self):
11071140 self .edit_ui_btn .config (text = "Edit UI" , state = "normal" )
11081141 self .vendor_convert_btn .config (text = "Vendor UI" , state = "normal" )
11091142 self .classic_convert_btn .config (text = "Classic->DAT" , state = "normal" )
1143+ self .classic_batch_btn .config (text = "Classic batch" , state = "normal" )
11101144 self .new_ui_btn .config (text = "New UI" , state = "normal" )
11111145 self .force_upload_btn .config (state = "normal" )
11121146 else :
@@ -1118,6 +1152,7 @@ def refresh_theme_selector(self):
11181152 self .edit_ui_btn .config (text = "Edit UI" , state = "disabled" )
11191153 self .vendor_convert_btn .config (text = "Vendor UI" , state = "disabled" )
11201154 self .classic_convert_btn .config (text = "Classic->DAT" , state = "disabled" )
1155+ self .classic_batch_btn .config (text = "Classic batch" , state = "disabled" )
11211156 self .new_ui_btn .config (text = "New UI" , state = "disabled" )
11221157 self .force_upload_btn .config (state = "disabled" )
11231158
@@ -1771,6 +1806,75 @@ def on_smartmonitor_classic_theme_convert_click(self):
17711806 parent = self .window ,
17721807 )
17731808
1809+ def on_smartmonitor_classic_batch_convert_click (self ):
1810+ batch_root = filedialog .askdirectory (
1811+ parent = self .window ,
1812+ title = "Choose classic themes root for batch conversion" ,
1813+ initialdir = THEMES_DIR ,
1814+ mustexist = True ,
1815+ )
1816+ if not batch_root :
1817+ return
1818+
1819+ overwrite = messagebox .askyesno (
1820+ "Batch convert classic themes?" ,
1821+ "This will scan the selected folder for every classic theme.yaml,\n "
1822+ "compile each compatible theme to SmartMonitor .dat and overwrite\n "
1823+ "existing themes in the SmartMonitor library if names match.\n \n "
1824+ "Continue?" ,
1825+ parent = self .window ,
1826+ )
1827+ if not overwrite :
1828+ return
1829+
1830+ try :
1831+ imported , failed , results = compile_and_import_classic_theme_batch (batch_root )
1832+ except Exception as exc :
1833+ messagebox .showerror ("Classic batch conversion failed" , str (exc ), parent = self .window )
1834+ return
1835+
1836+ self .refresh_theme_selector ()
1837+ if imported :
1838+ self .theme_cb .set (imported [0 ])
1839+ self .on_theme_change ()
1840+
1841+ converted = sum (1 for _ , result in results if not result .placeholder_items and not result .skipped_items )
1842+ skipped = len (results ) - converted
1843+
1844+ message_lines = [
1845+ f"Imported: { len (imported )} " ,
1846+ f"Converted cleanly: { converted } " ,
1847+ f"Imported with placeholders/skips: { skipped } " ,
1848+ f"Failed: { len (failed )} " ,
1849+ ]
1850+
1851+ if imported :
1852+ preview = ", " .join (imported [:6 ])
1853+ if len (imported ) > 6 :
1854+ preview += ", ..."
1855+ message_lines .append (f"Themes: { preview } " )
1856+
1857+ skipped_entries = [
1858+ f"{ theme_name } : placeholders={ len (result .placeholder_items )} , skipped={ len (result .skipped_items )} "
1859+ for theme_name , result in results
1860+ if result .placeholder_items or result .skipped_items
1861+ ]
1862+ if skipped_entries :
1863+ message_lines .append ("" )
1864+ message_lines .append ("Themes with placeholders/skipped features:" )
1865+ message_lines .extend (skipped_entries [:8 ])
1866+ if len (skipped_entries ) > 8 :
1867+ message_lines .append ("..." )
1868+
1869+ if failed :
1870+ message_lines .append ("" )
1871+ message_lines .append ("Errors:" )
1872+ message_lines .extend (failed [:8 ])
1873+ if len (failed ) > 8 :
1874+ message_lines .append ("..." )
1875+
1876+ messagebox .showinfo ("Classic batch conversion" , "\n " .join (message_lines ), parent = self .window )
1877+
17741878 def on_smartmonitor_new_ui_project_click (self ):
17751879 os .makedirs (DEFAULT_SMARTMONITOR_PROJECTS_DIR , exist_ok = True )
17761880 parent_dir = filedialog .askdirectory (
0 commit comments