Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import tkinter as tk
- from tkinter import ttk, messagebox
- import ttkbootstrap as ttk
- from ttkbootstrap.constants import *
- from tkinter import scrolledtext
- import pytz
- from datetime import datetime, timedelta, time
- import threading
- import time as t
- import pygame
- import json
- import os
- import sys
- from ctypes import windll
- from google.oauth2.credentials import Credentials
- from google_auth_oauthlib.flow import InstalledAppFlow
- from googleapiclient.discovery import build
- from google.auth.transport.requests import Request
- # Initialize pygame mixer for sound playing
- pygame.mixer.init()
- # Google Calendar API scope and token file
- SCOPES = ['https://www.googleapis.com/auth/calendar']
- TOKEN_FILE = 'token.json'
- # Initialize the main window with a modern theme
- root = ttk.Window(themename="superhero") # Change theme if needed
- root.title("Task Tracker")
- root.geometry("1500x950")
- # Ensure the window appears in the taskbar and remove the default window frame
- root.attributes('-fullscreen', True) # Not full screen, but this helps manage taskbar appearance
- # Function to minimize the window
- def minimize_window():
- root.iconify()
- # Function to maximize or restore the window size
- is_maximized = False
- def toggle_maximize_restore():
- global is_maximized
- if not is_maximized:
- root.state('zoomed') # Maximize window
- is_maximized = True
- else:
- root.state('normal') # Restore to original size
- is_maximized = False
- # Function to close the window
- def close_window():
- root.quit()
- # Create a custom title bar frame at the top
- title_bar = ttk.Frame(root, bootstyle="primary", relief='raised', height=30)
- title_bar.grid(row=0, column=0, columnspan=4, sticky="ew")
- # Add a title label
- title_label = ttk.Label(title_bar, text="Task Tracker", bootstyle="inverse-primary")
- title_label.pack(side="left", padx=10)
- # Add minimize, maximize, and close buttons in the correct order
- close_button = ttk.Button(title_bar, text="X", command=close_window, bootstyle="danger-outline")
- close_button.pack(side="right", padx=5, pady=2)
- minimize_button = ttk.Button(title_bar, text="_", command=minimize_window, bootstyle="warning-outline")
- minimize_button.pack(side="right", padx=5, pady=2)
- # Variables to track window movement
- x_offset = 0
- y_offset = 0
- # Functions to move the window
- def get_window_position(event):
- global x_offset, y_offset
- x_offset = event.x
- y_offset = event.y
- def move_window(event):
- root.geometry(f'+{event.x_root - x_offset}+{event.y_root - y_offset}')
- # Bind the title bar to allow dragging the window
- title_bar.bind("<Button-1>", get_window_position)
- title_bar.bind("<B1-Motion>", move_window)
- # Function to create a rounded rectangle in a canvas
- def round_rectangle(canvas, x1, y1, x2, y2, radius=25, **kwargs):
- points = [x1 + radius, y1,
- x1 + radius, y1,
- x2 - radius, y1,
- x2 - radius, y1,
- x2, y1,
- x2, y1 + radius,
- x2, y1 + radius,
- x2, y2 - radius,
- x2, y2 - radius,
- x2, y2,
- x2 - radius, y2,
- x2 - radius, y2,
- x1 + radius, y2,
- x1 + radius, y2,
- x1, y2,
- x1, y2 - radius,
- x1, y2 - radius,
- x1, y1 + radius,
- x1, y1 + radius,
- x1, y1]
- return canvas.create_polygon(points, **kwargs, smooth=True)
- # Create a canvas and use grid to place it in the window
- canvas = tk.Canvas(root, width=300, height=300, bg="blue", bd=0, highlightthickness=0)
- canvas.grid(row=1, column=0, sticky="nsew")
- # Configure the grid to allow the canvas to expand and fill the window
- root.grid_rowconfigure(1, weight=1)
- root.grid_columnconfigure(0, weight=1)
- # Define a list to store tasks and their completion status
- tasks = []
- alarm_playing = False # Global flag to stop alarm
- # Path to the task file
- TASK_FILE = "tasks.json"
- # Path to the alarm sound file (ensure you have a sound file like "alarm.wav" in the directory)
- ALARM_SOUND_FILE = "alarm.wav"
- # Global variable to store Google Calendar API service
- google_service = None
- # Global dictionary to store event IDs from Google Calendar
- task_event_ids = {}
- def play_alarm_sound():
- global alarm_playing
- if not alarm_playing:
- try:
- print("Playing alarm sound on a new thread...")
- threading.Thread(target=_play_alarm_sound_worker).start()
- except Exception as e:
- print(f"Error starting sound thread: {e}")
- def _play_alarm_sound_worker():
- global alarm_playing
- try:
- pygame.mixer.music.load(ALARM_SOUND_FILE)
- pygame.mixer.music.play(-1) # Loop the sound indefinitely
- alarm_playing = True
- except Exception as e:
- print(f"Error playing sound: {e}")
- # Function to stop the alarm sound
- def stop_alarm():
- global alarm_playing
- if alarm_playing:
- print("Stopping alarm...")
- pygame.mixer.music.stop() # Stop the sound
- alarm_playing = False
- # Function to save tasks to a JSON file (including in-progress flag)
- def save_tasks():
- try:
- with open(TASK_FILE, "w") as f:
- # Convert datetime objects to ISO format strings for JSON serialization
- tasks_serializable = []
- for task in tasks:
- task_list = list(task)
- # Convert datetime objects to strings
- if task_list[14]:
- task_list[14] = task_list[14].isoformat()
- if task_list[15]:
- task_list[15] = task_list[15].isoformat()
- # Convert time objects to strings
- if task_list[2]:
- task_list[2] = task_list[2].isoformat()
- if task_list[3]:
- task_list[3] = task_list[3].isoformat()
- if task_list[7]:
- task_list[7] = task_list[7].isoformat()
- if task_list[9]:
- task_list[9] = task_list[9].isoformat()
- if task_list[11]:
- task_list[11] = task_list[11].isoformat()
- # event_id does not need conversion, but ensure it's included
- tasks_serializable.append(task_list)
- json.dump(tasks_serializable, f)
- except Exception as e:
- print(f"Error saving tasks: {e}")
- # Function to load tasks from a JSON file (including in-progress flag)
- def load_tasks():
- global tasks
- if os.path.exists(TASK_FILE):
- try:
- with open(TASK_FILE, "r") as f:
- loaded_tasks = json.load(f)
- tasks = []
- for task in loaded_tasks:
- task_data = (
- task[0], # Task name
- task[1], # Notes
- time.fromisoformat(task[2]) if task[2] else None, # Complete at time
- time.fromisoformat(task[3]) if task[3] else None, # Complete by time
- task[4], # Daily
- task[5], # Weekly
- task[6], # Monthly
- time.fromisoformat(task[7]) if task[7] else None, # Daily time
- task[8], # Weekly day
- time.fromisoformat(task[9]) if task[9] else None, # Weekly time
- task[10], # Monthly day
- time.fromisoformat(task[11]) if task[11] else None, # Monthly time
- task[12], # Completed
- task[13], # In-progress
- datetime.fromisoformat(task[14]) if task[14] else None, # Complete at datetime
- datetime.fromisoformat(task[15]) if task[15] else None, # Complete by datetime
- task[16] if len(task) > 16 else None # event_id (Handle backward compatibility)
- )
- tasks.append(task_data)
- update_task_listbox() # Load tasks into the listboxes
- except Exception as e:
- print(f"Error loading tasks: {e}")
- # Function to show notification after alarm sound
- def show_alarm_popup(task, time_type):
- messagebox.showinfo("Task Alarm", f"It's time to {time_type} for task: {task}")
- # Function to trigger alarm (plays sound first, then popup)
- def trigger_alarm(task, time_type):
- global alarm_playing
- # If the alarm is not already playing, start playing it in a new thread
- if not alarm_playing:
- print(f"Triggering alarm for task '{task}' at {time_type}")
- play_alarm_sound() # This will run in a separate thread
- # Show the task alarm popup using Tkinter's event loop
- root.after(0, lambda: show_alarm_popup(task, time_type))
- # Function to convert hour, minute, AM/PM to a time object
- def get_time_from_dropdown(hour, minute, am_pm):
- hour = int(hour)
- minute = int(minute)
- if am_pm == "PM" and hour != 12:
- hour += 12
- if am_pm == "AM" and hour == 12:
- hour = 0
- return time(hour, minute)
- # Function to enable/disable options based on selection
- def update_state():
- # Reset all controls (enable all checkboxes and disable all time fields by default)
- daily_checkbox.config(state=tk.NORMAL)
- weekly_checkbox.config(state=tk.NORMAL)
- monthly_checkbox.config(state=tk.NORMAL)
- at_radio_button.config(state=tk.NORMAL)
- by_radio_button.config(state=tk.NORMAL)
- at_date_checkbox.config(state=tk.DISABLED)
- by_date_checkbox.config(state=tk.DISABLED)
- # Disable time selectors initially
- daily_hour_combo.config(state=tk.DISABLED)
- daily_minute_combo.config(state=tk.DISABLED)
- daily_am_pm_combo.config(state=tk.DISABLED)
- weekly_day_combo.config(state=tk.DISABLED)
- weekly_hour_combo.config(state=tk.DISABLED)
- weekly_minute_combo.config(state=tk.DISABLED)
- weekly_am_pm_combo.config(state=tk.DISABLED)
- monthly_day_combo.config(state=tk.DISABLED)
- monthly_hour_combo.config(state=tk.DISABLED)
- monthly_minute_combo.config(state=tk.DISABLED)
- monthly_am_pm_combo.config(state=tk.DISABLED)
- at_hour_combo.config(state=tk.DISABLED)
- at_minute_combo.config(state=tk.DISABLED)
- at_am_pm_combo.config(state=tk.DISABLED)
- by_hour_combo.config(state=tk.DISABLED)
- by_minute_combo.config(state=tk.DISABLED)
- by_am_pm_combo.config(state=tk.DISABLED)
- # Handle mutually exclusive behavior between Daily, Weekly, Monthly, Complete at, and Complete by
- # If Daily is selected
- if daily_var.get():
- # Disable Weekly, Monthly, Complete at, and Complete by
- weekly_checkbox.config(state=tk.DISABLED)
- monthly_checkbox.config(state=tk.DISABLED)
- at_radio_button.config(state=tk.DISABLED)
- by_radio_button.config(state=tk.DISABLED)
- # Enable Daily time selectors
- daily_hour_combo.config(state=tk.NORMAL)
- daily_minute_combo.config(state=tk.NORMAL)
- daily_am_pm_combo.config(state=tk.NORMAL)
- # If Weekly is selected
- elif weekly_var.get():
- # Disable Daily, Monthly, Complete at, and Complete by
- daily_checkbox.config(state=tk.DISABLED)
- monthly_checkbox.config(state=tk.DISABLED)
- at_radio_button.config(state=tk.DISABLED)
- by_radio_button.config(state=tk.DISABLED)
- # Enable Weekly time and day selectors
- weekly_day_combo.config(state=tk.NORMAL)
- weekly_hour_combo.config(state=tk.NORMAL)
- weekly_minute_combo.config(state=tk.NORMAL)
- weekly_am_pm_combo.config(state=tk.NORMAL)
- # If Monthly is selected
- elif monthly_var.get():
- # Disable Daily, Weekly, Complete at, and Complete by
- daily_checkbox.config(state=tk.DISABLED)
- weekly_checkbox.config(state=tk.DISABLED)
- at_radio_button.config(state=tk.DISABLED)
- by_radio_button.config(state=tk.DISABLED)
- # Enable Monthly day and time selectors
- monthly_day_combo.config(state=tk.NORMAL)
- monthly_hour_combo.config(state=tk.NORMAL)
- monthly_minute_combo.config(state=tk.NORMAL)
- monthly_am_pm_combo.config(state=tk.NORMAL)
- # If Complete at is selected (it does not disable Complete by)
- if at_radio_var.get():
- # Disable Daily, Weekly, and Monthly
- daily_checkbox.config(state=tk.DISABLED)
- weekly_checkbox.config(state=tk.DISABLED)
- monthly_checkbox.config(state=tk.DISABLED)
- # Enable Complete at time selectors and Add Date checkbox
- at_hour_combo.config(state=tk.NORMAL)
- at_minute_combo.config(state=tk.NORMAL)
- at_am_pm_combo.config(state=tk.NORMAL)
- at_date_checkbox.config(state=tk.NORMAL)
- # If "Add Date" for Complete at is checked, show the date selectors
- if at_date_var.get():
- at_date_frame.grid()
- else:
- at_date_frame.grid_remove()
- # If Complete by is selected (it does not disable Complete at)
- if by_radio_var.get():
- # Disable Daily, Weekly, and Monthly
- daily_checkbox.config(state=tk.DISABLED)
- weekly_checkbox.config(state=tk.DISABLED)
- monthly_checkbox.config(state=tk.DISABLED)
- # Enable Complete by time selectors and Add Date checkbox
- by_hour_combo.config(state=tk.NORMAL)
- by_minute_combo.config(state=tk.NORMAL)
- by_am_pm_combo.config(state=tk.NORMAL)
- by_date_checkbox.config(state=tk.NORMAL)
- # If "Add Date" for Complete by is checked, show the date selectors
- if by_date_var.get():
- by_date_frame.grid()
- else:
- by_date_frame.grid_remove()
- # Ensure the date frames are hidden if neither "Add Date" checkbox is checked
- if not at_date_var.get():
- at_date_frame.grid_remove()
- if not by_date_var.get():
- by_date_frame.grid_remove()
- # If neither "Complete at" nor "Complete by" is selected, ensure both date checkboxes and frames are disabled
- if not at_radio_var.get() and not by_radio_var.get():
- at_date_checkbox.config(state=tk.DISABLED)
- by_date_checkbox.config(state=tk.DISABLED)
- at_date_frame.grid_remove()
- by_date_frame.grid_remove()
- # Initialize the states to make sure the correct controls are disabled on start
- def initialize_state():
- # Disable the "Add Date" checkboxes and date frames initially
- at_date_checkbox.config(state=tk.DISABLED)
- by_date_checkbox.config(state=tk.DISABLED)
- # Disable time selectors initially
- at_hour_combo.config(state=tk.DISABLED)
- at_minute_combo.config(state=tk.DISABLED)
- at_am_pm_combo.config(state=tk.DISABLED)
- by_hour_combo.config(state=tk.DISABLED)
- by_minute_combo.config(state=tk.DISABLED)
- by_am_pm_combo.config(state=tk.DISABLED)
- # Disable recurrence time selectors initially
- daily_hour_combo.config(state=tk.DISABLED)
- daily_minute_combo.config(state=tk.DISABLED)
- daily_am_pm_combo.config(state=tk.DISABLED)
- weekly_day_combo.config(state=tk.DISABLED)
- weekly_hour_combo.config(state=tk.DISABLED)
- weekly_minute_combo.config(state=tk.DISABLED)
- weekly_am_pm_combo.config(state=tk.DISABLED)
- monthly_day_combo.config(state=tk.DISABLED)
- monthly_hour_combo.config(state=tk.DISABLED)
- monthly_minute_combo.config(state=tk.DISABLED)
- monthly_am_pm_combo.config(state=tk.DISABLED)
- # Function to get the path to the credentials.json file
- def get_credentials_path():
- """
- Returns the correct path to the 'credentials.json' file depending on whether
- the script is running as a standalone executable (PyInstaller) or as a Python script.
- """
- if hasattr(sys, '_MEIPASS'):
- # If bundled with PyInstaller, look for the file in the _MEIPASS folder
- return os.path.join(sys._MEIPASS, 'credentials.json')
- else:
- # When running normally as a script, use the current working directory
- return os.path.join(os.getcwd(), 'credentials.json')
- # Function to handle Google Login
- def google_login():
- creds = None
- SCOPES = ['https://www.googleapis.com/auth/calendar']
- credentials_path = get_credentials_path() # Get the path to credentials.json
- if not os.path.exists(credentials_path):
- # If credentials.json is missing, show an error message
- root.after(0, lambda: messagebox.showerror("Credentials Missing", "credentials.json file not found in the program directory."))
- return
- # Load credentials from the token.json file, if they exist
- token_path = 'token.json'
- if os.path.exists(token_path):
- with open(token_path, 'r') as token_file:
- creds = Credentials.from_authorized_user_info(json.load(token_file), SCOPES)
- # If there are no valid credentials, let the user log in
- if not creds or not creds.valid:
- if creds and creds.expired and creds.refresh_token:
- try:
- creds.refresh(Request()) # Refresh credentials if they're expired
- except Exception as e:
- root.after(0, lambda: messagebox.showerror("Login Error", f"Failed to refresh credentials: {e}"))
- return
- else:
- # If no credentials are available, prompt the user to log in
- try:
- flow = InstalledAppFlow.from_client_secrets_file(credentials_path, SCOPES)
- creds = flow.run_local_server(port=0)
- except Exception as e:
- root.after(0, lambda: messagebox.showerror("Login Error", f"Failed to authenticate using Google: {e}"))
- return
- # Save the credentials for the next run
- with open(token_path, 'w') as token_file:
- token_file.write(creds.to_json())
- # Now that we have valid credentials, build the Google Calendar service
- try:
- global google_service
- google_service = build('calendar', 'v3', credentials=creds)
- root.after(0, lambda: messagebox.showinfo("Login Success", "Successfully logged in to Google Calendar!"))
- except Exception as e:
- root.after(0, lambda: messagebox.showerror("Login Error", f"Failed to connect to Google Calendar: {e}"))
- # Get the local time zone (you can change this to a specific time zone if needed)
- local_tz = pytz.timezone('America/New_York') # Replace 'America/New_York' with your local time zone
- # Function to add a task to Google Calendar with recurrence
- def add_task_to_google_calendar(task, start_time, notes=None, recurrence=None, duration_minutes=60):
- """
- Add a task to Google Calendar with a start time, optional recurrence, and duration.
- """
- if google_service is None:
- messagebox.showerror("Not Logged In", "Please log in to Google Calendar first!")
- return
- # Convert start_time to the user's local time zone
- start_time = local_tz.localize(start_time)
- # Calculate end time by adding the duration to the start time (only if duration is provided)
- if duration_minutes is not None:
- end_time = start_time + timedelta(minutes=duration_minutes)
- else:
- end_time = start_time + timedelta(minutes=60) # Default to 60 minutes if no duration is specified
- # Create the basic event structure
- event = {
- 'summary': task,
- 'description': notes,
- 'start': {
- 'dateTime': start_time.isoformat(), # Ensure time zone info is included
- 'timeZone': str(local_tz), # Explicitly set the user's time zone
- },
- 'end': {
- 'dateTime': end_time.isoformat(), # Ensure time zone info is included
- 'timeZone': str(local_tz), # Explicitly set the user's time zone
- },
- 'reminders': {
- 'useDefault': False,
- 'overrides': [
- {'method': 'popup', 'minutes': 10}, # Popup reminder 10 minutes before the task
- ],
- }
- }
- # Add recurrence if provided
- if recurrence:
- event['recurrence'] = recurrence
- try:
- # Insert the event into Google Calendar
- event = google_service.events().insert(calendarId='primary', body=event).execute()
- print(f'Event created: {event.get("htmlLink")}')
- return event['id'] # Return the event ID to store it
- except Exception as e:
- print(f"An error occurred: {e}")
- messagebox.showerror("Error", f"Failed to add task to Google Calendar: {e}")
- return None
- # Function to remove task from Google Calendar
- def remove_task_from_google_calendar(event_id):
- if google_service is None:
- messagebox.showerror("Not Logged In", "Please log in to Google Calendar first!")
- return
- try:
- google_service.events().delete(calendarId='primary', eventId=event_id).execute()
- print(f"Event with ID {event_id} deleted successfully from Google Calendar.")
- except Exception as e:
- print(f"An error occurred while deleting task: {e}")
- messagebox.showerror("Error", f"Failed to delete task from Google Calendar: {e}")
- # Function to convert hour, minute, AM/PM to a time object
- def get_time_from_dropdown(hour, minute, am_pm):
- hour = int(hour)
- minute = int(minute)
- if am_pm == "PM" and hour != 12:
- hour += 12
- if am_pm == "AM" and hour == 12:
- hour = 0
- return time(hour, minute)
- # Define checkboxes for date selection
- at_date_var = tk.IntVar()
- by_date_var = tk.IntVar()
- # Function to add a task with a "Complete at" or "Complete by" time
- def add_task():
- task = task_entry.get().strip() # Get the task name from the entry field
- notes = notes_entry.get("1.0", tk.END).strip() # Get the notes from the Text widget
- complete_at_selected = at_radio_var.get()
- complete_by_selected = by_radio_var.get()
- complete_at_time = None
- complete_by_time = None
- start_time = None
- end_time = None # Used for "Complete by" and duration calculations
- duration_minutes = 60 # Default duration
- complete_at_datetime = None
- complete_by_datetime = None
- # Check for "Complete at" selection and retrieve the time
- if complete_at_selected:
- complete_at_time = get_time_from_dropdown(at_hour_combo.get(), at_minute_combo.get(), at_am_pm_combo.get())
- if at_date_var.get(): # If "Add Date" checkbox is checked
- selected_date = datetime(
- int(at_year_combo.get()),
- int(at_month_combo.get()),
- int(at_day_combo.get())
- ).date()
- else:
- selected_date = datetime.now().date() # Default to today's date
- start_time = datetime.combine(selected_date, complete_at_time)
- complete_at_datetime = start_time # Store the datetime object
- # Check for "Complete by" selection and retrieve the time
- if complete_by_selected:
- complete_by_time = get_time_from_dropdown(by_hour_combo.get(), by_minute_combo.get(), by_am_pm_combo.get())
- if by_date_var.get(): # If "Add Date" checkbox is checked
- selected_date = datetime(
- int(by_year_combo.get()),
- int(by_month_combo.get()),
- int(by_day_combo.get())
- ).date()
- else:
- selected_date = datetime.now().date() # Default to today's date
- end_time = datetime.combine(selected_date, complete_by_time)
- complete_by_datetime = end_time # Store the datetime object
- # Calculate duration if both Complete at and Complete by are selected
- if complete_at_selected:
- duration = end_time - start_time
- duration_minutes = int(duration.total_seconds() / 60) # Convert duration to minutes
- else:
- # If only Complete by is selected, set start_time based on default duration
- start_time = end_time - timedelta(minutes=duration_minutes)
- # Handle recurrence (daily, weekly, monthly)
- daily = daily_var.get()
- weekly = weekly_var.get()
- monthly = monthly_var.get()
- # Get time for recurrence options (if selected)
- daily_time = get_time_from_dropdown(daily_hour_combo.get(), daily_minute_combo.get(), daily_am_pm_combo.get()) if daily else None
- weekly_day = weekly_day_combo.get() if weekly else None
- weekly_time = get_time_from_dropdown(weekly_hour_combo.get(), weekly_minute_combo.get(), weekly_am_pm_combo.get()) if weekly else None
- monthly_day = monthly_day_combo.get() if monthly else None
- monthly_time = get_time_from_dropdown(monthly_hour_combo.get(), monthly_minute_combo.get(), monthly_am_pm_combo.get()) if monthly else None
- event_id = None # Initialize event_id
- if daily:
- # Create a datetime object for daily tasks using the selected time for today
- start_time = datetime.combine(datetime.now().date(), daily_time)
- if weekly:
- # For weekly tasks, calculate the next occurrence of the selected day
- day_mapping = {"Monday": "MO", "Tuesday": "TU", "Wednesday": "WE", "Thursday": "TH", "Friday": "FR", "Saturday": "SA", "Sunday": "SU"}
- days_ahead = (["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"].index(weekly_day) - datetime.now().weekday()) % 7
- next_weekly_date = datetime.now() + timedelta(days=days_ahead)
- # Combine the date of the next occurrence with the selected time
- start_time = datetime.combine(next_weekly_date.date(), weekly_time)
- if monthly:
- # For monthly tasks, calculate the next occurrence of the selected day of the month
- today = datetime.now()
- current_month = today.month
- current_year = today.year
- # Adjust if the selected day is not valid for the month
- try:
- if today.day > int(monthly_day):
- if current_month == 12: # Handle December to January rollover
- next_month_date = datetime(current_year + 1, 1, int(monthly_day))
- else:
- next_month_date = datetime(current_year, current_month + 1, int(monthly_day))
- else:
- # If the day is in the future, schedule for the current month
- next_month_date = datetime(current_year, current_month, int(monthly_day))
- except ValueError:
- messagebox.showerror("Invalid Date", "The selected day is invalid for the current month.")
- return
- # Combine the date of the selected day with the selected time
- start_time = datetime.combine(next_month_date.date(), monthly_time)
- # Validate the task before adding it
- if task:
- # Add the task to Google Calendar
- if google_service:
- if daily:
- event_id = add_task_to_google_calendar(task, start_time, notes, recurrence=['RRULE:FREQ=DAILY'], duration_minutes=duration_minutes)
- elif weekly:
- day_code = day_mapping[weekly_day] # Get the correct day code for the recurrence rule
- event_id = add_task_to_google_calendar(task, start_time, notes, recurrence=[f'RRULE:FREQ=WEEKLY;BYDAY={day_code}'], duration_minutes=duration_minutes)
- elif monthly:
- event_id = add_task_to_google_calendar(task, start_time, notes, recurrence=[f'RRULE:FREQ=MONTHLY;BYMONTHDAY={monthly_day}'], duration_minutes=duration_minutes)
- elif complete_at_selected or complete_by_selected:
- event_id = add_task_to_google_calendar(task, start_time, notes, duration_minutes=duration_minutes)
- else:
- if messagebox.askyesno("Not Logged In", "You are not logged in to Google Calendar. Do you want to add the task without syncing to Google Calendar?"):
- pass
- else:
- return
- # Append the task to the tasks list, including the event_id
- tasks.append((
- task, notes, complete_at_time, complete_by_time, daily, weekly, monthly,
- daily_time, weekly_day, weekly_time, monthly_day, monthly_time, False, False,
- complete_at_datetime, complete_by_datetime, event_id # Include event_id here
- ))
- update_task_listbox() # Update the UI
- task_entry.delete(0, tk.END) # Clear the task entry field
- notes_entry.delete("1.0", tk.END) # Clear the notes entry field
- save_tasks() # Save the task list
- else:
- messagebox.showwarning("Input Error", "Task cannot be empty!")
- # Function to show the notes for the selected task in a separate window
- def show_notes(event):
- listbox = event.widget
- selected_index = listbox.curselection()
- if selected_index:
- # Fetch the original index based on the listbox type (Main, Daily, Weekly, Monthly)
- if listbox == task_listbox:
- task_index = main_task_indices[selected_index[0]]
- elif listbox == daily_task_listbox:
- task_index = daily_task_indices[selected_index[0]]
- elif listbox == weekly_task_listbox:
- task_index = weekly_task_indices[selected_index[0]]
- elif listbox == monthly_task_listbox:
- task_index = monthly_task_indices[selected_index[0]]
- task_details = tasks[task_index]
- notes = task_details[1] # Get the notes associated with the task
- if notes.strip(): # Only show the window if there are notes
- # Create a new window to show the notes
- notes_window = tk.Toplevel(root)
- notes_window.title(f"Notes for {task_details[0]}")
- # Enable word wrapping in the Text widget by setting wrap="word"
- notes_text = tk.Text(notes_window, height=10, width=40, wrap="word")
- notes_text.pack(padx=10, pady=10)
- notes_text.insert(tk.END, notes)
- notes_text.config(state=tk.DISABLED) # Make the notes read-only
- else:
- messagebox.showinfo("No Notes", f"There are no notes for the task: {task_details[0]}")
- # Function to mark a task as in-progress and stop the alarm if it's playing
- def mark_task_in_progress():
- selected_task_index = task_listbox.curselection()
- selected_daily_task_index = daily_task_listbox.curselection()
- selected_weekly_task_index = weekly_task_listbox.curselection()
- selected_monthly_task_index = monthly_task_listbox.curselection()
- task_index = None
- # Check which listbox has a selected task
- if selected_task_index:
- task_index = main_task_indices[selected_task_index[0]] # Use the mapped main task index
- elif selected_daily_task_index:
- task_index = daily_task_indices[selected_daily_task_index[0]] # Use the mapped daily task index
- elif selected_weekly_task_index:
- task_index = weekly_task_indices[selected_weekly_task_index[0]] # Use the mapped weekly task index
- elif selected_monthly_task_index:
- task_index = monthly_task_indices[selected_monthly_task_index[0]] # Use the mapped monthly task index
- else:
- messagebox.showwarning("Selection Error", "Please select a task to mark as In-Progress")
- return
- if task_index is not None:
- task = tasks[task_index]
- # Mark the task as in-progress (set `in_progress` to True, `completed` to False)
- tasks[task_index] = (
- task[0], # Task name
- task[1], # Notes
- task[2], # Complete at time
- task[3], # Complete by time
- task[4], # Daily flag
- task[5], # Weekly flag
- task[6], # Monthly flag
- task[7], # Daily time
- task[8], # Weekly day
- task[9], # Weekly time
- task[10], # Monthly day
- task[11], # Monthly time
- False, # Completed flag set to False
- True, # In-progress flag set to True
- task[14], # Complete at datetime
- task[15], # Complete by datetime
- task[16] # event_id (Include this)
- )
- # Stop the alarm if it is playing
- stop_alarm() # Ensure alarm stops immediately when a task is marked in-progress
- # Update the listboxes to reflect the change
- update_task_listbox()
- save_tasks() # Save the updated tasks
- # Clear selection from all listboxes
- task_listbox.selection_clear(0, tk.END)
- daily_task_listbox.selection_clear(0, tk.END)
- weekly_task_listbox.selection_clear(0, tk.END)
- monthly_task_listbox.selection_clear(0, tk.END)
- print(f"Task '{task[0]}' marked as In-Progress.") # Debugging message
- else:
- print("No task index found for In-Progress.") # Debugging message
- # Function to mark a task as completed and stop the alarm if it's playing
- def mark_task_completed():
- selected_task_index = task_listbox.curselection()
- selected_daily_task_index = daily_task_listbox.curselection()
- selected_weekly_task_index = weekly_task_listbox.curselection()
- selected_monthly_task_index = monthly_task_listbox.curselection()
- task_index = None
- # Check which listbox has a selected task
- if selected_task_index:
- task_index = main_task_indices[selected_task_index[0]] # Use the mapped main task index
- elif selected_daily_task_index:
- task_index = daily_task_indices[selected_daily_task_index[0]] # Use the mapped daily task index
- elif selected_weekly_task_index:
- task_index = weekly_task_indices[selected_weekly_task_index[0]] # Use the mapped weekly task index
- elif selected_monthly_task_index:
- task_index = monthly_task_indices[selected_monthly_task_index[0]] # Use the mapped monthly task index
- else:
- messagebox.showwarning("Selection Error", "Please select a task to mark as completed")
- return
- if task_index is not None:
- task_data = tasks[task_index]
- # Unpack task data
- (task, notes, complete_at_time, complete_by_time, daily, weekly, monthly,
- daily_time, weekly_day, weekly_time, monthly_day, monthly_time, completed,
- in_progress, complete_at_datetime, complete_by_datetime, event_id) = task_data
- # Mark the task as completed (set `completed` to True and `in_progress` to False)
- tasks[task_index] = (
- task, notes, complete_at_time, complete_by_time, daily, weekly, monthly,
- daily_time, weekly_day, weekly_time, monthly_day, monthly_time, True, False,
- complete_at_datetime, complete_by_datetime, event_id # Repack with new status
- )
- # Stop the alarm if it is playing
- stop_alarm() # Ensure alarm stops immediately when a task is marked completed
- # Update the listboxes to reflect the change (it will remain in the list with a green checkmark)
- update_task_listbox()
- save_tasks() # Save the updated tasks
- print(f"Task '{task}' marked as completed.") # Debugging message
- # Initialize lists to map listbox selections to task indices
- daily_task_indices = []
- weekly_task_indices = []
- monthly_task_indices = []
- main_task_indices = []
- # Function to update the task listbox with colors and checkmarks for completed tasks
- # Initialize lists to map listbox selections to task indices
- daily_task_indices = []
- weekly_task_indices = []
- monthly_task_indices = []
- main_task_indices = []
- # Function to update the task listboxes with colors and checkmarks for completed tasks
- def update_task_listbox():
- # Clear existing tasks from the listboxes
- daily_task_listbox.delete(0, tk.END)
- weekly_task_listbox.delete(0, tk.END)
- monthly_task_listbox.delete(0, tk.END)
- task_listbox.delete(0, tk.END) # Clear the main task listbox
- # Clear the index mapping lists
- daily_task_indices.clear()
- weekly_task_indices.clear()
- monthly_task_indices.clear()
- main_task_indices.clear()
- # Define checkmark and colors
- checkmark = '✓'
- in_progress_marker = '▼'
- incomplete_color = 'white'
- complete_color = 'lightgreen'
- in_progress_color = 'yellow' # Dark goldenrod color
- # Function to get the sorting key for tasks
- def get_sort_key(task_data):
- (task, notes, complete_at_time, complete_by_time, daily, weekly, monthly,
- daily_time, weekly_day, weekly_time, monthly_day, monthly_time, completed,
- in_progress, complete_at_datetime, complete_by_datetime, event_id) = task_data
- now = datetime.now()
- if complete_at_datetime:
- return complete_at_datetime
- elif complete_by_datetime:
- return complete_by_datetime
- elif daily and daily_time:
- return datetime.combine(now.date(), daily_time)
- elif weekly and weekly_time:
- # Calculate the next occurrence of the weekly task
- days_ahead = (["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"].index(weekly_day) - now.weekday()) % 7
- next_weekly_date = now + timedelta(days=days_ahead)
- return datetime.combine(next_weekly_date.date(), weekly_time)
- elif monthly and monthly_time:
- # Calculate the next occurrence of the monthly task
- today = now
- current_month = today.month
- current_year = today.year
- monthly_day_int = int(monthly_day)
- try:
- if today.day > monthly_day_int:
- if current_month == 12: # Handle December to January rollover
- next_month_date = datetime(current_year + 1, 1, monthly_day_int)
- else:
- next_month_date = datetime(current_year, current_month + 1, monthly_day_int)
- else:
- next_month_date = datetime(current_year, current_month, monthly_day_int)
- return datetime.combine(next_month_date.date(), monthly_time)
- except ValueError:
- # If the day is invalid for the month (e.g., February 30), place it at the end
- return datetime.max
- else:
- return datetime.max # Tasks without a specific time are placed at the end
- # Build a list of tasks with their original indices
- tasks_with_indices = list(enumerate(tasks))
- # Sort tasks by the calculated sort key while keeping the original indices
- tasks_sorted = sorted(tasks_with_indices, key=lambda x: get_sort_key(x[1]))
- # Loop through sorted tasks and populate the respective listboxes
- for task_index, task_data in tasks_sorted:
- if len(task_data) == 17:
- (task, notes, complete_at_time, complete_by_time, daily, weekly, monthly,
- daily_time, weekly_day, weekly_time, monthly_day, monthly_time, completed,
- in_progress, complete_at_datetime, complete_by_datetime, event_id) = task_data
- else:
- continue # Skip if the task data doesn't have the right format
- # Decide the symbol and color based on the status
- if completed:
- task_str = f"{checkmark} {task}"
- color = complete_color
- elif in_progress:
- task_str = f"{in_progress_marker} {task}"
- color = in_progress_color
- else:
- task_str = task
- color = incomplete_color
- # Add task to the appropriate listbox
- if daily:
- task_str += f" [Daily at {daily_time.strftime('%I:%M %p')}]"
- daily_task_listbox.insert(tk.END, task_str)
- daily_task_listbox.itemconfig(tk.END, {'foreground': color})
- daily_task_indices.append(task_index)
- elif weekly:
- task_str += f" [Weekly on {weekly_day} at {weekly_time.strftime('%I:%M %p')}]"
- weekly_task_listbox.insert(tk.END, task_str)
- weekly_task_listbox.itemconfig(tk.END, {'foreground': color})
- weekly_task_indices.append(task_index)
- elif monthly:
- task_str += f" [Monthly on day {monthly_day} at {monthly_time.strftime('%I:%M %p')}]"
- monthly_task_listbox.insert(tk.END, task_str)
- monthly_task_listbox.itemconfig(tk.END, {'foreground': color})
- monthly_task_indices.append(task_index)
- else:
- # For Main tasks (Complete at/by)
- if complete_at_datetime:
- task_str += f" (Complete at: {complete_at_datetime.strftime('%Y-%m-%d %I:%M %p')})"
- if complete_by_datetime:
- task_str += f" (Complete by: {complete_by_datetime.strftime('%Y-%m-%d %I:%M %p')})"
- task_listbox.insert(tk.END, task_str)
- task_listbox.itemconfig(tk.END, {'foreground': color})
- main_task_indices.append(task_index)
- print("Listboxes updated with sorted tasks, colors, and checkmarks.") # Debug statement
- # Function to remove a task and its associated Google Calendar events
- def remove_task():
- # Capture the selection index before any potential unhighlighting
- selected_task_index = task_listbox.curselection()
- selected_daily_task_index = daily_task_listbox.curselection()
- selected_weekly_task_index = weekly_task_listbox.curselection()
- selected_monthly_task_index = monthly_task_listbox.curselection()
- task_index_to_remove = None
- # Find which task is selected and map it to the original task index
- if selected_task_index:
- task_index_to_remove = main_task_indices[selected_task_index[0]] # Use the mapped main task index
- elif selected_daily_task_index:
- task_index_to_remove = daily_task_indices[selected_daily_task_index[0]] # Use the mapped daily task index
- elif selected_weekly_task_index:
- task_index_to_remove = weekly_task_indices[selected_weekly_task_index[0]] # Use the mapped weekly task index
- elif selected_monthly_task_index:
- task_index_to_remove = monthly_task_indices[selected_monthly_task_index[0]] # Use the mapped monthly task index
- # If no task was selected, show an error message
- if task_index_to_remove is None:
- messagebox.showwarning("Selection Error", "Please select a task to remove")
- return
- # Get the task to remove
- task_to_remove = tasks[task_index_to_remove]
- event_id = task_to_remove[16] # Assuming event_id is at index 16
- # Remove the task from Google Calendar (if it has an associated event ID)
- if event_id:
- # Run deletion in a separate thread to ensure UI remains responsive
- threading.Thread(target=remove_task_from_google_calendar, args=(event_id,)).start()
- # Remove the task from the tasks list using the mapped index
- tasks.pop(task_index_to_remove)
- # Update the listbox UI and save tasks
- update_task_listbox()
- save_tasks()
- # Stop the alarm if the task being removed is currently causing the alarm to play
- global alarm_playing
- if alarm_playing:
- print(f"[DEBUG] Stopping alarm for removed task '{task_to_remove[0]}'")
- stop_alarm() # Stop the alarm sound
- # Clear the selection AFTER the task is removed
- task_listbox.selection_clear(0, tk.END)
- daily_task_listbox.selection_clear(0, tk.END)
- weekly_task_listbox.selection_clear(0, tk.END)
- monthly_task_listbox.selection_clear(0, tk.END)
- print(f"Task '{task_to_remove[0]}' removed successfully.") # Debugging message
- # Function to check if the user is logged in
- def is_logged_in():
- return os.path.exists(TOKEN_FILE)
- # Perform login check when the program starts
- def check_login_status():
- if is_logged_in():
- google_login() # Automatically log in if token is found
- else:
- messagebox.showinfo("Login Required", "Please log in to Google Calendar")
- # Run the login check in a separate thread
- def check_login_status_in_background():
- login_thread = threading.Thread(target=check_login_status)
- login_thread.start()
- # Function to clear selection of all listboxes
- def clear_all_selections(event):
- # Get the widget that was clicked
- widget = event.widget
- # List of widgets that should not trigger the clearing of selections
- excluded_widgets = [remove_task_button, add_task_button, mark_completed_button, mark_in_progress_button]
- # Check if the click was outside of the listboxes and excluded buttons
- if widget not in [task_listbox, daily_task_listbox, weekly_task_listbox, monthly_task_listbox] + excluded_widgets:
- task_listbox.selection_clear(0, tk.END)
- daily_task_listbox.selection_clear(0, tk.END)
- weekly_task_listbox.selection_clear(0, tk.END)
- monthly_task_listbox.selection_clear(0, tk.END)
- # Function to reset daily, weekly, and monthly tasks, even if in-progress
- def reset_tasks():
- for i, task_data in enumerate(tasks):
- # Unpack task data
- (task, notes, complete_at_time, complete_by_time, daily, weekly, monthly,
- daily_time, weekly_day, weekly_time, monthly_day, monthly_time, completed,
- in_progress, complete_at_datetime, complete_by_datetime, event_id) = task_data
- # Reset the completed and in_progress flags for all recurring tasks
- if daily or weekly or monthly:
- tasks[i] = (task, notes, complete_at_time, complete_by_time, daily, weekly, monthly,
- daily_time, weekly_day, weekly_time, monthly_day, monthly_time, False, False,
- complete_at_datetime, complete_by_datetime, event_id)
- print(f"Recurring task '{task}' has been reset.")
- save_tasks() # Save the updated tasks after resetting
- update_task_listbox() # Update the UI
- # Function to check for tasks that need to alarm at the specified time and reset tasks at midnight
- def check_task_times():
- last_checked_day = datetime.now().day
- while True:
- now = datetime.now()
- current_time = now.time().replace(second=0, microsecond=0)
- current_day = now.day
- print(f"[DEBUG] Current time: {current_time}") # Debugging: Print current time
- # Reset tasks at midnight or at the start of the next day
- if current_day != last_checked_day:
- print("[DEBUG] Midnight passed, resetting tasks...")
- reset_tasks()
- last_checked_day = current_day
- # Check task alarms
- for i, task_data in enumerate(tasks):
- if len(task_data) == 17: # Ensure task data has the expected structure
- (task, notes, complete_at_time, complete_by_time, daily, weekly, monthly,
- daily_time, weekly_day, weekly_time, monthly_day, monthly_time, completed,
- in_progress, complete_at_datetime, complete_by_datetime, event_id) = task_data
- # Print debug information about each task
- print(f"[DEBUG] Checking task: {task}, In Progress: {in_progress}, Completed: {completed}")
- # "Complete at" task check
- if not in_progress and not completed and complete_at_datetime:
- complete_at_date_check = complete_at_datetime.date()
- complete_at_time_check = complete_at_datetime.time().replace(second=0, microsecond=0)
- # Compare both the date and the time
- if now.date() == complete_at_date_check and current_time == complete_at_time_check:
- print(f"[DEBUG] Complete at alarm triggered for: {task}")
- trigger_alarm(task, "Complete at")
- # "Complete by" task check
- if not in_progress and not completed and complete_by_datetime:
- complete_by_date_check = complete_by_datetime.date()
- complete_by_time_check = complete_by_datetime.time().replace(second=0, microsecond=0)
- # Compare both the date and the time
- if now.date() == complete_by_date_check and current_time == complete_by_time_check:
- print(f"[DEBUG] Complete by alarm triggered for: {task}")
- trigger_alarm(task, "Complete by")
- # Daily recurrence check
- if not in_progress and not completed and daily and daily_time:
- daily_time_check = daily_time.replace(second=0, microsecond=0)
- if current_time == daily_time_check:
- print(f"[DEBUG] Daily alarm triggered for: {task}")
- trigger_alarm(task, "Daily")
- # Weekly recurrence check
- if not in_progress and not completed and weekly and now.strftime("%A") == weekly_day and weekly_time:
- weekly_time_check = weekly_time.replace(second=0, microsecond=0)
- if current_time == weekly_time_check:
- print(f"[DEBUG] Weekly alarm triggered for: {task}")
- trigger_alarm(task, "Weekly")
- # Monthly recurrence check
- if not in_progress and not completed and monthly and now.day == int(monthly_day) and monthly_time:
- monthly_time_check = monthly_time.replace(second=0, microsecond=0)
- if current_time == monthly_time_check:
- print(f"[DEBUG] Monthly alarm triggered for: {task}")
- trigger_alarm(task, "Monthly")
- # Sleep for a short duration to avoid constant polling
- t.sleep(30) # Check every 30 seconds
- # Function to start the background thread to check task times
- def start_time_checker():
- time_checker_thread = threading.Thread(target=check_task_times, daemon=True)
- time_checker_thread.start()
- # Function to manage the layout better by grouping inputs in a Frame
- def create_time_selection_frame(parent, hour_var, minute_var, am_pm_var, is_at):
- """Create a frame with time selection widgets (hour, minute, AM/PM) for compact layout."""
- frame = tk.Frame(parent)
- hour_combo = ttk.Combobox(frame, values=[str(i) for i in range(1, 13)], width=3)
- hour_combo.set(hour_var)
- hour_combo.grid(row=0, column=0, padx=(0, 1), pady=0)
- minute_combo = ttk.Combobox(frame, values=[f"{i:02}" for i in range(0, 60)], width=3)
- minute_combo.set(minute_var)
- minute_combo.grid(row=0, column=1, padx=(1, 1), pady=0)
- am_pm_combo = ttk.Combobox(frame, values=["AM", "PM"], width=3)
- am_pm_combo.set(am_pm_var)
- am_pm_combo.grid(row=0, column=2, padx=(1, 0), pady=0)
- if is_at:
- global at_hour_combo, at_minute_combo, at_am_pm_combo
- at_hour_combo, at_minute_combo, at_am_pm_combo = hour_combo, minute_combo, am_pm_combo
- else:
- global by_hour_combo, by_minute_combo, by_am_pm_combo
- by_hour_combo, by_minute_combo, by_am_pm_combo = hour_combo, minute_combo, am_pm_combo
- return frame
- def create_date_selection_frame(parent):
- """Create a frame with month, day, and year selection dropdowns for date."""
- frame = tk.Frame(parent)
- # Month dropdown
- month_combo = ttk.Combobox(frame, values=[f"{i:02}" for i in range(1, 13)], width=3)
- month_combo.set(datetime.now().strftime('%m'))
- month_combo.grid(row=0, column=0, padx=(0, 1), pady=0)
- # Day dropdown
- day_combo = ttk.Combobox(frame, values=[f"{i:02}" for i in range(1, 32)], width=3)
- day_combo.set(datetime.now().strftime('%d'))
- day_combo.grid(row=0, column=1, padx=(1, 1), pady=0)
- # Year dropdown
- current_year = datetime.now().year
- year_combo = ttk.Combobox(frame, values=[str(i) for i in range(current_year, current_year + 5)], width=5)
- year_combo.set(str(current_year))
- year_combo.grid(row=0, column=2, padx=(1, 0), pady=0)
- return frame, month_combo, day_combo, year_combo
- # Function to toggle the visibility of the date selection frame
- def toggle_date_selection(at_checked, date_frame):
- if at_checked.get():
- date_frame.grid() # Show the date frame
- else:
- date_frame.grid_remove() # Hide the date frame
- available_styles = root.style.theme_names()
- print(available_styles)
- # Create UI Elements
- # Define variables for "Complete at" and "Complete by"
- at_radio_var = ttk.IntVar()
- by_radio_var = ttk.IntVar()
- at_date_var = ttk.IntVar()
- by_date_var = ttk.IntVar()
- # Adjust the grid configuration for proper alignment and centering
- root.grid_columnconfigure(0, weight=1)
- root.grid_columnconfigure(1, weight=1)
- root.grid_columnconfigure(2, weight=1)
- root.grid_columnconfigure(3, weight=1)
- # Create a central Frame to hold both the time and date components for "Complete at" and "Complete by"
- login_button = ttk.Button(root, text="Login to Google", command=google_login, bootstyle="primary")
- login_button.grid(row=1, column=0, padx=20, pady=(10, 10), sticky="w")
- # Task entry frame
- task_notes_frame = ttk.Frame(root)
- task_notes_frame.grid(row=1, column=1, columnspan=2, padx=10, pady=(10, 10), sticky="ew")
- task_label = ttk.Label(task_notes_frame, text="Task:", bootstyle="info")
- task_label.grid(row=0, column=0, padx=(10, 0), pady=(10, 0), sticky="e")
- task_entry = ttk.Entry(task_notes_frame, width=50)
- task_entry.grid(row=0, column=1, padx=(5, 10), pady=(10, 0), sticky="ew")
- notes_label = ttk.Label(task_notes_frame, text="Notes:", bootstyle="info")
- notes_label.grid(row=1, column=0, padx=(10, 0), pady=(5, 0), sticky="ne")
- notes_entry = scrolledtext.ScrolledText(task_notes_frame, height=5, width=50)
- notes_entry.grid(row=1, column=1, padx=(5, 10), pady=(5, 5), sticky="ew")
- # Create the at_date_frame and by_date_frame globally
- global at_date_frame, by_date_frame
- # Create the central_frame (Complete at/by section)
- central_frame = ttk.Frame(root)
- central_frame.grid(row=2, column=1, columnspan=2, padx=10, pady=10, sticky="n")
- # "Complete at" date selection
- at_date_frame, at_month_combo, at_day_combo, at_year_combo = create_date_selection_frame(central_frame)
- at_date_frame.grid(row=0, column=4, padx=(5, 5), pady=0, sticky="w")
- at_date_frame.grid_remove() # Initially hidden until the checkbox is selected
- # "Complete by" date selection
- by_date_frame, by_month_combo, by_day_combo, by_year_combo = create_date_selection_frame(central_frame)
- by_date_frame.grid(row=1, column=4, padx=(5, 5), pady=0, sticky="w")
- by_date_frame.grid_remove() # Initially hidden until the checkbox is selected
- # "Complete at" time selection and date
- at_radio_button = ttk.Checkbutton(central_frame, text="Complete at", variable=at_radio_var, command=update_state, bootstyle="primary-round-toggle")
- at_radio_button.grid(row=0, column=0, padx=(5, 5), pady=0, sticky="w")
- at_time_frame = create_time_selection_frame(central_frame, "12", "00", "AM", is_at=True)
- at_time_frame.grid(row=0, column=1, padx=(5, 5), pady=0, sticky="w")
- at_date_checkbox = ttk.Checkbutton(central_frame, text="Add Date", variable=at_date_var, command=lambda: toggle_date_selection(at_date_var, at_date_frame), bootstyle="primary-round-toggle")
- at_date_checkbox.grid(row=0, column=3, padx=(5, 5), pady=0, sticky="w")
- # "Complete by" time selection and date
- by_radio_button = ttk.Checkbutton(central_frame, text="Complete by", variable=by_radio_var, command=update_state, bootstyle="primary-round-toggle")
- by_radio_button.grid(row=1, column=0, padx=(5, 5), pady=0, sticky="w")
- by_time_frame = create_time_selection_frame(central_frame, "12", "00", "AM", is_at=False)
- by_time_frame.grid(row=1, column=1, padx=(5, 5), pady=0, sticky="w")
- by_date_checkbox = ttk.Checkbutton(central_frame, text="Add Date", variable=by_date_var, command=lambda: toggle_date_selection(by_date_var, by_date_frame), bootstyle="primary-round-toggle")
- by_date_checkbox.grid(row=1, column=3, padx=(5, 5), pady=0, sticky="w")
- # Create the separator line between Complete at/by and Recurrence options
- separator = ttk.Separator(root, orient='horizontal')
- separator.grid(row=3, column=0, columnspan=4, sticky="ew", padx=10, pady=10)
- # Recurrence checkboxes and day selectors
- daily_var = ttk.IntVar()
- weekly_var = ttk.IntVar()
- monthly_var = ttk.IntVar()
- # Frame for Recurrence settings
- recurrence_frame = ttk.Frame(root)
- recurrence_frame.grid(row=4, column=1, columnspan=2, padx=5, pady=5, sticky="n")
- # Daily recurrence
- daily_checkbox = ttk.Checkbutton(recurrence_frame, text="Daily", variable=daily_var, command=update_state, bootstyle="primary-round-toggle")
- daily_checkbox.grid(row=0, column=0, padx=5, pady=5, sticky="w")
- daily_hour_combo = ttk.Combobox(recurrence_frame, values=[str(i) for i in range(1, 13)], width=3, bootstyle="superhero")
- daily_hour_combo.set("12")
- daily_hour_combo.grid(row=0, column=1, padx=(2, 2), pady=5, sticky="e")
- daily_minute_combo = ttk.Combobox(recurrence_frame, values=[f"{i:02}" for i in range(0, 60)], width=3)
- daily_minute_combo.set("00")
- daily_minute_combo.grid(row=0, column=2, padx=(2, 2), pady=5, sticky="w")
- daily_am_pm_combo = ttk.Combobox(recurrence_frame, values=["AM", "PM"], width=3)
- daily_am_pm_combo.set("AM")
- daily_am_pm_combo.grid(row=0, column=3, padx=(2, 5), pady=5, sticky="w")
- # Weekly recurrence
- weekly_checkbox = ttk.Checkbutton(recurrence_frame, text="Weekly", variable=weekly_var, command=update_state, bootstyle="primary-round-toggle")
- weekly_checkbox.grid(row=1, column=0, padx=5, pady=5, sticky="w")
- weekly_day_combo = ttk.Combobox(recurrence_frame, values=["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"], width=10)
- weekly_day_combo.set("Monday")
- weekly_day_combo.grid(row=1, column=1, padx=(2, 2), pady=5, sticky="w")
- weekly_hour_combo = ttk.Combobox(recurrence_frame, values=[str(i) for i in range(1, 13)], width=3)
- weekly_hour_combo.set("12")
- weekly_hour_combo.grid(row=1, column=2, padx=(2, 2), pady=5, sticky="w")
- weekly_minute_combo = ttk.Combobox(recurrence_frame, values=[f"{i:02}" for i in range(0, 60)], width=3)
- weekly_minute_combo.set("00")
- weekly_minute_combo.grid(row=1, column=3, padx=(2, 2), pady=5, sticky="w")
- weekly_am_pm_combo = ttk.Combobox(recurrence_frame, values=["AM", "PM"], width=3)
- weekly_am_pm_combo.set("AM")
- weekly_am_pm_combo.grid(row=1, column=4, padx=(2, 5), pady=5, sticky="w")
- # Monthly recurrence
- monthly_checkbox = ttk.Checkbutton(recurrence_frame, text="Monthly", variable=monthly_var, command=update_state, bootstyle="primary-round-toggle")
- monthly_checkbox.grid(row=2, column=0, padx=5, pady=5, sticky="w")
- monthly_day_combo = ttk.Combobox(recurrence_frame, values=[str(i) for i in range(1, 32)], width=5)
- monthly_day_combo.set("1")
- monthly_day_combo.grid(row=2, column=1, padx=(2, 2), pady=5, sticky="e")
- monthly_hour_combo = ttk.Combobox(recurrence_frame, values=[str(i) for i in range(1, 13)], width=3)
- monthly_hour_combo.set("12")
- monthly_hour_combo.grid(row=2, column=2, padx=(2, 2), pady=5, sticky="w")
- monthly_minute_combo = ttk.Combobox(recurrence_frame, values=[f"{i:02}" for i in range(0, 60)], width=3)
- monthly_minute_combo.set("00")
- monthly_minute_combo.grid(row=2, column=3, padx=(2, 2), pady=5, sticky="w")
- monthly_am_pm_combo = ttk.Combobox(recurrence_frame, values=["AM", "PM"], width=3)
- monthly_am_pm_combo.set("AM")
- monthly_am_pm_combo.grid(row=2, column=4, padx=(2, 5), pady=5, sticky="w")
- # Task list frame with scrollbars
- task_frame = ttk.Frame(root)
- task_frame.grid(row=5, column=0, columnspan=4, padx=10, pady=10, sticky="nsew")
- # Labels for task types
- daily_label = ttk.Label(task_frame, text="Daily Tasks", bootstyle="info")
- daily_label.grid(row=0, column=0, padx=5, pady=5)
- weekly_label = ttk.Label(task_frame, text="Weekly Tasks", bootstyle="info")
- weekly_label.grid(row=0, column=1, padx=5, pady=5)
- monthly_label = ttk.Label(task_frame, text="Monthly Tasks", bootstyle="info")
- monthly_label.grid(row=0, column=2, padx=5, pady=5)
- main_label = ttk.Label(task_frame, text="Main Tasks (Complete at/by)", bootstyle="info")
- main_label.grid(row=0, column=3, padx=5, pady=5)
- # Create task listboxes
- daily_task_listbox = tk.Listbox(task_frame, height=25, width=70)
- daily_task_listbox.grid(row=1, column=0, padx=5, pady=5, sticky="nsew")
- daily_scrollbar = ttk.Scrollbar(task_frame, orient="vertical")
- daily_scrollbar.grid(row=1, column=0, sticky="nse")
- daily_task_listbox.config(yscrollcommand=daily_scrollbar.set)
- daily_scrollbar.config(command=daily_task_listbox.yview)
- weekly_task_listbox = tk.Listbox(task_frame, height=25, width=70)
- weekly_task_listbox.grid(row=1, column=1, padx=5, pady=5, sticky="nsew")
- weekly_scrollbar = ttk.Scrollbar(task_frame, orient="vertical")
- weekly_scrollbar.grid(row=1, column=1, sticky="nse")
- weekly_task_listbox.config(yscrollcommand=weekly_scrollbar.set)
- weekly_scrollbar.config(command=weekly_task_listbox.yview)
- monthly_task_listbox = tk.Listbox(task_frame, height=25, width=70)
- monthly_task_listbox.grid(row=1, column=2, padx=5, pady=5, sticky="nsew")
- monthly_scrollbar = ttk.Scrollbar(task_frame, orient="vertical")
- monthly_scrollbar.grid(row=1, column=2, sticky="nse")
- monthly_task_listbox.config(yscrollcommand=monthly_scrollbar.set)
- monthly_scrollbar.config(command=monthly_task_listbox.yview)
- task_listbox = tk.Listbox(task_frame, height=25, width=90)
- task_listbox.grid(row=1, column=3, padx=5, pady=5, sticky="nsew")
- main_scrollbar = ttk.Scrollbar(task_frame, orient="vertical")
- main_scrollbar.grid(row=1, column=3, sticky="nse")
- task_listbox.config(yscrollcommand=main_scrollbar.set)
- main_scrollbar.config(command=task_listbox.yview)
- # Button to add tasks
- button_frame = ttk.Frame(root)
- button_frame.grid(row=6, column=0, columnspan=4)
- add_task_button = ttk.Button(button_frame, text="Add Task", command=add_task, bootstyle="success")
- add_task_button.grid(row=0, column=0, padx=5, pady=10)
- mark_in_progress_button = ttk.Button(button_frame, text="Mark In-Progress", command=mark_task_in_progress, bootstyle="warning")
- mark_in_progress_button.grid(row=0, column=1, padx=5, pady=10)
- mark_completed_button = ttk.Button(button_frame, text="Mark Completed", command=mark_task_completed, bootstyle="succcess")
- mark_completed_button.grid(row=0, column=2, padx=5, pady=10)
- remove_task_button = ttk.Button(button_frame, text="Remove Task", command=remove_task, bootstyle="danger")
- remove_task_button.grid(row=0, column=3, padx=5, pady=10)
- # Bind double-click event to listboxes to show task notes
- task_listbox.bind("<Double-Button-1>", show_notes)
- daily_task_listbox.bind("<Double-Button-1>", show_notes)
- weekly_task_listbox.bind("<Double-Button-1>", show_notes)
- monthly_task_listbox.bind("<Double-Button-1>", show_notes)
- # Bind the click event on the root window to clear task selection
- root.bind("<Button-1>", clear_all_selections)
- # Allow natural selection behavior in listboxes
- task_listbox.bind("<Button-1>", lambda e: task_listbox.selection_anchor(task_listbox.nearest(e.y)))
- daily_task_listbox.bind("<Button-1>", lambda e: daily_task_listbox.selection_anchor(daily_task_listbox.nearest(e.y)))
- weekly_task_listbox.bind("<Button-1>", lambda e: weekly_task_listbox.selection_anchor(weekly_task_listbox.nearest(e.y)))
- monthly_task_listbox.bind("<Button-1>", lambda e: monthly_task_listbox.selection_anchor(monthly_task_listbox.nearest(e.y)))
- # Call this function right after creating all the widgets to set the initial state
- initialize_state()
- # Call the login check on startup in a separate thread
- check_login_status_in_background()
- # Load tasks on startup
- load_tasks()
- # Start the Tkinter event loop
- start_time_checker()
- root.mainloop()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement