# -*- coding: utf-8 -*-
"""
节气计算器 GUI v1.0
- 所有文本框支持全选、复制、右键菜单
"""

import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext
from datetime import datetime
import math

# 尝试导入 lunar-python
try:
    from lunar_python import Solar, Lunar
    LUNAR_AVAILABLE = True
except ImportError:
    LUNAR_AVAILABLE = False


# ============ 文本框右键菜单功能 ============

def create_context_menu(widget):
    """为文本控件创建右键菜单"""
    menu = tk.Menu(widget, tearoff=0)
    menu.add_command(label="全选", command=lambda: widget.event_generate("<<SelectAll>>"))
    menu.add_command(label="复制", command=lambda: widget.event_generate("<<Copy>>"))
    menu.add_command(label="全选并复制", command=lambda: select_all_and_copy(widget))
    menu.add_separator()
    menu.add_command(label="剪切", command=lambda: widget.event_generate("<<Cut>>"))
    menu.add_command(label="粘贴", command=lambda: widget.event_generate("<<Paste>>"))
    
    def show_menu(event):
        menu.post(event.x_root, event.y_root)
    
    widget.bind("<Button-3>", show_menu)  # 右键
    widget.bind("<Control-a>", lambda e: widget.event_generate("<<SelectAll>>"))
    widget.bind("<Control-A>", lambda e: widget.event_generate("<<SelectAll>>"))
    return menu

def select_all_and_copy(widget):
    """全选并复制文本"""
    try:
        # 先全选
        widget.event_generate("<<SelectAll>>")
        # 再复制
        widget.event_generate("<<Copy>>")
    except tk.TclError:
        pass  # 忽略可能的错误
    return "break"

class CopyableEntry(ttk.Entry):
    """支持全选复制的 Entry"""
    def __init__(self, master=None, **kwargs):
        super().__init__(master, **kwargs)
        create_context_menu(self)
        # 额外绑定,确保 Entry 的 select all 有效
        self.bind("<Control-a>", self.select_all)
        self.bind("<Control-A>", self.select_all)
        # 绑定全选并复制快捷键
        self.bind("<Control-Shift-C>", self.select_all_and_copy)
        self.bind("<Control-Shift-c>", self.select_all_and_copy)
    
    def select_all(self, event=None):
        self.select_range(0, tk.END)
        self.icursor(tk.END)
        return "break"  # 阻止默认行为
    
    def select_all_and_copy(self, event=None):
        self.select_range(0, tk.END)
        self.icursor(tk.END)
        self.event_generate("<<Copy>>")
        return "break"



class CopyableSpinbox(ttk.Spinbox):
    """支持全选复制的 Spinbox"""
    def __init__(self, master=None, **kwargs):
        super().__init__(master, **kwargs)
        create_context_menu(self)
        self.bind("<Control-a>", self.select_all)
        self.bind("<Control-A>", self.select_all)
        # 绑定全选并复制快捷键
        self.bind("<Control-Shift-C>", self.select_all_and_copy)
        self.bind("<Control-Shift-c>", self.select_all_and_copy)
    
    def select_all(self, event=None):
        self.select_range(0, tk.END)
        self.icursor(tk.END)
        return "break"
    
    def select_all_and_copy(self, event=None):
        self.select_range(0, tk.END)
        self.icursor(tk.END)
        self.event_generate("<<Copy>>")
        return "break"


class CopyableScrolledText(scrolledtext.ScrolledText):
    """支持全选复制的 ScrolledText"""
    def __init__(self, master=None, **kwargs):
        super().__init__(master, **kwargs)
        create_context_menu(self)
        # ScrolledText 默认已有 Ctrl+C,补充 Ctrl+A
        self.bind("<Control-a>", self.select_all)
        self.bind("<Control-A>", self.select_all)
        # 绑定全选并复制快捷键
        self.bind("<Control-Shift-C>", self.select_all_and_copy)
        self.bind("<Control-Shift-c>", self.select_all_and_copy)
    
    def select_all(self, event=None):
        self.tag_add(tk.SEL, "1.0", tk.END)
        self.mark_set(tk.INSERT, tk.END)
        self.see(tk.END)
        return "break"
    
    def select_all_and_copy(self, event=None):
        self.tag_add(tk.SEL, "1.0", tk.END)
        self.mark_set(tk.INSERT, tk.END)
        self.see(tk.END)
        self.event_generate("<<Copy>>")
        return "break"

# ============ 数据类 ============

class JieQiResult:
    def __init__(self, year, month, day, hour, minute, second, name, is_jie):
        self.year = year
        self.month = month
        self.day = day
        self.hour = hour
        self.minute = minute
        self.second = second
        self.name = name
        self.is_jie = is_jie
    
    def __str__(self):
        return f"{self.year}{self.month:02d}{self.day:02d}{self.hour:02d}:{self.minute:02d}:{self.second:02d} {self.name}"
    
    def to_datetime(self):
        return datetime(self.year, self.month, self.day, self.hour, self.minute, self.second)


# ============ 节气名称表 ============

JIEQI_NAMES = [
    '冬至', '小寒', '大寒',
    '立春', '雨水', '惊蛰',
    '春分', '清明', '谷雨',
    '立夏', '小满', '芒种',
    '夏至', '小暑', '大暑',
    '立秋', '处暑', '白露',
    '秋分', '寒露', '霜降',
    '立冬', '小雪', '大雪',
]

JIE_QI_TYPE = [
    False, True, True,
    True, False, True,
    False, True, False,
    True, False, True,
    False, True, True,
    True, False, True,
    False, True, False,
    True, False, True,
]

# 节气名称映射表(处理 lunar-python 可能返回的英文/拼音)
JIEQI_NAME_MAP = {
    # 大写英文/拼音
    'DONGZHI': '冬至', 'XIAOHAN': '小寒', 'DAHAN': '大寒',
    'LICHUN': '立春', 'YUSHUI': '雨水', 'JINGZHE': '惊蛰',
    'CHUNFEN': '春分', 'QINGMING': '清明', 'GUYU': '谷雨',
    'LIXIA': '立夏', 'XIAOMAN': '小满', 'MANGZHONG': '芒种',
    'XIAZHI': '夏至', 'XIAOSHU': '小暑', 'DASHU': '大暑',
    'LIQIU': '立秋', 'CHUSHU': '处暑', 'BAILU': '白露',
    'QIUFEN': '秋分', 'HANLU': '寒露', 'SHUANGJIANG': '霜降',
    'LIDONG': '立冬', 'XIAOXUE': '小雪', 'DAXUE': '大雪',
    # 小写英文/拼音
    'dongzhi': '冬至', 'xiaohan': '小寒', 'dahan': '大寒',
    'lichun': '立春', 'yushui': '雨水', 'jingzhe': '惊蛰',
    'chunfen': '春分', 'qingming': '清明', 'guyu': '谷雨',
    'lixia': '立夏', 'xiaoman': '小满', 'mangzhong': '芒种',
    'xiazhi': '夏至', 'xiaoshu': '小暑', 'dashu': '大暑',
    'liqiu': '立秋', 'chushu': '处暑', 'bailu': '白露',
    'qiufen': '秋分', 'hanlu': '寒露', 'shuangjiang': '霜降',
    'lidong': '立冬', 'xiaoxue': '小雪', 'daxue': '大雪',
    # 其他可能变体
    'DongZhi': '冬至', 'XiaoHan': '小寒', 'DaHan': '大寒',
    'LiChun': '立春', 'YuShui': '雨水', 'JingZhe': '惊蛰',
    'ChunFen': '春分', 'QingMing': '清明', 'GuYu': '谷雨',
    'LiXia': '立夏', 'XiaoMan': '小满', 'MangZhong': '芒种',
    'XiaZhi': '夏至', 'XiaoShu': '小暑', 'DaShu': '大暑',
    'LiQiu': '立秋', 'ChuShu': '处暑', 'BaiLu': '白露',
    'QiuFen': '秋分', 'HanLu': '寒露', 'ShuangJiang': '霜降',
    'LiDong': '立冬', 'XiaoXue': '小雪', 'DaXue': '大雪',
}


def normalize_jieqi_name(name):
    """统一转换为中文节气名"""
    if not name:
        return None
    if name in JIEQI_NAMES:
        return name
    mapped = JIEQI_NAME_MAP.get(name)
    if mapped:
        return mapped
    lower_name = name.lower() if isinstance(name, str) else name
    for key, val in JIEQI_NAME_MAP.items():
        if key.lower() == lower_name:
            return val
    clean_name = name.replace('_', '').replace('-', '').replace(' ', '')
    mapped = JIEQI_NAME_MAP.get(clean_name)
    if mapped:
        return mapped
    print(f"警告:未识别的节气名称: {repr(name)}")
    return name


def get_jieqi_index(name):
    """安全获取节气索引"""
    normalized = normalize_jieqi_name(name)
    if normalized in JIEQI_NAMES:
        return JIEQI_NAMES.index(normalized)
    return 0


# ============ 天文算法核心 ============

def gregorian_to_jd(year, month, day, hour=0, minute=0, second=0):
    if month <= 2:
        year -= 1
        month += 12
    a = int(year / 100)
    b = 2 - a + int(a / 4)
    if year < 1582 or (year == 1582 and month < 10) or (year == 1582 and month == 10 and day <= 4):
        b = 0
    jd = int(365.25 * (year + 4716)) + int(30.6001 * (month + 1)) + day + b - 1524.5
    jd += (hour + minute / 60 + second / 3600) / 24
    return jd


def jd_to_gregorian(jd):
    jd += 0.5
    z = int(jd)
    f = jd - z
    if z < 2299161:
        a = z
    else:
        alpha = int((z - 1867216.25) / 36524.25)
        a = z + 1 + alpha - int(alpha / 4)
    b = a + 1524
    c = int((b - 122.1) / 365.25)
    d = int(365.25 * c)
    e = int((b - d) / 30.6001)
    day = b - d - int(30.6001 * e)
    month = e - 1 if e < 14 else e - 13
    year = c - 4716 if month > 2 else c - 4715
    f *= 24
    hour = int(f)
    f = (f - hour) * 60
    minute = int(f)
    second = int((f - minute) * 60)
    return year, month, day, hour, minute, second


def sun_longitude(jd):
    t = (jd - 2451545.0) / 36525
    l0 = 280.46646 + 36000.76983 * t + 0.0003032 * t * t
    m = 357.52911 + 35999.05029 * t - 0.0001537 * t * t
    e = 0.016708634 - 0.000042037 * t - 0.0000001267 * t * t
    c = (1.914602 - 0.004817 * t - 0.000014 * t * t) * math.sin(math.radians(m))
    c += (0.019993 - 0.000101 * t) * math.sin(math.radians(2 * m))
    c += 0.000289 * math.sin(math.radians(3 * m))
    true_l = l0 + c
    omega = 125.04452 - 1934.136261 * t
    nutation = -0.00478 * math.sin(math.radians(omega))
    apparent_l = true_l + nutation - 0.00569 - 0.00479 * math.sin(math.radians(omega))
    apparent_l = apparent_l % 360
    if apparent_l < 0:
        apparent_l += 360
    return apparent_l


def find_solar_term(target_lon, start_jd, tolerance=0.00001):
    jd = start_jd
    for _ in range(50):
        lon = sun_longitude(jd)
        diff = (target_lon - lon) % 360
        if diff > 180:
            diff -= 360
        if abs(diff) < tolerance:
            return jd
        step = diff / 0.9856
        step = max(-5, min(5, step))
        jd += step
    return jd


# ============ lunar-python 精确计算 ============

def get_jieqi_by_lunar_python(year, month, day, hour=0, minute=0, second=0):
    if not LUNAR_AVAILABLE:
        return None
    
    try:
        solar = Solar.fromYmd(year, month, day)
        lunar = solar.getLunar()
        
        jie_qi = lunar.getJieQi()
        if jie_qi:
            jie_qi = normalize_jieqi_name(jie_qi)
            jq_table = lunar.getJieQiTable()
            if jie_qi in jq_table:
                dt = jq_table[jie_qi]
                idx = get_jieqi_index(jie_qi)
                is_jie = JIE_QI_TYPE[idx]
                return JieQiResult(
                    dt.getYear(), dt.getMonth(), dt.getDay(),
                    dt.getHour(), dt.getMinute(), dt.getSecond(),
                    jie_qi, is_jie
                )
        
        jq_table = lunar.getJieQiTable()
        jq_list = lunar.getJieQiList()
        
        target_dt = datetime(year, month, day, hour, minute, second)
        closest = None
        min_diff = float('inf')
        
        for name in jq_list:
            cn_name = normalize_jieqi_name(name)
            if cn_name in jq_table:
                dt = jq_table[cn_name]
                jq_dt = datetime(dt.getYear(), dt.getMonth(), dt.getDay(),
                               dt.getHour(), dt.getMinute(), dt.getSecond())
                diff = abs((jq_dt - target_dt).total_seconds())
                if diff < min_diff and jq_dt <= target_dt:
                    min_diff = diff
                    closest = (dt, cn_name)
        
        if closest:
            dt, name = closest
            idx = get_jieqi_index(name)
            is_jie = JIE_QI_TYPE[idx]
            return JieQiResult(dt.getYear(), dt.getMonth(), dt.getDay(),
                             dt.getHour(), dt.getMinute(), dt.getSecond(),
                             name, is_jie)
        
        return None
        
    except Exception as e:
        print(f"lunar-python 计算错误: {e}")
        return None


def get_all_jieqi_year_lunar_python(year):
    if not LUNAR_AVAILABLE:
        return None
    
    results = []
    seen_names = set()  # 新增:用于去重
    
    try:
        solar = Solar.fromYmd(year, 1, 1)
        lunar = solar.getLunar()
        jq_table = lunar.getJieQiTable()
        jq_list = lunar.getJieQiList()
        
        for name in jq_list:
            cn_name = normalize_jieqi_name(name)
            
            # 跳过已处理的节气
            if cn_name in seen_names:
                continue
            seen_names.add(cn_name)
            
            if cn_name in jq_table:
                dt = jq_table[cn_name]
                idx = get_jieqi_index(cn_name)
                is_jie = JIE_QI_TYPE[idx]
                results.append(JieQiResult(
                    dt.getYear(), dt.getMonth(), dt.getDay(),
                    dt.getHour(), dt.getMinute(), dt.getSecond(),
                    cn_name, is_jie
                ))
        
        return sorted(results, key=lambda x: x.to_datetime())
        
    except Exception as e:
        print(f"lunar-python 获取全年节气错误: {e}")
        return None


# ============ 天文算法 ============

def get_jieqi_by_astronomy(year, month, day, hour=0, minute=0, second=0):
    jd = gregorian_to_jd(year, month, day, hour, minute, second)
    current_lon = sun_longitude(jd)
    
    term_lon = 270
    for i in range(24):
        next_term_lon = term_lon + 15
        if next_term_lon >= 360:
            next_term_lon -= 360
        
        if term_lon < next_term_lon:
            in_range = term_lon <= current_lon < next_term_lon
        else:
            in_range = current_lon >= term_lon or current_lon < next_term_lon
        
        if in_range:
            search_jd = jd - 30
            exact_jd = find_solar_term(term_lon, search_jd)
            y, m, d, h, mi, s = jd_to_gregorian(exact_jd)
            return JieQiResult(y, m, d, h, mi, s, JIEQI_NAMES[i], JIE_QI_TYPE[i])
        
        term_lon = next_term_lon
    
    return get_jieqi_by_astronomy_approx(year, month, day, hour, minute, second)


def get_jieqi_by_astronomy_approx(year, month, day, hour=0, minute=0, second=0):
    jd = gregorian_to_jd(year, month, day, hour, minute, second)
    dongzhi_jd = gregorian_to_jd(year, 12, 21, 0, 0, 0)
    exact_dongzhi = find_solar_term(270, dongzhi_jd - 5)
    
    results = []
    for i in range(24):
        target_lon = 270 + i * 15
        target_lon = target_lon % 360
        search_jd = exact_dongzhi + i * 15.2 - 2
        exact_jd = find_solar_term(target_lon, search_jd)
        y, m, d, h, mi, s = jd_to_gregorian(exact_jd)
        results.append(JieQiResult(y, m, d, h, mi, s, JIEQI_NAMES[i], JIE_QI_TYPE[i]))
    
    target_dt = datetime(year, month, day, hour, minute, second)
    closest = None
    min_diff = float('inf')
    
    for r in results:
        r_dt = r.to_datetime()
        if r_dt <= target_dt:
            diff = (target_dt - r_dt).total_seconds()
            if diff < min_diff:
                min_diff = diff
                closest = r
    
    return closest if closest else results[0]


def get_all_jieqi_year_astronomy(year):
    results = []
    search_jd = gregorian_to_jd(year, 1, 1) - 10
    dongzhi_jd = find_solar_term(270, search_jd)
    
    y, m, d, h, mi, s = jd_to_gregorian(dongzhi_jd)
    if y < year:
        start_jd = dongzhi_jd
        start_idx = 0
    else:
        start_jd = find_solar_term(255, search_jd - 15)
        start_idx = 23
    
    for i in range(24):
        idx = (start_idx + i) % 24
        target_lon = 270 + idx * 15
        target_lon = target_lon % 360
        
        if i > 0:
            search_start = current_jd + 13
        else:
            search_start = start_jd
        
        exact_jd = find_solar_term(target_lon, search_start)
        y, m, d, h, mi, s = jd_to_gregorian(exact_jd)
        
        results.append(JieQiResult(y, m, d, h, mi, s, JIEQI_NAMES[idx], JIE_QI_TYPE[idx]))
        current_jd = exact_jd
    
    year_results = [r for r in results if r.year == year]
    return year_results


# ============ 统一接口 ============

def get_current_jieqi(year, month, day, hour=0, minute=0, second=0):
    if year < 0 or year > 9999:
        raise ValueError("年份必须在 0-9999 范围内")
    
    if LUNAR_AVAILABLE and 1900 <= year <= 2100:
        result = get_jieqi_by_lunar_python(year, month, day, hour, minute, second)
        if result:
            dt_input = datetime(year, month, day, hour, minute, second)
            dt_jieqi = result.to_datetime()
            if dt_jieqi <= dt_input:
                return result
    
    return get_jieqi_by_astronomy(year, month, day, hour, minute, second)


def get_next_jieqi(year, month, day, hour=0, minute=0, second=0):
    current = get_current_jieqi(year, month, day, hour, minute, second)
    idx = JIEQI_NAMES.index(current.name)
    next_idx = (idx + 1) % 24
    
    jd = gregorian_to_jd(year, month, day, hour, minute, second)
    target_lon = 270 + next_idx * 15
    target_lon = target_lon % 360
    
    search_jd = jd + 5
    next_jd = find_solar_term(target_lon, search_jd)
    
    y, m, d, h, mi, s = jd_to_gregorian(next_jd)
    return JieQiResult(y, m, d, h, mi, s, JIEQI_NAMES[next_idx], JIE_QI_TYPE[next_idx])


def get_prev_jieqi(year, month, day, hour=0, minute=0, second=0):
    current = get_current_jieqi(year, month, day, hour, minute, second)
    idx = JIEQI_NAMES.index(current.name)
    prev_idx = (idx - 1) % 24
    
    jd = gregorian_to_jd(year, month, day, hour, minute, second)
    target_lon = 270 + prev_idx * 15
    target_lon = target_lon % 360
    
    search_jd = jd - 20
    prev_jd = find_solar_term(target_lon, search_jd)
    
    y, m, d, h, mi, s = jd_to_gregorian(prev_jd)
    return JieQiResult(y, m, d, h, mi, s, JIEQI_NAMES[prev_idx], JIE_QI_TYPE[prev_idx])


def get_all_jieqi_in_year(year):
    if year < 0 or year > 9999:
        raise ValueError("年份必须在 0-9999 范围内")
    
    if LUNAR_AVAILABLE and 1900 <= year <= 2100:
        results = get_all_jieqi_year_lunar_python(year)
        if results and len(results) >= 24:
            return results
    
    return get_all_jieqi_year_astronomy(year)


def get_jieqi_in_range(start_year, start_month, start_day, end_year, end_month, end_day):
    results = []
    current_year = start_year
    while current_year <= end_year:
        year_jieqi = get_all_jieqi_in_year(current_year)
        for jq in year_jieqi:
            dt = jq.to_datetime()
            start_dt = datetime(start_year, start_month, start_day)
            end_dt = datetime(end_year, end_month, end_day, 23, 59, 59)
            if start_dt <= dt <= end_dt:
                results.append(jq)
        current_year += 1
    return results


def is_jieqi_day(year, month, day):
    if LUNAR_AVAILABLE and 1900 <= year <= 2100:
        try:
            solar = Solar.fromYmd(year, month, day)
            lunar = solar.getLunar()
            jieqi = lunar.getJieQi()
            return jieqi is not None and normalize_jieqi_name(jieqi) is not None
        except:
            pass
    
    jieqi_list = get_all_jieqi_in_year(year)
    for jq in jieqi_list:
        if jq.year == year and jq.month == month and jq.day == day:
            return True
    return False


# ============ GUI 界面 ============

class JieQiApp:
    def __init__(self, root):
        self.root = root
        self.root.title("节气计算器 v4.0 - 基于 lunar-python")
        self.root.geometry("950x750")
        
        self.status_var = tk.StringVar()
        if LUNAR_AVAILABLE:
            self.status_var.set("lunar-python 已加载 | 1900-2100年精确 | 其他年份天文算法")
        else:
            self.status_var.set("lunar-python 未安装 | 使用纯天文算法 | pip install lunar-python")
        
        self.create_widgets()
    
    def create_widgets(self):
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)
        main_frame.columnconfigure(1, weight=1)
        main_frame.rowconfigure(2, weight=1)
        
        # === 输入区域 ===
        input_frame = ttk.LabelFrame(main_frame, text="输入时间", padding="10")
        input_frame.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=5)
        
        # 日期
        ttk.Label(input_frame, text="年:").grid(row=0, column=0, padx=5)
        self.year_var = tk.StringVar(value=str(datetime.now().year))
        CopyableSpinbox(input_frame, from_=0, to=9999, textvariable=self.year_var, width=6).grid(row=0, column=1, padx=5)
        
        ttk.Label(input_frame, text="月:").grid(row=0, column=2, padx=5)
        self.month_var = tk.StringVar(value=str(datetime.now().month))
        CopyableSpinbox(input_frame, from_=1, to=12, textvariable=self.month_var, width=4).grid(row=0, column=3, padx=5)
        
        ttk.Label(input_frame, text="日:").grid(row=0, column=4, padx=5)
        self.day_var = tk.StringVar(value=str(datetime.now().day))
        CopyableSpinbox(input_frame, from_=1, to=31, textvariable=self.day_var, width=4).grid(row=0, column=5, padx=5)
        
        # 时间
        ttk.Label(input_frame, text="时:").grid(row=0, column=6, padx=5)
        self.hour_var = tk.StringVar(value="12")
        CopyableSpinbox(input_frame, from_=0, to=23, textvariable=self.hour_var, width=4, format="%02.0f").grid(row=0, column=7, padx=5)
        
        ttk.Label(input_frame, text="分:").grid(row=0, column=8, padx=5)
        self.minute_var = tk.StringVar(value="00")
        CopyableSpinbox(input_frame, from_=0, to=59, textvariable=self.minute_var, width=4, format="%02.0f").grid(row=0, column=9, padx=5)
        
        ttk.Label(input_frame, text="秒:").grid(row=0, column=10, padx=5)
        self.second_var = tk.StringVar(value="00")
        CopyableSpinbox(input_frame, from_=0, to=59, textvariable=self.second_var, width=4, format="%02.0f").grid(row=0, column=11, padx=5)
        
        # === 按钮区域 ===
        btn_frame = ttk.Frame(main_frame)
        btn_frame.grid(row=1, column=0, columnspan=2, pady=10)
        
        ttk.Button(btn_frame, text="查询当前节气", command=self.query_current).grid(row=0, column=0, padx=5)
        ttk.Button(btn_frame, text="查询全年节气", command=self.query_year).grid(row=0, column=1, padx=5)
        ttk.Button(btn_frame, text="查询范围节气", command=self.query_range).grid(row=0, column=2, padx=5)
        ttk.Button(btn_frame, text="使用当前时间", command=self.use_now).grid(row=0, column=3, padx=5)
        ttk.Button(btn_frame, text="清空结果", command=self.clear).grid(row=0, column=4, padx=5)
        
        # === 结果显示区域 ===
        result_frame = ttk.LabelFrame(main_frame, text="计算结果", padding="10")
        result_frame.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
        result_frame.columnconfigure(0, weight=1)
        result_frame.rowconfigure(0, weight=1)
        
        self.result_text = CopyableScrolledText(result_frame, wrap=tk.WORD, width=85, height=35, font=('微软雅黑', 10))
        self.result_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # === 状态栏 ===
        status_bar = ttk.Label(main_frame, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
        status_bar.grid(row=3, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=5)
    
    def get_input_datetime(self):
        try:
            year = int(self.year_var.get())
            month = int(self.month_var.get())
            day = int(self.day_var.get())
            hour = int(self.hour_var.get())
            minute = int(self.minute_var.get())
            second = int(self.second_var.get())
            
            if not (0 <= year <= 9999 and 1 <= month <= 12 and 1 <= day <= 31):
                raise ValueError("日期范围错误")
            if not (0 <= hour <= 23 and 0 <= minute <= 59 and 0 <= second <= 59):
                raise ValueError("时间范围错误")
            
            return year, month, day, hour, minute, second
            
        except ValueError as e:
            messagebox.showerror("输入错误", f"请输入有效的日期时间: {e}")
            return None
    
    def query_current(self):
        dt = self.get_input_datetime()
        if not dt:
            return
        
        year, month, day, hour, minute, second = dt
        
        try:
            self.result_text.delete(1.0, tk.END)
            
            self.result_text.insert(tk.END, f"{'='*60}\n")
            self.result_text.insert(tk.END, f"查询时间: {year}{month:02d}{day:02d}{hour:02d}:{minute:02d}:{second:02d}\n")
            self.result_text.insert(tk.END, f"{'='*60}\n\n")
            
            # 当前节气
            current = get_current_jieqi(year, month, day, hour, minute, second)
            self.result_text.insert(tk.END, f"【当前所在节气】\n")
            self.result_text.insert(tk.END, f"  {current}\n")
            self.result_text.insert(tk.END, f"  类型: {'节' if current.is_jie else '气'}\n\n")
            
            # 上一个
            prev = get_prev_jieqi(year, month, day, hour, minute, second)
            self.result_text.insert(tk.END, f"【上一个节气】\n  {prev}\n\n")
            
            # 下一个
            next_jq = get_next_jieqi(year, month, day, hour, minute, second)
            self.result_text.insert(tk.END, f"【下一个节气】\n  {next_jq}\n\n")
            
            # 当天是否有节气
            if is_jieqi_day(year, month, day):
                self.result_text.insert(tk.END, f"★ 当天就是节气日!\n\n")
            
            # 对比
            if LUNAR_AVAILABLE and 1900 <= year <= 2100:
                lunar_result = get_jieqi_by_lunar_python(year, month, day, hour, minute, second)
                if lunar_result:
                    self.result_text.insert(tk.END, f"【lunar-python 精确值】\n  {lunar_result}\n")
                    diff = abs((current.to_datetime() - lunar_result.to_datetime()).total_seconds() / 60)
                    self.result_text.insert(tk.END, f"  天文算法误差: {diff:.1f}分钟\n")
            else:
                self.result_text.insert(tk.END, f"【计算方式】\n  使用天文算法(年份超出精确库范围)\n")
            
            self.result_text.insert(tk.END, f"\n{'='*60}\n")
            
        except Exception as e:
            messagebox.showerror("计算错误", str(e))
    
    def query_year(self):
        try:
            year = int(self.year_var.get())
            if not (0 <= year <= 9999):
                raise ValueError("年份必须在 0-9999 范围内")
        except ValueError as e:
            messagebox.showerror("输入错误", str(e))
            return
        
        try:
            self.result_text.delete(1.0, tk.END)
            
            self.result_text.insert(tk.END, f"{'='*60}\n")
            self.result_text.insert(tk.END, f"{year}年 二十四节气表\n")
            self.result_text.insert(tk.END, f"{'='*60}\n\n")
            
            results = get_all_jieqi_in_year(year)
            
            for i, r in enumerate(results, 1):
                jie_qi = "节" if r.is_jie else "气"
                self.result_text.insert(tk.END, f"{i:2d}. {r.name:4s} ({jie_qi})  {r}\n")
            
            self.result_text.insert(tk.END, f"\n{'='*60}\n")
            self.result_text.insert(tk.END, f"共 {len(results)} 个节气\n")
            
            if LUNAR_AVAILABLE and 1900 <= year <= 2100:
                self.result_text.insert(tk.END, f"数据来源: lunar-python 精确数据\n")
            else:
                self.result_text.insert(tk.END, f"数据来源: 天文算法计算\n")
            
            self.result_text.insert(tk.END, f"{'='*60}\n")
            
        except Exception as e:
            messagebox.showerror("计算错误", str(e))
    
    def query_range(self):
        """查询日期范围内的节气"""
        # 弹出对话框输入范围
        dialog = tk.Toplevel(self.root)
        dialog.title("查询日期范围")
        dialog.geometry("300x200")
        dialog.transient(self.root)
        dialog.grab_set()
        
        ttk.Label(dialog, text="开始日期 (年-月-日):").grid(row=0, column=0, padx=5, pady=5)
        start_entry = CopyableEntry(dialog, width=15)
        start_entry.grid(row=0, column=1, padx=5, pady=5)
        start_entry.insert(0, f"{self.year_var.get()}-{self.month_var.get()}-{self.day_var.get()}")
        
        ttk.Label(dialog, text="结束日期 (年-月-日):").grid(row=1, column=0, padx=5, pady=5)
        end_entry = CopyableEntry(dialog, width=15)
        end_entry.grid(row=1, column=1, padx=5, pady=5)
        end_entry.insert(0, f"{self.year_var.get()}-12-31")
        
        def do_query():
            try:
                s = start_entry.get().split('-')
                e = end_entry.get().split('-')
                sy, sm, sd = int(s[0]), int(s[1]), int(s[2])
                ey, em, ed = int(e[0]), int(e[1]), int(e[2])
                
                dialog.destroy()
                
                self.result_text.delete(1.0, tk.END)
                self.result_text.insert(tk.END, f"{'='*60}\n")
                self.result_text.insert(tk.END, f"日期范围: {sy}-{sm:02d}-{sd:02d}{ey}-{em:02d}-{ed:02d}\n")
                self.result_text.insert(tk.END, f"{'='*60}\n\n")
                
                results = get_jieqi_in_range(sy, sm, sd, ey, em, ed)
                
                for r in results:
                    jie_qi = "节" if r.is_jie else "气"
                    self.result_text.insert(tk.END, f"{r.name:4s} ({jie_qi})  {r}\n")
                
                self.result_text.insert(tk.END, f"\n{'='*60}\n")
                self.result_text.insert(tk.END, f"共 {len(results)} 个节气\n")
                self.result_text.insert(tk.END, f"{'='*60}\n")
                
            except Exception as e:
                messagebox.showerror("错误", str(e), parent=dialog)
        
        ttk.Button(dialog, text="查询", command=do_query).grid(row=2, column=0, columnspan=2, pady=20)
    
    def use_now(self):
        now = datetime.now()
        self.year_var.set(str(now.year))
        self.month_var.set(str(now.month))
        self.day_var.set(str(now.day))
        self.hour_var.set(f"{now.hour:02d}")
        self.minute_var.set(f"{now.minute:02d}")
        self.second_var.set(f"{now.second:02d}")
    
    def clear(self):
        self.result_text.delete(1.0, tk.END)


def main():
    root = tk.Tk()
    app = JieQiApp(root)
    root.mainloop()


if __name__ == "__main__":
    main()

更多推荐