from tkinter import * from tkinter import messagebox from functools import partial from datetime import date # Conversion rates relative to 1 litre UNIT_TO_LITER = { "Litre": 1, "Millilitre": 0.001, "Cup": 0.236588, "Gallon": 3.78541 } MAX_VALUE = 1e9 # Maximum value limit class Converter: def __init__(self): self.history = [] self.root = Tk() self.root.title("Volume Converter") self.main_frame = Frame(self.root, padx=25, pady=25, bg="#F9F9F9") self.main_frame.grid() Label(self.main_frame, text="Volume Converter", font=("Arial", 18, "bold"), bg="#F9F9F9", fg="#004C99").grid(row=0, column=0, columnspan=3, pady=15) # Input field self.entry = Entry(self.main_frame, font=("Arial", 14), width=18, justify="center", relief="solid", bd=1) self.entry.grid(row=1, column=0, columnspan=3, pady=12) # Dropdowns for units self.units = list(UNIT_TO_LITER.keys()) self.from_unit = StringVar(value="Litre") self.to_unit = StringVar(value="Millilitre") self.from_menu = OptionMenu(self.main_frame, self.from_unit, *self.units) self.from_menu.config(font=("Arial", 12), width=12, bg="#E6E6E6") self.from_menu.grid(row=2, column=0, padx=8) # Swap button self.swap_button = Button(self.main_frame, text="⇄", font=("Arial", 14, "bold"), command=self.swap_units, width=3, bg="#004C99", fg="white") self.swap_button.grid(row=2, column=1, padx=5) self.to_menu = OptionMenu(self.main_frame, self.to_unit, *self.units) self.to_menu.config(font=("Arial", 12), width=12, bg="#E6E6E6") self.to_menu.grid(row=2, column=2, padx=8) # Convert button self.convert_button = Button(self.main_frame, text="Convert", font=("Arial", 13, "bold"), bg="#228B22", fg="white", width=14, command=self.convert, relief="raised") self.convert_button.grid(row=3, column=0, columnspan=3, pady=15) # Result label self.result_label = Label(self.main_frame, text="Enter a value to convert", font=("Arial", 14), fg="#444", bg="#F9F9F9") self.result_label.grid(row=4, column=0, columnspan=3, pady=15) # History and Help buttons self.history_button = Button(self.main_frame, text="History / Export", font=("Arial", 11, "bold"), bg="#004C99", fg="white", width=15, command=self.show_history_export) self.history_button.grid(row=5, column=0, pady=10) self.help_button = Button(self.main_frame, text="Help", font=("Arial", 11, "bold"), bg="#FF8C00", fg="white", width=15, command=self.show_help) self.help_button.grid(row=5, column=2, pady=10) self.root.mainloop() def swap_units(self): f, t = self.from_unit.get(), self.to_unit.get() self.from_unit.set(t) self.to_unit.set(f) def convert(self): try: value = float(self.entry.get()) if self.from_unit.get() == self.to_unit.get(): self.result_label.config(text="Please select different units.", fg="red") return if value < 0: self.result_label.config(text="Value must be positive.", fg="red") return if value > MAX_VALUE: self.result_label.config(text="Value too large (limit 1 billion).", fg="red") return from_factor = UNIT_TO_LITER[self.from_unit.get()] to_factor = UNIT_TO_LITER[self.to_unit.get()] litres = value * from_factor converted = litres / to_factor # Keep 4 significant figures converted_str = "{:.4g}".format(converted) result = f"{value} {self.from_unit.get()} = {converted_str} {self.to_unit.get()}" self.result_label.config(text=result, fg="green") self.history.append(result) except ValueError: self.result_label.config(text="Please enter a number", fg="red") def show_history_export(self): HistoryExport(self, self.history) def show_help(self): messagebox.showinfo("Help", "Enter a positive number, choose two different units, and click Convert.\n" "Use ⇄ to swap units.\n" "View your past conversions under History / Export.\n" "Limits: positive values under 1,000,000,000.") class HistoryExport: def __init__(self, partner, calculations): self.history_box = Toplevel() partner.history_button.config(state=DISABLED) self.history_box.protocol('WM_DELETE_WINDOW', partial(self.close_history, partner)) self.history_frame = Frame(self.history_box, padx=15, pady=15, bg="#F9F9F9") self.history_frame.grid() Label(self.history_frame, text="Enter filename to save:", font=("Arial", 11), bg="#F9F9F9").grid(row=2, column=0, sticky="w", padx=5, pady=5) self.filename_entry = Entry(self.history_frame, font=("Arial", 12), width=25) self.filename_entry.grid(row=2, column=1, sticky="w", padx=5, pady=5) if not calculations: Label(self.history_frame, text="No history yet. Make a conversion first!", font=("Arial", 12), fg="red", bg="#F9F9F9").grid(row=1, pady=10) self.export_button = Button(self.history_frame, text="Export", font=("Arial", 12, "bold"), state=DISABLED, width=12, bg="#004C99", fg="white") self.export_button.grid(row=3, column=0, padx=10, pady=10) else: newest_first_string = "\n".join(reversed(calculations)) history_label = Label(self.history_frame, text=newest_first_string, font=("Arial", 12), bg="#F0F0F0", justify="left", width=40, height=10, relief="solid", anchor="nw") history_label.grid(row=1, pady=10) Label(self.history_frame, text="Enter filename to save:", font=("Arial", 11), bg="#F9F9F9").grid(row=2, sticky="w", pady=5) self.filename_entry = Entry(self.history_frame, font=("Arial", 12), width=25) self.filename_entry.grid(row=2, column=1, pady=5) self.export_button = Button(self.history_frame, text="Export", font=("Arial", 12, "bold"), width=12, bg="#004C99", fg="white", command=lambda: self.export_data(calculations)) self.export_button.grid(row=3, column=0, padx=10, pady=10) Button(self.history_frame, text="Close", font=("Arial", 12, "bold"), bg="#666666", fg="white", width=12, command=partial(self.close_history, partner)).grid(row=3, column=1, padx=10, pady=10) def export_data(self, calculations): filename = self.filename_entry.get().strip() if not filename: messagebox.showerror("Error", "Please enter a file name.") return filename += ".txt" try: with open(filename, "w") as f: f.write("***** Volume Conversion History *****\n") f.write(f"Date: {date.today()}\n\n") for item in calculations: f.write(item + "\n") messagebox.showinfo("Exported", f"History saved as {filename}") except Exception as e: messagebox.showerror("Error", f"Could not save file: {e}") def close_history(self, partner): partner.history_button.config(state=NORMAL) self.history_box.destroy() if __name__ == "__main__": Converter()