#!/usr/bin/env python # -*- coding: utf-8 -*- """ 配置管理模块 - 负责读取和保存应用配置 """ import json import os import sys from typing import Dict, List, Optional from .icon_config import IconConfigManager from .constants import DEFAULT_TASK_FOLDER_TEMPLATES class ConfigManager: """管理NexusLauncher的配置文件""" def __init__(self, config_file: str = "config.json"): self.config_file = config_file self.config_data = self._load_config() # 如果配置文件不存在,保存默认配置 if not os.path.exists(self.config_file): print("[INFO] Config file not found, creating default config.json") self.save_config() # 创建图标配置管理器 self.icon_config = IconConfigManager(self.config_data, self._get_icons_dir) def _get_icons_dir(self) -> str: """获取 icons 目录路径(避免循环导入)""" if getattr(sys, 'frozen', False): # 打包后的应用 return os.path.join(sys._MEIPASS, "icons") else: # 开发环境 config_dir = os.path.dirname(os.path.abspath(__file__)) project_root = os.path.dirname(config_dir) return os.path.join(project_root, "icons") def _load_config(self) -> Dict: """加载配置文件""" if os.path.exists(self.config_file): try: with open(self.config_file, 'r', encoding='utf-8') as f: return json.load(f) except Exception as e: print(f"Failed to load configuration file: {e}") return self._get_default_config() else: return self._get_default_config() def _get_default_config(self) -> Dict: """获取默认配置""" return { "projects": { "Project_01": { "icon": "NexusLauncher.ico", # 默认项目图标 "color": "", # 项目背景颜色 "apps": [] } }, "current_project": "Project_01", "window_size": { "width": 425, "height": 480 }, "icon_size": 80, # 图标大小,默认80x80 "app_icons": {}, # 应用图标映射,格式:{"app_path": "icon_path"} "app_colors": {}, # 应用按钮颜色映射,格式:{"app_path": "#RRGGBB"} "task_folder_templates": self._get_default_task_templates(), # 任务类型默认文件夹结构 "task_settings": {} # 已废弃:旧的任务设置存储位置,现在存储在 projects.项目名.task_settings } def save_config(self) -> bool: """保存配置到文件""" try: # 重新排序项目字段:icon, color, apps, task_settings if "projects" in self.config_data: for project_name, project_data in self.config_data["projects"].items(): ordered_project = {} # 按顺序添加字段 if "icon" in project_data: ordered_project["icon"] = project_data["icon"] if "color" in project_data: ordered_project["color"] = project_data["color"] if "apps" in project_data: ordered_project["apps"] = project_data["apps"] if "task_settings" in project_data: ordered_project["task_settings"] = project_data["task_settings"] # 替换原有项目数据 self.config_data["projects"][project_name] = ordered_project with open(self.config_file, 'w', encoding='utf-8') as f: json.dump(self.config_data, f, ensure_ascii=False, indent=4) return True except Exception as e: print(f"Failed to save configuration file: {e}") return False def reload_config(self) -> bool: """重新加载配置文件 Returns: 是否加载成功 """ try: self.config_data = self._load_config() # 重新创建图标配置管理器以使用新的config_data引用 self.icon_config = IconConfigManager(self.config_data, self._get_icons_dir) return True except Exception as e: print(f"Failed to reload config file: {e}") return False def get_projects(self) -> List[str]: """获取所有项目名称""" return list(self.config_data.get("projects", {}).keys()) def get_current_project(self) -> str: """获取当前选中的项目""" return self.config_data.get("current_project", "") def set_current_project(self, project_name: str): """设置当前项目""" if project_name in self.config_data.get("projects", {}): self.config_data["current_project"] = project_name self.save_config() def get_apps(self, project_name: Optional[str] = None) -> List[Dict]: """获取指定项目的应用列表""" if project_name is None: project_name = self.get_current_project() projects = self.config_data.get("projects", {}) if project_name in projects: return projects[project_name].get("apps", []) return [] def add_project(self, project_name: str, default_icon: str = None) -> bool: """添加新项目 Args: project_name: 项目名称 default_icon: 默认图标路径(可选) """ if "projects" not in self.config_data: self.config_data["projects"] = {} if project_name in self.config_data["projects"]: return False self.config_data["projects"][project_name] = { "icon": default_icon if default_icon else "", "color": "", "apps": [] } return self.save_config() def delete_project(self, project_name: str) -> bool: """删除项目""" if project_name in self.config_data.get("projects", {}): # 删除项目配置(包含图标和颜色) del self.config_data["projects"][project_name] # 如果删除的是当前项目,切换到第一个项目 if self.config_data.get("current_project") == project_name: projects = self.get_projects() if projects: self.config_data["current_project"] = projects[0] else: self.config_data["current_project"] = "" return self.save_config() return False def rename_project(self, old_name: str, new_name: str) -> bool: """重命名项目""" # 检查旧项目是否存在 if old_name not in self.config_data.get("projects", {}): return False # 检查新名称是否已存在 if new_name in self.config_data.get("projects", {}): return False # 重命名项目 self.config_data["projects"][new_name] = self.config_data["projects"].pop(old_name) # 如果重命名的是当前项目,更新当前项目名称 if self.config_data.get("current_project") == old_name: self.config_data["current_project"] = new_name return self.save_config() def add_app(self, project_name: str, app_name: str, app_path: str, version: str) -> bool: """添加应用到指定项目""" if project_name not in self.config_data.get("projects", {}): return False app_data = { "name": app_name, "path": app_path, "version": version } self.config_data["projects"][project_name]["apps"].append(app_data) return self.save_config() def update_app(self, project_name: str, app_index: int, app_name: str, app_path: str, version: str) -> bool: """更新应用信息""" if project_name not in self.config_data.get("projects", {}): return False apps = self.config_data["projects"][project_name]["apps"] if 0 <= app_index < len(apps): apps[app_index] = { "name": app_name, "path": app_path, "version": version } return self.save_config() return False def delete_app(self, project_name: str, app_index: int) -> bool: """删除应用""" if project_name not in self.config_data.get("projects", {}): return False apps = self.config_data["projects"][project_name]["apps"] if 0 <= app_index < len(apps): apps.pop(app_index) return self.save_config() return False def reorder_apps(self, project_name: str, from_index: int, to_index: int) -> bool: """重新排序应用,支持拖到列表末尾""" if project_name not in self.config_data.get("projects", {}): return False apps = self.config_data["projects"][project_name]["apps"] app_count = len(apps) # 允许目标索引等于列表长度(表示插入到末尾) if not (0 <= from_index < app_count and 0 <= to_index <= app_count): return False app = apps.pop(from_index) # 如果从上方拖到下方,移除后目标索引需要左移一位 if from_index < to_index: to_index -= 1 # 再次夹紧,防止越界 to_index = max(0, min(to_index, len(apps))) apps.insert(to_index, app) return self.save_config() def get_window_size(self) -> tuple: """获取窗口大小""" size = self.config_data.get("window_size", {"width": 400, "height": 400}) return (size.get("width", 400), size.get("height", 400)) def save_window_size(self, width: int, height: int): """保存窗口大小""" self.config_data["window_size"] = {"width": width, "height": height} self.save_config() def get_app_icon(self, app_path: str) -> str: """获取应用图标路径""" return self.icon_config.get_app_icon(app_path) def set_app_icon(self, app_path: str, icon_path: str) -> bool: """设置应用图标路径""" result = self.icon_config.set_app_icon(app_path, icon_path) if result: return self.save_config() return False def remove_app_icon(self, app_path: str) -> bool: """移除应用图标设置""" result = self.icon_config.remove_app_icon(app_path) if result: return self.save_config() return False def get_all_app_icons(self) -> Dict[str, str]: """获取所有应用图标映射""" return self.config_data.get("app_icons", {}) def get_app_color(self, app_path: str) -> str: """获取应用按钮颜色""" return self.icon_config.get_app_color(app_path) def set_app_color(self, app_path: str, color: str) -> bool: """设置应用按钮颜色""" result = self.icon_config.set_app_color(app_path, color) if result: return self.save_config() return False def remove_app_color(self, app_path: str) -> bool: """移除应用按钮颜色设置""" result = self.icon_config.remove_app_color(app_path) if result: return self.save_config() return False def get_all_app_colors(self) -> Dict[str, str]: """获取所有应用按钮颜色映射""" return self.config_data.get("app_colors", {}) def get_icon_size(self) -> int: """获取图标大小""" return self.config_data.get("icon_size", 80) def save_icon_size(self, size: int) -> bool: """保存图标大小""" self.config_data["icon_size"] = size return self.save_config() def get_project_icon(self, project_name: str) -> str: """获取项目图标路径""" return self.icon_config.get_project_icon(project_name) def set_project_icon(self, project_name: str, icon_path: str) -> bool: """设置项目图标路径""" result = self.icon_config.set_project_icon(project_name, icon_path) if result: return self.save_config() return False def remove_project_icon(self, project_name: str) -> bool: """移除项目图标设置""" result = self.icon_config.remove_project_icon(project_name) if result: return self.save_config() return False def get_project_color(self, project_name: str) -> str: """获取项目背景颜色""" return self.icon_config.get_project_color(project_name) def set_project_color(self, project_name: str, color: str) -> bool: """设置项目背景颜色""" result = self.icon_config.set_project_color(project_name, color) if result: return self.save_config() return False def remove_project_color(self, project_name: str) -> bool: """移除项目背景颜色设置""" result = self.icon_config.remove_project_color(project_name) if result: return self.save_config() return False def _get_default_task_templates(self) -> Dict[str, List[str]]: """获取默认任务类型文件夹模板""" return DEFAULT_TASK_FOLDER_TEMPLATES def get_task_folder_template(self, task_type: str) -> List[str]: """获取指定任务类型的文件夹模板 Args: task_type: 任务类型名称 Returns: 文件夹路径列表 """ templates = self.config_data.get("task_folder_templates", {}) if task_type in templates: return templates[task_type] # 如果配置中没有,返回默认模板 default_templates = self._get_default_task_templates() return default_templates.get(task_type, []) def get_task_types(self) -> List[str]: """获取所有可用的任务类型名称列表 Returns: 任务类型名称列表 """ templates = self.config_data.get("task_folder_templates", {}) if not templates: # 如果配置中没有,使用默认模板 templates = self._get_default_task_templates() return sorted(list(templates.keys())) def get_all_task_folder_templates(self) -> Dict[str, List[str]]: """获取所有任务类型的文件夹模板 Returns: 任务类型到文件夹列表的映射 """ templates = self.config_data.get("task_folder_templates", {}) if not templates: # 如果配置中没有,初始化默认模板并保存 templates = self._get_default_task_templates() self.config_data["task_folder_templates"] = templates self.save_config() return templates def set_task_folder_template(self, task_type: str, folders: List[str]) -> bool: """设置指定任务类型的文件夹模板 Args: task_type: 任务类型名称 folders: 文件夹路径列表 Returns: 是否保存成功 """ if "task_folder_templates" not in self.config_data: self.config_data["task_folder_templates"] = {} # 统一路径格式为正斜杠(JSON 存储格式) normalized_folders = [folder.replace("\\", "/") for folder in folders] self.config_data["task_folder_templates"][task_type] = normalized_folders return self.save_config() def update_all_task_folder_templates(self, templates: Dict[str, List[str]]) -> bool: """更新所有任务类型的文件夹模板 Args: templates: 任务类型到文件夹列表的映射 Returns: 是否保存成功 """ # 统一所有模板的路径格式为正斜杠(JSON 存储格式) normalized_templates = {} for task_type, folders in templates.items(): normalized_templates[task_type] = [folder.replace("\\", "/") for folder in folders] self.config_data["task_folder_templates"] = normalized_templates return self.save_config() def get_task_settings(self, project_name: str) -> Dict: """获取指定项目的 Task 面板设置 Args: project_name: 项目名称 Returns: Task 设置字典,包含 workspace, task_type, use_type_hierarchy """ # 确保 projects 字段存在 if "projects" not in self.config_data: self.config_data["projects"] = {} # 确保项目存在 if project_name not in self.config_data["projects"]: self.config_data["projects"][project_name] = {} # 从 projects.项目名.task_settings 读取 project_data = self.config_data["projects"][project_name] settings = project_data.get("task_settings", { "workspace": "D:\\Workspace", "task_type": "Character", "use_type_hierarchy": False }) # 标准化路径格式(JSON中存储正斜杠,转换为反斜杠供UI显示) if "workspace" in settings and settings["workspace"]: settings["workspace"] = settings["workspace"].replace("/", "\\") if "maya_plugin_path" in settings and settings["maya_plugin_path"]: settings["maya_plugin_path"] = settings["maya_plugin_path"].replace("/", "\\") if "sp_shelf_path" in settings and settings["sp_shelf_path"]: settings["sp_shelf_path"] = settings["sp_shelf_path"].replace("/", "\\") return settings def set_task_settings(self, project_name: str, workspace: str = None, task_type: str = None, use_type_hierarchy: bool = None, maya_plugin_path: str = None, sp_shelf_path: str = None) -> bool: """设置指定项目的 Task 面板设置 Args: project_name: 项目名称 workspace: 工作空间路径 task_type: 任务类型 use_type_hierarchy: 是否使用类型层级 maya_plugin_path: Maya 插件路径 sp_shelf_path: Substance Painter 架子路径 Returns: 是否保存成功 """ # 确保 projects 字段存在 if "projects" not in self.config_data: self.config_data["projects"] = {} # 确保项目存在 if project_name not in self.config_data["projects"]: self.config_data["projects"][project_name] = {} # 确保 task_settings 字段存在 if "task_settings" not in self.config_data["projects"][project_name]: self.config_data["projects"][project_name]["task_settings"] = {} # 保存到 projects.项目名.task_settings settings = self.config_data["projects"][project_name]["task_settings"] # 保存 SubFolders(如果存在,先临时保存) subfolders = settings.get("SubFolders") # 按顺序更新字段 if workspace is not None: settings["workspace"] = workspace.replace("\\", "/") if task_type is not None: settings["task_type"] = task_type if use_type_hierarchy is not None: settings["use_type_hierarchy"] = use_type_hierarchy if maya_plugin_path is not None: settings["maya_plugin_path"] = maya_plugin_path.replace("\\", "/") if sp_shelf_path is not None: settings["sp_shelf_path"] = sp_shelf_path.replace("\\", "/") # 重新构建有序字典,确保字段顺序:workspace, maya_plugin_path, sp_shelf_path, task_type, use_type_hierarchy, SubFolders ordered_settings = {} for key in ["workspace", "maya_plugin_path", "sp_shelf_path", "task_type", "use_type_hierarchy"]: if key in settings: ordered_settings[key] = settings[key] # 将 SubFolders 放在最后 if subfolders is not None: ordered_settings["SubFolders"] = subfolders # 替换原有的 task_settings self.config_data["projects"][project_name]["task_settings"] = ordered_settings print(f"[OK] Save the task settings to projects.{project_name}.task_settings") return self.save_config() def copy_apps_to_config(self, apps_data: List[Dict]) -> bool: """将复制的应用数据保存到配置文件""" try: if "clipboard" not in self.config_data: self.config_data["clipboard"] = {} self.config_data["clipboard"]["apps"] = apps_data print(f"[DEBUG] Saved {len(apps_data)} apps to config clipboard") return self.save_config() except Exception as e: print(f"[ERROR] Failed to save apps to config clipboard: {e}") return False def get_clipboard_apps(self) -> List[Dict]: """从配置文件获取复制的应用数据""" try: if "clipboard" in self.config_data and "apps" in self.config_data["clipboard"]: apps = self.config_data["clipboard"]["apps"] print(f"[DEBUG] Retrieved {len(apps)} apps from config clipboard") return apps else: print("[DEBUG] No apps in config clipboard") return [] except Exception as e: print(f"[ERROR] Failed to get apps from config clipboard: {e}") return [] def clear_clipboard_apps(self) -> bool: """清空配置文件中的应用剪贴板""" try: if "clipboard" in self.config_data: self.config_data["clipboard"]["apps"] = [] print("[DEBUG] Cleared config clipboard") return self.save_config() return True except Exception as e: print(f"[ERROR] Failed to clear config clipboard: {e}") return False def save_selection_state(self, project_name: str, selected_indices: List[int]) -> bool: """保存项目的选择状态到配置文件""" try: if "selection_states" not in self.config_data: self.config_data["selection_states"] = {} self.config_data["selection_states"][project_name] = selected_indices print(f"[DEBUG] Saved selection state for {project_name}: {selected_indices}") return self.save_config() except Exception as e: print(f"[ERROR] Failed to save selection state: {e}") return False def get_selection_state(self, project_name: str) -> List[int]: """从配置文件获取项目的选择状态""" try: if ("selection_states" in self.config_data and project_name in self.config_data["selection_states"]): indices = self.config_data["selection_states"][project_name] print(f"[DEBUG] Retrieved selection state for {project_name}: {indices}") return indices else: print(f"[DEBUG] No selection state found for {project_name}") return [] except Exception as e: print(f"[ERROR] Failed to get selection state: {e}") return [] def select_all_apps(self, project_name: str) -> List[int]: """选择项目的所有应用并保存状态""" try: apps = self.get_apps(project_name) all_indices = list(range(len(apps))) self.save_selection_state(project_name, all_indices) print(f"[DEBUG] Selected all {len(all_indices)} apps in {project_name}") return all_indices except Exception as e: print(f"[ERROR] Failed to select all apps: {e}") return [] def clear_selection_state(self, project_name: str) -> bool: """清空项目的选择状态""" try: if ("selection_states" in self.config_data and project_name in self.config_data["selection_states"]): self.config_data["selection_states"][project_name] = [] print(f"[DEBUG] Cleared selection state for {project_name}") return self.save_config() return True except Exception as e: print(f"[ERROR] Failed to clear selection state: {e}") return False