Files
NexusLauncher/config/config_manager.py
2025-11-23 20:41:50 +08:00

624 lines
25 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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