Guest User

Untitled

a guest
Jan 23rd, 2026
84
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.86 KB | None | 0 0
  1. #!/usr/bin/env python3
  2. """
  3. Daily Routine Tracker - A Tkinter GUI application for tracking daily repetitive tasks.
  4. Optimized for large diary files - only reads/writes current day's section.
  5. """
  6.  
  7. import tkinter as tk
  8. from tkinter import messagebox
  9. from datetime import datetime
  10. from pathlib import Path
  11. import re
  12. import os
  13.  
  14.  
  15. class RoutineDiary:
  16. def __init__(self, root):
  17. self.root = root
  18. self.root.title("Daily Routine Tracker")
  19. self.root.geometry("400x600")
  20.  
  21. self.tasks_file = Path("tasks.txt")
  22. self.diary_file = Path("diary.yaml")
  23.  
  24. self.tasks = []
  25. self.today_clicks = {} # {task_name: [datetime, datetime, ...]}
  26. self.buttons = {} # {task_name: button_widget}
  27.  
  28. self.load_tasks()
  29. self.load_today_clicks()
  30. self.create_gui()
  31.  
  32. def load_tasks(self):
  33. """Load tasks from tasks.txt file."""
  34. if not self.tasks_file.exists():
  35. messagebox.showerror("Error", "tasks.txt file not found!")
  36. self.root.destroy()
  37. return
  38.  
  39. with open(self.tasks_file, "r", encoding="utf-8") as f:
  40. self.tasks = [line.strip() for line in f if line.strip()]
  41.  
  42. self.tasks = sorted(self.tasks, key=str.lower)
  43.  
  44. def _is_date_line(self, line):
  45. """Check if a line is a date entry (- 'YYYY-MM-DD')."""
  46. return bool(re.match(r"^- ['\"]?\d{4}-\d{2}-\d{2}['\"]?\s*$", line))
  47.  
  48. def _extract_date(self, line):
  49. """Extract date string from a date line."""
  50. match = re.search(r"\d{4}-\d{2}-\d{2}", line)
  51. return match.group(0) if match else None
  52.  
  53. def _parse_task_line(self, line):
  54. """Parse a task line and return (task_name, timestamps) or None."""
  55. # Pattern: - task_name: ['timestamp1', 'timestamp2']
  56. match = re.match(r"^- (.+?):\s*\[(.+)\]\s*$", line)
  57. if not match:
  58. return None
  59.  
  60. task_name = match.group(1).strip()
  61. timestamps_str = match.group(2)
  62.  
  63. # Extract timestamps from the list
  64. timestamps = re.findall(r"['\"](\d{4}-\d{2}-\d{2} \d{2}:\d{2})['\"]", timestamps_str)
  65. return task_name, timestamps
  66.  
  67. def load_today_clicks(self):
  68. """Load today's clicks by scanning file for today's section only."""
  69. today_str = datetime.now().strftime("%Y-%m-%d")
  70. self.today_clicks = {}
  71.  
  72. if not self.diary_file.exists():
  73. return
  74.  
  75. try:
  76. with open(self.diary_file, "r", encoding="utf-8") as f:
  77. in_today_section = False
  78.  
  79. for line in f:
  80. line = line.rstrip("\n")
  81.  
  82. if self._is_date_line(line):
  83. date = self._extract_date(line)
  84. if date == today_str:
  85. in_today_section = True
  86. continue
  87. elif in_today_section:
  88. # Reached next day, stop reading
  89. break
  90. continue
  91.  
  92. if in_today_section and line.startswith("- "):
  93. parsed = self._parse_task_line(line)
  94. if parsed:
  95. task_name, timestamps = parsed
  96. self.today_clicks[task_name] = [
  97. datetime.strptime(ts, "%Y-%m-%d %H:%M")
  98. for ts in timestamps
  99. ]
  100. except (IOError, OSError):
  101. pass
  102.  
  103. def _format_today_section(self):
  104. """Format today's data as YAML lines."""
  105. today_str = datetime.now().strftime("%Y-%m-%d")
  106. lines = [f"- '{today_str}'"]
  107.  
  108. for task_name in sorted(self.today_clicks.keys(), key=str.lower):
  109. timestamps = self.today_clicks[task_name]
  110. ts_strings = [f"'{dt.strftime('%Y-%m-%d %H:%M')}'" for dt in timestamps]
  111. lines.append(f"- {task_name}: [{', '.join(ts_strings)}]")
  112.  
  113. return lines
  114.  
  115. def save_diary(self):
  116. """Save today's clicks by updating only today's section in the file."""
  117. today_str = datetime.now().strftime("%Y-%m-%d")
  118.  
  119. if not self.diary_file.exists():
  120. # File doesn't exist, create with today's section
  121. if self.today_clicks:
  122. with open(self.diary_file, "w", encoding="utf-8") as f:
  123. for line in self._format_today_section():
  124. f.write(line + "\n")
  125. return
  126.  
  127. # Read file and find today's section boundaries
  128. temp_file = self.diary_file.with_suffix(".yaml.tmp")
  129.  
  130. try:
  131. with open(self.diary_file, "r", encoding="utf-8") as f_in, \
  132. open(temp_file, "w", encoding="utf-8") as f_out:
  133.  
  134. today_section_found = False
  135. in_today_section = False
  136. today_section_written = False
  137.  
  138. for line in f_in:
  139. line_stripped = line.rstrip("\n")
  140.  
  141. if self._is_date_line(line_stripped):
  142. date = self._extract_date(line_stripped)
  143.  
  144. if date == today_str:
  145. # Found today's section - skip old content, write new
  146. today_section_found = True
  147. in_today_section = True
  148.  
  149. if self.today_clicks and not today_section_written:
  150. for new_line in self._format_today_section():
  151. f_out.write(new_line + "\n")
  152. today_section_written = True
  153. continue
  154.  
  155. elif in_today_section:
  156. # Reached next day after today's section
  157. in_today_section = False
  158. f_out.write(line)
  159. continue
  160. else:
  161. f_out.write(line)
  162. continue
  163.  
  164. if in_today_section:
  165. # Skip old today's content
  166. continue
  167.  
  168. f_out.write(line)
  169.  
  170. # If today's section wasn't found, append it at the end
  171. if not today_section_found and self.today_clicks:
  172. for new_line in self._format_today_section():
  173. f_out.write(new_line + "\n")
  174.  
  175. # Replace original file with temp file
  176. os.replace(temp_file, self.diary_file)
  177.  
  178. except (IOError, OSError) as e:
  179. # Clean up temp file on error
  180. if temp_file.exists():
  181. temp_file.unlink()
  182. raise e
  183.  
  184. def create_gui(self):
  185. """Create the main GUI."""
  186. # Title label
  187. title_label = tk.Label(
  188. self.root,
  189. text="Daily Routines",
  190. font=("Arial", 16, "bold"),
  191. pady=10
  192. )
  193. title_label.pack()
  194.  
  195. # Date label
  196. today_str = datetime.now().strftime("%Y-%m-%d")
  197. date_label = tk.Label(
  198. self.root,
  199. text=f"Today: {today_str}",
  200. font=("Arial", 12),
  201. pady=5
  202. )
  203. date_label.pack()
  204.  
  205. # Scrollable frame for buttons
  206. container = tk.Frame(self.root)
  207. container.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
  208.  
  209. canvas = tk.Canvas(container)
  210. scrollbar = tk.Scrollbar(container, orient="vertical", command=canvas.yview)
  211. self.scrollable_frame = tk.Frame(canvas)
  212.  
  213. self.scrollable_frame.bind(
  214. "<Configure>",
  215. lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
  216. )
  217.  
  218. canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
  219. canvas.configure(yscrollcommand=scrollbar.set)
  220.  
  221. # Bind mouse wheel scrolling
  222. def on_mousewheel(event):
  223. canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
  224.  
  225. def on_mousewheel_linux(event):
  226. if event.num == 4:
  227. canvas.yview_scroll(-1, "units")
  228. elif event.num == 5:
  229. canvas.yview_scroll(1, "units")
  230.  
  231. canvas.bind_all("<MouseWheel>", on_mousewheel)
  232. canvas.bind_all("<Button-4>", on_mousewheel_linux)
  233. canvas.bind_all("<Button-5>", on_mousewheel_linux)
  234.  
  235. scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
  236. canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
  237.  
  238. self.canvas = canvas
  239. self.update_buttons()
  240.  
  241. def get_sorted_tasks(self):
  242. """Get tasks sorted according to the rules."""
  243. not_clicked = []
  244. clicked = []
  245.  
  246. for task in self.tasks:
  247. if task in self.today_clicks and len(self.today_clicks[task]) > 0:
  248. clicked.append(task)
  249. else:
  250. not_clicked.append(task)
  251.  
  252. not_clicked.sort(key=str.lower)
  253. clicked.sort(key=str.lower)
  254.  
  255. return not_clicked + clicked
  256.  
  257. def update_buttons(self):
  258. """Update the button display."""
  259. # Clear existing buttons
  260. for widget in self.scrollable_frame.winfo_children():
  261. widget.destroy()
  262.  
  263. self.buttons = {}
  264. sorted_tasks = self.get_sorted_tasks()
  265.  
  266. for task in sorted_tasks:
  267. click_count = len(self.today_clicks.get(task, []))
  268.  
  269. if click_count > 0:
  270. button_text = f"{task} ({click_count})"
  271. bg_color = "#90EE90" # Light green for clicked
  272. else:
  273. button_text = task
  274. bg_color = "#F0F0F0" # Default gray
  275.  
  276. btn = tk.Button(
  277. self.scrollable_frame,
  278. text=button_text,
  279. font=("Arial", 11),
  280. bg=bg_color,
  281. activebackground="#ADD8E6",
  282. anchor="w",
  283. padx=10,
  284. pady=5,
  285. command=lambda t=task: self.on_task_click(t)
  286. )
  287. btn.pack(fill=tk.X, pady=2)
  288. self.buttons[task] = btn
  289.  
  290. # Update scroll region
  291. self.scrollable_frame.update_idletasks()
  292. self.canvas.configure(scrollregion=self.canvas.bbox("all"))
  293.  
  294. def on_task_click(self, task_name):
  295. """Handle task button click."""
  296. now = datetime.now()
  297. # Round to minute (remove seconds/microseconds for cleaner timestamps)
  298. now = now.replace(second=0, microsecond=0)
  299.  
  300. if task_name not in self.today_clicks:
  301. self.today_clicks[task_name] = []
  302.  
  303. self.today_clicks[task_name].append(now)
  304.  
  305. self.save_diary()
  306. self.update_buttons()
  307.  
  308.  
  309. def main():
  310. root = tk.Tk()
  311. app = RoutineDiary(root)
  312. root.mainloop()
  313.  
  314.  
  315. if __name__ == "__main__":
  316. main()
Advertisement
Add Comment
Please, Sign In to add comment