Skip to content

Instantly share code, notes, and snippets.

@mbildner
Last active December 20, 2015 07:49
Show Gist options
  • Select an option

  • Save mbildner/6096118 to your computer and use it in GitHub Desktop.

Select an option

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.
"""
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()
@mbildner
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment