This commit is contained in:
2025-11-23 20:41:50 +08:00
commit f7d5b7be07
65 changed files with 14986 additions and 0 deletions

623
config/config_manager.py Normal file
View File

@@ -0,0 +1,623 @@
#!/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