Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env python3
- """
- Daily Routine Tracker - A Tkinter GUI application for tracking daily repetitive tasks.
- Optimized for large diary files - only reads/writes current day's section.
- """
- import tkinter as tk
- from tkinter import messagebox
- from datetime import datetime
- from pathlib import Path
- import re
- import os
- class RoutineDiary:
- def __init__(self, root):
- self.root = root
- self.root.title("Daily Routine Tracker")
- self.root.geometry("400x600")
- self.tasks_file = Path("tasks.txt")
- self.diary_file = Path("diary.yaml")
- self.tasks = []
- self.today_clicks = {} # {task_name: [datetime, datetime, ...]}
- self.buttons = {} # {task_name: button_widget}
- self.load_tasks()
- self.load_today_clicks()
- self.create_gui()
- def load_tasks(self):
- """Load tasks from tasks.txt file."""
- if not self.tasks_file.exists():
- messagebox.showerror("Error", "tasks.txt file not found!")
- self.root.destroy()
- return
- with open(self.tasks_file, "r", encoding="utf-8") as f:
- self.tasks = [line.strip() for line in f if line.strip()]
- self.tasks = sorted(self.tasks, key=str.lower)
- def _is_date_line(self, line):
- """Check if a line is a date entry (- 'YYYY-MM-DD')."""
- return bool(re.match(r"^- ['\"]?\d{4}-\d{2}-\d{2}['\"]?\s*$", line))
- def _extract_date(self, line):
- """Extract date string from a date line."""
- match = re.search(r"\d{4}-\d{2}-\d{2}", line)
- return match.group(0) if match else None
- def _parse_task_line(self, line):
- """Parse a task line and return (task_name, timestamps) or None."""
- # Pattern: - task_name: ['timestamp1', 'timestamp2']
- match = re.match(r"^- (.+?):\s*\[(.+)\]\s*$", line)
- if not match:
- return None
- task_name = match.group(1).strip()
- timestamps_str = match.group(2)
- # Extract timestamps from the list
- timestamps = re.findall(r"['\"](\d{4}-\d{2}-\d{2} \d{2}:\d{2})['\"]", timestamps_str)
- return task_name, timestamps
- def load_today_clicks(self):
- """Load today's clicks by scanning file for today's section only."""
- today_str = datetime.now().strftime("%Y-%m-%d")
- self.today_clicks = {}
- if not self.diary_file.exists():
- return
- try:
- with open(self.diary_file, "r", encoding="utf-8") as f:
- in_today_section = False
- for line in f:
- line = line.rstrip("\n")
- if self._is_date_line(line):
- date = self._extract_date(line)
- if date == today_str:
- in_today_section = True
- continue
- elif in_today_section:
- # Reached next day, stop reading
- break
- continue
- if in_today_section and line.startswith("- "):
- parsed = self._parse_task_line(line)
- if parsed:
- task_name, timestamps = parsed
- self.today_clicks[task_name] = [
- datetime.strptime(ts, "%Y-%m-%d %H:%M")
- for ts in timestamps
- ]
- except (IOError, OSError):
- pass
- def _format_today_section(self):
- """Format today's data as YAML lines."""
- today_str = datetime.now().strftime("%Y-%m-%d")
- lines = [f"- '{today_str}'"]
- for task_name in sorted(self.today_clicks.keys(), key=str.lower):
- timestamps = self.today_clicks[task_name]
- ts_strings = [f"'{dt.strftime('%Y-%m-%d %H:%M')}'" for dt in timestamps]
- lines.append(f"- {task_name}: [{', '.join(ts_strings)}]")
- return lines
- def save_diary(self):
- """Save today's clicks by updating only today's section in the file."""
- today_str = datetime.now().strftime("%Y-%m-%d")
- if not self.diary_file.exists():
- # File doesn't exist, create with today's section
- if self.today_clicks:
- with open(self.diary_file, "w", encoding="utf-8") as f:
- for line in self._format_today_section():
- f.write(line + "\n")
- return
- # Read file and find today's section boundaries
- temp_file = self.diary_file.with_suffix(".yaml.tmp")
- try:
- with open(self.diary_file, "r", encoding="utf-8") as f_in, \
- open(temp_file, "w", encoding="utf-8") as f_out:
- today_section_found = False
- in_today_section = False
- today_section_written = False
- for line in f_in:
- line_stripped = line.rstrip("\n")
- if self._is_date_line(line_stripped):
- date = self._extract_date(line_stripped)
- if date == today_str:
- # Found today's section - skip old content, write new
- today_section_found = True
- in_today_section = True
- if self.today_clicks and not today_section_written:
- for new_line in self._format_today_section():
- f_out.write(new_line + "\n")
- today_section_written = True
- continue
- elif in_today_section:
- # Reached next day after today's section
- in_today_section = False
- f_out.write(line)
- continue
- else:
- f_out.write(line)
- continue
- if in_today_section:
- # Skip old today's content
- continue
- f_out.write(line)
- # If today's section wasn't found, append it at the end
- if not today_section_found and self.today_clicks:
- for new_line in self._format_today_section():
- f_out.write(new_line + "\n")
- # Replace original file with temp file
- os.replace(temp_file, self.diary_file)
- except (IOError, OSError) as e:
- # Clean up temp file on error
- if temp_file.exists():
- temp_file.unlink()
- raise e
- def create_gui(self):
- """Create the main GUI."""
- # Title label
- title_label = tk.Label(
- self.root,
- text="Daily Routines",
- font=("Arial", 16, "bold"),
- pady=10
- )
- title_label.pack()
- # Date label
- today_str = datetime.now().strftime("%Y-%m-%d")
- date_label = tk.Label(
- self.root,
- text=f"Today: {today_str}",
- font=("Arial", 12),
- pady=5
- )
- date_label.pack()
- # Scrollable frame for buttons
- container = tk.Frame(self.root)
- container.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
- canvas = tk.Canvas(container)
- scrollbar = tk.Scrollbar(container, orient="vertical", command=canvas.yview)
- self.scrollable_frame = tk.Frame(canvas)
- self.scrollable_frame.bind(
- "<Configure>",
- lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
- )
- canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
- canvas.configure(yscrollcommand=scrollbar.set)
- # Bind mouse wheel scrolling
- def on_mousewheel(event):
- canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
- def on_mousewheel_linux(event):
- if event.num == 4:
- canvas.yview_scroll(-1, "units")
- elif event.num == 5:
- canvas.yview_scroll(1, "units")
- canvas.bind_all("<MouseWheel>", on_mousewheel)
- canvas.bind_all("<Button-4>", on_mousewheel_linux)
- canvas.bind_all("<Button-5>", on_mousewheel_linux)
- scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
- canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
- self.canvas = canvas
- self.update_buttons()
- def get_sorted_tasks(self):
- """Get tasks sorted according to the rules."""
- not_clicked = []
- clicked = []
- for task in self.tasks:
- if task in self.today_clicks and len(self.today_clicks[task]) > 0:
- clicked.append(task)
- else:
- not_clicked.append(task)
- not_clicked.sort(key=str.lower)
- clicked.sort(key=str.lower)
- return not_clicked + clicked
- def update_buttons(self):
- """Update the button display."""
- # Clear existing buttons
- for widget in self.scrollable_frame.winfo_children():
- widget.destroy()
- self.buttons = {}
- sorted_tasks = self.get_sorted_tasks()
- for task in sorted_tasks:
- click_count = len(self.today_clicks.get(task, []))
- if click_count > 0:
- button_text = f"{task} ({click_count})"
- bg_color = "#90EE90" # Light green for clicked
- else:
- button_text = task
- bg_color = "#F0F0F0" # Default gray
- btn = tk.Button(
- self.scrollable_frame,
- text=button_text,
- font=("Arial", 11),
- bg=bg_color,
- activebackground="#ADD8E6",
- anchor="w",
- padx=10,
- pady=5,
- command=lambda t=task: self.on_task_click(t)
- )
- btn.pack(fill=tk.X, pady=2)
- self.buttons[task] = btn
- # Update scroll region
- self.scrollable_frame.update_idletasks()
- self.canvas.configure(scrollregion=self.canvas.bbox("all"))
- def on_task_click(self, task_name):
- """Handle task button click."""
- now = datetime.now()
- # Round to minute (remove seconds/microseconds for cleaner timestamps)
- now = now.replace(second=0, microsecond=0)
- if task_name not in self.today_clicks:
- self.today_clicks[task_name] = []
- self.today_clicks[task_name].append(now)
- self.save_diary()
- self.update_buttons()
- def main():
- root = tk.Tk()
- app = RoutineDiary(root)
- root.mainloop()
- if __name__ == "__main__":
- main()
Advertisement
Add Comment
Please, Sign In to add comment