try: import Tkinter except ImportError: import tkinter as Tkinter class Application: "Graphical user interface for a checkbook register application" def __init__(self): # Set up register from register import Register self.register = Register() self.currentTransaction = None # Create the overall frame self.frame = Tkinter.Frame() self.top = self.frame.winfo_toplevel() self.top.title("Checkbook Register") # Whether a frame/widget expands when the top level # window is resized depends on two factors: (1) whether # the containing (or master) frame has a non-zero weight, # and (2) whether the frame is "sticky" to the master. # There is only one frame under the top level, so we # give column 0, row 0 a weight of 1 so that the frame # will expand and contract with the top level window self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) # There are two frames under the main frame: the data # entry widget and the register display. Both frames # should expand width-wise when the window expands, so # we give column 0 a weight of 1. The data entry frame # does not need to expand vertically, so we do not # bother changing the weight for row 0 (thereyby using # the default weight of 0). The register display frame # should expand vertically, so we give row 1 a weight # of 1. self.frame.columnconfigure(0, weight=1) self.frame.grid(row=0, column=0, sticky="news") # Note that we ask for "stickiness" in all four directions # of north, south, east and west. So expansion/contraction # either horizontally or vertically change the size of # this frame. self.frame.rowconfigure(1, weight=1) # Create frame containing data entry widgets f = Tkinter.Frame(master=self.frame) f.grid(row=0, column=0, sticky="ew") # Note that we only ask for "stickiness" in two directions: # east and west. So only expansion/contraction in the # horizontal direction will change the size of this frame. self._makeDataEntryWidgets(f) # Create text widget for displaying transactions import tkFont self.font = tkFont.Font(family="courier", size=12) self.boldFont = tkFont.Font(family="courier", size=12, weight="bold") self.text = Tkinter.Text(master=self.frame, width=100, wrap="none", font=self.font) self.text.grid(row=1, column=0, sticky="news") self.text.bind("", self._textClick) self.showRegister() def _makeDataEntryWidgets(self, f): # Create list of buttons for initiating actions f.columnconfigure(0, weight=1) buttonFrame = Tkinter.Frame(master=f, pady=5) buttonFrame.grid(row=1, column=0, sticky="e") buttonRemove = Tkinter.Button(master=buttonFrame, width=8, text="Remove", command=self._cbRemove) buttonRemove.grid(row=0, column=0, padx=5) buttonUpdate = Tkinter.Button(master=buttonFrame, width=8, text="Update", command=self._cbUpdate) buttonUpdate.grid(row=0, column=1, padx=5) buttonComplete = Tkinter.Button(master=buttonFrame, width=8, text="Complete", command=self._cbComplete) buttonComplete.grid(row=0, column=2, padx=5) buttonAdd = Tkinter.Button(master=buttonFrame, width=8, text="Add", command=self._cbAdd) buttonAdd.grid(row=0, column=3, padx=5) # Create transaction data entry widgets widgetFrame = Tkinter.Frame(master=f, pady=5, padx=5) widgetFrame.grid(row=0, column=0, sticky="ew") widgetFrame.columnconfigure(1, weight=1) l = Tkinter.Label(master=widgetFrame, text="Transaction Type:") l.grid(row=0, column=0, sticky="e") typeFrame = Tkinter.Frame(master=widgetFrame) typeFrame.grid(row=0, column=1, sticky="w") self.typeVar = Tkinter.StringVar(typeFrame) b = Tkinter.Radiobutton(master=typeFrame, text="Deposit", value="deposit", variable=self.typeVar) b.grid(row=0, column=0) b = Tkinter.Radiobutton(master=typeFrame, text="Withdrawal", value="withdrawal", variable=self.typeVar) b.grid(row=0, column=1) self.typeVar.set("deposit") self.number = self._addEntry(widgetFrame, 1, "Number") self.date = self._addEntry(widgetFrame, 2, "Date") self.cDate = self._addEntry(widgetFrame, 3, "Completion Date") self.desc = self._addEntry(widgetFrame, 4, "Description") self.amount = self._addEntry(widgetFrame, 5, "Amount") def _addEntry(self, f, row, label): l = Tkinter.Label(master=f, text=label + ":") l.grid(row=row, column=0, sticky="e") e = Tkinter.Entry(master=f) e.grid(row=row, column=1, sticky="ew") return e def _cbRemove(self): "Callback function when Remove button is pressed" if self.currentTransaction is None: self.complain("No selected transaction") return self.register.remove(self.currentTransaction) self.currentTransaction = NOne self.showRegister() self.showCurrentTransaction() def _cbUpdate(self): "Callback function when Update button is pressed" if self.currentTransaction is None: self.complain("No selected transaction") return try: number = self.getNumber(emptyOkay=True) date = self.getDate(emptyOkay=True) cDate = self.getCompletionDate(emptyOkay=True) desc = self.getDescription(emptyOkay=True) amt = self.getAmount(emptyOkay=True) self.currentTransaction.update(number=number, date=date, completionDate=cDate, description=desc, amount=amt) except ValueError as s: self.complain(s) return self.showRegister() self.showCurrentTransaction() def _cbComplete(self): "Callback function when Complete button is pressed" if self.currentTransaction is None: self.complain("No selected transaction") return try: cDate = self.getCompletionDate() amt = self.getAmount() self.currentTransaction.complete(cDate, amt) except ValueError as s: self.complain(s) return self.showRegister() self.showCurrentTransaction() def _cbAdd(self): "Callback function when Add button is pressed" try: number = self.getNumber(emptyOkay=True) date = self.getDate() cDate = self.getCompletionDate(emptyOkay=True) desc = self.getDescription() amt = self.getAmount() except ValueError as s: self.complain(s) return from transaction import Transaction t = Transaction(date, desc, amt, number=number, completionDate=cDate) self.register.add(t) self.currentTransaction = t self.showRegister() self.showCurrentTransaction() def getNumber(self, emptyOkay=False): "Get value from 'Number' widget" data = self.number.get().strip() if not data: if emptyOkay: return None else: raise ValueError("Number is missing") return data def getDate(self, emptyOkay=False): "Get value from 'Date' widget" return self._getDate(self.date, "Date", emptyOkay) def getCompletionDate(self, emptyOkay=False): "Get value from 'Completion Date' widget" return self._getDate(self.cDate, "Completion date", emptyOkay) def _getDate(self, widget, which, emptyOkay): data = widget.get().strip() if not data: if emptyOkay: return None else: raise ValueError("%s is missing" % which) fields = data.split('-') if len(fields) != 3: raise ValueError("%s format must be YYYY-MM-DD" % which) try: year = int(fields[0]) month = int(fields[1]) day = int(fields[2]) except ValueError: raise ValueError("%s values must be integers" % which) if year < 50: year += 2000 elif year < 100: year += 1900 from datetime import date return date(year, month, day) def getDescription(self, emptyOkay=False): "Get value from 'Description' widget" data = self.desc.get().strip() if not data: if emptyOkay: return None else: raise ValueError("Description is missing") return data def getAmount(self, emptyOkay=False): "Get value from 'Amount' widget" data = self.amount.get().strip() if not data: if emptyOkay: return None else: raise ValueError("Amount is missing") amt = float(data) type = self.typeVar.get() if type == "withdrawal": amt = -amt return amt def complain(self, msg): "Display an error message and wait for user to hit Okay" from tkMessageBox import showerror showerror(title="Error", message=msg) def showRegister(self): "Display register in text widget" from StringIO import StringIO f = StringIO() self.register.printStatement(f) self.text.delete("1.0", "end") self.text.insert("1.0", f.getvalue()) self.text.tag_add("header", "1.0", "1.0 lineend") self.text.tag_configure("header", font=self.boldFont) if self.currentTransaction is not None: n = self.register.transactions.index( self.currentTransaction) tag = str(n + 2) + ".0" # +1 for Tk text being 1-based # +1 for first line being header self.text.tag_add("current", tag, tag + " lineend") self.text.tag_configure("current", background="gray") def showCurrentTransaction(self): "Display current transaction values in input widgets" if self.currentTransaction is None: # Shoud we erase what's there? return t = self.currentTransaction if t.number is None: self._updateEntry(self.number, "") else: self._updateEntry(self.number, str(t.number)) if t.date is None: self._updateEntry(self.date, "") else: self._updateEntry(self.date, str(t.date)) if t.completionDate is None: self._updateEntry(self.cDate, "") else: self._updateEntry(self.cDate, str(t.completionDate)) if t.description is None: self._updateEntry(self.desc, "") else: self._updateEntry(self.desc, str(t.description)) if t.amount < 0: self.typeVar.set("withdrawal") self._updateEntry(self.amount, "%.2f" % -t.amount) else: self.typeVar.set("deposit") self._updateEntry(self.amount, "%.2f" % t.amount) def _updateEntry(self, e, value): e.delete(0, "end") e.insert(0, value) def _textClick(self, event): place = self.text.index("@%d,%d" % (event.x, event.y)) line, char = place.split('.') n = int(line) - 2 if n < 0 or n >= len(self.register.transactions): selected = None else: selected = self.register.transactions[n] if self.currentTransaction is selected: return if self.currentTransaction: self.text.tag_delete("current") self.currentTransaction = selected self.showCurrentTransaction() self.showRegister() def run(self): "Run the Tk event loop for the graphical user interface" self.top.mainloop() if __name__ == "__main__": app = Application() app.run()