#!/usr/bin/python#Libraries to importimport wximport timeimport threading#This is the data for numbers in the 7-segment display.#The item is a list, so segment[0] = "1110111". Each char in this string is whether that particular segment should be on or off.segments = ("1110111","0010010","1011101","0111010","1101011","1101111","1010010","1111111","1111101")#Various IDs for use with wxpython (elements require IDs and IDs are used for setting up event handlers and the like)ident = {"buttonSet":1,"buttonZeroPump":2,"buttonZeroTotals":3,"buttonNozzle1":4,"buttonNozzle2":5,"txtSet":6,"spinSet":7,"txtStatus":8,"txtConsolePetrol":9,"txtConsolePrice":10,"txtConsoletotalPetrolDispensed":11,"txtConsoleTotalTakings":12,"txtPumpPetrol":13,"txtPumpPrice":14,"txtPencePerLitre":15,"menuQuit":16,"menuAlwaysOnTop":17}#Button settings: label and enabled.button = {ident["buttonSet"]:{"enable":False,"text":"set"},ident["buttonZeroPump"]:{"enable":False,"text":"Zero Pump"},ident["buttonZeroTotals"]:{"enable":False,"text":"Zero Totals"},ident["buttonNozzle1"]:{"enable":True,"text":"Take Nozzle"},ident["buttonNozzle2"]:{"enable":False,"text":"Squeese"}}#Config to be used throughoutconfig = {"fillrate":0.1,"spinrate":0.1,"runthread":0,"titlestart":"Peter's Petrol Pumps","windowstyles":0,"segw":8,"segh":1}#The main variables that are used a lot.uiControls = {}pencePerLitre = 95.0pencePerLitreBox = 95.0pencePerLitreQueue = 0petrolDispensed=0totalTakings=0totalPetrolDispensed=0status = 'Ready'#Threading is required to make the UI still usable while the pump is filling up.#Essentially, the pump-filling routine needs to loop until the "Unsqueeze pump" button is pressed#This looping would freeze the UI (as you would expect), so had to make made asyncronous.class Filler(threading.Thread): def run(self): global config, petrolDispensed, status while config["runthread"]: #runthread is set from the main class asyncronously. petrolDispensed = petrolDispensed + 0.1 #Increment the petrol if(petrolDispensed >= 100): #We can't fill above 100 litres petrolDispensed = 100 #Disable stuff: status = "Out Of Use" button[ident["buttonNozzle1"]]["enable"] = False button[ident["buttonNozzle2"]]["enable"] = False button[ident["buttonNozzle1"]]["text"] = "Take nozzle" button[ident["buttonNozzle2"]]["text"] = "Squeese" config["runthread"] = 0 #Stop loop. frame.UpdateUI() time.sleep(config["fillrate"]) #Slow down the filling-up#This is the main class. __init__ is called when the app startsclass MainControl(wx.Frame): def __init__(self, parent, id, title): self.MakeUI(parent, id, title); self.MakeEventHandlers(); self.UpdateUI(); def MakeUI(self, parent, id, title): #This function is called only once, on startup. It uses the wxwidgets toolkit to set up the GUI wx.Frame.__init__(self, parent, id, title, wx.DefaultPosition, (600, 400)) #Create the frame uiControls["panel"] = wx.Panel(self, -1, style=wx.EXPAND) #Create the panel #Menu setup: menubar = wx.MenuBar() options = wx.Menu() uiControls["menuAlwaysOnTop"] = options.Append(ident["menuAlwaysOnTop"], '&Always On Top', 'The window will always show above other windows', kind=wx.ITEM_CHECK) options.Append(ident["menuQuit"], '&Quit', 'Exit the application') menubar.Append(options, '&Options') self.SetMenuBar(menubar) #End menu setup. heading = wx.Font(9, wx.NORMAL, wx.NORMAL, wx.BOLD) # This will be reused for subtitles. #--- TOP LEFT CONTENT --------------------------------------------------- uiControls["txtStatus"] = wx.StaticText(uiControls["panel"], ident["txtStatus"], "Ready"); uiControls["txtConsolePetrol"] = wx.StaticText(uiControls["panel"], ident["txtConsolePetrol"] , "0.0 litres") uiControls["txtConsolePrice"] = wx.StaticText(uiControls["panel"], ident["txtConsolePrice"] , "0.00") uiControls["txtConsoletotalPetrolDispensed"] = wx.StaticText(uiControls["panel"], ident["txtConsoletotalPetrolDispensed"] , "0.0 litres") uiControls["txtConsoleTotalTakings"] = wx.StaticText(uiControls["panel"], ident["txtConsoleTotalTakings"] , "0.00") sizera1cont = [ wx.StaticText(uiControls["panel"], -1, "Pump details"),wx.StaticText(uiControls["panel"], -1, ""), wx.StaticText(uiControls["panel"], -1, " Status:"),uiControls["txtStatus"], wx.StaticText(uiControls["panel"], -1, " Petrol dispensed:"),uiControls["txtConsolePetrol"], wx.StaticText(uiControls["panel"], -1, " Price:"),uiControls["txtConsolePrice"], wx.StaticText(uiControls["panel"], -1, "Today's totals"),wx.StaticText(uiControls["panel"], -1, ""), wx.StaticText(uiControls["panel"], -1, " Petrol:"),uiControls["txtConsoletotalPetrolDispensed"], wx.StaticText(uiControls["panel"], -1, " Takings:"),uiControls["txtConsoleTotalTakings"] ] #--- BOTTOM LEFT CONTENT ------------------------------------------------ uiControls["buttonSet"] = wx.Button(uiControls["panel"], ident["buttonSet"], "set",style=wx.EXPAND) uiControls["buttonSet"].SetMinSize((40, 26)) uiControls["txtSet"] = wx.TextCtrl(uiControls["panel"], ident["txtSet"], "%.1f" % pencePerLitre,style=wx.EXPAND) sizerb1acont = [ wx.StaticText(uiControls["panel"], -1, "Pence per litre:",style=wx.EXPAND), uiControls["txtSet"], wx.SpinButton(uiControls["panel"], ident["spinSet"],style=wx.EXPAND), uiControls["buttonSet"] ] sizerb1bcont = [ wx.Button(uiControls["panel"], ident["buttonZeroPump"], "Zero pump",style=wx.EXPAND), wx.Button(uiControls["panel"], ident["buttonZeroTotals"], "Zero today's totals",style=wx.EXPAND) ] #--- TOP RIGHT CONTENT -------------------------------------------------- uiControls["txtPumpPrice"] = self.MakeLCD(5,3,"7sPumpPrice") uiControls["txtPumpPetrol"] = self.MakeLCD(3,2,"7sPumpPetrol") uiControls["txtPencePerLitre"] = self.MakeLCD(4,3,"7sPencePerLitre") sizerc1cont = [ wx.StaticText(uiControls["panel"], -1, " Amount to pay:"), (uiControls["txtPumpPrice"], 1, wx.SHAPED, 0), wx.StaticText(uiControls["panel"], -1, " Petrol"), uiControls["txtPumpPetrol"], wx.StaticText(uiControls["panel"], -1, " Pence per litre"),uiControls["txtPencePerLitre"] ] #--- BOTTOM RIGHT CONTENT ----------------------------------------------- uiControls["buttonNozzle1"] = wx.ToggleButton(uiControls["panel"], ident["buttonNozzle1"], 'Take Nozzle',style=wx.EXPAND) uiControls["buttonNozzle1"].SetMinSize((128,32)) uiControls["buttonNozzle2"] = wx.ToggleButton(uiControls["panel"], ident["buttonNozzle2"], "Squeese",style=wx.EXPAND) uiControls["buttonNozzle2"].SetMinSize((128,32)) uiControls["buttonNozzle2"].Enable(False) sizerd1cont = [uiControls["buttonNozzle1"],uiControls["buttonNozzle2"]] #--- MAIN LAYOUT -------------------------------------------------------- #This is where all the pieces created above are put together # Set up root sizer sizer0 = wx.GridSizer(2, 2, 0, 8) # Set up quadrants temp = wx.StaticBox( uiControls["panel"], id, "Console display" ) temp.SetFont(heading) sizera = wx.StaticBoxSizer( temp, wx.VERTICAL ) temp = wx.StaticBox( uiControls["panel"], id, "Console controls" ) temp.SetFont(heading) sizerb = wx.StaticBoxSizer( temp, wx.VERTICAL ) temp = wx.StaticBox( uiControls["panel"], id, "Pump display" ) temp.SetFont(heading) sizerc = wx.StaticBoxSizer( temp, wx.VERTICAL ) temp = wx.StaticBox( uiControls["panel"], id, "Pump controls" ) temp.SetFont(heading) sizerd = wx.StaticBoxSizer( temp, wx.VERTICAL ) #Set up lower-level sizers sizera1 = wx.GridSizer(9, 2, 3, 3) sizerb1 = wx.BoxSizer(wx.VERTICAL) sizerb1a = wx.BoxSizer(wx.HORIZONTAL) sizerb1b = wx.BoxSizer(wx.HORIZONTAL) sizerc1 = wx.GridSizer(3, 2, 3, 3) sizerd1 = wx.BoxSizer(wx.HORIZONTAL) #Add content to lower-level sizers sizera1.AddMany(sizera1cont) sizerb1.AddMany([sizerb1a, (sizerb1b,0,wx.ALIGN_CENTER_HORIZONTAL)]) sizerb1a.AddMany(sizerb1acont) sizerb1b.AddMany(sizerb1bcont) sizerc1.AddMany(sizerc1cont) sizerd1.AddMany(sizerd1cont) #Add lower-level sizers to quadrants sizera.Add(sizera1) sizerb.Add(sizerb1, flag=wx.EXPAND) sizerc.Add(sizerc1, 1, wx.EXPAND, 0) sizerd.Add(sizerd1, flag=wx.ALIGN_CENTER_HORIZONTAL) #Add quadrants to root sizer sizer0.Add(sizera, flag=wx.EXPAND) sizer0.Add(sizerc, flag=wx.EXPAND) sizer0.Add(sizerb, flag=wx.EXPAND) sizer0.Add(sizerd, flag=wx.EXPAND) #Setup main frame #uiControls["panel"].SetSize(uiControls["panel"].GetEffectiveMinSize()) uiControls["panel"].SetSizer(sizer0) self.Centre() config["windowstyles"] = self.GetWindowStyleFlag() self.MakeLCD(3,2,"lol") def UpdateLCD(self, value, key): value=str(value) #Formatting! lhs=uiControls[key][point] #number of digits left of the decimal point rhs=lhs-uiControls[key][digits] split = value.split(".") split[0] = split[0].rjust(lhs,"0") split[0] = split[0][-lhs:] split[1] = split[1].ljust(rhs,"0") split[1] = split[1][:rhs] value = split[0] + split[1] for y in range(0,value.len()-1): #Loop through each character for z in range(0,6): #Loop through each segment if (segments[int(value[y])][z]==1): uiControls[key][y][z].SetBackgroundColour(wx.BLACK) #Black the segment else: uiControls[key][y][z].SetBackgroundColour(wx.DEFAULT) #Make it transparent again def MakeLCD(self, digits, point, key): width=(digits*4)+2 #This is the number of columns in the gridsizer uiControls[key] = {"ref":wx.GridSizer(5, width, 5, 5),0:{},1:{},2:{},3:{},4:{},"digits":digits,"point":point} #Basic setup for the gridsizer for x in range(0,5): #Loop through rows switch = x % 2 # Are we on an odd or an even row? #Each sement is given a key, 0 through 6. These references below calculate this reference based on the row. ref1 = (3/2)*x #segments 0, 3, 6 ref2 = (3/2)*x-(1/2) #segments 1, 4 ref3 = (3/2)*x+(1/2) #segments 2, 5 for y in range(0,digits): #Loop through each digit if(y==point): #Should we insert a decimal point? if(x<4): uiControls[key]["ref"].Add(wx.Panel(uiControls["panel"], -1)) #space uiControls[key]["ref"].Add(wx.Panel(uiControls["panel"], -1)) #space else: uiControls[key]["point"] = wx.Panel(uiControls["panel"], -1, size=(config["segh"],config["segh"])) uiControls[key]["point"] .SetBackgroundColour(wx.BLACK) uiControls[key]["ref"].Add(uiControls[key]["point"] ) #point uiControls[key]["ref"].Add(wx.Panel(uiControls["panel"], -1)) #space if(switch==0): #An even row (meaning space|segment|space|space|) uiControls[key]["ref"].Add(wx.Panel(uiControls["panel"], -1, size=(config["segh"],config["segh"]), style=wx.EXPAND)) #blank uiControls[key][y][ref1] = wx.Panel(uiControls["panel"], -1, size=(config["segw"],config["segh"]), style=wx.EXPAND) uiControls[key][y][ref1].SetBackgroundColour(wx.BLACK) uiControls[key]["ref"].Add(uiControls[key][y][ref1]) #seg uiControls[key]["ref"].Add(wx.Panel(uiControls["panel"], -1, size=(config["segh"],config["segh"]), style=wx.EXPAND)) #blank uiControls[key]["ref"].Add(wx.Panel(uiControls["panel"], -1, size=(config["segh"],config["segh"]), style=wx.EXPAND)) #space else: #An odd row (meaning segment|space|segment|space|) uiControls[key][y][ref2] = wx.Panel(uiControls["panel"], -1, size=(config["segh"],config["segw"]), style=wx.EXPAND) uiControls[key][y][ref2].SetBackgroundColour(wx.BLACK) uiControls[key]["ref"].Add(uiControls[key][y][ref2]) #seg uiControls[key]["ref"].Add(wx.Panel(uiControls["panel"], -1, size=(config["segw"],config["segw"]), style=wx.EXPAND)) #blank uiControls[key][y][ref3] = wx.Panel(uiControls["panel"], -1, size=(config["segh"],config["segw"]), style=wx.EXPAND) uiControls[key][y][ref3].SetBackgroundColour(wx.BLACK) uiControls[key]["ref"].Add(uiControls[key][y][ref3]) #seg uiControls[key]["ref"].Add(wx.Panel(uiControls["panel"], -1, size=(config["segh"],config["segw"]), style=wx.EXPAND)) #space return uiControls[key]["ref"] #return the created ui object''' def MakeEventHandlers(self): #This sets up the event handlers for various UI elements self.Bind(wx.EVT_BUTTON, self.OnSet, id=ident["buttonSet"]) self.Bind(wx.EVT_SPIN_UP, self.OnSetSpinUp, id=ident["spinSet"]) self.Bind(wx.EVT_SPIN_DOWN, self.OnSetSpinDown, id=ident["spinSet"]) self.Bind(wx.EVT_BUTTON, self.OnZeroPump, id=ident["buttonZeroPump"]) self.Bind(wx.EVT_BUTTON, self.OnZeroTotals, id=ident["buttonZeroTotals"]) self.Bind(wx.EVT_TOGGLEBUTTON, self.OnNozzle1, id=ident["buttonNozzle1"]) self.Bind(wx.EVT_TOGGLEBUTTON, self.OnNozzle2, id=ident["buttonNozzle2"]) self.Bind(wx.EVT_MENU, self.OnQuit, id=ident["menuQuit"]) self.Bind(wx.EVT_MENU, self.OnAlwaysOnTop, id=ident["menuAlwaysOnTop"]) def UpdateUI(self): #As suggested by the title, this function updates things in the UI (text contents, button labels, etc) temp_price = petrolDispensed * pencePerLitre / 100 if(petrolDispensed < 0.5 and config["runthread"]): temp_price = 0.5 * pencePerLitre / 100 #--- Console Display ---# uiControls["txtStatus"].SetLabel(status) uiControls["txtConsolePetrol"].SetLabel("%.1f litres" % petrolDispensed) uiControls["txtConsolePrice"].SetLabel("%.2f" % temp_price) uiControls["txtConsoletotalPetrolDispensed"].SetLabel("%.1f litres" % totalPetrolDispensed) uiControls["txtConsoleTotalTakings"].SetLabel("%.2f" % totalTakings) #--- Console Controls ---# uiControls["txtSet"].SetValue(str(pencePerLitreBox)) #--- Pump Display ---# UpdateLCD("%.2f" % temp_price, "7sPumpPrice"); UpdateLCD("%.1f" % petrolDispensed, "7sPumpPetrol") UpdateLCD("%.1f" % pencePerLitre, "7sPencePerLitre") #--- Pump Controls ---# uiControls['buttonNozzle1'].Enable(button[ident["buttonNozzle1"]]["enable"]) uiControls['buttonNozzle2'].Enable(button[ident["buttonNozzle2"]]["enable"]) uiControls['buttonNozzle1'].SetLabel(button[ident["buttonNozzle1"]]["text"]) uiControls['buttonNozzle2'].SetLabel(button[ident["buttonNozzle2"]]["text"]) #---- Title ---# self.SetTitle(config["titlestart"]+" :: "+status) def OnQuit(self, event): self.Close() def OnAlwaysOnTop(self,event): #"Always On Top" was checked or unchecked on the menu. Act accordingly. if uiControls["menuAlwaysOnTop"].IsChecked(): self.SetWindowStyleFlag(config["windowstyles"]|wx.STAY_ON_TOP) else: self.SetWindowStyleFlag(config["windowstyles"]) def OnSet(self, event): #The "set" button was clicked to set the pence-per-litre global pencePerLitre, pencePerLitreQueue, pencePerLitreBox pencePerLitreBox = float(uiControls["txtSet"].GetValue()) #First get the float value of the input box if(pencePerLitreBox >= 0 and pencePerLitreBox < 199.9): #Is it in range? if status == "Ready": #PPL can only be changed if the pump is ready (to avoid ripping people off) pencePerLitre = float("%.1f" % float(pencePerLitreBox)) # Change it! (be sure to format it too) else: pencePerLitreQueue = "%.1f" % float(pencePerLitreBox) # Add the new value to the queue #Display a handy dialog box notifying pete of his incompetence. d = wx.MessageDialog(self, "As the pump isn't ready, the change in pence-per-litre will take effect when you next zero the pump", "Notice", wx.OK) d.ShowModal() d.Destroy() else: pencePerLitreBox = pencePerLitre #Revert input box value #Display a handy dialog box notifying pete of his incompetence. d = wx.MessageDialog(self, "Pence Per Litre can only be set to values between 0 and 199.9", "Notice", wx.OK) d.ShowModal() d.Destroy() self.UpdateUI(); def OnSetSpinUp(self, event): #Increment the PPL input box value global pencePerLitreBox pencePerLitreBox = pencePerLitreBox+config["spinrate"] self.UpdateUI(); def OnSetSpinDown(self, event): #Decrement the PPL input box value global pencePerLitreBox pencePerLitreBox = pencePerLitreBox-config["spinrate"] self.UpdateUI(); def OnZeroPump(self, event): #Zero the amount of petrol dispensed and add it to today's total global petrolDispensed, pencePerLitre, pencePerLitreQueue, totalPetrolDispensed, totalTakings, status if status == "In Use": d = wx.MessageDialog(self, "Pump cannot be zeroed until Nozzle is replaced", "Error", wx.OK | wx.ICON_ERROR) d.ShowModal() d.Destroy() else: button[ident["buttonNozzle1"]]["enable"] = True #Re-enable use of the pump totalPetrolDispensed = totalPetrolDispensed + petrolDispensed #Add petrol to total totalTakings = petrolDispensed * pencePerLitre / 100 #Add moneyz to total petrolDispensed = 0 #Reset pump petrol status = "Ready" #Reset status if pencePerLitreQueue: #Adjust PPL if queued. pencePerLitre = pencePerLitreQueue pencePerLitreQueue = 0; self.UpdateUI() def OnZeroTotals(self, event): #Zero today's totals #Really we should have a confirmation here. global totalPetrolDispensed, totalTakings totalPetrolDispensed = 0 totalTakings = 0 self.UpdateUI() def OnNozzle1(self, event): #Handler for the "take nozel" / "replace nozel" button global status if status == "In Use": #Nozel is being replaced button[ident["buttonNozzle2"]]["enable"] = False #Disallow squeesing button[ident["buttonNozzle1"]]["text"] = "Take Nozzle" #Set label accordingly if petrolDispensed == 0: status = "Ready" else: status = "Out of Use" button[ident["buttonNozzle1"]]["enable"] = False #As petrol has been dispensed, the pump can no longer be used else: #Nozel is being taken button[ident["buttonNozzle1"]]["text"] = "Replace Nozzle" button[ident["buttonNozzle2"]]["enable"] = True status = "In Use" self.UpdateUI() def OnNozzle2(self, event): global config if config["runthread"]: # Is the pump already being filled? config["runthread"]=0 # Stop the filling button[ident["buttonNozzle2"]]["text"] = "Squeese" button[ident["buttonNozzle1"]]["enable"] = True else: #Start up the pumping class config["runthread"]=1 thread = Filler() thread.start() button[ident["buttonNozzle2"]]["text"] = "Unsqueese" button[ident["buttonNozzle1"]]["enable"] = False self.UpdateUI()#Run the app!app = wx.App(0) frame = MainControl(None, -1, config["titlestart"])frame.Show(True)app.MainLoop()