|
| 1 | +import tkinter as tk |
| 2 | +from tkinter import ttk, messagebox |
| 3 | +import json |
| 4 | +import os |
| 5 | +from typing import Dict, List |
| 6 | + |
| 7 | +# 默认键位配置 |
| 8 | +DEFAULT_KEY_MAPS = { |
| 9 | + "开放空间": { |
| 10 | + "low_map": {str(i): k for i, k in zip(range(1, 8), list("asdfghj"))}, |
| 11 | + "mid_map": {str(i): k for i, k in zip(range(1, 8), list("qwertyu"))}, |
| 12 | + "high_map": {str(i): k for i, k in zip(range(1, 8), list("1234567"))} |
| 13 | + }, |
| 14 | + "原神": { |
| 15 | + "low_map": {str(i): k for i, k in zip(range(1, 8), list("zxcvbnm"))}, |
| 16 | + "mid_map": {str(i): k for i, k in zip(range(1, 8), list("asdfghj"))}, |
| 17 | + "high_map": {str(i): k for i, k in zip(range(1, 8), list("qwertyu"))} |
| 18 | + } |
| 19 | +} |
| 20 | + |
| 21 | +CONFIG_FILE = "key_map_config.json" |
| 22 | + |
| 23 | + |
| 24 | +class CustomKeyMap: |
| 25 | + """自定义键位映射管理类""" |
| 26 | + |
| 27 | + def __init__(self): |
| 28 | + self.current_profile = "开放空间" |
| 29 | + self.key_maps = DEFAULT_KEY_MAPS.copy() |
| 30 | + self.load_config() |
| 31 | + |
| 32 | + def load_config(self): |
| 33 | + """从配置文件加载键位配置""" |
| 34 | + try: |
| 35 | + if os.path.exists(CONFIG_FILE): |
| 36 | + with open(CONFIG_FILE, 'r', encoding='utf-8') as f: |
| 37 | + config = json.load(f) |
| 38 | + # 兼容旧版本配置(移除和弦映射) |
| 39 | + for profile_name, profile_data in config.get("profiles", {}).items(): |
| 40 | + if "chord_map" in profile_data: |
| 41 | + del profile_data["chord_map"] |
| 42 | + self.key_maps.update(config.get("profiles", {})) |
| 43 | + self.current_profile = config.get("current_profile", "开放空间") |
| 44 | + except Exception as e: |
| 45 | + print(f"加载配置文件失败: {e}") |
| 46 | + |
| 47 | + def save_config(self): |
| 48 | + """保存键位配置到文件""" |
| 49 | + try: |
| 50 | + config = { |
| 51 | + "current_profile": self.current_profile, |
| 52 | + "profiles": self.key_maps |
| 53 | + } |
| 54 | + with open(CONFIG_FILE, 'w', encoding='utf-8') as f: |
| 55 | + json.dump(config, f, ensure_ascii=False, indent=2) |
| 56 | + except Exception as e: |
| 57 | + print(f"保存配置文件失败: {e}") |
| 58 | + |
| 59 | + def get_current_map(self) -> Dict: |
| 60 | + """获取当前配置的键位映射""" |
| 61 | + return self.key_maps.get(self.current_profile, DEFAULT_KEY_MAPS["开放空间"]) |
| 62 | + |
| 63 | + def get_profile_names(self) -> List[str]: |
| 64 | + """获取所有配置文件的名称""" |
| 65 | + return list(self.key_maps.keys()) |
| 66 | + |
| 67 | + def create_profile(self, name: str, profile_data: Dict): |
| 68 | + """创建新的键位配置""" |
| 69 | + if name in self.key_maps: |
| 70 | + return False |
| 71 | + self.key_maps[name] = profile_data |
| 72 | + self.save_config() |
| 73 | + return True |
| 74 | + |
| 75 | + def update_profile(self, name: str, profile_data: Dict): |
| 76 | + """更新键位配置""" |
| 77 | + if name not in self.key_maps: |
| 78 | + return False |
| 79 | + self.key_maps[name] = profile_data |
| 80 | + self.save_config() |
| 81 | + return True |
| 82 | + |
| 83 | + def delete_profile(self, name: str): |
| 84 | + """删除键位配置""" |
| 85 | + if name in ["开放空间", "原神"]: |
| 86 | + return False # 不允许删除默认配置 |
| 87 | + if name in self.key_maps: |
| 88 | + del self.key_maps[name] |
| 89 | + if self.current_profile == name: |
| 90 | + self.current_profile = "开放空间" |
| 91 | + self.save_config() |
| 92 | + return True |
| 93 | + return False |
| 94 | + |
| 95 | + def set_current_profile(self, profile_name: str): |
| 96 | + """设置当前使用的配置""" |
| 97 | + if profile_name in self.key_maps: |
| 98 | + self.current_profile = profile_name |
| 99 | + self.save_config() |
| 100 | + return True |
| 101 | + return False |
| 102 | + |
| 103 | + |
| 104 | +class KeyMapEditor: |
| 105 | + """键位自定义映射界面""" |
| 106 | + |
| 107 | + def __init__(self, master, key_map_manager: CustomKeyMap): |
| 108 | + self.master = master |
| 109 | + self.manager = key_map_manager |
| 110 | + self.current_profile = self.manager.current_profile |
| 111 | + self.setup_ui() |
| 112 | + self.load_current_profile() |
| 113 | + |
| 114 | + def setup_ui(self): |
| 115 | + """设置UI界面""" |
| 116 | + self.master.title("钢琴键位自定义映射") |
| 117 | + self.master.geometry("350x400") |
| 118 | + |
| 119 | + # 主框架 |
| 120 | + main_frame = ttk.Frame(self.master, padding="10") |
| 121 | + main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) |
| 122 | + |
| 123 | + # 配置文件选择 |
| 124 | + ttk.Label(main_frame, text="选择配置:").grid(row=0, column=0, sticky=tk.W, pady=5) |
| 125 | + self.profile_var = tk.StringVar() |
| 126 | + self.profile_combo = ttk.Combobox(main_frame, textvariable=self.profile_var, state="readonly") |
| 127 | + self.profile_combo.grid(row=0, column=1, sticky=(tk.W, tk.E), pady=5, padx=5) |
| 128 | + self.profile_combo.bind('<<ComboboxSelected>>', self.on_profile_change) |
| 129 | + |
| 130 | + # 新建配置按钮 |
| 131 | + ttk.Button(main_frame, text="新建配置", command=self.create_new_profile).grid(row=0, column=2, padx=5) |
| 132 | + |
| 133 | + # 配置名称编辑 |
| 134 | + ttk.Label(main_frame, text="配置名称:").grid(row=1, column=0, sticky=tk.W, pady=5) |
| 135 | + self.name_var = tk.StringVar() |
| 136 | + ttk.Entry(main_frame, textvariable=self.name_var).grid(row=1, column=1, sticky=(tk.W, tk.E), pady=5, padx=5) |
| 137 | + |
| 138 | + # 笔记本控件 |
| 139 | + notebook = ttk.Notebook(main_frame) |
| 140 | + notebook.grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=10) |
| 141 | + |
| 142 | + # 低音区标签页 |
| 143 | + low_frame = ttk.Frame(notebook, padding="10") |
| 144 | + notebook.add(low_frame, text="低音区 (L1-L7)") |
| 145 | + self.setup_key_frame(low_frame, "low") |
| 146 | + |
| 147 | + # 中音区标签页 |
| 148 | + mid_frame = ttk.Frame(notebook, padding="10") |
| 149 | + notebook.add(mid_frame, text="中音区 (M1-M7)") |
| 150 | + self.setup_key_frame(mid_frame, "mid") |
| 151 | + |
| 152 | + # 高音区标签页 |
| 153 | + high_frame = ttk.Frame(notebook, padding="10") |
| 154 | + notebook.add(high_frame, text="高音区 (H1-H7)") |
| 155 | + self.setup_key_frame(high_frame, "high") |
| 156 | + |
| 157 | + # 按钮框架 |
| 158 | + button_frame = ttk.Frame(main_frame) |
| 159 | + button_frame.grid(row=3, column=0, columnspan=3, pady=10) |
| 160 | + |
| 161 | + ttk.Button(button_frame, text="保存当前配置", command=self.save_profile).pack(side=tk.LEFT, padx=5) |
| 162 | + ttk.Button(button_frame, text="删除当前配置", command=self.delete_profile).pack(side=tk.LEFT, padx=5) |
| 163 | + ttk.Button(button_frame, text="应用当前配置", command=self.apply_changes).pack(side=tk.LEFT, padx=5) |
| 164 | + |
| 165 | + # 配置网格权重 |
| 166 | + main_frame.columnconfigure(1, weight=1) |
| 167 | + main_frame.rowconfigure(2, weight=1) |
| 168 | + |
| 169 | + self.update_profile_list() |
| 170 | + |
| 171 | + def setup_key_frame(self, parent, key_type): |
| 172 | + """设置音区键位框架""" |
| 173 | + for i in range(7): |
| 174 | + note_num = i + 1 |
| 175 | + ttk.Label(parent, text=f"{key_type.upper()} {note_num}:").grid(row=i, column=0, sticky=tk.W, pady=2) |
| 176 | + var = tk.StringVar() |
| 177 | + entry = ttk.Entry(parent, textvariable=var, width=5) |
| 178 | + entry.grid(row=i, column=1, sticky=tk.W, pady=2, padx=5) |
| 179 | + setattr(self, f"{key_type}_{note_num}_var", var) |
| 180 | + |
| 181 | + def update_profile_list(self): |
| 182 | + """更新配置文件列表""" |
| 183 | + profiles = self.manager.get_profile_names() |
| 184 | + self.profile_combo['values'] = profiles |
| 185 | + if self.current_profile in profiles: |
| 186 | + self.profile_var.set(self.current_profile) |
| 187 | + |
| 188 | + def load_current_profile(self): |
| 189 | + """加载当前配置""" |
| 190 | + profile = self.manager.get_current_map() |
| 191 | + self.name_var.set(self.current_profile) |
| 192 | + |
| 193 | + # 加载低音区 |
| 194 | + for i in range(1, 8): |
| 195 | + var = getattr(self, f"low_{i}_var") |
| 196 | + var.set(profile["low_map"].get(str(i), "")) |
| 197 | + |
| 198 | + # 加载中音区 |
| 199 | + for i in range(1, 8): |
| 200 | + var = getattr(self, f"mid_{i}_var") |
| 201 | + var.set(profile["mid_map"].get(str(i), "")) |
| 202 | + |
| 203 | + # 加载高音区 |
| 204 | + for i in range(1, 8): |
| 205 | + var = getattr(self, f"high_{i}_var") |
| 206 | + var.set(profile["high_map"].get(str(i), "")) |
| 207 | + |
| 208 | + def on_profile_change(self, event): |
| 209 | + """配置文件变更事件""" |
| 210 | + self.current_profile = self.profile_var.get() |
| 211 | + self.manager.set_current_profile(self.current_profile) |
| 212 | + self.load_current_profile() |
| 213 | + |
| 214 | + def create_new_profile(self): |
| 215 | + """创建新配置""" |
| 216 | + name = f"custom_{len(self.manager.key_maps) + 1}" |
| 217 | + new_profile = DEFAULT_KEY_MAPS["开放空间"].copy() |
| 218 | + |
| 219 | + if self.manager.create_profile(name, new_profile): |
| 220 | + self.update_profile_list() |
| 221 | + self.profile_var.set(name) |
| 222 | + self.on_profile_change(None) |
| 223 | + |
| 224 | + def save_profile(self): |
| 225 | + """保存当前配置""" |
| 226 | + profile_data = { |
| 227 | + "low_map": {}, |
| 228 | + "mid_map": {}, |
| 229 | + "high_map": {} |
| 230 | + } |
| 231 | + |
| 232 | + # 收集低音区 |
| 233 | + for i in range(1, 8): |
| 234 | + var = getattr(self, f"low_{i}_var") |
| 235 | + key_val = var.get().strip() |
| 236 | + if key_val: # 只保存非空的键位 |
| 237 | + profile_data["low_map"][str(i)] = key_val |
| 238 | + |
| 239 | + # 收集中音区 |
| 240 | + for i in range(1, 8): |
| 241 | + var = getattr(self, f"mid_{i}_var") |
| 242 | + key_val = var.get().strip() |
| 243 | + if key_val: |
| 244 | + profile_data["mid_map"][str(i)] = key_val |
| 245 | + |
| 246 | + # 收集高音区 |
| 247 | + for i in range(1, 8): |
| 248 | + var = getattr(self, f"high_{i}_var") |
| 249 | + key_val = var.get().strip() |
| 250 | + if key_val: |
| 251 | + profile_data["high_map"][str(i)] = key_val |
| 252 | + |
| 253 | + if self.manager.update_profile(self.current_profile, profile_data): |
| 254 | + messagebox.showinfo("成功", "配置保存成功!") |
| 255 | + |
| 256 | + def delete_profile(self): |
| 257 | + """删除当前配置""" |
| 258 | + if self.current_profile in ["开放空间", "原神"]: |
| 259 | + messagebox.showwarning("警告", "不能删除默认配置!") |
| 260 | + return |
| 261 | + |
| 262 | + if messagebox.askyesno("确认", f"确定要删除配置 '{self.current_profile}' 吗?"): |
| 263 | + if self.manager.delete_profile(self.current_profile): |
| 264 | + self.update_profile_list() |
| 265 | + self.load_current_profile() |
| 266 | + messagebox.showinfo("成功", "配置已删除!") |
| 267 | + |
| 268 | + def apply_changes(self): |
| 269 | + """应用更改""" |
| 270 | + self.save_profile() |
| 271 | + messagebox.showinfo("成功", "配置已应用!") |
| 272 | + |
| 273 | + |
| 274 | +def show_key_map_editor(): |
| 275 | + root = tk.Tk() |
| 276 | + manager = CustomKeyMap() |
| 277 | + editor = KeyMapEditor(root, manager) |
| 278 | + root.mainloop() |
| 279 | + |
| 280 | + |
| 281 | +if __name__ == "__main__": |
| 282 | + show_key_map_editor() |
0 commit comments