Last active
December 20, 2015 07:49
-
-
Save mbildner/6096118 to your computer and use it in GitHub Desktop.
Python program allowing multiple users to split a check by item, automatically updating their tax, tip, and total bill based on user preference.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| One of my first Desktop apps, this lets users split a check among several users. Just add each user, and the % amount they like to tip (eg: "Moshe Bildner, 16%"), add any foods on the bill, as well as their prices (the app comes preloaded with a sample dinner for two, but you can delete that and add your own), and assign each person the foods they are responsible for. The consequent personalized bills will be shown in the command line. | |
| Commands: | |
| Ctrl+Left-Click-drag: from a food to a person to assign responsibility to that person. Responsibility may be split among an unlimited number of people. | |
| Left-Click-drag: move a food or person for easier viewing. Responsibility lines and the checks will be kept in sync. | |
| """ | |
| from __future__ import division | |
| import Tkinter as Tk | |
| import random | |
| REGISTRY = {} | |
| PEOPLE = [] | |
| FOODS = [] | |
| TAX_RATE = .07 | |
| TIP_RATE = 15 # in % | |
| people = PEOPLE | |
| foods = FOODS | |
| tax_rate= TAX_RATE | |
| class GUI(object): | |
| def __init__(self): | |
| self.root = Tk.Tk() | |
| self.canvas = Tk.Canvas(self.root, width=700, height=500, bg="white") | |
| self.canvas.pack(fill="both", expand=1) | |
| # frames | |
| self.people_frame = Tk.Frame(self.root) | |
| self.people_frame.pack(side="left") | |
| self.food_frame = Tk.Frame(self.root) | |
| self.food_frame.pack(side="right") | |
| self.interface_frame = Tk.Frame(self.root) | |
| self.interface_frame.pack(side="top") | |
| # frames | |
| # forms | |
| # foods | |
| self.food_input_label = Tk.Label(self.food_frame, text="Food") | |
| self.food_input_label.pack() | |
| self.food_input_entry = Tk.Entry(self.food_frame) | |
| self.food_input_entry.pack() | |
| self.food_price_label = Tk.Label(self.food_frame, text="Price") | |
| self.food_price_label.pack() | |
| self.food_price_entry = Tk.Entry(self.food_frame) | |
| self.food_price_entry.pack() | |
| self.add_food_button = Tk.Button(self.food_frame, text="Add Food", command=self.add_food) | |
| self.add_food_button.pack() | |
| #foods | |
| #people | |
| self.person_input_label = Tk.Label(self.people_frame, text="Person") | |
| self.person_input_label.pack() | |
| self.person_input_entry = Tk.Entry(self.people_frame) | |
| self.person_input_entry.pack() | |
| self.person_tip_label = Tk.Label(self.people_frame, text="Tip %:") | |
| self.person_tip_label.pack() | |
| self.person_tip_entry = Tk.Entry(self.people_frame) | |
| self.person_tip_entry.pack() | |
| self.register_person_button = Tk.Button(self.people_frame, text="Add Person", command=self.register_person) | |
| self.register_person_button.pack() | |
| #people | |
| #forms | |
| self.canvas.bind("<Control-Button-1>", self.drop_anchor) | |
| self.canvas.bind("<ButtonRelease-1>", self.release) | |
| self.canvas.bind("<Control-B1-Motion>", self.drag) | |
| self.canvas.bind("<B1-Motion>", self.move) | |
| self.canvas.bind("<Button-1>", self.grab) | |
| self.root.bind("<Control-z>", self.debug) | |
| self.root.bind("<Return>", self.process_entries) | |
| self.selected = None | |
| self.anchor=None | |
| self.populate_foods() | |
| self.connectors = [None] | |
| self.root.mainloop() | |
| def connect(self, event=None): | |
| for i in self.canvas.find_all(): | |
| if self.canvas.type(i)=="line": | |
| self.canvas.delete(i) | |
| for i in self.canvas.find_all(): | |
| tags = self.canvas.gettags(i) | |
| item_type = tags[1] | |
| uid = tags[0] | |
| item = REGISTRY[uid] | |
| if item_type=="food": | |
| for person in item.owners: | |
| food_box, person_box = item.bbox, person.bbox | |
| self.canvas.create_line(food_box[0], food_box[1], person_box[0], person_box[1], fill="green") | |
| def drag(self, event=None): | |
| pass | |
| def drop_anchor(self, event=None): | |
| #self.anchor = (event.x, event.y) | |
| self.anchor = self.canvas.find_overlapping(event.x, event.y, event.x, event.y) | |
| def release(self, event=None): | |
| if not self.anchor: | |
| pass | |
| else: | |
| self.target = self.canvas.find_overlapping(event.x, event.y, event.x, event.y) | |
| tags = self.canvas.gettags(self.target) | |
| if "person" in tags: | |
| # uid of the person target | |
| person = self.canvas.gettags(self.target)[0] | |
| # uid of the food anchor | |
| food = self.canvas.gettags(self.anchor)[0] | |
| REGISTRY[food].assign(REGISTRY[person]) | |
| self.connect() | |
| self.debug() | |
| else: | |
| pass | |
| self.target=None | |
| self.anchor=None | |
| self.selected = None | |
| def debug(self, event=None): | |
| for person in people: | |
| print "%s:\n\tbill: %r\n\ttip: %r\n\ttax: %r\nGrand Total: %r\n\n\n" % (person.name, round(person.bill,2), round(person.tip, 2), round(person.tax,2), round(person.grand_total,2)) | |
| def move(self, event=None): | |
| if not self.selected: | |
| pass | |
| else: | |
| self.canvas.coords(self.selected, event.x, event.y) | |
| moved_item = self.canvas.find_overlapping(event.x, event.y, event.x, event.y) | |
| try: | |
| uid = self.canvas.gettags(moved_item)[0] | |
| REGISTRY[uid].bbox = (event.x, event.y, event.x+20, event.y+20) | |
| self.connect() | |
| except: | |
| # two non-connectible items passing over each other triggered an ugly error | |
| pass | |
| def grab(self, event=None): | |
| x,y = event.x, event.y | |
| self.selected = self.canvas.find_overlapping(x,y,x,y) | |
| def populate_foods(self): | |
| for food in foods: | |
| t = "%s: $%d" % (food.name, food.price) | |
| box = self.canvas.create_text(food.bbox[0], food.bbox[1], text=t, font=20, activefill="red") | |
| self.canvas.itemconfig(box, tags=(food.uid, "food", food.name)) | |
| def process_entries(self, event=None): | |
| # binding on the individual frames was not working for some reason, so call this function that calls each of these, only the ones with real inputs will return results | |
| self.add_food() | |
| self.register_person() | |
| def add_food(self, event=None): | |
| new_food_name = self.food_input_entry.get().title() | |
| new_food_price = self.food_price_entry.get() | |
| self.food_input_entry.delete(0, "end") | |
| self.food_price_entry.delete(0, "end") | |
| if new_food_name == '' or new_food_price == '': | |
| pass | |
| else: | |
| new_food = Food(new_food_name, float(new_food_price)) | |
| REGISTRY[new_food.uid] = new_food | |
| t = "%s: $%d" % (new_food.name, new_food.price) | |
| box = self.canvas.create_text(new_food.bbox[0], new_food.bbox[1], text=t, font=20, activefill="red") | |
| self.canvas.itemconfig(box, tags=(new_food.uid, "food", new_food.name)) | |
| def populate_people(self): | |
| for i in self.canvas.find_withtag("person"): | |
| self.canvas.delete(i) | |
| for person in people: | |
| #box = self.canvas.create_rectangle(person.bbox[0], person.bbox[1], person.bbox[2], person.bbox[3], fill="blue") | |
| box = self.canvas.create_text(person.bbox[0], person.bbox[1], text=person.name, font=20, fill="purple", activefill="red") | |
| self.canvas.itemconfig(box, tags=(person.uid, "person", person.name)) | |
| def register_person(self, event=None): | |
| name = self.person_input_entry.get().title() | |
| self.person_input_entry.delete(0, "end") | |
| if self.person_tip_entry.get() != '': | |
| tip = float(self.person_tip_entry.get())/100 | |
| new_person = Person(name, tip) | |
| self.person_tip_entry.delete(0, "end") | |
| else: | |
| new_person = Person(name) | |
| x1, y1 = random.randrange(0,400), random.randrange(0, 300) | |
| x2, y2 = x1+30, y1+30 | |
| new_person.bbox = (x1, y1, x2, y2) | |
| # remember to add label with each person's bill, grand total, tax, tip, and tip % maybe as an entry so it can be changed | |
| self.populate_people() | |
| class Check(object): | |
| def __init__(self, tax_rate=.07): | |
| self.people = [] | |
| self.foods = [] | |
| self.tax_rate = tax_rate | |
| class Food(object): | |
| def __init__(self, name, price): | |
| self.uid = str(random.randrange(1,100000000)) | |
| #temporary | |
| x_width=400 | |
| y_height=300 | |
| self.name = name | |
| self.price = float(price) | |
| self.owners = [] | |
| self.bbox = () | |
| x1,y1 = random.randrange(0, x_width), random.randrange(0, y_height) | |
| self.bbox = (x1, y1, x1+20, y1+20) | |
| foods.append(self) | |
| REGISTRY[self.uid] = self | |
| def assign (self, person=None): | |
| if person: | |
| self.owners.append(person) | |
| person.foods.append(self) | |
| else: | |
| for person in people: | |
| self.assign(person) | |
| for person in people: | |
| person.update_bill() | |
| def __repr__(self): | |
| #return ("%s: %d" % (self.name, self.price)) | |
| return self.uid | |
| class Person(object): | |
| def __init__(self, name, tip_rate=.15): | |
| self.uid = str(random.randrange(1,1000000000)) | |
| self.name = name | |
| self.bill = 0 | |
| self.tax_rate = tax_rate | |
| self.tip_rate = tip_rate | |
| self.foods = [] | |
| people.append(self) | |
| self.grand_total = 0 | |
| self.bbox = () | |
| REGISTRY[self.uid] = self | |
| def update_bill(self): | |
| self.bill = float(0) | |
| for food in self.foods: | |
| proportion = len(food.owners) | |
| self.bill += food.price/proportion | |
| self.grand_total = float(0) | |
| self.grand_total += self.bill | |
| self.tip, self.tax = self.bill*self.tip_rate, self.bill*self.tax_rate | |
| self.grand_total += self.tip | |
| self.grand_total += self.tax | |
| return self.bill, self.grand_total | |
| def __repr__(self): | |
| #return ("%s" % self.name) | |
| return self.uid | |
| def assign(self, person=None): | |
| if person: | |
| self.owners.append(person) | |
| person.foods.append(self) | |
| else: | |
| for person in people: | |
| assign(self, person) | |
| ###################### | |
| fries = Food('Fries', 4) | |
| burger = Food('Burger', 8.99) | |
| steak= Food('Steak', 25.99) | |
| mashed_potatoes = Food('Mashed Potatoes', 5) | |
| cake = Food('Chocolate Cake', 8.99) | |
| gui = GUI() | |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
One of my first Desktop apps, this lets users split a check among several users. Just add each user, and the % amount they like to tip (eg: "Moshe Bildner, 16%"), add any foods on the bill, as well as their prices (the app comes preloaded with a sample dinner for two, but you can delete that and add your own), and assign each person the foods they are responsible for. The consequent personalized bills will be shown in the command line.
Commands:
Ctrl+Left-Click-drag: from a food to a person to assign responsibility to that person. Responsibility may be split among an unlimited number of people.
Left-Click-drag: move a food or person for easier viewing. Responsibility lines and the checks will be kept in sync.