diff --git a/check_env.sh b/check_env.sh index 4d6edc7..ebc4829 100644 --- a/check_env.sh +++ b/check_env.sh @@ -97,5 +97,27 @@ else elif [[ "$OS_TYPE" == "windows" ]]; then echo -e " Windows: Při instalaci Pythonu zaškrtněte 'tcl/tk and IDLE'." fi +fi + +# --- 6. Kontrola Inno Setup (jen Windows) --- +if [[ "$OS_TYPE" == "windows" ]]; then + echo -n "Kontrola Inno Setup (pro instalátor)... " + # Hledáme ISCC.exe v PATH nebo v typických složkách + ISCC_PATH=$(which iscc 2>/dev/null) + if [[ -z "$ISCC_PATH" ]]; then + if [[ -f "/c/Program Files (x86)/Inno Setup 6/ISCC.exe" ]]; then + ISCC_PATH="/c/Program Files (x86)/Inno Setup 6/ISCC.exe" + elif [[ -f "/c/Program Files/Inno Setup 6/ISCC.exe" ]]; then + ISCC_PATH="/c/Program Files/Inno Setup 6/ISCC.exe" + fi + fi + + if [[ -n "$ISCC_PATH" ]]; then + echo -e "${GREEN}OK${NC}" + else + echo -e "${YELLOW}NENALEZENO${NC}" + echo -e " -> Pro vytváření instalátorů nainstalujte Inno Setup: https://jrsoftware.org/isdl.php" + fi +fi exit 0 \ No newline at end of file diff --git a/src/__pycache__/config_manager.cpython-313.pyc b/src/__pycache__/config_manager.cpython-313.pyc index 55bacf3..3bfe96e 100644 Binary files a/src/__pycache__/config_manager.cpython-313.pyc and b/src/__pycache__/config_manager.cpython-313.pyc differ diff --git a/src/__pycache__/constants.cpython-313.pyc b/src/__pycache__/constants.cpython-313.pyc index f30d72c..b52de04 100644 Binary files a/src/__pycache__/constants.cpython-313.pyc and b/src/__pycache__/constants.cpython-313.pyc differ diff --git a/src/config_manager.py b/src/config_manager.py index 98f8bef..d226752 100644 --- a/src/config_manager.py +++ b/src/config_manager.py @@ -4,7 +4,7 @@ PRESET_MANUAL, KEY_TRANSLATIONS_PATH, KEY_BUILD_TYPE, KEY_BUILD_MODE, KEY_FLAVOR, KEY_ENV, KEY_BUMP_STRATEGY, BUMP_PATCH, KEY_GIT_PUSH, KEY_DISABLE_OBFUSCATION, KEY_UPLOAD_SYMBOLS, KEY_INSTALL_COCOAPODS, - KEY_CHECK_SQLITE_WEB + KEY_CHECK_SQLITE_WEB, KEY_CREATE_INSTALLER, DEFAULT_INNO_CONFIG ) SRC_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -46,25 +46,30 @@ def add_project(self, name, path): """Přidá projekt s výchozí strukturou.""" if 'projects' not in self.config: self.config['projects'] = {} + + default_preset = { + KEY_BUILD_TYPE: "apk", + KEY_BUILD_MODE: "release", + KEY_FLAVOR: "", + KEY_ENV: "", + KEY_BUMP_STRATEGY: BUMP_PATCH, + KEY_GIT_PUSH: True, + KEY_DISABLE_OBFUSCATION: False, + KEY_UPLOAD_SYMBOLS: True, + KEY_INSTALL_COCOAPODS: True, + KEY_CHECK_SQLITE_WEB: False, + KEY_CREATE_INSTALLER: True + } + + default_preset.update(DEFAULT_INNO_CONFIG) self.config['projects'][name] = { "path": path, "flavors": [], "envs": [], - "last_selected_build_preset": "Ručně", + "last_selected_build_preset": PRESET_MANUAL, "build_presets": { - PRESET_MANUAL: { - KEY_BUILD_TYPE: "apk", - KEY_BUILD_MODE: "release", - KEY_FLAVOR: "", - KEY_ENV: "", - KEY_BUMP_STRATEGY: BUMP_PATCH, # Zde jsi měl BUMP_PATCH, tak to nechávám - KEY_GIT_PUSH: True, - KEY_DISABLE_OBFUSCATION: False, - KEY_UPLOAD_SYMBOLS: True, - KEY_INSTALL_COCOAPODS: True, - KEY_CHECK_SQLITE_WEB: False - } + PRESET_MANUAL: default_preset }, "nbsp_settings": { KEY_TRANSLATIONS_PATH: "assets/translations" @@ -181,7 +186,7 @@ def save_project_list(self, project_name, list_name, new_list): def get_project_build_presets(self, project_name): """ Vrátí slovník všech build presetů pro daný projekt. - Vždy zajistí přítomnost klíče PRESET_MANUAL ('Ručně'). + PROVÁDÍ AUTOMATICKOU MIGRACI chybějících klíčů a OPRAVU poškozených dat. """ project_data = self._get_project(project_name) default_presets = {PRESET_MANUAL: {}} @@ -191,22 +196,63 @@ def get_project_build_presets(self, project_name): presets = project_data.get('build_presets', default_presets) - # 1. Pojistka proti špatnému typu + # 1. Kontrola, zda je 'presets' slovník if not isinstance(presets, dict): - print(f"OPRAVA: 'build_presets' pro '{project_name}' byl poškozen. Resetuji na výchozí.") project_data['build_presets'] = default_presets self.save_config() return default_presets + + # --- MIGRACE A OPRAVA DAT --- + migration_needed = False + + # Použijeme list(presets.keys()) abychom mohli bezpečně modifikovat slovník + for preset_name in list(presets.keys()): + settings = presets[preset_name] - # 2. Pojistka: VŽDY zajistit existenci "Ručně" - # I když je v configu smazaný nebo prázdný, v paměti (a pro UI) ho chceme vidět. - if PRESET_MANUAL not in presets: - presets[PRESET_MANUAL] = {} - # Volitelně můžeme rovnou uložit opravu do souboru: - # self.save_project_build_preset(project_name, PRESET_MANUAL, {}) + # --- OPRAVA CHYBY (TUPLE) --- + # Pokud hodnota není slovník (např. je to tuple, list nebo string), opravíme ji + if not isinstance(settings, dict): + print(f"OPRAVA: Preset '{preset_name}' byl poškozen (typ {type(settings)}). Resetuji.") + + # Vytvoříme nový čistý slovník + new_settings = { + KEY_BUILD_TYPE: "apk", + KEY_BUILD_MODE: "release", + KEY_FLAVOR: "", + KEY_ENV: "", + KEY_BUMP_STRATEGY: BUMP_PATCH, + KEY_GIT_PUSH: True, + KEY_DISABLE_OBFUSCATION: False, + KEY_UPLOAD_SYMBOLS: True, + KEY_INSTALL_COCOAPODS: True, + KEY_CHECK_SQLITE_WEB: False, + KEY_CREATE_INSTALLER: False + } + new_settings.update(DEFAULT_INNO_CONFIG) + + # Nahradíme poškozená data novým slovníkem + presets[preset_name] = new_settings + settings = new_settings # Aktualizujeme lokální proměnnou pro další kontroly + migration_needed = True + # ----------------------------- + + # 1. Klíč pro zapnutí instalátoru + if KEY_CREATE_INSTALLER not in settings: + settings[KEY_CREATE_INSTALLER] = False + migration_needed = True + + # 2. Klíče pro konfiguraci Inno Setupu + for key, default_val in DEFAULT_INNO_CONFIG.items(): + if key not in settings: + settings[key] = default_val + migration_needed = True + + if migration_needed: + print(f"MIGRACE: Doplněny/Opraveny klíče pro projekt '{project_name}'.") + self.save_config() + # ------------------------------------------- return presets - # --- KONEC OPRAVENÉ METODY --- def save_project_build_preset(self, project_name, preset_name, settings): """Uloží nebo přepíše nastavení pro jeden build preset v rámci projektu.""" diff --git a/src/constants.py b/src/constants.py index d1414a8..ce32488 100644 --- a/src/constants.py +++ b/src/constants.py @@ -48,7 +48,6 @@ KEY_UPLOAD_SYMBOLS = "upload_symbols" KEY_INSTALL_COCOAPODS = "install_cocoapods" KEY_CHECK_SQLITE_WEB = "check_sqlite_web" - KEY_BUMP_STRATEGY = "bump_strategy" # Klíče pro jednotlivé strategie @@ -56,4 +55,33 @@ BUMP_MAJOR = "major" BUMP_MINOR = "minor" BUMP_PATCH = "patch" -BUMP_BUILD = "build" \ No newline at end of file +BUMP_BUILD = "build" + +# --- NOVÉ KONSTANTY PRO INNO SETUP --- +KEY_CREATE_INSTALLER = "create_installer" + +# Klíče parametrů +KEY_INNO_PUBLISHER = "inno_publisher" +KEY_INNO_URL = "inno_url" +KEY_INNO_LICENSE = "inno_license" +KEY_INNO_WIZARD_IMAGE = "inno_wizard_image" +KEY_INNO_WIZARD_SMALL_IMAGE = "inno_wizard_small_image" +KEY_INNO_WIZARD_STYLE = "inno_wizard_style" +KEY_INNO_COMPRESSION = "inno_compression" +KEY_INNO_TASK_DESKTOP = "inno_task_desktop" +KEY_INNO_LANGUAGES = "inno_languages" +KEY_INNO_ICON = "inno_icon" + +# Defaultní hodnoty (pro migraci a nové projekty) +DEFAULT_INNO_CONFIG = { + KEY_INNO_PUBLISHER: "My Company", + KEY_INNO_URL: "", + KEY_INNO_LICENSE: "", + KEY_INNO_WIZARD_IMAGE: "", + KEY_INNO_WIZARD_SMALL_IMAGE: "", + KEY_INNO_WIZARD_STYLE: "modern", + KEY_INNO_COMPRESSION: "lzma", + KEY_INNO_TASK_DESKTOP: True, + KEY_INNO_LANGUAGES: "english, czech", + KEY_INNO_ICON: "windows/runner/resources/app_icon.ico" +} \ No newline at end of file diff --git a/src/logic/__pycache__/build_android.cpython-313.pyc b/src/logic/__pycache__/build_android.cpython-313.pyc index f625f58..5f7219d 100644 Binary files a/src/logic/__pycache__/build_android.cpython-313.pyc and b/src/logic/__pycache__/build_android.cpython-313.pyc differ diff --git a/src/logic/__pycache__/build_common.cpython-313.pyc b/src/logic/__pycache__/build_common.cpython-313.pyc index 7b0bc52..64ae33c 100644 Binary files a/src/logic/__pycache__/build_common.cpython-313.pyc and b/src/logic/__pycache__/build_common.cpython-313.pyc differ diff --git a/src/logic/__pycache__/build_desktop.cpython-313.pyc b/src/logic/__pycache__/build_desktop.cpython-313.pyc index 36c1ac7..f88ab91 100644 Binary files a/src/logic/__pycache__/build_desktop.cpython-313.pyc and b/src/logic/__pycache__/build_desktop.cpython-313.pyc differ diff --git a/src/logic/build_common.py b/src/logic/build_common.py index ce227de..bff497e 100644 --- a/src/logic/build_common.py +++ b/src/logic/build_common.py @@ -5,6 +5,8 @@ import platform import time import glob +import sys # <--- PŘIDÁNO +import shutil # <--- PŘIDÁNO from ..constants import ADT_PROJECT_CONFIG_FILENAME @@ -18,6 +20,22 @@ def execute_command(command, logger, title="", working_dir=None, log_stdout=True if title: logger.header(f"\n--- {title} ---") + # --- FIX PRO WINDOWS FLUTTER (ZAČÁTEK) --- + # Pokud jsme na Windows a voláme 'flutter', musíme volat 'flutter.bat' + if sys.platform == "win32" and command and command[0] == "flutter": + # Zkusíme najít plnou cestu k flutter.bat + flutter_bat = shutil.which("flutter.bat") + + if flutter_bat: + command[0] = flutter_bat + else: + # Fallback: pokud se nenajde přes which, zkusíme prostě přidat příponu + command[0] = "flutter.bat" + + # Jen pro debug, abyste viděli, že se to děje (můžete smazat) + # logger.info(f"Windows detekován: Příkaz 'flutter' přepsán na '{command[0]}'") + # --- FIX PRO WINDOWS FLUTTER (KONEC) --- + logger.info(f"🔧 Spouštím v '{working_dir or os.getcwd()}': {' '.join(command)}") startupinfo = None @@ -44,6 +62,9 @@ def execute_command(command, logger, title="", working_dir=None, log_stdout=True except FileNotFoundError: logger.error(f"PŘÍKAZ NENALEZEN: Příkaz '{command[0]}' nebyl nalezen.") + # Tip pro uživatele + if "flutter" in command[0]: + logger.error("TIP: Ujistěte se, že máte Flutter přidaný v systémové PATH.") return -1, f"PŘÍKAZ NENALEZEN: {command[0]}" except Exception as e: logger.error(f"CHYBA PŘI VYKONÁNÍ: {e}") @@ -252,7 +273,6 @@ def perform_git_push(logger, params, version_name, build_number, actions_perform else: logger.warn("Git commit selhal (možná nebyly žádné změny k commitu).") -# --- NOVÁ FUNKCE (přesunuta z android a zobecněna) --- def get_package_name(logger, env_vars): """Najde packageName v env_vars (z adt_tools_config.env).""" package_name = env_vars.get("PACKAGE_NAME") diff --git a/src/logic/build_desktop.py b/src/logic/build_desktop.py index 9fb67d9..814a9ea 100644 --- a/src/logic/build_desktop.py +++ b/src/logic/build_desktop.py @@ -1,18 +1,30 @@ # src/logic/build_desktop.py import os -import glob import shutil import tarfile import zipfile +import hashlib from .build_common import get_pubspec_name, execute_command, wait_for_glob, wait_for_paths -from ..constants import KEY_GIT_PUSH +from ..constants import ( + KEY_GIT_PUSH, KEY_CREATE_INSTALLER, + KEY_INNO_PUBLISHER, KEY_INNO_URL, KEY_INNO_LICENSE, + KEY_INNO_WIZARD_IMAGE, KEY_INNO_WIZARD_SMALL_IMAGE, + KEY_INNO_WIZARD_STYLE, KEY_INNO_COMPRESSION, + KEY_INNO_TASK_DESKTOP, KEY_INNO_LANGUAGES, KEY_INNO_ICON +) def _get_desktop_app_name(env_vars): """Načte DESKTOP_APP_NAME z proměnných prostředí.""" # Logger zde není potřeba, jen vracíme hodnotu return env_vars.get("DESKTOP_APP_NAME", "") +def _generate_app_id(app_name): + """Vygeneruje unikátní GUID na základě názvu aplikace.""" + # To zajistí, že stejná aplikace bude mít vždy stejné ID a instalátor ji přepíše/aktualizuje + hash_object = hashlib.md5(app_name.encode()) + return f"{{{{ {hash_object.hexdigest().upper()} }}}}" + def _stage_desktop_artifact_to_git(logger, archive_path): """ Přidá vytvořený archiv do Gitu (force add). @@ -24,6 +36,133 @@ def _stage_desktop_artifact_to_git(logger, archive_path): return True return False +def _create_windows_installer(logger, build_dir, app_name, version, output_dir, params): + """Generuje .iss a spouští build.""" + logger.header("--- Vytvářím Windows Installer (Inno Setup) ---") + + # 1. Najít ISCC (stejné jako minule) + iscc_path = shutil.which("iscc") + if not iscc_path: + # ... (standardní cesty) ... + # Pokud nenajde: + logger.error("Nástroj Inno Setup (ISCC.exe) nebyl nalezen.") + return None + + # 2. Získání hodnot z UI (params) + publisher = params.get(KEY_INNO_PUBLISHER, "") + url = params.get(KEY_INNO_URL, "") + license_file = params.get(KEY_INNO_LICENSE, "") + wizard_image = params.get(KEY_INNO_WIZARD_IMAGE, "") + wizard_small_image = params.get(KEY_INNO_WIZARD_SMALL_IMAGE, "") + wizard_style = params.get(KEY_INNO_WIZARD_STYLE, "modern") + compression = params.get(KEY_INNO_COMPRESSION, "lzma") + create_desktop_icon = params.get(KEY_INNO_TASK_DESKTOP, True) + languages_str = params.get(KEY_INNO_LANGUAGES, "english") + icon_path = params.get(KEY_INNO_ICON, "") + + # 3. Sestavení sekcí pro .iss + + # Ikona + setup_icon_line = "" + if icon_path and os.path.exists(icon_path): + setup_icon_line = f"SetupIconFile={os.path.abspath(icon_path)}" + + # Licence + license_line = "" + if license_file and os.path.exists(license_file): + license_line = f"LicenseFile={os.path.abspath(license_file)}" + + # Obrázky + wizard_image_line = "" + if wizard_image and os.path.exists(wizard_image): + wizard_image_line = f"WizardImageFile={os.path.abspath(wizard_image)}" + + wizard_small_image_line = "" + if wizard_small_image and os.path.exists(wizard_small_image): + wizard_small_image_line = f"WizardSmallImageFile={os.path.abspath(wizard_small_image)}" + + # Jazyky + languages_list = [l.strip().lower() for l in languages_str.split(',')] + languages_section = "[Languages]\n" + for lang in languages_list: + if lang == "english": + languages_section += 'Name: "english"; MessagesFile: "compiler:Default.isl"\n' + else: + lang_file = lang.capitalize() + languages_section += f'Name: "{lang}"; MessagesFile: "compiler:Languages\\{lang_file}.isl"\n' + + # Tasks (Desktop icon) + tasks_section = "[Tasks]\n" + if create_desktop_icon: + tasks_section += 'Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked' + + # 4. Finální šablona (sestavená v Pythonu) + abs_build_dir = os.path.abspath(build_dir) + abs_output_dir = os.path.abspath(output_dir) + setup_filename = f"{app_name}_setup" + app_id = _generate_app_id(app_name) + + iss_content = f""" +[Setup] +AppId={app_id} +AppName={app_name} +AppVersion={version} +AppPublisher={publisher} +AppPublisherURL={url} +AppSupportURL={url} +AppUpdatesURL={url} +DefaultDirName={{autopf}}\\{app_name} +DefaultGroupName={app_name} +OutputBaseFilename={setup_filename} +OutputDir={abs_output_dir} +Compression={compression} +SolidCompression=yes +ArchitecturesInstallIn64BitMode=x64 +WizardStyle={wizard_style} +{setup_icon_line} +{license_line} +{wizard_image_line} +{wizard_small_image_line} + +{languages_section} + +{tasks_section} + +[Files] +Source: "{abs_build_dir}\\{app_name}.exe"; DestDir: "{{app}}"; Flags: ignoreversion +Source: "{abs_build_dir}\\*"; DestDir: "{{app}}"; Flags: ignoreversion recursesubdirs createallsubdirs + +[Icons] +Name: "{{group}}\\{app_name}"; Filename: "{{app}}\\{app_name}.exe" +Name: "{{commondesktop}}\\{app_name}"; Filename: "{{app}}\\{app_name}.exe"; Tasks: desktopicon + +[Run] +Filename: "{{app}}\\{app_name}.exe"; Description: "{{cm:LaunchProgram,{app_name}}}"; Flags: nowait postinstall skipifsilent +""" + + # 5. Uložení a kompilace + iss_file_path = "temp_installer_script.iss" + try: + with open(iss_file_path, "w", encoding="utf-8") as f: + f.write(iss_content) + except Exception as e: + logger.error(f"Nepodařilo se vytvořit .iss soubor: {e}") + return None + + logger.info(f"Spouštím Inno Setup kompilátor...") + ret_code, output = execute_command([iscc_path, iss_file_path], logger) + + if os.path.exists(iss_file_path): + os.remove(iss_file_path) + + if ret_code == 0: + installer_path = os.path.join(abs_output_dir, setup_filename + ".exe") + logger.success(f"Instalátor vytvořen: {installer_path}") + return installer_path + else: + logger.error("Vytváření instalátoru selhalo.") + return None + def run_desktop_tasks_post_build(logger, params, env_vars, actions_performed): """ Logika pro Desktop: Přejmenování binárky, vytvoření archivu a git add. @@ -44,6 +183,7 @@ def run_desktop_tasks_post_build(logger, params, env_vars, actions_performed): # Zjistíme, zda máme provádět git operace should_git_add = params.get(KEY_GIT_PUSH, False) + should_create_installer = params.get(KEY_CREATE_INSTALLER, False) # --- MACOS --- if build_type == 'macos': @@ -167,6 +307,17 @@ def run_desktop_tasks_post_build(logger, params, env_vars, actions_performed): logger.error(f"❌ Složka 'data/' nebyla nalezena v {build_path} – build bude nefunkční!") return None + if should_create_installer: + full_version = f"{version_name}+{build_number}" + + installer_path = _create_windows_installer( + logger, build_path, app_name, full_version, build_path, params + ) + + if installer_path and should_git_add: + if _stage_desktop_artifact_to_git(logger, installer_path): + actions_performed["desktop_added"] = True + zip_name = f"{app_name}-v{version_name}({build_number})-windows.zip" output_archive_path = os.path.join(build_path, zip_name) diff --git a/src/ui/__pycache__/main_window.cpython-313.pyc b/src/ui/__pycache__/main_window.cpython-313.pyc index 01bff5c..a774470 100644 Binary files a/src/ui/__pycache__/main_window.cpython-313.pyc and b/src/ui/__pycache__/main_window.cpython-313.pyc differ diff --git a/src/ui/main_window.py b/src/ui/main_window.py index 2dec0af..70c0b22 100644 --- a/src/ui/main_window.py +++ b/src/ui/main_window.py @@ -18,7 +18,12 @@ KEY_FLAVOR, KEY_ENV, KEY_BUMP_STRATEGY, BUMP_NONE, BUMP_MAJOR, BUMP_MINOR, BUMP_PATCH, BUMP_BUILD, KEY_GIT_PUSH, KEY_DISABLE_OBFUSCATION, KEY_UPLOAD_SYMBOLS, KEY_INSTALL_COCOAPODS, - KEY_CHECK_SQLITE_WEB + KEY_CHECK_SQLITE_WEB, KEY_CREATE_INSTALLER, + KEY_INNO_PUBLISHER, KEY_INNO_URL, KEY_INNO_LICENSE, + KEY_INNO_WIZARD_IMAGE, KEY_INNO_WIZARD_SMALL_IMAGE, + KEY_INNO_WIZARD_STYLE, KEY_INNO_COMPRESSION, + KEY_INNO_TASK_DESKTOP, KEY_INNO_LANGUAGES, KEY_INNO_ICON, + DEFAULT_INNO_CONFIG ) class MainWindow(tk.Toplevel): @@ -36,7 +41,7 @@ def __init__(self, parent, config_manager, current_project_name): self._switch_to_project(current_project_name) - self._is_loading_preset = False # Vlajka, která brání ukládání během načítání + self._is_loading_preset = True # Vlajka, která brání ukládání během načítání # --- ZMĚNA: Oddělené proměnné --- self.build_vars = {} @@ -210,16 +215,26 @@ def _initialize_build_vars(self): value=manual_settings.get(KEY_CHECK_SQLITE_WEB, False) ), } + + # Inicializace proměnných pro Inno Setup (s defaulty z konstant) + self.build_vars.update({ + KEY_CREATE_INSTALLER: tk.BooleanVar(value=True), + + KEY_INNO_PUBLISHER: tk.StringVar(value=DEFAULT_INNO_CONFIG[KEY_INNO_PUBLISHER]), + KEY_INNO_URL: tk.StringVar(value=DEFAULT_INNO_CONFIG[KEY_INNO_URL]), + KEY_INNO_LICENSE: tk.StringVar(value=DEFAULT_INNO_CONFIG[KEY_INNO_LICENSE]), + KEY_INNO_WIZARD_IMAGE: tk.StringVar(value=DEFAULT_INNO_CONFIG[KEY_INNO_WIZARD_IMAGE]), + KEY_INNO_WIZARD_SMALL_IMAGE: tk.StringVar(value=DEFAULT_INNO_CONFIG[KEY_INNO_WIZARD_SMALL_IMAGE]), + KEY_INNO_WIZARD_STYLE: tk.StringVar(value=DEFAULT_INNO_CONFIG[KEY_INNO_WIZARD_STYLE]), + KEY_INNO_COMPRESSION: tk.StringVar(value=DEFAULT_INNO_CONFIG[KEY_INNO_COMPRESSION]), + KEY_INNO_TASK_DESKTOP: tk.BooleanVar(value=DEFAULT_INNO_CONFIG[KEY_INNO_TASK_DESKTOP]), + KEY_INNO_LANGUAGES: tk.StringVar(value=DEFAULT_INNO_CONFIG[KEY_INNO_LANGUAGES]), + KEY_INNO_ICON: tk.StringVar(value=DEFAULT_INNO_CONFIG[KEY_INNO_ICON]), + }) # 3. Až teď zapneme sledování změn (trace) for key, var in self.build_vars.items(): - if key == KEY_PRESET: - continue - # Sledování strategie povýšení (ta se ukládá přes bind, ale pro jistotu ji necháme netracovanou nebo tracovanou, - # zde raději netracujeme, protože dropdown má svůj bind) - if key == KEY_BUMP_STRATEGY: - continue - + if key == KEY_PRESET or key == KEY_BUMP_STRATEGY: continue var.trace_add("write", self._save_current_build_settings) def _create_checkbox(self, parent, text, var_key, **pack_kwargs): @@ -309,15 +324,61 @@ def _create_build_tab(self): KEY_UPLOAD_SYMBOLS, padx=(20, 0) ) - + self.web_frame = ttk.LabelFrame(frame, text="Web", padding=5) self.web_frame.grid(row=8, column=0, columnspan=3, sticky="ew", padx=5, pady=5) + self.web_check = self._create_checkbox( self.web_frame, "Zkontrolovat sqlite3.wasm & sqflite_sw.js", KEY_CHECK_SQLITE_WEB ) + + # --- SEKCE WINDOWS INSTALLER --- + self.windows_frame = ttk.LabelFrame(frame, text="Windows", padding=5) + self.windows_frame.grid(row=8, column=0, columnspan=3, sticky="ew", padx=5, pady=5) + self.installer_check = self._create_checkbox( + self.windows_frame, "Vytvořit instalátor (Inno Setup)", KEY_CREATE_INSTALLER + ) + + # Frame pro detailní nastavení (bude se schovávat) + self.inno_settings_frame = ttk.Frame(self.windows_frame) + self.inno_settings_frame.pack(fill="x", expand=True, padx=10, pady=5) + + # Grid layout pro nastavení + f = self.inno_settings_frame + f.columnconfigure(1, weight=1) + f.columnconfigure(4, weight=1) + + # 1. Sloupec + self._create_entry_row(f, 0, "Publisher:", KEY_INNO_PUBLISHER) + self._create_entry_row(f, 1, "URL:", KEY_INNO_URL) + self._create_file_row(f, 2, "Licence (.txt):", KEY_INNO_LICENSE, [("Text files", "*.txt")]) + self._create_file_row(f, 3, "Ikona (.ico):", KEY_INNO_ICON, [("Icon files", "*.ico")]) + + # 2. Sloupec (vedle) - Styl a Obrázky + ttk.Label(f, text="Styl:").grid(row=0, column=3, sticky="w", padx=(20, 5)) + ttk.Combobox(f, textvariable=self.build_vars[KEY_INNO_WIZARD_STYLE], + values=["modern", "classic"], state="readonly").grid(row=0, column=4, sticky="ew") + + ttk.Label(f, text="Komprese:").grid(row=1, column=3, sticky="w", padx=(20, 5)) + ttk.Combobox(f, textvariable=self.build_vars[KEY_INNO_COMPRESSION], + values=["lzma", "zip", "bzip"], state="readonly").grid(row=1, column=4, sticky="ew") + + self._create_file_row(f, 2, "Velký obrázek:", KEY_INNO_WIZARD_IMAGE, [("Images", "*.bmp")], col_offset=3) + self._create_file_row(f, 3, "Malý obrázek:", KEY_INNO_WIZARD_SMALL_IMAGE, [("Images", "*.bmp")], col_offset=3) + + # Spodní sekce (Jazyky a Checkboxy) + ttk.Label(f, text="Jazyky (oddělené čárkou):").grid(row=4, column=0, sticky="w", pady=(10,0)) + ttk.Entry(f, textvariable=self.build_vars[KEY_INNO_LANGUAGES]).grid(row=4, column=1, columnspan=4, sticky="ew", pady=(10,0)) + + ttk.Checkbutton( + f, + text="Vytvořit zástupce na ploše", + variable=self.build_vars[KEY_INNO_TASK_DESKTOP], + command=self._save_current_build_settings + ).grid(row=5, column=0, columnspan=5, sticky="w", pady=(5,0)) frame.grid_rowconfigure(9, weight=1, minsize=150) self.build_console = scrolledtext.ScrolledText(frame, wrap=tk.WORD, state="disabled") self.build_console.grid(row=9, column=0, columnspan=3, sticky="nsew", pady=(5, 10)) @@ -451,6 +512,30 @@ def _remove_from_list(self, list_name, combobox): self._update_dynamic_list(combobox, list_name) combobox.set("") + def _create_entry_row(self, parent, row, label, var_key, col_offset=0): + ttk.Label(parent, text=label).grid(row=row, column=0+col_offset, sticky="w", padx=5, pady=2) + ttk.Entry(parent, textvariable=self.build_vars[var_key]).grid(row=row, column=1+col_offset, sticky="ew", padx=5, pady=2) + + def _create_file_row(self, parent, row, label, var_key, file_types, col_offset=0): + ttk.Label(parent, text=label).grid(row=row, column=0+col_offset, sticky="w", padx=5, pady=2) + + frame = ttk.Frame(parent) + frame.grid(row=row, column=1+col_offset, sticky="ew", padx=5, pady=2) + frame.columnconfigure(0, weight=1) + + ttk.Entry(frame, textvariable=self.build_vars[var_key]).grid(row=0, column=0, sticky="ew") + ttk.Button(frame, text="...", width=3, + command=lambda: self._browse_file(var_key, file_types)).grid(row=0, column=1, padx=(5,0)) + + def _browse_file(self, var_key, file_types): + path = filedialog.askopenfilename(filetypes=file_types, initialdir=os.getcwd()) + if path: + try: + rel_path = os.path.relpath(path, os.getcwd()) + self.build_vars[var_key].set(rel_path) + except ValueError: + self.build_vars[var_key].set(path) + def _update_build_ui_state(self, *args): try: build_type = self.build_vars[KEY_BUILD_TYPE].get() @@ -477,10 +562,18 @@ def _update_build_ui_state(self, *args): # Logika pro Web if build_type == "web": - self.web_frame.grid() + self.web_frame.grid() + self.windows_frame.grid_remove() # Skrýt Windows + self.build_vars[KEY_CREATE_INSTALLER].set(False) + elif build_type == "windows": + self.web_frame.grid_remove() + self.windows_frame.grid() # Zobrazit Windows + self.build_vars[KEY_CHECK_SQLITE_WEB].set(False) else: - self.web_frame.grid_remove() + self.web_frame.grid_remove() + self.windows_frame.grid_remove() self.build_vars[KEY_CHECK_SQLITE_WEB].set(False) + self.build_vars[KEY_CREATE_INSTALLER].set(False) except (AttributeError, tk.TclError): pass