change tk

This commit is contained in:
zj
2026-02-09 03:14:35 +08:00
parent 8c53b9c0a0
commit 1a3a4746a3
13 changed files with 3595 additions and 221 deletions

557
dialogs_tkinter.py Normal file
View File

@@ -0,0 +1,557 @@
# dialogs_tkinter.py
import os
import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
import logging
logger = logging.getLogger(__name__)
class BaseDialog:
"""对话框基类"""
def __init__(self, parent, title, width=400, height=300):
self.parent = parent
self.result = None
# 创建顶层窗口
self.dialog = tk.Toplevel(parent)
self.dialog.title(title)
self.dialog.geometry(f"{width}x{height}")
self.dialog.transient(parent) # 设置父窗口
self.dialog.grab_set() # 模态对话框
# 居中显示
self._center_window()
# 创建主框架
self.main_frame = ttk.Frame(self.dialog, padding="10")
self.main_frame.pack(fill=tk.BOTH, expand=True)
def _center_window(self):
"""将窗口居中显示"""
self.dialog.update_idletasks()
width = self.dialog.winfo_width()
height = self.dialog.winfo_height()
x = (self.dialog.winfo_screenwidth() // 2) - (width // 2)
y = (self.dialog.winfo_screenheight() // 2) - (height // 2)
self.dialog.geometry(f"{width}x{height}+{x}+{y}")
def _create_button_box(self):
"""创建确定/取消按钮"""
btn_frame = ttk.Frame(self.main_frame)
btn_frame.pack(fill=tk.X, pady=(10, 0))
self.confirm_btn = ttk.Button(btn_frame, text="确定", command=self._on_confirm)
self.confirm_btn.pack(side=tk.RIGHT, padx=5)
self.cancel_btn = ttk.Button(btn_frame, text="取消", command=self._on_cancel)
self.cancel_btn.pack(side=tk.RIGHT, padx=5)
# 绑定回车键和 ESC 键
self.dialog.bind('<Return>', lambda e: self._on_confirm())
self.dialog.bind('<Escape>', lambda e: self._on_cancel())
def _on_confirm(self):
"""确定按钮回调 - 子类重写"""
pass
def _on_cancel(self):
"""取消按钮回调"""
self.dialog.destroy()
def wait_for_result(self):
"""等待对话框关闭并返回结果"""
self.dialog.wait_window()
return self.result
class CreatePartitionDialog(BaseDialog):
"""创建分区对话框"""
def __init__(self, parent, disk_path="", total_disk_mib=0.0, max_available_mib=0.0):
self.disk_path = disk_path
self.total_disk_mib = total_disk_mib
self.max_available_mib = max_available_mib
super().__init__(parent, "创建分区", 450, 280)
logger.debug(f"CreatePartitionDialog initialized for {disk_path}. Total MiB: {total_disk_mib}, Max Available MiB: {max_available_mib}")
self._setup_ui()
self._initialize_state()
def _setup_ui(self):
"""设置 UI"""
# 创建内容框架
content_frame = ttk.Frame(self.main_frame)
content_frame.pack(fill=tk.BOTH, expand=True)
# 磁盘路径
ttk.Label(content_frame, text="磁盘路径:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
ttk.Label(content_frame, text=self.disk_path).grid(row=0, column=1, sticky=tk.W, padx=5, pady=5)
# 分区表类型
ttk.Label(content_frame, text="分区表类型:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
self.part_type_var = tk.StringVar(value="gpt")
self.part_type_combo = ttk.Combobox(content_frame, textvariable=self.part_type_var,
values=["gpt", "msdos"], state="readonly", width=15)
self.part_type_combo.grid(row=1, column=1, sticky=tk.W, padx=5, pady=5)
# 使用最大空间复选框
self.use_max_var = tk.BooleanVar(value=True)
self.use_max_check = ttk.Checkbutton(content_frame, text="使用最大可用空间",
variable=self.use_max_var,
command=self._toggle_size_input)
self.use_max_check.grid(row=2, column=0, columnspan=2, sticky=tk.W, padx=5, pady=5)
# 分区大小
ttk.Label(content_frame, text="分区大小 (GB):").grid(row=3, column=0, sticky=tk.W, padx=5, pady=5)
calculated_max_gb = self.max_available_mib / 1024.0
self.max_gb = max(0.01, calculated_max_gb)
self.size_var = tk.DoubleVar(value=self.max_gb)
self.size_spin = tk.Spinbox(content_frame, from_=0.01, to=self.max_gb,
increment=0.01, textvariable=self.size_var, width=12)
self.size_spin.grid(row=3, column=1, sticky=tk.W, padx=5, pady=5)
logger.debug(f"Size spinbox max set to: {self.max_gb} GB (calculated from {calculated_max_gb} GB)")
# 按钮
self._create_button_box()
def _initialize_state(self):
"""初始化状态"""
self._toggle_size_input()
def _toggle_size_input(self):
"""切换大小输入框状态"""
is_checked = self.use_max_var.get()
logger.debug(f"[_toggle_size_input] Called. Checkbox isChecked(): {is_checked}")
if is_checked:
logger.debug(f"[_toggle_size_input] Checkbox is CHECKED. Disabling spinbox and setting value to max.")
self.size_spin.configure(state=tk.DISABLED)
self.size_var.set(self.max_gb)
logger.debug(f"[_toggle_size_input] Spinbox value set to max: {self.max_gb} GB.")
else:
logger.debug(f"[_toggle_size_input] Checkbox is UNCHECKED. Enabling spinbox.")
self.size_spin.configure(state=tk.NORMAL)
current_val = self.size_var.get()
if current_val >= self.max_gb:
self.size_var.set(0.01)
logger.debug(f"[_toggle_size_input] Spinbox was at max, reset to min: 0.01 GB.")
else:
logger.debug(f"[_toggle_size_input] Spinbox was not at max, keeping current value: {current_val} GB.")
def _on_confirm(self):
"""确定按钮回调"""
size_gb = self.size_var.get()
use_max_space = self.use_max_var.get()
if not use_max_space and size_gb <= 0:
messagebox.showwarning("输入错误", "分区大小必须大于0。")
return
if not use_max_space and size_gb > self.max_gb:
messagebox.showwarning("输入错误", "分区大小不能超过最大可用空间。")
return
self.result = {
'disk_path': self.disk_path,
'partition_table_type': self.part_type_var.get(),
'size_gb': size_gb,
'total_disk_mib': self.total_disk_mib,
'use_max_space': use_max_space
}
self.dialog.destroy()
def get_partition_info(self):
"""获取分区信息"""
return self.result
class MountDialog(BaseDialog):
"""挂载对话框"""
def __init__(self, parent, device_path=""):
self.device_path = device_path
super().__init__(parent, "挂载分区", 400, 200)
self._setup_ui()
def _setup_ui(self):
"""设置 UI"""
# 创建内容框架
content_frame = ttk.Frame(self.main_frame)
content_frame.pack(fill=tk.BOTH, expand=True)
# 设备路径
ttk.Label(content_frame, text="设备路径:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
ttk.Label(content_frame, text=self.device_path).grid(row=0, column=1, sticky=tk.W, padx=5, pady=5)
# 挂载点
ttk.Label(content_frame, text="挂载点:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
self.mount_point_var = tk.StringVar(value=f"/mnt/{os.path.basename(self.device_path)}")
self.mount_point_entry = ttk.Entry(content_frame, textvariable=self.mount_point_var, width=30)
self.mount_point_entry.grid(row=1, column=1, sticky=tk.W, padx=5, pady=5)
# 开机自动挂载复选框
self.add_to_fstab_var = tk.BooleanVar(value=False)
self.fstab_check = ttk.Checkbutton(content_frame, text="开机自动挂载 (添加到 /etc/fstab)",
variable=self.add_to_fstab_var)
self.fstab_check.grid(row=2, column=0, columnspan=2, sticky=tk.W, padx=5, pady=5)
# 按钮
self._create_button_box()
def _on_confirm(self):
"""确定按钮回调"""
mount_point = self.mount_point_var.get().strip()
if not mount_point:
messagebox.showwarning("输入错误", "挂载点不能为空。")
return
if not mount_point.startswith('/'):
messagebox.showwarning("输入错误", "挂载点必须是绝对路径 (以 '/' 开头)。")
return
self.result = {
'mount_point': mount_point,
'add_to_fstab': self.add_to_fstab_var.get()
}
self.dialog.destroy()
def get_mount_info(self):
"""获取挂载信息"""
return self.result
class CreateRaidDialog(BaseDialog):
"""创建 RAID 阵列对话框"""
def __init__(self, parent, available_devices=None):
self.available_devices = available_devices if available_devices is not None else []
self.selected_devices = []
super().__init__(parent, "创建 RAID 阵列", 450, 450)
self._setup_ui()
def _setup_ui(self):
"""设置 UI"""
# 创建内容框架
content_frame = ttk.Frame(self.main_frame)
content_frame.pack(fill=tk.BOTH, expand=True)
# RAID 级别
ttk.Label(content_frame, text="RAID 级别:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
self.raid_level_var = tk.StringVar(value="raid0")
self.raid_level_combo = ttk.Combobox(content_frame, textvariable=self.raid_level_var,
values=["raid0", "raid1", "raid5", "raid6", "raid10"],
state="readonly", width=12)
self.raid_level_combo.grid(row=0, column=1, sticky=tk.W, padx=5, pady=5)
# Chunk 大小
ttk.Label(content_frame, text="Chunk 大小 (KB):").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
self.chunk_size_var = tk.IntVar(value=512)
self.chunk_size_spin = tk.Spinbox(content_frame, from_=4, to=1024, increment=4,
textvariable=self.chunk_size_var, width=10)
self.chunk_size_spin.grid(row=1, column=1, sticky=tk.W, padx=5, pady=5)
# 设备选择
ttk.Label(content_frame, text="选择设备:").grid(row=2, column=0, sticky=tk.NW, padx=5, pady=5)
# 创建设备选择框架
device_frame = ttk.Frame(content_frame)
device_frame.grid(row=2, column=1, sticky=tk.W, padx=5, pady=5)
# 设备复选框列表
self.device_vars = {}
for i, dev in enumerate(self.available_devices):
var = tk.BooleanVar(value=False)
self.device_vars[dev] = var
cb = ttk.Checkbutton(device_frame, text=dev, variable=var)
cb.pack(anchor=tk.W)
# 按钮
self._create_button_box()
def _on_confirm(self):
"""确定按钮回调"""
# 收集选中的设备
self.selected_devices = [dev for dev, var in self.device_vars.items() if var.get()]
if not self.selected_devices:
messagebox.showwarning("输入错误", "请选择至少一个设备来创建 RAID 阵列。")
return
raid_level = self.raid_level_var.get()
num_devices = len(self.selected_devices)
# RAID 级别检查
if raid_level == "raid0" and num_devices < 1:
messagebox.showwarning("输入错误", "RAID0 至少需要一个设备。")
return
elif raid_level == "raid1" and num_devices < 2:
messagebox.showwarning("输入错误", "RAID1 至少需要两个设备。")
return
elif raid_level == "raid5" and num_devices < 3:
messagebox.showwarning("输入错误", "RAID5 至少需要三个设备。")
return
elif raid_level == "raid6" and num_devices < 4:
messagebox.showwarning("输入错误", "RAID6 至少需要四个设备。")
return
elif raid_level == "raid10" and num_devices < 2:
messagebox.showwarning("输入错误", "RAID10 至少需要两个设备。")
return
self.result = {
'devices': self.selected_devices,
'level': raid_level.replace('raid', ''),
'chunk_size': self.chunk_size_var.get()
}
self.dialog.destroy()
def get_raid_info(self):
"""获取 RAID 信息"""
return self.result
class CreatePvDialog(BaseDialog):
"""创建物理卷 (PV) 对话框"""
def __init__(self, parent, available_partitions=None):
self.available_partitions = available_partitions if available_partitions is not None else []
super().__init__(parent, "创建物理卷 (PV)", 400, 200)
self._setup_ui()
def _setup_ui(self):
"""设置 UI"""
# 创建内容框架
content_frame = ttk.Frame(self.main_frame)
content_frame.pack(fill=tk.BOTH, expand=True)
# 设备选择
ttk.Label(content_frame, text="选择设备:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
self.device_var = tk.StringVar()
if self.available_partitions:
self.device_var.set(self.available_partitions[0])
self.device_combo = ttk.Combobox(content_frame, textvariable=self.device_var,
values=self.available_partitions, state="readonly", width=30)
self.device_combo.grid(row=0, column=1, sticky=tk.W, padx=5, pady=5)
# 按钮
self._create_button_box()
def _on_confirm(self):
"""确定按钮回调"""
device_path = self.device_var.get()
if not device_path:
messagebox.showwarning("输入错误", "请选择一个设备来创建物理卷。")
return
self.result = {'device_path': device_path}
self.dialog.destroy()
def get_pv_info(self):
"""获取 PV 信息"""
return self.result
class CreateVgDialog(BaseDialog):
"""创建卷组 (VG) 对话框"""
def __init__(self, parent, available_pvs=None):
self.available_pvs = available_pvs if available_pvs is not None else []
self.selected_pvs = []
super().__init__(parent, "创建卷组 (VG)", 450, 350)
self._setup_ui()
def _setup_ui(self):
"""设置 UI"""
# 创建内容框架
content_frame = ttk.Frame(self.main_frame)
content_frame.pack(fill=tk.BOTH, expand=True)
# 卷组名称
ttk.Label(content_frame, text="卷组名称:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
self.vg_name_var = tk.StringVar()
self.vg_name_entry = ttk.Entry(content_frame, textvariable=self.vg_name_var, width=25)
self.vg_name_entry.grid(row=0, column=1, sticky=tk.W, padx=5, pady=5)
# 物理卷选择
ttk.Label(content_frame, text="选择物理卷:").grid(row=1, column=0, sticky=tk.NW, padx=5, pady=5)
pv_frame = ttk.Frame(content_frame)
pv_frame.grid(row=1, column=1, sticky=tk.W, padx=5, pady=5)
self.pv_vars = {}
for pv in self.available_pvs:
var = tk.BooleanVar(value=False)
self.pv_vars[pv] = var
cb = ttk.Checkbutton(pv_frame, text=pv, variable=var)
cb.pack(anchor=tk.W)
# 按钮
self._create_button_box()
def _on_confirm(self):
"""确定按钮回调"""
vg_name = self.vg_name_var.get().strip()
self.selected_pvs = [pv for pv, var in self.pv_vars.items() if var.get()]
if not vg_name:
messagebox.showwarning("输入错误", "卷组名称不能为空。")
return
if not self.selected_pvs:
messagebox.showwarning("输入错误", "请选择至少一个物理卷来创建卷组。")
return
self.result = {
'vg_name': vg_name,
'pvs': self.selected_pvs
}
self.dialog.destroy()
def get_vg_info(self):
"""获取 VG 信息"""
return self.result
class CreateLvDialog(BaseDialog):
"""创建逻辑卷 (LV) 对话框"""
def __init__(self, parent, available_vgs=None, vg_sizes=None):
self.available_vgs = available_vgs if available_vgs is not None else []
self.vg_sizes = vg_sizes if vg_sizes is not None else {}
super().__init__(parent, "创建逻辑卷", 450, 300)
logger.debug(f"CreateLvDialog initialized. Available VGs: {self.available_vgs}, VG Sizes: {self.vg_sizes}")
self._setup_ui()
self._initialize_state()
def _setup_ui(self):
"""设置 UI"""
# 逻辑卷名称
# 创建内容框架
content_frame = ttk.Frame(self.main_frame)
content_frame.pack(fill=tk.BOTH, expand=True)
ttk.Label(content_frame, text="逻辑卷名称:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
self.lv_name_var = tk.StringVar()
self.lv_name_entry = ttk.Entry(content_frame, textvariable=self.lv_name_var, width=25)
self.lv_name_entry.grid(row=0, column=1, sticky=tk.W, padx=5, pady=5)
# 卷组选择
ttk.Label(content_frame, text="选择卷组:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
self.vg_var = tk.StringVar()
if self.available_vgs:
self.vg_var.set(self.available_vgs[0])
self.vg_combo = ttk.Combobox(content_frame, textvariable=self.vg_var,
values=self.available_vgs, state="readonly", width=25)
self.vg_combo.grid(row=1, column=1, sticky=tk.W, padx=5, pady=5)
self.vg_combo.bind('<<ComboboxSelected>>', lambda e: self._update_size_options())
# 使用最大空间复选框
self.use_max_var = tk.BooleanVar(value=True)
self.use_max_check = ttk.Checkbutton(content_frame, text="使用最大可用空间",
variable=self.use_max_var,
command=self._toggle_size_input)
self.use_max_check.grid(row=2, column=0, columnspan=2, sticky=tk.W, padx=5, pady=5)
# 逻辑卷大小
ttk.Label(content_frame, text="大小 (GB):").grid(row=3, column=0, sticky=tk.W, padx=5, pady=5)
self.size_var = tk.DoubleVar(value=1.0)
self.size_spin = tk.Spinbox(content_frame, from_=0.01, to=100000.0, increment=0.01,
textvariable=self.size_var, width=12)
self.size_spin.grid(row=3, column=1, sticky=tk.W, padx=5, pady=5)
# 按钮
self._create_button_box()
def _initialize_state(self):
"""初始化状态"""
self._update_size_options()
def _update_size_options(self):
"""根据选中的卷组更新大小选项"""
selected_vg = self.vg_var.get()
max_size_gb = self.vg_sizes.get(selected_vg, 0.0)
logger.debug(f"[_update_size_options] Selected VG: {selected_vg}, Max available GB: {max_size_gb}")
# 确保最大值至少大于等于最小值
effective_max = max(0.01, max_size_gb)
self.size_spin.configure(to=effective_max)
self.max_gb = effective_max
logger.debug(f"[_update_size_options] Spinbox max set to: {effective_max} GB.")
# 应用复选框状态
self._toggle_size_input()
logger.debug("[_update_size_options] Completed.")
def _toggle_size_input(self):
"""切换大小输入框状态"""
is_checked = self.use_max_var.get()
logger.debug(f"[_toggle_size_input] Called. Checkbox isChecked(): {is_checked}")
if is_checked:
logger.debug(f"[_toggle_size_input] Checkbox is CHECKED. Disabling spinbox and setting value to max.")
self.size_spin.configure(state=tk.DISABLED)
self.size_var.set(self.max_gb)
logger.debug(f"[_toggle_size_input] Spinbox value set to max: {self.max_gb} GB.")
else:
logger.debug(f"[_toggle_size_input] Checkbox is UNCHECKED. Enabling spinbox.")
self.size_spin.configure(state=tk.NORMAL)
current_val = self.size_var.get()
if current_val >= self.max_gb:
self.size_var.set(0.01)
logger.debug(f"[_toggle_size_input] Spinbox was at max, reset to min: 0.01 GB.")
else:
logger.debug(f"[_toggle_size_input] Spinbox was not at max, keeping current value: {current_val} GB.")
logger.debug("[_toggle_size_input] Completed.")
def _on_confirm(self):
"""确定按钮回调"""
lv_name = self.lv_name_var.get().strip()
vg_name = self.vg_var.get()
size_gb = self.size_var.get()
use_max_space = self.use_max_var.get()
if not lv_name:
messagebox.showwarning("输入错误", "逻辑卷名称不能为空。")
return
if not vg_name:
messagebox.showwarning("输入错误", "请选择一个卷组。")
return
if not use_max_space:
if size_gb <= 0:
messagebox.showwarning("输入错误", "逻辑卷大小必须大于0。")
return
if size_gb > self.max_gb:
messagebox.showwarning("输入错误", "逻辑卷大小不能超过卷组的可用空间。")
return
self.result = {
'lv_name': lv_name,
'vg_name': vg_name,
'size_gb': size_gb,
'use_max_space': use_max_space
}
self.dialog.destroy()
def get_lv_info(self):
"""获取 LV 信息"""
return self.result