import Tkinter import tkMessageBox import article class BibDB: JournalAny = "Any" YearAny = "Any" def __init__(self, filename): f = open(filename) try: self.articles = article.readArticles(f) finally: f.close() def journalList(self): "Return sorted list of journals" d = {} for a in self.articles: d[a.journal().name()] = 1 l = d.keys() l.sort() return l def yearList(self): "Return sorted list of years for all articles" d = {} for a in self.articles: d[a.date().year()] = 1 l = d.keys() l.sort() return l def runSearchInterface(self): "Create and run the search user interface" # Create top level frame, which contains a control frame # and a text box for output self.top = Tkinter.Frame() self.top.winfo_toplevel().title("Bibliography Search") self.top.pack(expand=1, fill="both") # Create control frame container, which has two subframes. # Top frame will hold the value entry fields. # Bottom frame will hold the action buttons f = Tkinter.Frame(self.top) f.pack(side="top", fill="x") self._createSearchFields(f) self._createSearchButtons(f) # Create output text box (this is last since we want # the control frame to be completely shown even when # the window is resized to be smaller) self.text = Tkinter.Text(self.top, height=20, width=60, wrap="word") self.text.pack(side="top", expand=1, fill="both") # Run until user selects quit self.top.mainloop() def _createSearchFields(self, f): "Create top subframe of entry fields" sf = Tkinter.Frame(f) sf.pack(side="top", expand=1, fill="both", pady=5, padx=5) sf.columnconfigure(1, weight=1) # make column 1 resizable row = 0 # Create author entry field self.authorField = self._createEntry(sf, "Author:", row) row += 1 # Create title entry field self.titleField = self._createEntry(sf, "Title:", row) row += 1 # Create journal selector jList = self.journalList() jList.insert(0, self.JournalAny) self.journalVar, self.journalField = \ self._createOptionMenu(sf, "Journal:", row, jList) row += 1 # Create year selector yList = [str(y) for y in self.yearList()] yList.insert(0, self.YearAny) self.yearVar, self.yearField = \ self._createOptionMenu(sf, "Year:", row, yList) row += 1 def _createEntry(self, sf, title, row): "Create an entry field with given title on given row" e = Tkinter.Entry(sf) e.grid(row=row, column=1, pady=2, sticky="nsew") l = Tkinter.Label(sf, text=title) l.grid(row=row, column=0, sticky="e") return e def _createOptionMenu(self, sf, title, row, values): """Create an option menu with given title on given row using given list of values""" v = Tkinter.StringVar(sf) v.set(values[0]) om = Tkinter.OptionMenu(sf, v, *values) om.config(highlightthickness=0) om.grid(row=row, column=1, pady=2, sticky="nsew") l = Tkinter.Label(sf, text=title) l.grid(row=row, column=0, sticky="e") return v, om def _createSearchButtons(self, f): "Create button subframe of action buttons" sf = Tkinter.Frame(f) sf.pack(side="bottom", fill="x", pady=5, padx=5) b = Tkinter.Button(sf, text="Search", width=6, command=self.search) b.pack(side="right") b = Tkinter.Button(sf, text="Reset", width=6, command=self.resetForm) b.pack(side="right") b = Tkinter.Button(sf, text="List", width=6, command=self.showList) b.pack(side="right") b = Tkinter.Button(sf, text="Quit", width=6, command=self.top.quit) b.pack(side="right") def showList(self): "Show list of articles in text output" self.text.delete("1.0", "end") # Clear output area for a in self.articles: self.text.insert(Tkinter.END, str(a) + "\n\n") def resetForm(self): "Reset search form to original state" self.text.delete("1.0", "end") # Clear output area self.authorField.delete("0", "end") self.titleField.delete("0", "end") self.journalVar.set(self.JournalAny) self.yearVar.set(self.YearAny) def search(self): "Search for articles that fit values in entry fields" # We start with the full list of articles and apply # filtering methods until we either run out of # articles or filters. At the end, we have the list # of articles that pass all filters. answer = self.articles for method in self.SearchList: # Because the method is unbound (the search # list is defined in the class, not in the # instance), we must explicitly pass "self" # as the first argument. answer = method(self, answer) if not answer: break if not answer: # Pop up a dialog to complain. We can, # alternatively, place the message in # the output text area. tkMessageBox.showerror(parent=self.top, title="Search Error", message="No matching article found.") return # Show result articles self.text.delete("1.0", "end") # Clear output area for a in answer: self.text.insert(Tkinter.END, str(a) + "\n\n") def _searchYear(self, articles): # Get the current year value from our year variable. # If it's "Any", just return; otherwise, loop through # list of articles and return those whose year matches # the user-specified year. s = self.yearVar.get() if s == self.YearAny: return articles year = int(s) answer = [] for a in articles: if a.date().year() == year: answer.append(a) return answer def _searchJournal(self, articles): # Get the current year value from our journal variable. # If it's "Any", just return; otherwise, loop through # list of articles and return those whose journal matches # the user-specified journal. Note that we can use an # exact match (==) because the option menu values are # constructed from the journal names from our article list. s = self.journalVar.get() if s == self.JournalAny: return articles answer = [] for a in articles: if a.journal().name() == s: answer.append(a) return answer def _searchTitle(self, articles): # Get the current title value from our title entry. # If it's empty, just return; otherwise, loop through # list of articles and return those whose title contains # the user-specified string. s = self.titleField.get() if not s: return articles s = s.lower() answer = [] for a in articles: title = a.title().lower() if title.find(s) >= 0: answer.append(a) return answer def _searchAuthor(self, articles): # Get the current author value from our author entry. # If it's empty, just return; otherwise, loop through # list of articles and return those whose author contains # the user-specified string. s = self.authorField.get() if not s: return articles s = s.lower() answer = [] for a in articles: for au in a.authors(): name = au.fullName().lower() if name.find(s) >= 0: found = 1 break else: found = 0 if found: answer.append(a) return answer # Here is our list of search filters used in the "search" method. # Each filter method takes a list of articles, and returns the # subset of articles that satisfy its matching criteria. # This declaration has to be at the end since the methods must # be defined first. SearchList = [ _searchYear, _searchJournal, _searchTitle, _searchAuthor, ] if __name__ == "__main__": db = BibDB("db") db.runSearchInterface() print "Done"