Initial commit with all project files
This commit is contained in:
parent
fccc8acb63
commit
44a7ef6179
3
.idea/.gitignore
generated
vendored
Normal file
3
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
8
.idea/Treker.iml
generated
Normal file
8
.idea/Treker.iml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.13" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.13" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/Treker.iml" filepath="$PROJECT_DIR$/.idea/Treker.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
103
core.py
Normal file
103
core.py
Normal file
@ -0,0 +1,103 @@
|
||||
from datetime import date, datetime
|
||||
|
||||
|
||||
class HarmCalculator:
|
||||
|
||||
harms = {
|
||||
"Никотин": {"сигарета": 11, "стик": 5, "тяжка": 3},
|
||||
"Алкоголь": {"бутылка (500мл)": 15, "банка (500мл)": 12, "бокал (150мл)": 8, "рюмка (50мл)": 28, "глоток (30мл)": 4, "порция (250мл)": 18},
|
||||
"Кофе": {"чашка (200мл)": 1, "стакан (350мл)": 1.5, "глоток (30мл)": 0.2, "порция (200мл)": 1},
|
||||
"Энергетики": {"банка (250мл)": 2, "банка (330мл)": 3, "банка (500мл)": 5},
|
||||
}
|
||||
|
||||
icons = {"Никотин": "🚬", "Алкоголь": "🍷", "Кофе": "☕", "Энергетики": "⚡"}
|
||||
colors = {"Никотин": "#8fb0d9", "Алкоголь": "#f0a2bc", "Кофе": "#c8a07a", "Энергетики": "#7ea8ff"}
|
||||
gender_mult = {"Мужской": 1.0, "Женский": 1.2, "М": 1.0, "Ж": 1.2}
|
||||
|
||||
def __init__(self, gender="Мужской", age=20):
|
||||
self.gender = gender
|
||||
self.age = age
|
||||
|
||||
def set_profile(self, gender, age):
|
||||
self.gender = gender
|
||||
self.age = age
|
||||
|
||||
def age_mult(self, age):
|
||||
if age < 18: return 0.9
|
||||
if age < 25: return 0.95
|
||||
if age < 40: return 1.0
|
||||
if age < 60: return 1.2
|
||||
return 1.5
|
||||
|
||||
def calc_one(self, kind, unit, qty):
|
||||
base = self.harms[kind][unit]
|
||||
g = self.gender_mult.get(self.gender, 1.0)
|
||||
a = self.age_mult(self.age)
|
||||
return base * qty * g * a
|
||||
|
||||
def calc_total(self, hist):
|
||||
total = 0
|
||||
for e in hist:
|
||||
total += self.calc_one(e["kind"], e["unit"], e["qty"])
|
||||
return total
|
||||
|
||||
def fmt_time(self, mins):
|
||||
if mins < 60: return f"{int(mins)} мин"
|
||||
if mins < 1440: return f"{mins/60:.1f} ч"
|
||||
if mins < 43200: return f"{mins/1440:.2f} дн"
|
||||
return f"{mins/43200:.2f} мес"
|
||||
|
||||
def fmt_timer(self, mins):
|
||||
h = int(mins // 60)
|
||||
m = int(mins % 60)
|
||||
s = int((mins * 60) % 60)
|
||||
return f"{h:02d}:{m:02d}:{s:02d}"
|
||||
|
||||
def get_units(self, kind):
|
||||
return list(self.harms.get(kind, {}).keys())
|
||||
|
||||
def get_config(self, kind):
|
||||
return {
|
||||
"icon": self.icons.get(kind, "📌"),
|
||||
"color": self.colors.get(kind, "#cccccc"),
|
||||
"units": self.get_units(kind),
|
||||
}
|
||||
|
||||
def period_harm(self, hist, period="day"):
|
||||
today = date.today()
|
||||
filt = []
|
||||
for e in hist:
|
||||
d = datetime.fromisoformat(e["date"]).date()
|
||||
if period == "day" and d == today:
|
||||
filt.append(e)
|
||||
elif period == "month" and d.year == today.year and d.month == today.month:
|
||||
filt.append(e)
|
||||
elif period == "year" and d.year == today.year:
|
||||
filt.append(e)
|
||||
elif period == "all":
|
||||
filt.append(e)
|
||||
return self.calc_total(filt), len(filt)
|
||||
|
||||
def days_with_harms(self, hist, year, month):
|
||||
days = {}
|
||||
for e in hist:
|
||||
d = datetime.fromisoformat(e["date"]).date()
|
||||
if d.year == year and d.month == month:
|
||||
h = self.calc_one(e["kind"], e["unit"], e["qty"])
|
||||
days[d.day] = days.get(d.day, 0) + h
|
||||
return days
|
||||
|
||||
def summary(self, hist):
|
||||
day = self.period_harm(hist, "day")
|
||||
month = self.period_harm(hist, "month")
|
||||
year = self.period_harm(hist, "year")
|
||||
allt = self.period_harm(hist, "all")
|
||||
return {
|
||||
"day": {"mins": day[0], "fmt": self.fmt_time(day[0]), "cnt": day[1]},
|
||||
"month": {"mins": month[0], "fmt": self.fmt_time(month[0]), "cnt": month[1]},
|
||||
"year": {"mins": year[0], "fmt": self.fmt_time(year[0]), "cnt": year[1]},
|
||||
"all": {"mins": allt[0], "fmt": self.fmt_time(allt[0]), "cnt": allt[1]},
|
||||
}
|
||||
|
||||
def to_days(self, mins):
|
||||
return mins / 1440
|
||||
449
main.py
Normal file
449
main.py
Normal file
@ -0,0 +1,449 @@
|
||||
import calendar
|
||||
from datetime import date
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
|
||||
from core import HarmCalculator
|
||||
|
||||
bg = "#eef6ff"
|
||||
surf = "#ffffff"
|
||||
surf_alt = "#f6fbff"
|
||||
head = "#dcebff"
|
||||
acc = "#4f8df7"
|
||||
acc_dark = "#3e73cc"
|
||||
acc_soft = "#d6e9ff"
|
||||
txt = "#15314a"
|
||||
muted = "#6b86a1"
|
||||
bord = "#c8dcf4"
|
||||
harm_col = "#ff6b6b"
|
||||
|
||||
def_habits = [
|
||||
{"kind": "Алкоголь", "title": "Вечер", "qty": 2, "unit": "бутылка (500мл)", "icon": "🍷", "accent": "#f0a2bc"},
|
||||
{"kind": "Никотин", "title": "Перерыв", "qty": 4, "unit": "сигарета", "icon": "🚬", "accent": "#8fb0d9"},
|
||||
{"kind": "Кофе", "title": "Утренний кофе", "qty": 1, "unit": "чашка (200мл)", "icon": "☕", "accent": "#c8a07a"},
|
||||
]
|
||||
|
||||
|
||||
class MainWindow(tk.Tk):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.title("Трекер вредных привычек")
|
||||
self.geometry("430x780")
|
||||
self.minsize(370, 700)
|
||||
self.configure(bg=bg)
|
||||
|
||||
self.calc = HarmCalculator(gender="Мужской", age=30)
|
||||
self.p_name = "Пользователь"
|
||||
self.p_gender = "Мужской"
|
||||
self.p_age = 30
|
||||
|
||||
self.sett_win = None
|
||||
self.cal_win = None
|
||||
self.hab_win = None
|
||||
self.edit_idx = None
|
||||
self.cur_date = date.today().replace(day=1)
|
||||
self.habits = list(def_habits)
|
||||
|
||||
self._styles()
|
||||
self._ui()
|
||||
self._upd_timer()
|
||||
|
||||
def _styles(self):
|
||||
self.style = ttk.Style(self)
|
||||
self.style.theme_use("clam")
|
||||
self.style.configure("Light.Vertical.TScrollbar", troughcolor=bg, background="#9ec5fb", bordercolor=bg, arrowcolor=acc, relief="flat", width=7)
|
||||
self.style.configure("Light.TCombobox", fieldbackground=surf, foreground=txt, arrowcolor=acc, bordercolor=bord, padding=6)
|
||||
|
||||
def _ui(self):
|
||||
root = tk.Frame(self, bg=bg)
|
||||
root.pack(fill="both", expand=True)
|
||||
self._top_bar(root)
|
||||
self._center(root)
|
||||
self._list(root)
|
||||
|
||||
def _circle(self, parent, text, cmd, size=36, fill=acc, outline=acc):
|
||||
c = tk.Canvas(parent, width=size, height=size, bg=parent.cget("bg"), highlightthickness=0, cursor="hand2")
|
||||
def draw(_=None):
|
||||
c.delete("all")
|
||||
c.create_oval(1, 1, size-1, size-1, fill=fill, outline=outline)
|
||||
c.create_text(size/2, size/2, text=text, fill="white", font=("Helvetica", 14, "bold"))
|
||||
c.bind("<Configure>", draw)
|
||||
draw()
|
||||
c.bind("<Button-1>", lambda e: cmd())
|
||||
return c
|
||||
|
||||
def _top_bar(self, parent):
|
||||
bar = tk.Frame(parent, bg=head, padx=18, pady=16)
|
||||
bar.pack(fill="x")
|
||||
self.p_label = tk.Label(bar, text=self.p_name, bg=head, fg=txt, font=("Helvetica", 16, "bold"))
|
||||
self.p_label.pack(side="left")
|
||||
btns = tk.Frame(bar, bg=head)
|
||||
btns.pack(side="right")
|
||||
self._circle(btns, "🗓", self._cal, 36).pack(side="left", padx=(0, 10))
|
||||
self._circle(btns, "⚙", self._sett, 36).pack(side="left")
|
||||
|
||||
def _center(self, parent):
|
||||
c = tk.Frame(parent, bg=bg, pady=14)
|
||||
c.pack(fill="x")
|
||||
w = tk.Frame(c, bg=bg)
|
||||
w.pack(pady=8)
|
||||
self.circ = tk.Canvas(w, width=200, height=200, bg=bg, highlightthickness=0)
|
||||
self.circ.pack()
|
||||
self._draw_circ(self.circ, 100, 100, 72)
|
||||
self.circ.bind("<Button-1>", lambda e: self._open_form())
|
||||
self.timer = tk.Label(c, text="00:00:00", bg=bg, fg=txt, font=("Helvetica", 28, "bold"))
|
||||
self.timer.pack(pady=2)
|
||||
tk.Label(c, text="потеряно за сегодня", bg=bg, fg=muted, font=("Helvetica", 10)).pack(pady=6)
|
||||
|
||||
def _list(self, parent):
|
||||
s = tk.Frame(parent, bg=bg, padx=18, pady=10)
|
||||
s.pack(fill="both", expand=True)
|
||||
tk.Label(s, text="Сегодня Вы употребляли", bg=bg, fg=txt, font=("Helvetica", 14, "bold"), anchor="w").pack(fill="x", pady=(0, 12))
|
||||
w = tk.Frame(s, bg=bg)
|
||||
w.pack(fill="both", expand=True)
|
||||
self.canv = tk.Canvas(w, bg=bg, highlightthickness=0)
|
||||
scr = ttk.Scrollbar(w, orient="vertical", command=self.canv.yview, style="Light.Vertical.TScrollbar")
|
||||
self.canv.configure(yscrollcommand=scr.set)
|
||||
scr.pack(side="right", fill="y")
|
||||
self.canv.pack(side="left", fill="both", expand=True)
|
||||
self.inner = tk.Frame(self.canv, bg=bg)
|
||||
self.canv.create_window((0, 0), window=self.inner, anchor="nw")
|
||||
self.inner.bind("<Configure>", lambda e: self.canv.configure(scrollregion=self.canv.bbox("all")))
|
||||
self.canv.bind("<Configure>", lambda e: self.canv.itemconfig(self.canv.find_withtag("win")[0], width=e.width) if self.canv.find_withtag("win") else None)
|
||||
self.canv.create_window((0, 0), window=self.inner, anchor="nw", tags="win")
|
||||
self._render()
|
||||
|
||||
def _render(self):
|
||||
for w in self.inner.winfo_children():
|
||||
w.destroy()
|
||||
for i, h in enumerate(self.habits):
|
||||
self._card(self.inner, h, i)
|
||||
|
||||
def _card(self, parent, habit, idx):
|
||||
card = tk.Canvas(parent, height=78, bg=bg, highlightthickness=0)
|
||||
card.pack(fill="x", pady=7)
|
||||
card.bind("<Configure>", lambda e, c=card: self._draw_card(c, e.width, habit))
|
||||
self._draw_card(card, 380, habit)
|
||||
card.bind("<Button-1>", lambda e, i=idx: self._open_form(i))
|
||||
|
||||
def _draw_card(self, c, w, h):
|
||||
c.delete("all")
|
||||
self._round(c, 0, 0, w, 78, 20, fill=surf, outline=bord)
|
||||
c.create_oval(18, 15, 66, 63, fill=h["accent"], outline="")
|
||||
c.create_text(42, 39, text=h["icon"], fill="white", font=("Helvetica", 17, "bold"))
|
||||
c.create_text(78, 28, anchor="w", text=h["title"], fill=txt, font=("Helvetica", 12, "bold"))
|
||||
c.create_text(78, 50, anchor="w", text=h["kind"], fill=muted, font=("Helvetica", 10))
|
||||
c.create_text(w-18, 39, anchor="e", text=f'{h["qty"]} {h["unit"]}', fill=acc_dark, font=("Helvetica", 12, "bold"))
|
||||
|
||||
def _round(self, c, x1, y1, x2, y2, r, **kw):
|
||||
pts = [x1+r, y1, x2-r, y1, x2, y1, x2, y1+r, x2, y2-r, x2, y2, x2-r, y2, x1+r, y2, x1, y2, x1, y2-r, x1, y1+r, x1, y1]
|
||||
c.create_polygon(pts, smooth=True, splinesteps=24, **kw)
|
||||
|
||||
def _draw_circ(self, c, cx, cy, r):
|
||||
c.create_oval(cx-r-9, cy-r-9, cx+r+9, cy+r+9, fill="#dcecff", outline="")
|
||||
c.create_oval(cx-r, cy-r, cx+r, cy+r, fill="white", outline=bord, width=2)
|
||||
c.create_oval(cx-r+12, cy-r+12, cx-r+34, cy-r+34, fill=acc_soft, outline="")
|
||||
c.create_text(cx, cy, text="+", fill=acc, font=("Helvetica", 26, "bold"))
|
||||
|
||||
def _upd_timer(self):
|
||||
total = sum(self.calc.calc_one(h["kind"], h["unit"], h["qty"]) for h in self.habits)
|
||||
self.timer.config(text=self.calc.fmt_timer(total))
|
||||
|
||||
def _day_stat(self):
|
||||
total = sum(self.calc.calc_one(h["kind"], h["unit"], h["qty"]) for h in self.habits)
|
||||
return total, len(self.habits)
|
||||
|
||||
def _month_stat(self):
|
||||
d, c = self._day_stat()
|
||||
return d * 30, c * 30
|
||||
|
||||
def _year_stat(self):
|
||||
d, c = self._day_stat()
|
||||
return d * 365, c * 365
|
||||
|
||||
def _open_form(self, idx=None):
|
||||
if self.hab_win and self.hab_win.winfo_exists():
|
||||
self.hab_win.destroy()
|
||||
self.edit_idx = idx
|
||||
data = self.habits[idx] if idx is not None else None
|
||||
|
||||
win = tk.Toplevel(self)
|
||||
win.title("Привычка" if idx is None else "Редактировать")
|
||||
win.configure(bg=bg)
|
||||
win.geometry("450x520")
|
||||
win.resizable(False, False)
|
||||
win.transient(self)
|
||||
win.grab_set()
|
||||
self.hab_win = win
|
||||
|
||||
c = tk.Frame(win, bg=bg, padx=18, pady=18)
|
||||
c.pack(fill="both", expand=True)
|
||||
|
||||
tk.Label(c, text="Привычка" if idx is None else "Редактировать", bg=bg, fg=txt, font=("Helvetica", 16, "bold")).pack(anchor="w", pady=(0, 16))
|
||||
|
||||
tk.Label(c, text="Тип", bg=bg, fg=muted, font=("Helvetica", 10, "bold")).pack(anchor="w")
|
||||
self.kind_var = tk.StringVar(value=data["kind"] if data else "Никотин")
|
||||
kind_cb = ttk.Combobox(c, textvariable=self.kind_var, values=list(self.calc.harms.keys()), state="readonly", style="Light.TCombobox")
|
||||
kind_cb.pack(fill="x", ipady=4, pady=(0, 12))
|
||||
kind_cb.bind("<<ComboboxSelected>>", self._kind_chg)
|
||||
|
||||
tk.Label(c, text="Единица", bg=bg, fg=muted, font=("Helvetica", 10, "bold")).pack(anchor="w")
|
||||
self.unit_var = tk.StringVar()
|
||||
self.unit_cb = ttk.Combobox(c, textvariable=self.unit_var, state="readonly", style="Light.TCombobox")
|
||||
self.unit_cb.pack(fill="x", ipady=4, pady=(0, 12))
|
||||
|
||||
self.title_en = self._entry(c, "Название", data["title"] if data else "")
|
||||
self.qty_en = self._entry(c, "Количество", str(data["qty"]) if data else "1")
|
||||
self.qty_en.bind("<KeyRelease>", lambda e: self._prev())
|
||||
|
||||
self.prev = tk.Label(c, text="", bg=bg, fg=harm_col, font=("Helvetica", 10, "bold"))
|
||||
self.prev.pack(anchor="w", pady=6)
|
||||
|
||||
self.err = tk.Label(c, text="", bg=bg, fg="#ef6f8a", font=("Helvetica", 10))
|
||||
self.err.pack(anchor="w", pady=(10, 10))
|
||||
|
||||
self._upd_units()
|
||||
if data:
|
||||
self.unit_var.set(data["unit"])
|
||||
self._prev()
|
||||
|
||||
if idx is None:
|
||||
tk.Button(c, text="Добавить", command=self._save, bg=acc, fg="white", font=("Helvetica", 12, "bold"), relief="flat", padx=20, pady=10).pack(fill="x")
|
||||
else:
|
||||
row = tk.Frame(c, bg=bg)
|
||||
row.pack(fill="x")
|
||||
self._circle(row, "✓", self._save, 42, acc).pack(side="left")
|
||||
self._circle(row, "✕", self._del, 42, "#ef6f8a").pack(side="right")
|
||||
|
||||
def _entry(self, parent, label, init):
|
||||
f = tk.Frame(parent, bg=bg)
|
||||
f.pack(fill="x", pady=(0, 12))
|
||||
tk.Label(f, text=label, bg=bg, fg=muted, font=("Helvetica", 10, "bold")).pack(anchor="w")
|
||||
e = tk.Entry(f, bg=surf, fg=txt, relief="flat", font=("Helvetica", 11), highlightthickness=1, highlightbackground=bord, highlightcolor=acc)
|
||||
e.pack(fill="x", ipady=8)
|
||||
e.insert(0, init)
|
||||
return e
|
||||
|
||||
def _upd_units(self):
|
||||
units = self.calc.get_units(self.kind_var.get())
|
||||
self.unit_cb['values'] = units
|
||||
if units:
|
||||
self.unit_var.set(units[0])
|
||||
|
||||
def _kind_chg(self, e=None):
|
||||
self._upd_units()
|
||||
self._prev()
|
||||
|
||||
def _prev(self):
|
||||
try:
|
||||
q = float(self.qty_en.get().strip() if self.qty_en else "1")
|
||||
k = self.kind_var.get()
|
||||
u = self.unit_var.get()
|
||||
if k and u:
|
||||
h = self.calc.calc_one(k, u, q)
|
||||
self.prev.config(text=f"⚠️ Потеря: {self.calc.fmt_time(h)} жизни")
|
||||
except:
|
||||
self.prev.config(text="⚠️ Введите число")
|
||||
|
||||
def _save(self):
|
||||
k = self.kind_var.get()
|
||||
u = self.unit_var.get()
|
||||
t = self.title_en.get().strip()
|
||||
try:
|
||||
q = float(self.qty_en.get().strip())
|
||||
except:
|
||||
self.err.config(text="Количество — число.")
|
||||
return
|
||||
if not t:
|
||||
t = k
|
||||
cfg = self.calc.get_config(k)
|
||||
self.err.config(text="")
|
||||
pay = {"kind": k, "title": t, "qty": q, "unit": u, "icon": cfg["icon"], "accent": cfg["color"]}
|
||||
if self.edit_idx is None:
|
||||
self.habits.append(pay)
|
||||
else:
|
||||
self.habits[self.edit_idx] = pay
|
||||
self._render()
|
||||
self._upd_timer()
|
||||
self._close()
|
||||
h = self.calc.calc_one(k, u, q)
|
||||
self._notify(k, q, u, h)
|
||||
|
||||
def _notify(self, k, q, u, h):
|
||||
pop = tk.Toplevel(self)
|
||||
pop.title("⚠️ Вред")
|
||||
pop.configure(bg="#fff3e0")
|
||||
pop.geometry("350x160")
|
||||
pop.resizable(False, False)
|
||||
pop.transient(self)
|
||||
pop.update_idletasks()
|
||||
x = self.winfo_x() + (self.winfo_width() - 350) // 2
|
||||
y = self.winfo_y() + (self.winfo_height() - 160) // 2
|
||||
pop.geometry(f"+{x}+{y}")
|
||||
ic = self.calc.icons.get(k, "⚠️")
|
||||
tk.Label(pop, text=f"{ic} {k}", font=("Helvetica", 14, "bold"), bg="#fff3e0", fg="#e65100").pack(pady=(20, 5))
|
||||
tk.Label(pop, text=f"{q} {u}", font=("Helvetica", 12), bg="#fff3e0", fg=txt).pack()
|
||||
tk.Label(pop, text=f"Потеряно: {self.calc.fmt_time(h)} жизни", font=("Helvetica", 12, "bold"), bg="#fff3e0", fg=harm_col).pack(pady=(10, 0))
|
||||
pop.after(3000, pop.destroy)
|
||||
|
||||
def _del(self):
|
||||
if self.edit_idx is not None and 0 <= self.edit_idx < len(self.habits):
|
||||
del self.habits[self.edit_idx]
|
||||
self._render()
|
||||
self._upd_timer()
|
||||
self._close()
|
||||
|
||||
def _close(self):
|
||||
if self.hab_win:
|
||||
self.hab_win.destroy()
|
||||
self.hab_win = None
|
||||
self.edit_idx = None
|
||||
|
||||
def _sett(self):
|
||||
if self.sett_win and self.sett_win.winfo_exists():
|
||||
self.sett_win.focus_set()
|
||||
return
|
||||
win = tk.Toplevel(self)
|
||||
win.title("Настройки")
|
||||
win.configure(bg=bg)
|
||||
win.geometry("400x380")
|
||||
win.resizable(False, False)
|
||||
win.transient(self)
|
||||
win.grab_set()
|
||||
self.sett_win = win
|
||||
c = tk.Frame(win, bg=bg, padx=18, pady=18)
|
||||
c.pack(fill="both", expand=True)
|
||||
tk.Label(c, text="Профиль", bg=bg, fg=txt, font=("Helvetica", 16, "bold")).pack(anchor="w", pady=(0, 16))
|
||||
self.name_en = self._entry(c, "Имя", "" if self.p_name == "Пользователь" else self.p_name)
|
||||
tk.Label(c, text="Пол", bg=bg, fg=muted, font=("Helvetica", 10, "bold")).pack(anchor="w")
|
||||
self.gen_var = tk.StringVar(value=self.p_gender)
|
||||
ttk.Combobox(c, textvariable=self.gen_var, values=["Мужской", "Женский"], state="readonly", style="Light.TCombobox").pack(fill="x", ipady=4, pady=(0, 12))
|
||||
self.age_en = self._entry(c, "Возраст", str(self.p_age))
|
||||
tk.Button(c, text="Сохранить", command=self._save_prof, bg=acc, fg="white", font=("Helvetica", 12, "bold"), relief="flat", padx=20, pady=10).pack(fill="x", pady=(8, 0))
|
||||
|
||||
def _save_prof(self):
|
||||
n = self.name_en.get().strip()
|
||||
self.p_name = n or "Пользователь"
|
||||
self.p_gender = self.gen_var.get()
|
||||
try:
|
||||
self.p_age = int(self.age_en.get().strip())
|
||||
except:
|
||||
self.p_age = 20
|
||||
self.calc.set_profile(self.p_gender, self.p_age)
|
||||
self.p_label.config(text=self.p_name)
|
||||
self._upd_timer()
|
||||
if self.sett_win:
|
||||
self.sett_win.destroy()
|
||||
self.sett_win = None
|
||||
|
||||
def _cal_title(self):
|
||||
m = ["Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь"]
|
||||
return f"{m[self.cur_date.month - 1]} {self.cur_date.year}"
|
||||
|
||||
def _cal(self):
|
||||
if self.cal_win and self.cal_win.winfo_exists():
|
||||
self.cal_win.focus_set()
|
||||
return
|
||||
win = tk.Toplevel(self)
|
||||
win.title("Календарь")
|
||||
win.configure(bg=bg)
|
||||
win.geometry("460x600")
|
||||
win.resizable(False, False)
|
||||
win.transient(self)
|
||||
win.grab_set()
|
||||
self.cal_win = win
|
||||
|
||||
h = tk.Frame(win, bg=head, padx=18, pady=18)
|
||||
h.pack(fill="x")
|
||||
nav = tk.Frame(h, bg=head)
|
||||
nav.pack(fill="x")
|
||||
self._circle(nav, "←", self._prev_m, 30).pack(side="left")
|
||||
self.cal_lbl = tk.Label(nav, text=self._cal_title(), bg=head, fg=txt, font=("Helvetica", 14, "bold"))
|
||||
self.cal_lbl.pack(side="left", expand=True)
|
||||
self._circle(nav, "→", self._next_m, 30).pack(side="right")
|
||||
|
||||
body = tk.Frame(win, bg=bg, padx=18, pady=4)
|
||||
body.pack(fill="both", expand=True)
|
||||
self.grid = tk.Frame(body, bg=surf)
|
||||
self.grid.pack(fill="x", pady=(0, 10))
|
||||
|
||||
sf = tk.Frame(body, bg=surf)
|
||||
sf.pack(fill="x", pady=5)
|
||||
si = tk.Frame(sf, bg=surf, padx=12, pady=12)
|
||||
si.pack(fill="x")
|
||||
tk.Label(si, text="📊 Статистика", bg=surf, fg=txt, font=("Helvetica", 12, "bold"), anchor="w").pack(fill="x", pady=(0, 8))
|
||||
self.day_lbl = tk.Label(si, text="", bg=surf, fg=muted, font=("Helvetica", 10), anchor="w")
|
||||
self.day_lbl.pack(fill="x")
|
||||
self.mon_lbl = tk.Label(si, text="", bg=surf, fg=muted, font=("Helvetica", 10), anchor="w")
|
||||
self.mon_lbl.pack(fill="x")
|
||||
self.yr_lbl = tk.Label(si, text="", bg=surf, fg=muted, font=("Helvetica", 10), anchor="w")
|
||||
self.yr_lbl.pack(fill="x")
|
||||
|
||||
self.cht = tk.Text(body, bg=surf, fg=txt, font=("Courier", 9), height=10, relief="flat", borderwidth=0)
|
||||
self.cht.pack(fill="both", expand=True, pady=5)
|
||||
self._refresh()
|
||||
|
||||
def _upd_period(self):
|
||||
d, dc = self._day_stat()
|
||||
m, mc = self._month_stat()
|
||||
y, yc = self._year_stat()
|
||||
self.day_lbl.config(text=f"📅 Сегодня: {self.calc.fmt_time(d)} ({dc} пр.)")
|
||||
self.mon_lbl.config(text=f"📆 Месяц: {self.calc.fmt_time(m)} (×30)")
|
||||
self.yr_lbl.config(text=f"📈 Год: {self.calc.fmt_time(y)} (×365)")
|
||||
|
||||
def _draw_cht(self):
|
||||
self.cht.delete("1.0", tk.END)
|
||||
self.cht.insert(tk.END, f"{self._cal_title()}:\n\n")
|
||||
d, _ = self._day_stat()
|
||||
bar = "█" * min(30, int(d/10)) + "░" * (30 - min(30, int(d/10)))
|
||||
self.cht.insert(tk.END, f"Сегодня: {bar} {self.calc.fmt_time(d)}\n")
|
||||
|
||||
def _refresh(self):
|
||||
if not self.cal_win: return
|
||||
self.cal_lbl.config(text=self._cal_title())
|
||||
self._draw_grid(self.grid, self.cur_date.year, self.cur_date.month)
|
||||
self._upd_period()
|
||||
self._draw_cht()
|
||||
|
||||
def _draw_grid(self, p, y, m):
|
||||
for w in p.winfo_children():
|
||||
w.destroy()
|
||||
today = date.today()
|
||||
dm, _ = self._day_stat()
|
||||
for col, day in enumerate(["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"]):
|
||||
tk.Label(p, text=day, bg=surf, fg=muted, font=("Helvetica", 10, "bold"), width=4, pady=6).grid(row=0, column=col, sticky="nsew")
|
||||
for r, week in enumerate(calendar.monthcalendar(y, m), 1):
|
||||
for c, day in enumerate(week):
|
||||
if day == 0:
|
||||
cell = tk.Label(p, text="", bg=surf, width=4, height=2)
|
||||
else:
|
||||
is_t = (today.year == y and today.month == m and today.day == day)
|
||||
if is_t and dm > 0:
|
||||
if dm > 120: bg_c, fg_c = "#ff4444", "white"
|
||||
elif dm > 60: bg_c, fg_c = "#ff7777", "white"
|
||||
else: bg_c, fg_c = "#ffaaaa", txt
|
||||
elif is_t:
|
||||
bg_c, fg_c = acc, "white"
|
||||
else:
|
||||
bg_c, fg_c = surf_alt, txt
|
||||
cell = tk.Label(p, text=str(day), bg=bg_c, fg=fg_c, font=("Helvetica", 10, "bold"), width=4, height=2)
|
||||
cell.grid(row=r, column=c, padx=3, pady=3, sticky="nsew")
|
||||
for i in range(7):
|
||||
p.grid_columnconfigure(i, weight=1)
|
||||
|
||||
def _prev_m(self):
|
||||
y, m = self.cur_date.year, self.cur_date.month - 1
|
||||
if m == 0: m, y = 12, y - 1
|
||||
self.cur_date = date(y, m, 1)
|
||||
self._refresh()
|
||||
|
||||
def _next_m(self):
|
||||
y, m = self.cur_date.year, self.cur_date.month + 1
|
||||
if m == 13: m, y = 1, y + 1
|
||||
self.cur_date = date(y, m, 1)
|
||||
self._refresh()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = MainWindow()
|
||||
app.mainloop()
|
||||
Loading…
Reference in New Issue
Block a user