ToDoMate 📝 – Python GUI To-Do List Application with Priority, Filters, and Export
ToDoMate 📝 – Python GUI To-Do List Application
This Python script creates a modern Tkinter GUI To-Do List with features like priority levels, due dates, filters, sorting, and CSV/TXT export. Perfect for managing your tasks efficiently.
Key Features:
- Two-tab interface: Dashboard & To-Do List
- Add, remove, and mark tasks as done
- Priority levels (High, Medium, Low) with color coding
- Filter by Today, Overdue, or High-priority tasks
- Search tasks by title
- Sort tasks by Due Date or Priority
- Export tasks to CSV or TXT
Full Python Source Code:
import sys
import os
import csv
from datetime import datetime, date
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
# =========================
# Helpers
# =========================
def resource_path(file_name):
base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
return os.path.join(base_path, file_name)
TODO_FILE = resource_path("todo_list.csv")
tasks = []
def save_tasks():
try:
with open(TODO_FILE, "w", newline="") as f:
writer = csv.writer(f)
for task in tasks:
writer.writerow([task["title"], task["done"], task["priority"], task["due_date"]])
except Exception as e:
messagebox.showerror("Error", f"Saving tasks failed: {e}")
def load_tasks():
if not os.path.exists(TODO_FILE):
return
try:
with open(TODO_FILE, "r") as f:
reader = csv.reader(f)
for row in reader:
if len(row) == 4:
due_date = row[3].strip()
# Normalize date to YYYY-MM-DD
if due_date:
try:
datetime.strptime(due_date, "%Y-%m-%d")
except:
try:
dt = datetime.strptime(due_date, "%m/%d/%Y")
due_date = dt.strftime("%Y-%m-%d")
except:
due_date = ""
tasks.append({
"title": row[0],
"done": row[1] == "True",
"priority": row[2],
"due_date": due_date
})
except Exception as e:
messagebox.showerror("Error", f"Loading tasks failed: {e}")
def get_filtered_sorted_tasks(filter_type=None, sort_by=None, search_text=""):
filtered = tasks
today_str = date.today().strftime("%Y-%m-%d")
# Filters
if filter_type == "today":
filtered = [t for t in filtered if t["due_date"] == today_str]
elif filter_type == "overdue":
filtered = [t for t in filtered if t["due_date"] and t["due_date"] < today_str and not t["done"]]
elif filter_type == "high":
filtered = [t for t in filtered if t["priority"] == "High"]
# Search
if search_text:
filtered = [t for t in filtered if search_text.lower() in t["title"].lower()]
# Sorting
if sort_by == "due":
filtered.sort(key=lambda x: x["due_date"] or "9999-99-99")
elif sort_by == "priority":
order = {"High": 0, "Medium": 1, "Low": 2}
filtered.sort(key=lambda x: order.get(x["priority"], 3))
return filtered
# =========================
# GUI Functions
# =========================
def refresh_treeview(*args):
for row in tree.get_children():
tree.delete(row)
filter_type = filter_var.get()
sort_by = sort_var.get()
search_text = search_var.get()
for task in get_filtered_sorted_tasks(filter_type, sort_by, search_text):
due_display = task["due_date"] if task["due_date"] else "—"
tree.insert("", "end", values=(
task["title"],
"✅" if task["done"] else "❌",
task["priority"],
due_display
))
tags = []
if task["done"]:
tags.append("done")
elif task["priority"] == "High":
tags.append("high")
elif task["priority"] == "Medium":
tags.append("medium")
elif task["priority"] == "Low":
tags.append("low")
if task["due_date"]:
due_dt = datetime.strptime(task["due_date"], "%Y-%m-%d").date()
if due_dt < date.today() and not task["done"]:
tags.append("overdue")
tree.item(tree.get_children()[-1], tags=tags)
def add_task():
title = title_entry.get().strip()
if not title:
messagebox.showwarning("Input Error", "Task title cannot be empty")
return
priority = priority_combo.get()
due_date = due_entry.get().strip()
if due_date:
try:
datetime.strptime(due_date, "%Y-%m-%d")
except:
messagebox.showwarning("Input Error", "Invalid due date format. Use YYYY-MM-DD")
return
tasks.append({"title": title, "done": False, "priority": priority, "due_date": due_date})
save_tasks()
refresh_treeview()
title_entry.delete(0, tk.END)
due_entry.delete(0, tk.END)
def remove_task():
selected = tree.selection()
if not selected: return
idx = tree.index(selected[0])
removed = tasks.pop(idx)
save_tasks()
refresh_treeview()
messagebox.showinfo("🗑️ Removed", f"Removed task: {removed['title']}")
def mark_done():
selected = tree.selection()
if not selected: return
idx = tree.index(selected[0])
tasks[idx]["done"] = True
save_tasks()
refresh_treeview()
def clear_all_tasks():
if messagebox.askyesno("⚠️ Clear All", "Are you sure you want to remove all tasks?"):
tasks.clear()
save_tasks()
refresh_treeview()
def export_tasks():
file_path = filedialog.asksaveasfilename(defaultextension=".csv", filetypes=[("CSV file","*.csv"),("Text file","*.txt")])
if not file_path: return
try:
if file_path.endswith(".csv"):
with open(file_path,"w",newline="") as f:
writer = csv.writer(f)
for task in tasks:
writer.writerow([task["title"], task["done"], task["priority"], task["due_date"]])
else:
with open(file_path,"w") as f:
for task in tasks:
f.write(f"{task['title']} | {'Done' if task['done'] else 'Pending'} | {task['priority']} | {task['due_date'] or '—'}\n")
messagebox.showinfo("💾 Exported", f"Tasks exported to {file_path}")
except Exception as e:
messagebox.showerror("Error", f"Export failed: {e}")
# =========================
# GUI Setup
# =========================
root = tk.Tk()
root.title("ToDoMate 📝")
root.geometry("950x600")
root.configure(bg="#f0f4f8")
# Notebook
notebook = ttk.Notebook(root)
notebook.pack(fill="both", expand=True)
# ---------------- Dashboard Tab ----------------
dash_tab = ttk.Frame(notebook, padding=20)
notebook.add(dash_tab, text="🏠 Dashboard")
ttk.Label(dash_tab, text="ToDoMate 📝", font=("Segoe UI", 22, "bold")).pack(anchor="w", pady=(0,10))
ttk.Label(dash_tab, text="A modern productivity tool to manage your tasks efficiently with priorities, due dates, filters, and sorting.", wraplength=750, font=("Segoe UI",12)).pack(anchor="w", pady=(0,15))
ttk.Label(dash_tab, text="✨ Key Features:", font=("Segoe UI",14,"bold")).pack(anchor="w", pady=(10,5))
features = [
"🗂️ Two-tab interface: Dashboard & To-Do List",
"✅ Add, remove, mark tasks as done",
"📅 Priority levels and due dates with color coding",
"🔍 Filters: Today, Overdue, High-priority",
"🔎 Search tasks by title",
"↕ Sort tasks by due date or priority",
"💾 Export tasks to CSV or TXT"
]
for feat in features:
ttk.Label(dash_tab, text=feat, font=("Segoe UI",11)).pack(anchor="w")
ttk.Label(dash_tab, text="About Developer:", font=("Segoe UI",14,"bold")).pack(anchor="w", pady=(20,5))
ttk.Label(dash_tab, text="MateTools – Practical and user-friendly digital tools for productivity.\nWebsite: https://matetools.gumroad.com", wraplength=750, font=("Segoe UI",11)).pack(anchor="w")
# ---------------- To-Do Tab ----------------
todo_tab = ttk.Frame(notebook, padding=10)
notebook.add(todo_tab, text="📝 To-Do List")
# Form frame
form_frame = ttk.LabelFrame(todo_tab, text="➕ Add New Task", padding=10)
form_frame.pack(fill="x", padx=10, pady=5)
ttk.Label(form_frame, text="Title:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
title_entry = ttk.Entry(form_frame, width=40)
title_entry.grid(row=0, column=1, padx=5, pady=5)
ttk.Label(form_frame, text="Priority:").grid(row=0, column=2, sticky="w", padx=5, pady=5)
priority_combo = ttk.Combobox(form_frame, values=["High","Medium","Low"], width=10)
priority_combo.set("Medium")
priority_combo.grid(row=0, column=3, padx=5, pady=5)
ttk.Label(form_frame, text="Due Date:").grid(row=0, column=4, sticky="w", padx=5, pady=5)
due_entry = ttk.Entry(form_frame, width=15)
due_entry.grid(row=0, column=5, padx=5, pady=5)
ttk.Label(form_frame, text="YYYY-MM-DD").grid(row=0, column=6, sticky="w", padx=2)
ttk.Button(form_frame, text="✅ Add Task", command=add_task, style="Accent.TButton").grid(row=0, column=7, padx=5, pady=5)
# Toolbar frame
toolbar_frame = ttk.Frame(todo_tab)
toolbar_frame.pack(fill="x", padx=10, pady=5)
ttk.Button(toolbar_frame, text="🗑️ Remove", command=remove_task, style="Accent.TButton").pack(side="left", padx=5)
ttk.Button(toolbar_frame, text="✅ Done", command=mark_done, style="Accent.TButton").pack(side="left", padx=5)
ttk.Button(toolbar_frame, text="⚠️ Clear All", command=clear_all_tasks, style="Accent.TButton").pack(side="left", padx=5)
ttk.Button(toolbar_frame, text="💾 Export", command=export_tasks, style="Accent.TButton").pack(side="left", padx=5)
ttk.Label(toolbar_frame, text="Filter:").pack(side="left", padx=(20,5))
filter_var = tk.StringVar(value="all")
filter_menu = ttk.Combobox(toolbar_frame, textvariable=filter_var, values=["all","today","overdue","high"], width=10)
filter_menu.pack(side="left", padx=5)
filter_menu.bind("<>", refresh_treeview)
ttk.Label(toolbar_frame, text="Sort by:").pack(side="left", padx=(20,5))
sort_var = tk.StringVar(value="none")
sort_menu = ttk.Combobox(toolbar_frame, textvariable=sort_var, values=["none","due","priority"], width=10)
sort_menu.pack(side="left", padx=5)
sort_menu.bind("<>", refresh_treeview)
ttk.Label(toolbar_frame, text="🔎 Search:").pack(side="left", padx=(20,5))
search_var = tk.StringVar()
search_var.trace("w", refresh_treeview)
search_entry = ttk.Entry(toolbar_frame, textvariable=search_var, width=20)
search_entry.pack(side="left", padx=5)
# Treeview frame
tree_frame = ttk.Frame(todo_tab)
tree_frame.pack(fill="both", expand=True, padx=10, pady=5)
columns = ("Title", "Status", "Priority", "Due Date")
tree = ttk.Treeview(tree_frame, columns=columns, show="headings", selectmode="browse")
for col in columns:
tree.heading(col, text=col)
tree.column(col, width=200, anchor="center")
tree.pack(fill="both", expand=True)
# Color tags
tree.tag_configure("done", foreground="gray")
tree.tag_configure("high", background="#ffcccc")
tree.tag_configure("medium", background="#fff0b3")
tree.tag_configure("low", background="#ccffcc")
tree.tag_configure("overdue", foreground="red")
# =========================
# Style
# =========================
style = ttk.Style(root)
style.configure("Accent.TButton",
foreground="#000000",
background="#ffd966",
font=("Segoe UI", 10, "bold"),
padding=5)
style.map("Accent.TButton",
foreground=[("active", "#000000")],
background=[("active", "#ffbf00")])
# =========================
# Load tasks and run
# =========================
load_tasks()
refresh_treeview()
root.mainloop()
Download or Explore Useful Tools
Check out more useful tools and downloads here: MateTools on Gumroad




No comments