diff --git a/browser.py b/browser.py index b554bf3..6c14617 100644 --- a/browser.py +++ b/browser.py @@ -1,7 +1,7 @@ import sys import curses import db_layer as db -from gui import BookForm,CategoryForm +from form import BookForm,CategoryForm class browserWindow: hl=0 diff --git a/form.py b/form.py index 2e9110f..76b814d 100644 --- a/form.py +++ b/form.py @@ -1,202 +1,285 @@ import curses import sys -class formWindow: + +class TextEntry: + + """A part of a window that handles text entry. + Properties: + value holds the string that was entered + + Public Methods: + set_geom(row,column,width) Sets the geometry in the window + set_value(string) Set the value and redraw + gain_focus() Gives it focus, moving cursor and changing the drawing + lose_focus() Takes focus, moving cursor to start, changing drawing + handle_input(ch) Pass this the ncurses key, and it manages input + redraw() Redraw the text entry (should never need to do this + """ + + # Public + value = "" # Use the set_value function to set, but retrieve with value + + # Should be Private + cursor = 0 + start = 0 + focus = False + x = 0 + y = 0 + width = 10 + + # Public methods + def __init__(self, parent_window, value=""): + self.w = parent_window + self.value = value + + def set_geom(self,y,x,width): + self.x = x + self.y = y + self.width = width + + def set_value(self,v): + self.value=v + self.cursor=len(v) + self.redraw() + + def gain_focus(self): + #sys.stderr.write('I have focus!\n') + self.focus = True + self._mv_cursor(+len(self.value)) + self.start = max(0,self.cursor-self.width) + self.redraw() + + def lose_focus(self): + self.focus = False + self.cursor = 0 + self.start = 0 + self.redraw() + + def handle_input(self,ch): + if ch==curses.KEY_LEFT: + self._mv_cursor(-1) + elif ch==curses.KEY_HOME: + self._set_cursor(0) + elif ch==curses.KEY_RIGHT: + self._mv_cursor(+1) + elif ch==curses.KEY_END: + self._set_cursor(len(self.value)) + elif ch>=32 and ch<=126: + self._insert(curses.keyname(ch).decode('utf-8')) + elif ch==curses.KEY_BACKSPACE: + self._backspace() + elif ch==curses.KEY_DC: + self._delete() + + def redraw(self): + self.w.addnstr(self.y,self.x, self.value[self.start:]+" "*self.width, self.width) + if self.focus: + self.w.chgat(self.y, self.x, self.width, curses.A_UNDERLINE) + curses.curs_set(1) + + # Private functions + def _mv_cursor(self,delta): + self._set_cursor(self.cursor + delta) + + def _set_cursor(self, new_c): + self.cursor = max(0, min(len(self.value), new_c)) + self.start = max(0,self.cursor-self.width+1) + self.redraw() + # Place the drawn cursor in the correct spot + col = self.x + self.cursor - self.start + self.w.move(self.y,col) + + def _insert(self,ch): + c = self.cursor + self.value = self.value[:c] +ch+ self.value[c:] + self._mv_cursor(+1) + + def _backspace(self): + if self.cursor>0: + c = self.cursor + self.value=self.value[:c-1] + self.value[c:] + self._mv_cursor(-1) + + def _delete(self): + c = self.cursor + self.value = self.value[:c] + self.value[c+1:] + self._mv_cursor(0) + + + +class FormWindow: + + """General class for a Form Window. + + To use, make the window for it, call the constructor, then call event_loop. + """ + + # Private variables mx = my = 0 hl = 0 bt = -1 - cursor = 0 - textStart = 0 left = 0 top = 2 row = 2 caption = "Form" blabel = "Done" labels = ["label1"] - entries = [] commands = [('pU', 'top'),('pD', 'bottom'),('Es', 'cancel')] + + # Public functions + def __init__(self,window,helpbar,book={}): + self.w = window + self.w.resize(len(self.labels)+6,50) + self.hb = helpbar + self._make_entries() + self._update_geometry() + self._set_entries(book) + def clear(self): self.w.erase() self.w.refresh() - def __init__(self,window,helpbar,book={}): - self.w = window - self.w.resize(len(self.labels)+6,50) - self.hb = helpbar - self.updateEntries(book) - self.updateGeometry() - - def updateGeometry(self): - (self.my, self.mx) = self.w.getmaxyx() - self.left=0 - for l in self.labels: - self.left = max(len(l),self.left) - self.left += 4 - self.width = self.mx-self.left-2 - self.top = 2 - # next, the buttons - self.bcol = [self.mx-len(self.blabel)-14, self.mx-len(self.blabel)-4] - self.bwidth = [8,len(self.blabel)+2] - - def updateEntries(self,book): - self.entries=[] - for l in self.labels: - if l.lower() in book: - self.entries.append(str(book[l.lower()])) - else: - self.entries.append("") - - def refresh(self): - self.hb.commands = self.commands - self.hb.refresh() - self.updateGeometry() - self.w.box() - self.w.addstr(0,(self.mx-len(self.caption))//2,self.caption) - r=self.top - for l in self.labels: - c = self.left-len(l)-2 - self.w.addstr(r,c,l+":") - self.drawRow(r-self.top) - r+=1 - self.w.addstr(r+1,self.bcol[0], " <"+self.blabel+">") - self.w.refresh() - - def drawRow(self,row): - r = self.top + row - self.w.addnstr(r,self.left, self.entries[row]+" "*self.width, self.width) - - def highlight(self): - if self.bt == -1: - self.w.chgat(self.row, self.left, self.width, curses.A_UNDERLINE) - self.mvCursor(0) - curses.curs_set(1) - else: - self.w.chgat(self.row, self.bcol[self.bt], self.bwidth[self.bt], curses.A_REVERSE) - curses.curs_set(0) - - def unHighlight(self): - self.w.chgat(self.row,1,self.mx-2,curses.A_NORMAL) - - def mvHighlight(self,delta): - self.unHighlight() - new = self.hl+delta - new = max(new,0) - new = min(new,len(self.labels)) # the extra is for the buttons - self.hl = new - self.row = self.hl + self.top - if new == len(self.labels): - self.bt =1 - self.bt = min(self.bt,1) - self.row+=1 - else: - self.mvCursor(+len(self.entries[self.hl])) - self.bt=-1 - self.highlight() - - def mvCursor(self,delta): - n = self.cursor + delta - n = max(n,0) - n = min(n,len(self.entries[self.hl])) - self.cursor = n - col = self.left + self.cursor - self.textStart - if col >= self.left and col < self.left+self.width: - self.w.move(self.row,col) - - def insert(self,ch): - c = self.cursor - #sys.stderr.write(str(type(ch))) - self.entries[self.hl]=self.entries[self.hl][:c] +ch.decode('utf-8')+ self.entries[self.hl][c:] - self.drawRow(self.hl) - self.mvCursor(+1) - self.highlight() - - def backspace(self): - if self.cursor>0: - self.entries[self.hl]=self.entries[self.hl][:self.cursor-1] + self.entries[self.hl][self.cursor:] - self.drawRow(self.hl) - self.mvCursor(-1) - self.highlight() - - def delete(self): - c = self.cursor - self.entries[self.hl]=self.entries[self.hl][:c] + self.entries[self.hl][c+1:] - self.drawRow(self.hl) - self.highlight() - - def returnValues(self): - book = {} - for k,v in zip(self.labels, self.entries): - if v!="" and k.lower()!="publish date": - book[k.lower()]=v - return book - - def eventLoop(self): + def event_loop(self): self.w.keypad(1) self.refresh() - self.mvCursor(+len(self.entries[self.hl])) - self.highlight() + self.hl=0; + self.entries[self.hl].gain_focus() ch = self.w.getch() while ch != 27: - self.handleInput(ch) + #sys.stderr.write(curses.keyname(ch).decode('utf-8')) + self.handle_input(ch) if ch==10 or ch==curses.KEY_ENTER: if self.bt==0: return {} elif self.bt==1: - return self.returnValues() - self.mvHighlight(+1) + return self.return_values() + else: + self._mv_focus(+1) self.w.refresh() ch = self.w.getch() curses.curs_set(0) return {} + def _make_entries(self): + self.entries = [] + for e in range(len(self.labels)): + self.entries.append(TextEntry(self.w)) - def handleInput(self,ch): + def _update_geometry(self): + (self.my, self.mx) = self.w.getmaxyx() + self.left=0 + for l in self.labels: + self.left = max(len(l),self.left) + self.left += 4 + width = self.mx-self.left-2 + self.top = 2 + for r in range(len(self.entries)): + self.entries[r].set_geom(r+self.top, self.left, width) + # next, the buttons + self.brow = self.top+len(self.labels)+1 + self.bcol = [self.mx-len(self.blabel)-14, self.mx-len(self.blabel)-4] + self.bwidth = [8,len(self.blabel)+2] + + def _set_entries(self,book): + e = 0 + for l in self.labels: + #sys.stderr.write('updating label: '+l+'\n') + if l.lower() in book: + #sys.stderr.write(' '+l+' found\n') + self.entries[e].value = str(book[l.lower()]) + else: + #sys.stderr.write(' '+l+' notfound\n') + self.entries[e].value = "" + e += 1 + + def redraw(self): + self.w.box() + self.w.addstr(0,(self.mx-len(self.caption))//2,self.caption) + r=0 + for l in self.labels: + c = self.left-len(l)-2 + self.w.addstr(r+self.top,c,l+":") + self.entries[r].redraw() + r+=1 + self.w.addstr(self.brow,self.bcol[0], " <"+self.blabel+">") + self.w.refresh() + + def refresh(self): + self.hb.commands = self.commands + self.hb.refresh() + self._update_geometry() + self.redraw() + + def _highlight_button(self): + self.w.chgat(self.brow, self.bcol[self.bt], self.bwidth[self.bt], curses.A_REVERSE) + curses.curs_set(0) + + def _unhighlight_button(self): + self.w.chgat(self.brow,1,self.mx-2,curses.A_NORMAL) + + def _mv_focus(self,delta): + if self.bt==-1: + self.entries[self.hl].lose_focus() + else: + self._unhighlight_button() + new = self.hl+delta + new = max(0, min(len(self.labels), new)) # the extra is for the buttons + self.hl = new + if new == len(self.labels): + self.bt = 1 + self.bt = min(self.bt,1) + self._highlight_button() + else: + self.bt=-1 + self.entries[self.hl].gain_focus() + + + def _return_values(self): + book = {} + for k,e in zip(self.labels, self.entries): + if v!="" and k.lower()!="publish date": + book[k.lower()]=e.value + return book + + + def handle_input(self,ch): if ch==curses.KEY_UP: - self.mvHighlight(-1) + self._mv_focus(-1) elif ch==curses.KEY_PPAGE: - self.mvHighlight(-len(self.labels)) + self._mv_focus(-len(self.labels)) elif ch==curses.KEY_DOWN: - self.mvHighlight(+1) + self._mv_focus(+1) elif ch==curses.KEY_NPAGE: - self.mvHighlight(+len(self.labels)) - + self._mv_focus(+len(self.labels)) elif ch==curses.KEY_LEFT: if self.bt==-1: - self.mvCursor(-1) + self.entries[self.hl].handle_input(ch) else: - self.unHighlight() + self._unhighlight_button() self.bt=0 - self.highlight() + self._highlight_button() elif ch==curses.KEY_HOME: if self.bt==-1: - self.mvCursor(-len(self.entries[self.hl])) + self._mv_cursor(-len(self.entries[self.hl])) elif ch==curses.KEY_RIGHT: if self.bt==-1: - self.mvCursor(+1) + self.entries[self.hl].handle_input(ch) else: - self.unHighlight() + self._unhighlight_button() self.bt=1 - self.highlight() - elif ch==curses.KEY_END: + self._highlight_button() + else: if self.bt==-1: - self.mvCursor(+len(self.entries[self.hl])) + self.entries[self.hl].handle_input(ch) - elif ch>=32 and ch<=126: - if self.bt==-1: - self.insert(curses.keyname(ch)) - elif ch==curses.KEY_BACKSPACE: - if self.bt==-1: - self.backspace() - elif ch==curses.KEY_DC: - if self.bt==-1: - self.delete() -class bookForm(formWindow): +class BookForm(FormWindow): caption = "Add a Book" blabel = "Add" labels = ["ISBN", "LCCN", "Title", "Subtitle", "Authors", "Edition", @@ -212,30 +295,31 @@ class bookForm(formWindow): def lookup_lccn(self,lccn): return {'lccn':lccn} - def returnBook(self): - return self.returnValues() + def return_book(self): + return self.return_values() - def handleInput(self,ch): + def handle_input(self,ch): if ch==10 or ch==curses.KEY_ENTER: if self.hl==0: # lookup by isbn - book = self.lookup_isbn(self.entries[0]) + book = self.lookup_isbn(self.entries[0].value) if book != {}: - self.updateEntries(book) + #sys.stderr.write('updating entries\n') + self._set_entries(book) self.refresh() - self.mvHighlight(+7) + self._mv_focus(+7) if self.hl==1: # lookup by lccn - book = self.lookup_lccn(self.entries[1]) + book = self.lookup_lccn(self.entries[1].value) if book != {}: - self.updateEntries(book) + self._set_entries(book) self.refresh() - self.mvHighlight(+6) + self._mv_focus(+6) else: - formWindow.handleInput(self,ch) + FormWindow.handle_input(self,ch) -class categoryForm(formWindow): +class CategoryForm(FormWindow): caption = "Add a Category" blabel = "Add" labels = ["Category"] - def returnValues(self): + def return_values(self): return self.entries diff --git a/gui.py b/gui.py deleted file mode 100644 index 4be00a6..0000000 --- a/gui.py +++ /dev/null @@ -1,324 +0,0 @@ -import curses -import sys - - -class TextEntry: - - """A part of a window that handles text entry. - Properties: - value holds the string that was entered - - Public Methods: - set_geom(row,column,width) Sets the geometry in the window - set_value(string) Set the value and redraw - gain_focus() Gives it focus, moving cursor and changing the drawing - lose_focus() Takes focus, moving cursor to start, changing drawing - handle_input(ch) Pass this the ncurses key, and it manages input - redraw() Redraw the text entry (should never need to do this - """ - - # Public - value = "" # Use the set_value function to set, but retrieve with value - - # Should be Private - cursor = 0 - start = 0 - focus = False - x = 0 - y = 0 - width = 10 - - def __init__(self, parent_window, value=""): - self.w = parent_window - self.value = value - - def set_geom(self,y,x,width): - self.x = x - self.y = y - self.width = width - - def set_value(self,v): - self.value=v - self.cursor=len(v) - self.redraw() - - def gain_focus(self): - #sys.stderr.write('I have focus!\n') - self.focus = True - self._mv_cursor(+len(self.value)) - self.start = max(0,self.cursor-self.width) - self.redraw() - - def lose_focus(self): - self.focus = False - self.cursor = 0 - self.start = 0 - self.redraw() - - def handle_input(self,ch): - if ch==curses.KEY_LEFT: - self._mv_cursor(-1) - elif ch==curses.KEY_HOME: - self._set_cursor(0) - elif ch==curses.KEY_RIGHT: - self._mv_cursor(+1) - elif ch==curses.KEY_END: - self._set_cursor(len(self.value)) - elif ch>=32 and ch<=126: - self._insert(curses.keyname(ch).decode('utf-8')) - elif ch==curses.KEY_BACKSPACE: - self._backspace() - elif ch==curses.KEY_DC: - self._delete() - - def redraw(self): - self.w.addnstr(self.y,self.x, self.value[self.start:]+" "*self.width, self.width) - if self.focus: - self.w.chgat(self.y, self.x, self.width, curses.A_UNDERLINE) - curses.curs_set(1) - - # Private functions - def _mv_cursor(self,delta): - self._set_cursor(self.cursor + delta) - - def _set_cursor(self, new_c): - self.cursor = max(0, min(len(self.value), new_c)) - self.start = max(0,self.cursor-self.width+1) - self.redraw() - # Place the drawn cursor in the correct spot - col = self.x + self.cursor - self.start - self.w.move(self.y,col) - - def _insert(self,ch): - c = self.cursor - self.value = self.value[:c] +ch+ self.value[c:] - self._mv_cursor(+1) - - def _backspace(self): - if self.cursor>0: - c = self.cursor - self.value=self.value[:c-1] + self.value[c:] - self._mv_cursor(-1) - - def _delete(self): - c = self.cursor - self.value = self.value[:c] + self.value[c+1:] - self._mv_cursor(0) - - - -class FormWindow: - - """General class for a Form Window. - - To use, make the window for it, call the constructor, then call event_loop. - """ - - # Private variables - mx = my = 0 - hl = 0 - bt = -1 - left = 0 - top = 2 - row = 2 - caption = "Form" - blabel = "Done" - labels = ["label1"] - - commands = [('pU', 'top'),('pD', 'bottom'),('Es', 'cancel')] - - - # Public functions - def __init__(self,window,helpbar,book={}): - self.w = window - self.w.resize(len(self.labels)+6,50) - self.hb = helpbar - self._make_entries() - self._update_geometry() - self._set_entries(book) - - def clear(self): - self.w.erase() - self.w.refresh() - - def event_loop(self): - self.w.keypad(1) - self.refresh() - self.hl=0; - self.entries[self.hl].gain_focus() - - ch = self.w.getch() - while ch != 27: - #sys.stderr.write(curses.keyname(ch).decode('utf-8')) - self.handle_input(ch) - if ch==10 or ch==curses.KEY_ENTER: - if self.bt==0: - return {} - elif self.bt==1: - return self.return_values() - else: - self._mv_focus(+1) - self.w.refresh() - ch = self.w.getch() - curses.curs_set(0) - return {} - - def _make_entries(self): - self.entries = [] - for e in range(len(self.labels)): - self.entries.append(TextEntry(self.w)) - - def _update_geometry(self): - (self.my, self.mx) = self.w.getmaxyx() - self.left=0 - for l in self.labels: - self.left = max(len(l),self.left) - self.left += 4 - width = self.mx-self.left-2 - self.top = 2 - for r in range(len(self.entries)): - self.entries[r].set_geom(r+self.top, self.left, width) - # next, the buttons - self.brow = self.top+len(self.labels)+1 - self.bcol = [self.mx-len(self.blabel)-14, self.mx-len(self.blabel)-4] - self.bwidth = [8,len(self.blabel)+2] - - def _set_entries(self,book): - e = 0 - for l in self.labels: - #sys.stderr.write('updating label: '+l+'\n') - if l.lower() in book: - #sys.stderr.write(' '+l+' found\n') - self.entries[e].value = str(book[l.lower()]) - else: - #sys.stderr.write(' '+l+' notfound\n') - self.entries[e].value = "" - e += 1 - - def redraw(self): - self.w.box() - self.w.addstr(0,(self.mx-len(self.caption))//2,self.caption) - r=0 - for l in self.labels: - c = self.left-len(l)-2 - self.w.addstr(r+self.top,c,l+":") - self.entries[r].redraw() - r+=1 - self.w.addstr(self.brow,self.bcol[0], " <"+self.blabel+">") - self.w.refresh() - - def refresh(self): - self.hb.commands = self.commands - self.hb.refresh() - self._update_geometry() - self.redraw() - - def _highlight_button(self): - self.w.chgat(self.brow, self.bcol[self.bt], self.bwidth[self.bt], curses.A_REVERSE) - curses.curs_set(0) - - def _unhighlight_button(self): - self.w.chgat(self.brow,1,self.mx-2,curses.A_NORMAL) - - def _mv_focus(self,delta): - if self.bt==-1: - self.entries[self.hl].lose_focus() - else: - self._unhighlight_button() - new = self.hl+delta - new = max(0, min(len(self.labels), new)) # the extra is for the buttons - self.hl = new - if new == len(self.labels): - self.bt = 1 - self.bt = min(self.bt,1) - self._highlight_button() - else: - self.bt=-1 - self.entries[self.hl].gain_focus() - - - def _return_values(self): - book = {} - for k,e in zip(self.labels, self.entries): - if v!="" and k.lower()!="publish date": - book[k.lower()]=e.value - return book - - - def handle_input(self,ch): - if ch==curses.KEY_UP: - self._mv_focus(-1) - elif ch==curses.KEY_PPAGE: - self._mv_focus(-len(self.labels)) - elif ch==curses.KEY_DOWN: - self._mv_focus(+1) - elif ch==curses.KEY_NPAGE: - self._mv_focus(+len(self.labels)) - elif ch==curses.KEY_LEFT: - if self.bt==-1: - self.entries[self.hl].handle_input(ch) - else: - self._unhighlight_button() - self.bt=0 - self._highlight_button() - elif ch==curses.KEY_HOME: - if self.bt==-1: - self._mv_cursor(-len(self.entries[self.hl])) - elif ch==curses.KEY_RIGHT: - if self.bt==-1: - self.entries[self.hl].handle_input(ch) - else: - self._unhighlight_button() - self.bt=1 - self._highlight_button() - else: - if self.bt==-1: - self.entries[self.hl].handle_input(ch) - - - - -class BookForm(FormWindow): - caption = "Add a Book" - blabel = "Add" - labels = ["ISBN", "LCCN", "Title", "Subtitle", "Authors", "Edition", - "Publisher", "Publish Date", "Publish Year", "Publish Month", "Publish location", - "Pages", "Pagination", "Weight"] - - - # redefineable functions lookup is called when 'enter' is pressed on ISBN - # and returns the looked-up book. Default returns nothing - def lookup_isbn(self,isbn): - return {'isbn':isbn} - - def lookup_lccn(self,lccn): - return {'lccn':lccn} - - def return_book(self): - return self.return_values() - - def handle_input(self,ch): - if ch==10 or ch==curses.KEY_ENTER: - if self.hl==0: # lookup by isbn - book = self.lookup_isbn(self.entries[0].value) - if book != {}: - #sys.stderr.write('updating entries\n') - self._set_entries(book) - self.refresh() - self._mv_focus(+7) - if self.hl==1: # lookup by lccn - book = self.lookup_lccn(self.entries[1].value) - if book != {}: - self._set_entries(book) - self.refresh() - self._mv_focus(+6) - else: - FormWindow.handle_input(self,ch) - -class CategoryForm(FormWindow): - caption = "Add a Category" - blabel = "Add" - labels = ["Category"] - - def return_values(self): - return self.entries diff --git a/librarian.py b/librarian.py index c4dc884..13ebef7 100755 --- a/librarian.py +++ b/librarian.py @@ -3,7 +3,7 @@ import curses import db_layer as db import browser -import gui as form +import form import help_bar as helpBar import book_data