To-Do-List Python Code

Python

import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
import datetime
import json
import os

class ModernTodoApp:
    def __init__(self, root):
        self.root = root
        self.root.title("To-Do-List")
        self.root.geometry("800x600")
        self.root.configure(bg="#121212")
        self.root.resizable(True, True)
        
        # Set up data file
        self.data_file = "todo_data.json"
        
        # Modern color scheme
        self.dark_bg = "#121212"
        self.card_bg = "#1e1e1e"
        self.light_text = "#ffffff"
        self.muted_text = "#aaaaaa"
        self.primary_color = "#bb86fc"
        self.secondary_color = "#03dac6"
        self.accent_color = "#cf6679"
        self.completed_color = "#4CAF50"
        
        # Set up app fonts
        self.title_font = ("Segoe UI", 28, "bold")
        self.header_font = ("Segoe UI", 14)
        self.normal_font = ("Segoe UI", 12)
        self.small_font = ("Segoe UI", 10)
        
        # Create tasks list
        self.tasks = []
        
        # Create UI first - must be done before notification attempts
        self.create_widgets()
        
        # Now load tasks since UI is ready
        self.load_tasks()
        
        # Save tasks when closing the app
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
    
    def load_tasks(self):
        """Load tasks from JSON file if it exists"""
        if os.path.exists(self.data_file):
            try:
                with open(self.data_file, 'r') as f:
                    data = json.load(f)
                    
                    # Convert date strings back to datetime objects
                    for task in data:
                        if 'created' in task:
                            task['created'] = datetime.datetime.strptime(task['created'], "%Y-%m-%d %H:%M:%S")
                    
                    self.tasks = data
                    self.show_notification(f"Loaded {len(self.tasks)} saved tasks")
            except Exception as e:
                self.show_notification(f"Error loading tasks: {str(e)}")
                self.tasks = []
        else:
            self.show_notification("No saved tasks found")
    
    def save_tasks(self):
        """Save tasks to JSON file"""
        try:
            # Convert datetime objects to strings for JSON serialization
            tasks_to_save = []
            for task in self.tasks:
                task_copy = task.copy()
                if 'created' in task_copy:
                    task_copy['created'] = task_copy['created'].strftime("%Y-%m-%d %H:%M:%S")
                tasks_to_save.append(task_copy)
            
            with open(self.data_file, 'w') as f:
                json.dump(tasks_to_save, f, indent=2)
            
            self.show_notification("Tasks saved successfully")
            return True
        except Exception as e:
            self.show_notification(f"Error saving tasks: {str(e)}")
            return False
    
    def on_closing(self):
        """Handle window closing event"""
        if self.save_tasks():
            self.root.destroy()
    
    def create_widgets(self):
        # Header frame
        header_frame = tk.Frame(self.root, bg=self.dark_bg)
        header_frame.pack(fill=tk.X, padx=30, pady=(30, 15))
        
        # Title label
        title_label = tk.Label(
            header_frame,
            text="To-Do-List",
            font=self.title_font,
            fg=self.light_text,
            bg=self.dark_bg,
        )
        title_label.pack(side=tk.LEFT, anchor="w")
        
        # Stats label
        self.stats_label = tk.Label(
            header_frame,
            text=f"{len(self.tasks)} tasks",
            font=self.small_font,
            fg=self.muted_text,
            bg=self.dark_bg,
        )
        self.stats_label.pack(side=tk.RIGHT, anchor="e", padx=10)
        
        # Input frame
        input_frame = tk.Frame(self.root, bg=self.dark_bg)
        input_frame.pack(fill=tk.X, padx=30, pady=(0, 20))
        
        # Task entry
        self.task_entry = tk.Entry(
            input_frame,
            font=self.normal_font,
            bg=self.card_bg,
            fg=self.light_text,
            insertbackground=self.light_text,
            relief=tk.FLAT,
            width=40
        )
        self.task_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, ipady=8, padx=(0, 10))
        self.task_entry.bind("", self.add_task_from_entry)
        self.task_entry.focus_set()
        
        # Add placeholder text
        self.task_entry.insert(0, "Add a new task...")
        self.task_entry.bind("", lambda e: self.clear_placeholder() if self.task_entry.get() == "Add a new task..." else None)
        self.task_entry.bind("", lambda e: self.restore_placeholder() if not self.task_entry.get() else None)
        
        # Add task button
        add_btn = tk.Button(
            input_frame,
            text="+ Add",
            font=self.normal_font,
            bg=self.primary_color,
            fg="#000000",
            activebackground="#d0a0ff",
            relief=tk.FLAT,
            command=self.add_task_from_entry,
            padx=15,
            cursor="hand2"
        )
        add_btn.pack(side=tk.LEFT)
        
        # Filter frame
        filter_frame = tk.Frame(self.root, bg=self.dark_bg)
        filter_frame.pack(fill=tk.X, padx=30, pady=(0, 15))
        
        filter_label = tk.Label(
            filter_frame,
            text="Filter tasks:",
            font=self.small_font,
            bg=self.dark_bg,
            fg=self.muted_text
        )
        filter_label.pack(side=tk.LEFT, padx=(0, 15))
        
        # Filter options with modern toggle
        self.filter_var = tk.StringVar(value="all")
        
        filter_btn_all = tk.Button(
            filter_frame,
            text="All",
            font=self.small_font,
            bg=self.card_bg if self.filter_var.get() == "all" else self.dark_bg,
            fg=self.light_text if self.filter_var.get() == "all" else self.muted_text,
            relief=tk.FLAT,
            command=lambda: self.set_filter("all"),
            padx=10,
            cursor="hand2"
        )
        filter_btn_all.pack(side=tk.LEFT, padx=(0, 10))
        
        filter_btn_active = tk.Button(
            filter_frame,
            text="Active",
            font=self.small_font,
            bg=self.card_bg if self.filter_var.get() == "active" else self.dark_bg,
            fg=self.light_text if self.filter_var.get() == "active" else self.muted_text,
            relief=tk.FLAT,
            command=lambda: self.set_filter("active"),
            padx=10,
            cursor="hand2"
        )
        filter_btn_active.pack(side=tk.LEFT, padx=(0, 10))
        
        filter_btn_completed = tk.Button(
            filter_frame,
            text="Completed",
            font=self.small_font,
            bg=self.card_bg if self.filter_var.get() == "completed" else self.dark_bg,
            fg=self.light_text if self.filter_var.get() == "completed" else self.muted_text,
            relief=tk.FLAT,
            command=lambda: self.set_filter("completed"),
            padx=10,
            cursor="hand2"
        )
        filter_btn_completed.pack(side=tk.LEFT)
        
        # Save filter buttons for updating
        self.filter_buttons = {
            "all": filter_btn_all,
            "active": filter_btn_active,
            "completed": filter_btn_completed
        }
        
        # Tasks frame with scrollbar
        self.canvas = tk.Canvas(self.root, bg=self.dark_bg, highlightthickness=0)
        self.scrollbar = ttk.Scrollbar(self.root, orient="vertical", command=self.canvas.yview)
        self.scrollable_frame = tk.Frame(self.canvas, bg=self.dark_bg)
        
        self.scrollable_frame.bind(
            "",
            lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all"))
        )
        
        self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
        self.canvas.configure(yscrollcommand=self.scrollbar.set)
        
        self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(30, 0))
        self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y, padx=(0, 30))
        
        # Bind mousewheel for scrolling
        self.canvas.bind_all("", self.on_mousewheel)
        
        # Empty state
        self.empty_state_label = tk.Label(
            self.scrollable_frame,
            text="No tasks yet\nAdd your first task to get started",
            font=self.header_font,
            fg=self.muted_text,
            bg=self.dark_bg,
            justify="center",
            pady=50
        )
        
        # Footer with action buttons
        footer_frame = tk.Frame(self.root, bg=self.dark_bg, height=40)
        footer_frame.pack(fill=tk.X, side=tk.BOTTOM, pady=(0, 10), padx=30)
        
        # Clear completed button
        tk.Button(
            footer_frame,
            text="Clear Completed",
            font=self.small_font,
            bg=self.dark_bg,
            fg=self.muted_text,
            activebackground="#2a2a2a",
            relief=tk.FLAT,
            command=self.clear_completed,
            cursor="hand2"
        ).pack(side=tk.LEFT, padx=(0, 15))
        
        # Export tasks button
        tk.Button(
            footer_frame,
            text="Export Tasks",
            font=self.small_font,
            bg=self.dark_bg,
            fg=self.muted_text,
            activebackground="#2a2a2a",
            relief=tk.FLAT,
            command=self.export_tasks,
            cursor="hand2"
        ).pack(side=tk.LEFT)
        
        # Save tasks button
        tk.Button(
            footer_frame,
            text="Save Now",
            font=self.small_font,
            bg=self.dark_bg,
            fg=self.primary_color,
            activebackground="#2a2a2a",
            relief=tk.FLAT,
            command=self.save_tasks,
            cursor="hand2"
        ).pack(side=tk.LEFT, padx=(15, 0))
        
        # Add search functionality
        search_frame = tk.Frame(footer_frame, bg=self.dark_bg)
        search_frame.pack(side=tk.RIGHT)
        
        self.search_var = tk.StringVar()
        self.search_var.trace("w", self.update_task_list)
        search_entry = tk.Entry(
            search_frame,
            textvariable=self.search_var,
            font=self.small_font,
            bg=self.card_bg,
            fg=self.light_text,
            relief=tk.FLAT,
            width=20
        )
        search_entry.pack(side=tk.LEFT, padx=(0, 5), ipady=2)
        search_entry.insert(0, "Search tasks...")
        search_entry.bind("", lambda e: search_entry.delete(0, "end") if search_entry.get() == "Search tasks..." else None)
        search_entry.bind("", lambda e: search_entry.insert(0, "Search tasks...") if search_entry.get() == "" else None)
        
        # Notification system - MUST be created last in the UI
        self.notification_label = tk.Label(
            self.root, 
            text="", 
            font=self.small_font,
            bg=self.dark_bg,
            fg=self.primary_color
        )
        self.notification_label.pack(side=tk.BOTTOM, fill=tk.X, pady=5)
        
        # Update task list to show initial state
        self.update_task_list()
    
    def clear_placeholder(self):
        if self.task_entry.get() == "Add a new task...":
            self.task_entry.delete(0, "end")
            self.task_entry.config(fg=self.light_text)
    
    def restore_placeholder(self):
        if not self.task_entry.get():
            self.task_entry.insert(0, "Add a new task...")
            self.task_entry.config(fg=self.muted_text)
    
    def set_filter(self, filter_type):
        self.filter_var.set(filter_type)
        self.update_filter_buttons()
        self.update_task_list()
    
    def update_filter_buttons(self):
        for filter_type, button in self.filter_buttons.items():
            if filter_type == self.filter_var.get():
                button.config(bg=self.card_bg, fg=self.light_text)
            else:
                button.config(bg=self.dark_bg, fg=self.muted_text)
    
    def add_task(self, task_text):
        if task_text.strip() and task_text != "Add a new task...":
            task = {"task": task_text, "completed": False, "created": datetime.datetime.now()}
            self.tasks.append(task)
            self.update_task_list()
            self.show_notification(f"Task added: {task_text}")
            self.task_entry.delete(0, tk.END)
            self.restore_placeholder()
            self.save_tasks()  # Auto-save after adding
    
    def add_task_from_entry(self, event=None):
        task_text = self.task_entry.get()
        self.add_task(task_text)
    
    def mark_task_complete(self, index):
        if 0 <= index < len(self.tasks):
            self.tasks[index]["completed"] = not self.tasks[index]["completed"]
            status = "completed" if self.tasks[index]["completed"] else "reopened"
            self.update_task_list()
            self.show_notification(f"Task marked as {status}: {self.tasks[index]['task']}")
            self.save_tasks()  # Auto-save after status change
    
    def edit_task(self, index):
        if 0 <= index < len(self.tasks):
            new_text = simpledialog.askstring(
                "Edit Task",
                "Edit your task:",
                initialvalue=self.tasks[index]["task"]
            )
            
            if new_text and new_text.strip():
                self.tasks[index]["task"] = new_text.strip()
                self.update_task_list()
                self.show_notification("Task updated")
                self.save_tasks()  # Auto-save after edit
    
    def delete_task(self, index):
        if 0 <= index < len(self.tasks):
            task_text = self.tasks[index]["task"]
            del self.tasks[index]
            self.update_task_list()
            self.show_notification(f"Task deleted: {task_text}")
            self.save_tasks()  # Auto-save after delete
    
    def clear_completed(self):
        initial_count = len(self.tasks)
        self.tasks = [task for task in self.tasks if not task["completed"]]
        removed_count = initial_count - len(self.tasks)
        self.update_task_list()
        if removed_count:
            self.show_notification(f"Removed {removed_count} completed tasks")
            self.save_tasks()  # Auto-save after clearing
    
    def export_tasks(self):
        export_text = "Modern To-Do List Export\n" + "="*30 + "\n"
        export_text += f"Generated on: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}\n"
        export_text += f"Total tasks: {len(self.tasks)}\n\n"
        
        for i, task in enumerate(self.tasks):
            status = "✓" if task["completed"] else "✗"
            created = task["created"].strftime("%b %d, %H:%M")
            export_text += f"{i+1}. [{status}] {task['task']} ({created})\n"
        
        # In a real app, you would save this to a file
        messagebox.showinfo(
            "Export Tasks",
            "Tasks exported to clipboard!\n\n" + export_text
        )
        self.root.clipboard_clear()
        self.root.clipboard_append(export_text)
        self.show_notification("Tasks copied to clipboard")
    
    def update_task_list(self, *args):
        # Remove the empty state label if tasks exist
        if self.tasks:
            self.empty_state_label.pack_forget()
        
        # Clear the scrollable frame
        for widget in self.scrollable_frame.winfo_children():
            if widget != self.empty_state_label:
                widget.destroy()
        
        # Show empty state if no tasks
        if not self.tasks:
            self.empty_state_label.pack(fill=tk.X, pady=50)
            self.stats_label.config(text="0 tasks")
            return
        
        # Apply filters
        filter_type = self.filter_var.get()
        search_text = self.search_var.get().lower()
        
        filtered_tasks = []
        for task in self.tasks:
            # Apply search filter
            if search_text and search_text != "search tasks..." and search_text not in task["task"].lower():
                continue
            
            # Apply status filter
            if filter_type == "all":
                filtered_tasks.append(task)
            elif filter_type == "active" and not task["completed"]:
                filtered_tasks.append(task)
            elif filter_type == "completed" and task["completed"]:
                filtered_tasks.append(task)
        
        # Update stats
        total_tasks = len(self.tasks)
        completed_tasks = sum(1 for task in self.tasks if task["completed"])
        self.stats_label.config(text=f"{total_tasks} tasks ({completed_tasks} completed)")
        
        # Add tasks to the scrollable frame
        for i, task in enumerate(filtered_tasks):
            task_frame = tk.Frame(
                self.scrollable_frame,
                bg=self.card_bg,
                padx=15,
                pady=12,
                highlightthickness=0
            )
            task_frame.pack(fill=tk.X, pady=5)
            
            # Create a custom checkbox
            checkbox = tk.Label(
                task_frame,
                text="✓" if task["completed"] else " ",
                font=("Segoe UI", 14),
                bg=self.card_bg,
                fg=self.primary_color,
                width=2,
                height=1,
                relief="solid",
                bd=1,
                highlightbackground="#333333"
            )
            checkbox.grid(row=0, column=0, rowspan=2, padx=(0, 15), sticky="n")
            checkbox.bind("", lambda e, idx=i: self.mark_task_complete(self.get_task_index(filtered_tasks[idx])))
            checkbox.config(cursor="hand2")
            
            # Task text
            task_text = tk.Label(
                task_frame,
                text=task["task"],
                font=self.normal_font,
                bg=self.card_bg,
                fg=self.light_text if not task["completed"] else "#666666",
                anchor="w",
                wraplength=600,
                justify="left"
            )
            if task["completed"]:
                task_text.config(font=("Segoe UI", 12, "overstrike"))
            task_text.grid(row=0, column=1, sticky="w")
            
            # Task metadata
            created_str = task["created"].strftime("%b %d, %H:%M")
            meta_label = tk.Label(
                task_frame,
                text=created_str,
                font=self.small_font,
                bg=self.card_bg,
                fg=self.muted_text,
                anchor="w"
            )
            meta_label.grid(row=1, column=1, sticky="w", pady=(5, 0))
            
            # Task actions
            actions_frame = tk.Frame(
                task_frame,
                bg=self.card_bg
            )
            actions_frame.grid(row=0, column=2, rowspan=2, sticky="e")
            
            # Edit button
            edit_btn = tk.Label(
                actions_frame,
                text="✎",
                font=("Segoe UI", 12),
                fg=self.muted_text,
                bg=self.card_bg,
                cursor="hand2"
            )
            edit_btn.pack(side=tk.LEFT, padx=(0, 15))
            edit_btn.bind("", lambda e, idx=i: self.edit_task(self.get_task_index(filtered_tasks[idx])))
            
            # Delete button
            delete_btn = tk.Label(
                actions_frame,
                text="✕",
                font=("Segoe UI", 14),
                fg=self.accent_color,
                bg=self.card_bg,
                cursor="hand2"
            )
            delete_btn.pack(side=tk.LEFT)
            delete_btn.bind("", lambda e, idx=i: self.delete_task(self.get_task_index(filtered_tasks[idx])))
    
    def get_task_index(self, task):
        return self.tasks.index(task)
    
    def show_notification(self, message):
        if hasattr(self, 'notification_label'):  # Safeguard
            self.notification_label.config(text=message)
            self.root.after(3000, lambda: self.notification_label.config(text=""))
    
    def on_mousewheel(self, event):
        self.canvas.yview_scroll(int(-1*(event.delta/120)), "units")
    
    def run(self):
        self.root.mainloop()

if __name__ == "__main__":
    root = tk.Tk()
    app = ModernTodoApp(root)
    app.run()
            
Responsive Design
Copy to Clipboard
Syntax Highlighted
Scrollable Code