added customtkinter

This commit is contained in:
Blake Ridgway 2025-04-02 13:51:15 -05:00
parent 2fa75da0db
commit 702ddf3802
2 changed files with 101 additions and 148 deletions

View file

@ -1 +1,2 @@
customtkinter
reportlab reportlab

View file

@ -1,3 +1,4 @@
import customtkinter as ctk
import tkinter as tk import tkinter as tk
from tkinter import ttk from tkinter import ttk
import time import time
@ -9,51 +10,30 @@ from reportlab.lib import colors
from reportlab.platypus import Table, TableStyle, Paragraph from reportlab.platypus import Table, TableStyle, Paragraph
from reportlab.lib.styles import getSampleStyleSheet from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import inch from reportlab.lib.units import inch
import sys # Import the sys module import sys
class TimeLogix: class TimeLogix:
def __init__(self, root): def __init__(self):
self.root = root self.root = ctk.CTk()
self.root.title("TimeLogix Application") self.root.title("TimeLogix")
# Styling # Theme Configuration
self.bg_color = "#f0f0f0" # Light gray background ctk.set_appearance_mode("Dark") # Or "Light", "System"
self.font_family = "Segoe UI" # Modern font ctk.set_default_color_theme("blue")
self.font_size = 10
self.button_bg = "#4CAF50" # Green button background
self.button_fg = "white" # White button text
self.button_width = 15 # Consistent button width
self.root.configure(bg=self.bg_color) # --- Styling ---
self.font_family = "Segoe UI"
style = ttk.Style() self.font_size = 12
style.configure(
"TLabel", background=self.bg_color, font=(self.font_family, self.font_size)
)
style.configure(
"TButton",
background=self.button_bg,
foreground=self.button_fg,
font=(self.font_family, self.font_size),
padding=5,
width=self.button_width, # Set button width here
)
style.configure(
"TCombobox", background="white", font=(self.font_family, self.font_size)
)
style.configure(
"TEntry", background="white", font=(self.font_family, self.font_size)
)
style.configure("My.TFrame", background=self.bg_color) # Frame style
# --- App Data ---
self.project_file = "projects.txt"
self.projects = self.load_projects()
self.is_running = False self.is_running = False
self.start_time = None self.start_time = None
self.elapsed_time = 0 self.elapsed_time = 0
self.timer_id = None self.timer_id = None
self.log_entries = [] self.log_entries = []
self.project_file = "projects.txt"
self.projects = self.load_projects()
self.invoice_number = 1 self.invoice_number = 1
self.company_name = "Your Company Name" self.company_name = "Your Company Name"
self.company_address = "123 Main St, Anytown, USA" self.company_address = "123 Main St, Anytown, USA"
@ -61,123 +41,98 @@ class TimeLogix:
self.client_address = "Client Address" self.client_address = "Client Address"
self.hourly_rate = 60.00 self.hourly_rate = 60.00
# UI elements # --- Scrollable Frame ---
self.task_label = ttk.Label(root, text="Task Description:") self.scrollable_frame = ctk.CTkScrollableFrame(self.root, width=450, height=600) # Consider making height adaptable
self.task_label.grid(row=0, column=0, sticky=tk.W, padx=10, pady=5) self.scrollable_frame.pack(fill="both", expand=True, padx=10, pady=10)
self.task_entry = ttk.Entry(root, width=40)
self.task_entry.grid(row=0, column=1, sticky=tk.W, padx=10, pady=5)
self.project_label = ttk.Label(root, text="Project:") # --- UI Elements ---
self.project_label.grid(row=1, column=0, sticky=tk.W, padx=10, pady=5) self.task_label = ctk.CTkLabel(self.scrollable_frame, text="Task Description:", font=(self.font_family, self.font_size))
self.project_combo = ttk.Combobox(root, values=self.projects) self.task_label.pack(pady=(10, 2))
self.project_combo.grid(row=1, column=1, sticky=tk.W, padx=10, pady=5)
self.task_entry = ctk.CTkEntry(self.scrollable_frame, width=250)
self.task_entry.pack(pady=(2, 10))
self.project_label = ctk.CTkLabel(self.scrollable_frame, text="Project:", font=(self.font_family, self.font_size))
self.project_label.pack(pady=(10, 2))
self.project_combo = ctk.CTkComboBox(self.scrollable_frame, values=self.projects, width=250)
self.project_combo.pack(pady=(2, 10))
if self.projects: if self.projects:
self.project_combo.set(self.projects[0]) self.project_combo.set(self.projects[0])
self.time_label = ttk.Label( self.time_label = ctk.CTkLabel(self.scrollable_frame, text="00:00:00", font=(self.font_family, 36))
root, text="00:00:00", font=(self.font_family, 36) self.time_label.pack(pady=(15, 20))
) # Larger time
self.time_label.grid(row=2, column=0, columnspan=2, pady=15)
self.start_stop_button = ttk.Button( self.start_stop_button = ctk.CTkButton(self.scrollable_frame, text="Start", command=self.toggle_timer, corner_radius=8)
root, text="Start", command=self.toggle_timer self.start_stop_button.pack(pady=(5, 20))
)
self.start_stop_button.grid(row=3, column=0, columnspan=2, pady=10)
self.log_label = ttk.Label(root, text="Log Entries:") self.log_label = ctk.CTkLabel(self.scrollable_frame, text="Log Entries:", font=(self.font_family, self.font_size))
self.log_label.grid(row=4, column=0, sticky=tk.W, padx=10, pady=5) self.log_label.pack(pady=(10, 2))
self.log_text = tk.Text( self.log_text = ctk.CTkTextbox(self.scrollable_frame, width=400, height=100, font=(self.font_family, self.font_size))
root, self.log_text.pack(pady=5, padx=10, fill='both', expand=True)
height=10,
width=50,
bg="white",
font=(self.font_family, self.font_size),
)
self.log_text.grid(row=5, column=0, columnspan=2, padx=10, pady=5)
# Add Project UI self.new_project_label = ctk.CTkLabel(self.scrollable_frame, text="New Project:", font=(self.font_family, self.font_size))
self.new_project_label = ttk.Label(root, text="New Project:") self.new_project_label.pack(pady=(10, 2))
self.new_project_label.grid(row=6, column=0, sticky=tk.W, padx=10, pady=5)
self.new_project_entry = ttk.Entry(root, width=30)
self.new_project_entry.grid(row=6, column=1, sticky=tk.W, padx=10, pady=5)
self.add_project_button = ttk.Button( self.new_project_entry = ctk.CTkEntry(self.scrollable_frame, width=250)
root, text="Add Project", command=self.add_project self.new_project_entry.pack(pady=(2, 10))
)
self.add_project_button.grid(row=7, column=0, columnspan=2, pady=5)
# Button Grouping self.add_project_button = ctk.CTkButton(self.scrollable_frame, text="Add Project", command=self.add_project, corner_radius=8)
button_frame = ttk.Frame(root, padding=10, style="My.TFrame") self.add_project_button.pack(pady=(5, 15))
button_frame.grid(row=8, column=0, columnspan=2, pady=10)
self.export_csv_button = ttk.Button( # --- Button Frame ---
button_frame, text="Export to CSV", command=self.export_to_csv button_frame = ctk.CTkFrame(self.scrollable_frame)
) button_frame.pack(pady=(10, 15))
self.export_csv_button.grid(row=0, column=0, padx=5, pady=5)
self.export_pdf_button = ttk.Button( self.export_csv_button = ctk.CTkButton(button_frame, text="Export to CSV", command=self.export_to_csv, corner_radius=8)
button_frame, text="Export to PDF", command=self.export_to_pdf self.export_csv_button.pack(side="left", padx=5, pady=5) # Using side="left" for horizontal layout
)
self.export_pdf_button.grid(row=0, column=1, padx=5, pady=5)
self.exit_button = ttk.Button( # Create Exit button self.export_pdf_button = ctk.CTkButton(button_frame, text="Export to PDF", command=self.export_to_pdf, corner_radius=8)
button_frame, text="Exit", command=self.exit_app self.export_pdf_button.pack(side="left", padx=5, pady=5)
)
self.exit_button.grid(row=0, column=2, padx=5, pady=5) # Place in button_frame
self.total_time_button = ttk.Button( self.exit_button = ctk.CTkButton(button_frame, text="Exit", command=self.exit_app, corner_radius=8)
root, self.exit_button.pack(side="left", padx=5, pady=5)
text="Calculate Total Time",
command=self.calculate_total_time,
width=20, # Set specific width for this button
)
self.total_time_button.grid(row=9, column=0, columnspan=2, pady=5)
self.total_time_label = ttk.Label(root, text="Total Time: 00:00:00") self.total_time_button = ctk.CTkButton(self.scrollable_frame, text="Calculate Total Time", command=self.calculate_total_time, corner_radius=8)
self.total_time_label.grid(row=10, column=0, columnspan=2, pady=5) self.total_time_button.pack(pady=(5, 15))
# Settings UI self.total_time_label = ctk.CTkLabel(self.scrollable_frame, text="Total Time: 00:00:00", font=(self.font_family, self.font_size))
self.company_name_label = ttk.Label(root, text="Company Name:") self.total_time_label.pack(pady=(5, 15))
self.company_name_label.grid(row=11, column=0, sticky=tk.W, padx=10, pady=5)
self.company_name_entry = ttk.Entry(root, width=30)
self.company_name_entry.grid(row=11, column=1, sticky=tk.W, padx=10, pady=5)
self.company_name_entry.insert(0, self.company_name)
self.company_address_label = ttk.Label(root, text="Company Address:") # --- Settings UI ---
self.company_address_label.grid(row=12, column=0, sticky=tk.W, padx=10, pady=5) self.company_name_label = ctk.CTkLabel(self.scrollable_frame, text="Company Name:", font=(self.font_family, self.font_size))
self.company_address_entry = ttk.Entry(root, width=30) self.company_name_label.pack(pady=(10, 2))
self.company_address_entry.grid(row=12, column=1, sticky=tk.W, padx=10, pady=5)
self.company_address_entry.insert(0, self.company_address)
self.client_name_label = ttk.Label(root, text="Client Name:") self.company_name_entry = ctk.CTkEntry(self.scrollable_frame, width=250)
self.client_name_label.grid(row=13, column=0, sticky=tk.W, padx=10, pady=5) self.company_name_entry.pack(pady=(2, 10))
self.client_name_entry = ttk.Entry(root, width=30)
self.client_name_entry.grid(row=13, column=1, sticky=tk.W, padx=10, pady=5)
self.client_name_entry.insert(0, self.client_name)
self.client_address_label = ttk.Label(root, text="Client Address:") self.company_address_label = ctk.CTkLabel(self.scrollable_frame, text="Company Address:", font=(self.font_family, self.font_size))
self.client_address_label.grid(row=14, column=0, sticky=tk.W, padx=10, pady=5) self.company_address_label.pack(pady=(10, 2))
self.client_address_entry = ttk.Entry(root, width=30)
self.client_address_entry.grid(row=14, column=1, sticky=tk.W, padx=10, pady=5)
self.client_address_entry.insert(0, self.client_address)
self.hourly_rate_label = ttk.Label(root, text="Hourly Rate:") self.company_address_entry = ctk.CTkEntry(self.scrollable_frame, width=250)
self.hourly_rate_label.grid(row=15, column=0, sticky=tk.W, padx=10, pady=5) self.company_address_entry.pack(pady=(2, 10))
self.hourly_rate_entry = ttk.Entry(root, width=10)
self.hourly_rate_entry.grid(row=15, column=1, sticky=tk.W, padx=10, pady=5)
self.hourly_rate_entry.insert(0, str(self.hourly_rate))
self.update_settings_button = ttk.Button( self.client_name_label = ctk.CTkLabel(self.scrollable_frame, text="Client Name:", font=(self.font_family, self.font_size))
root, text="Update Settings", command=self.update_settings self.client_name_label.pack(pady=(10, 2))
)
self.update_settings_button.grid(row=16, column=0, columnspan=2, pady=10)
# Configure grid weights to make the layout expand self.client_name_entry = ctk.CTkEntry(self.scrollable_frame, width=250)
for i in range(17): self.client_name_entry.pack(pady=(2, 10))
root.grid_rowconfigure(i, weight=1)
root.grid_columnconfigure(0, weight=1) self.client_address_label = ctk.CTkLabel(self.scrollable_frame, text="Client Address:", font=(self.font_family, self.font_size))
root.grid_columnconfigure(1, weight=1) self.client_address_label.pack(pady=(10, 2))
self.client_address_entry = ctk.CTkEntry(self.scrollable_frame, width=250)
self.client_address_entry.pack(pady=(2, 10))
self.hourly_rate_label = ctk.CTkLabel(self.scrollable_frame, text="Hourly Rate:", font=(self.font_family, self.font_size))
self.hourly_rate_label.pack(pady=(10, 2))
self.hourly_rate_entry = ctk.CTkEntry(self.scrollable_frame, width=100)
self.hourly_rate_entry.pack(pady=(2, 10))
self.update_settings_button = ctk.CTkButton(self.scrollable_frame, text="Update Settings", command=self.update_settings, corner_radius=8)
self.update_settings_button.pack(pady=(15, 20))
def load_projects(self): def load_projects(self):
try: try:
@ -199,14 +154,14 @@ class TimeLogix:
new_project = self.new_project_entry.get().strip() new_project = self.new_project_entry.get().strip()
if new_project and new_project not in self.projects: if new_project and new_project not in self.projects:
self.projects.append(new_project) self.projects.append(new_project)
self.project_combo["values"] = self.projects # Update Combobox self.project_combo.configure(values=self.projects)
self.project_combo.set(new_project) # Set to the new project self.project_combo.set(new_project)
self.save_projects() self.save_projects()
self.new_project_entry.delete(0, tk.END) # Clear the entry self.new_project_entry.delete(0, tk.END)
elif new_project in self.projects: elif new_project in self.projects:
print("Project already exists.") # Replace with a GUI message box print("Project already exists.")
else: else:
print("Project name cannot be empty.") # Replace with a GUI message box print("Project name cannot be empty.")
def update_settings(self): def update_settings(self):
self.company_name = self.company_name_entry.get() self.company_name = self.company_name_entry.get()
@ -216,10 +171,8 @@ class TimeLogix:
try: try:
self.hourly_rate = float(self.hourly_rate_entry.get()) self.hourly_rate = float(self.hourly_rate_entry.get())
except ValueError: except ValueError:
print("Invalid hourly rate. Using default.") # Replace with GUI message print("Invalid hourly rate. Using default.")
self.hourly_rate = 50.00 # Revert to default, or handle the error self.hourly_rate = 50.00
# OPTIONAL: Save settings to a file here (e.g., JSON)
def toggle_timer(self): def toggle_timer(self):
if self.is_running: if self.is_running:
@ -229,13 +182,13 @@ class TimeLogix:
def start_timer(self): def start_timer(self):
self.is_running = True self.is_running = True
self.start_stop_button.config(text="Stop") self.start_stop_button.configure(text="Stop")
self.start_time = time.time() self.start_time = time.time()
self.update_timer() self.update_timer()
def stop_timer(self): def stop_timer(self):
self.is_running = False self.is_running = False
self.start_stop_button.config(text="Start") self.start_stop_button.configure(text="Start")
self.root.after_cancel(self.timer_id) self.root.after_cancel(self.timer_id)
self.log_time_entry() self.log_time_entry()
@ -247,7 +200,7 @@ class TimeLogix:
time_str = "{:02d}:{:02d}:{:02d}".format( time_str = "{:02d}:{:02d}:{:02d}".format(
int(hours), int(minutes), int(seconds) int(hours), int(minutes), int(seconds)
) )
self.time_label.config(text=time_str) self.time_label.configure(text=time_str)
self.timer_id = self.root.after(100, self.update_timer) self.timer_id = self.root.after(100, self.update_timer)
def log_time_entry(self): def log_time_entry(self):
@ -267,7 +220,7 @@ class TimeLogix:
self.log_entries.append(entry) self.log_entries.append(entry)
self.update_log_display() self.update_log_display()
self.elapsed_time = 0 self.elapsed_time = 0
self.time_label.config(text="00:00:00") self.time_label.configure(text="00:00:00")
def update_log_display(self): def update_log_display(self):
self.log_text.delete("1.0", tk.END) self.log_text.delete("1.0", tk.END)
@ -388,14 +341,13 @@ class TimeLogix:
time_str = "{:02d}:{:02d}:{:02d}".format( time_str = "{:02d}:{:02d}:{:02d}".format(
int(hours), int(minutes), int(seconds) int(hours), int(minutes), int(seconds)
) )
self.total_time_label.config(text=f"Total Time: {time_str}") self.total_time_label.configure(text=f"Total Time: {time_str}")
def exit_app(self): # Define the exit_app method def exit_app(self):
self.root.destroy() # Close the Tkinter window self.root.destroy()
sys.exit() sys.exit()
if __name__ == "__main__": if __name__ == "__main__":
root = tk.Tk() app = TimeLogix()
app = TimeLogix(root) app.root.mainloop()
root.mainloop()