"""
pytcper TCP调试助手 - 主窗口
============================
主窗口:包含 TCP 客户端 / 服务端两个标签页,记忆窗口大小,
菜单栏提供「关于开发者」对话框。
"""
import json
import re
import tkinter as tk
import webbrowser
from tkinter import ttk
from client_tab import ClientTab
from server_tab import ServerTab
from tcp_utils import CONFIG_FILE, __version__
class App(tk.Tk):
"""主窗口:包含 TCP 客户端 / 服务端两个标签页,记忆窗口大小。"""
def __init__(self):
super().__init__()
self.title("pytcper TCP 调试助手 v%s" % __version__)
self.geometry("900x680")
self._load_geometry()
self.minsize(1200, 560)
nb = ttk.Notebook(self)
nb.pack(fill="both", expand=True, padx=6, pady=6)
self.client_tab = ClientTab(nb)
self.server_tab = ServerTab(nb)
nb.add(self.client_tab, text="TCP 客户端")
nb.add(self.server_tab, text="TCP 服务端")
self.protocol("WM_DELETE_WINDOW", self._on_close)
menubar = tk.Menu(self)
help_menu = tk.Menu(menubar, tearoff=False)
help_menu.add_command(label="关于开发者", command=self._show_about)
menubar.add_cascade(label="帮助", menu=help_menu)
self.config(menu=menubar)
def _show_about(self):
"""显示「关于开发者」对话框。"""
win = tk.Toplevel(self)
win.title("关于开发者")
win.resizable(False, False)
win.transient(self)
win.grab_set()
frame = ttk.Frame(win, padding=16)
frame.pack(fill="both", expand=True)
ttk.Label(frame, text="pytcper TCP调试助手 v%s" % __version__,
font=("", 12, "bold")).pack()
ttk.Label(frame, text="版权所有者: micoder").pack(pady=(12, 0))
link = tk.Label(frame, text="项目地址: https://gitcode.com/micoder/pytcper",
fg="#0066CC", cursor="hand2")
link.pack(pady=(4, 0))
link.bind("<Button-1>",
lambda e: webbrowser.open("https://gitcode.com/micoder/pytcper"))
ttk.Button(frame, text="关闭", command=win.destroy).pack(pady=(12, 0))
def _load_geometry(self):
"""从配置文件恢复上次的窗口大小(位置不在任何屏幕内时忽略)。"""
try:
with open(CONFIG_FILE, encoding="utf-8") as f:
cfg = json.load(f)
geo = cfg.get("geometry")
if geo and self._geometry_visible(geo):
self.geometry(geo)
except (OSError, ValueError, tk.TclError):
pass
def _geometry_visible(self, geo):
"""校验几何字符串,且窗口至少部分落在当前屏幕上(避免恢复后不可见)。"""
try:
m = re.match(r"^(\d+)x(\d+)([+-]\d+)([+-]\d+)$", geo)
if not m:
return True
w, h = int(m.group(1)), int(m.group(2))
x, y = int(m.group(3)), int(m.group(4))
sw, sh = self.winfo_screenwidth(), self.winfo_screenheight()
return x < sw and y < sh and x + w > 0 and y + h > 0
except (ValueError, tk.TclError):
return False
def _save_geometry(self):
try:
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
json.dump({"geometry": self.geometry()}, f)
except OSError:
pass
def _on_close(self):
self._save_geometry()
self.client_tab.shutdown()
self.server_tab.shutdown()
self.destroy()
def main():
app = App()
app.mainloop()