can now browse books by category
[public/library.git] / browser.py
1 import sys
2 import curses
3 import dbLayer as db
4 from form import bookForm,categoryForm
5
6 class browserWindow:
7     hl=0
8     topline = 0
9     entries = []
10     selected = []
11     commands = [(' /', 'search'), (' n', 'find next'), (' N', 'find previous'), (' q', 'quit')]
12     cs = []
13     # column definitions are in (label, weight, specified width) triples
14     columnDefs = [('something',1,None)]
15     mx = my = 0
16     cx = cy = 0
17     # for searches
18     last_search = ""
19     found_index = 0
20
21     def __init__(self,window,helpbar):
22         self.w = window
23         self.hb = helpbar
24         self.updateGeometry()
25         self.commands = self.cs+self.commands
26
27     def sortByColumn(self, col):
28         self.entries.sort(key=lambda k: k.get(col)) # key=dict.get(col))
29         self.selected = map(lambda x: False, self.selected)
30
31     def updateGeometry(self):
32         (self.my,self.mx)=self.w.getmaxyx()
33         (y,x) = self.w.getbegyx()
34         self.cx = x + self.mx/2
35         self.cy = y + self.my/2
36         self.pageSize = self.my-3
37         self.calcColWidths()
38
39     def calcColWidths(self):
40         total_weights = 0
41         available_space = self.mx - len(self.columnDefs)
42         cols = []
43         for label,weight,value in self.columnDefs:
44             if value!=None:
45                 available_space -= value
46             else:
47                 total_weights+=weight
48
49         for label,weight,value in self.columnDefs:
50             if value!=None:
51                 cols.append((label,value))
52             else:
53                 cols.append((label,available_space*weight/total_weights))
54         self.columns=cols
55
56     def refresh(self):
57         self.hb.commands = self.commands
58         self.hb.refresh()
59         self.displayHeader()
60         for r in range(0,self.pageSize):
61             self.displayRow(r)
62         self.w.refresh()
63         self.highlight()
64
65     def clear(self):
66         self.w.erase()
67         self.w.refresh()
68
69     def centreChild(self,child):
70         (y,x)=child.getmaxyx()
71         child.mvwin(self.cy-y/2,self.cx-x/2)
72
73
74     def displayHeader(self):
75         cursor = 1
76         for header,width in self.columns:
77             self.w.addnstr(0,cursor,header+" "*width,width)
78             self.w.addstr(1,cursor,"-"*width)
79             cursor += width+1
80
81     def displayRow(self,row):
82         if self.topline+row < len(self.entries):
83             entry = self.entries[self.topline+row]
84             cursor = 1
85             if self.selected[self.topline+row]:
86                 self.w.addstr(row+2, 0, "*")
87             else:
88                 self.w.addstr(row+2, 0, " ")
89             for k,width in self.columns:
90                 if k.lower() in entry:
91                     self.w.addnstr(row+2,cursor,str(entry[k.lower()])+" "*width,width)
92                 cursor += width+1
93         else:
94             self.w.addstr(row+2,0," "*self.mx)
95
96     def highlight(self):
97         row = self.hl-self.topline+2
98         if row > 1 and row < self.my:
99             self.w.chgat(row,0,self.mx,curses.A_REVERSE)
100
101     def unHighlight(self):
102         row = self.hl-self.topline+2
103         if row > 1 and row < self.my:
104             self.w.chgat(row,0,self.mx,curses.A_NORMAL)
105
106     def mvHighlight(self,delta):
107         new = self.hl+delta
108         new = max(new,0)
109         new = min(new,len(self.entries)-1)
110         self.unHighlight()
111         self.hl = new
112         self.highlight()
113     
114     def scroll(self,delta):
115         self.unHighlight()
116         self.topline += delta
117         self.topline = min(self.topline,len(self.entries)-1)
118         self.topline = max(self.topline,0)
119         self.refresh()
120
121     def search(self, string):
122         i = 0
123         found = False
124         for e in self.entries:
125             for k,v in e.items():
126                 if str(v).find(string) != -1:
127                     found = True
128             if found:
129                 break
130             i += 1;
131         if found:
132             self.last_search = string
133             self.search_index = i
134             return i
135         else:
136             self.search_index = -1
137             return -1
138
139     def findNext(self):
140         if self.last_search == "" or self.search_index == -1:
141             return -1
142         found = False
143         for i in range(self.hl+1,len(self.entries)-1):
144             for k,v in self.entries[i].items():
145                 if str(v).find(self.last_search) != -1:
146                     found = True
147             if found:
148                 break
149         if found:
150             self.search_index = i
151             return i
152         else:
153             return -1
154
155     def findPrevious(self):
156         if self.last_search == "" or self.search_index == -1:
157             return -1
158         found = False
159         for i in range(self.hl-1, 0, -1):
160             for k,v in self.entries[i].items():
161                 if str(v).find(self.last_search) != -1:
162                     found = True
163             if found:
164                 break
165         if found:
166             self.search_index = i
167             return i
168         else:
169             return -1
170
171
172     def eventLoop(self):
173         self.w.keypad(1)
174         self.refresh()
175
176         ch = self.w.getch()
177         while ch != 27 and ch != 113:
178             ch = self.handleInput(ch)
179             if ch==113:
180                 return {}
181             self.w.refresh()
182             ch = self.w.getch()
183             self.hb.refresh()
184
185     def handleInput(self,ch):
186         if ch == curses.KEY_UP or ch == 107 or ch == 16:
187             if self.hl == self.topline:
188                 self.scroll(-self.pageSize/2-1)
189             self.mvHighlight(-1)
190         elif ch == curses.KEY_DOWN or ch == 106 or ch == 14:
191             if self.hl == self.topline+self.pageSize-1:
192                 self.scroll(+self.pageSize/2+1)
193             self.mvHighlight(+1)
194         elif ch == curses.KEY_PPAGE:
195             self.scroll(-self.pageSize)
196             self.mvHighlight(-self.pageSize)
197         elif ch == curses.KEY_NPAGE:
198             self.scroll(+self.pageSize)
199             self.mvHighlight(+self.pageSize)
200         elif ch == 47: # forward slash
201             string = self.hb.getSearch()
202             hl = self.search(string)
203             if hl != -1:
204                 delta = hl - self.hl
205                 self.scroll(delta)
206                 self.mvHighlight(delta)
207             else:
208                 self.hb.display(string+' not found')
209         elif ch == 110: # n
210             hl = self.findNext()
211             if hl != -1:
212                 delta = hl - self.hl
213                 self.scroll(delta)
214                 self.mvHighlight(delta)
215             else:
216                 self.hb.display(self.last_search+' not found')
217         elif ch == 78: # N
218             hl = self.findPrevious()
219             if hl != -1:
220                 delta = hl - self.hl
221                 self.scroll(delta)
222                 self.mvHighlight(delta)
223             else:
224                 self.hb.display(self.last_search+' not found')
225         elif ch == 32:
226             if len(self.selected)>0:
227                 self.selected[self.hl] = not self.selected[self.hl]
228             self.displayRow(self.hl-self.topline)
229             self.highlight()
230
231
232
233 class trashBrowser(browserWindow):
234     columnDefs = [('ID',0,3),
235                   ('ISBN',0,13),
236                   ('Authors',30,None),
237                   ('Title',60,None)]
238     
239     cs = [(' r', 'restore selected'), (' d', 'delete selected')]
240     
241     # redefinable functions
242     def viewSelection(self,book):
243         bookid = book['id']
244         w=curses.newwin(1,1,20,20)
245         bf = bookForm(w,self.hb,book)
246         self.centreChild(w)
247         bf.caption='Viewing Book '+str(bookid)
248         bf.blabel='done'
249         bf.eventLoop()
250         bf.clear()
251
252     def restoreSelected(self):
253         books = []
254         for sel,book in zip(self.selected, self.entries):
255             if sel:
256                 books.append(book)
257         db.restoreBooks(books)
258
259     def delSelected(self):
260         books = []
261         for sel,book in zip(self.selected, self.entries):
262             if sel:
263                 books.append(book)
264         db.deleteBooks(books)
265
266     def refreshBooks(self):
267         self.entries = db.getRemovedBooks()
268         self.selected = map(lambda x:False, self.entries)
269
270     def handleInput(self,ch):
271         browserWindow.handleInput(self,ch)
272         if ch == 10:
273             book = self.entries[self.hl]
274             self.viewSelection(book)
275             self.refresh()
276         if ch==114: #restore books
277             count=0
278             for s in self.selected[0:self.hl-1]:
279                 if s:
280                     count+=1
281             self.restoreSelected()
282             self.refreshBooks()
283             self.refresh()
284             self.scroll(-count)
285             self.mvHighlight(-count)
286         if ch==100: # delete books
287             count=0
288             for s in self.selected[0:self.hl-1]:
289                 if s:
290                     count+=1
291             self.delSelected()
292             self.refreshBooks()
293             self.refresh()
294             self.scroll(-count)
295             self.mvHighlight(-count)
296         return ch
297
298 class bookBrowser(browserWindow):
299     columnDefs = [('ID',0,3),
300                   ('ISBN',0,13),
301                   ('Authors',30,None),
302                   ('Title',60,None)]
303     
304     cs = [(' u', 'update'), (' d', 'delete selected')]
305     
306     # redefinable functions
307     def updateSelection(self,book):
308         bookid = book['id']
309         
310         w=curses.newwin(1,1)
311         bf=bookForm(w,self.hb,book)
312         self.centreChild(w)
313         bf.caption='Update Book '+str(bookid)
314         bf.blabel='update'
315         newbook = bf.eventLoop()
316         if len(newbook)!=0:
317             db.updateBook(newbook,bookid)
318         bf.clear()
319
320     def viewSelection(self,book):
321         bookid = book['id']
322         w=curses.newwin(1,1,20,20)
323         bf = bookForm(w,self.hb,book)
324         self.centreChild(w)
325         bf.caption='Viewing Book '+str(bookid)
326         bf.blabel='done'
327         bf.eventLoop()
328         bf.clear()
329
330     def categorizeSelection(self,book):
331         w = curses.newwin(40,20,20,20)
332         cs = categorySelector(w,self.hb)
333         self.centreChild(w)
334         cs.book = book
335         cs.refreshCategories()
336         cs.eventLoop()
337         cs.clear()
338     
339     def delSelected(self):
340         books = []
341         for sel,book in zip(self.selected, self.entries):
342             if sel:
343                 books.append(book)
344         db.removeBooks(books)
345
346     def refreshBooks(self):
347         self.entries = db.getBooks()
348         self.selected = map(lambda x:False, self.entries)
349
350     def refreshBooksInCategory(self,cat):
351         self.entries = db.getBooksByCategory(cat)
352         self.selected = map(lambda x:False, self.entries)
353
354     def handleInput(self,ch):
355         browserWindow.handleInput(self,ch)
356         if ch == 117: #update on 'u'
357             book = self.entries[self.hl]
358             self.updateSelection(book)
359             self.entries[self.hl]=db.getBookByID(book['id'])
360             self.refresh()
361         elif ch == 10:
362             book = self.entries[self.hl]
363             self.viewSelection(book)
364             self.refresh()
365         elif ch == 99:
366             book = self.entries[self.hl]
367             self.categorizeSelection(book)
368             self.refresh()
369         if ch==100:
370             count=0
371             for s in self.selected[0:self.hl-1]:
372                 if s:
373                     count+=1
374             self.delSelected()
375             self.refreshBooks()
376             self.refresh()
377             self.scroll(-count)
378             self.mvHighlight(-count)
379         return ch
380
381 class categoryBrowser(browserWindow):
382     columnDefs = [('Category',100,None)]
383     cs = [(' a', 'add category'), (' d', 'delete selected')]
384
385
386     def refreshCategories(self):
387         self.entries = db.getCategories()
388         self.sortByColumn('category')
389         self.selected = map(lambda x:False, self.entries)
390
391     def addCategory(self):
392         w = curses.newwin(1,1,10,10)
393         cf = categoryForm(w,self.hb)
394         self.centreChild(w)
395         cats = cf.eventLoop()
396         for c in cats:
397             db.addCategory(c)
398         cf.clear()
399
400     def viewCategory(self):
401         w = curses.newwin(20,80,20,20)
402         b = bookBrowser(w,self.hb)
403         self.centreChild(w)
404         b.refreshBooksInCategory(self.entries[self.hl])
405         b.eventLoop()
406         b.clear()
407
408     def delSelected(self):
409         categories = []
410         for sel,cat in zip(self.selected, self.entries):
411             if sel:
412                 categories.append(cat)
413         db.deleteCategories(categories)
414
415     def handleInput(self,ch):
416         browserWindow.handleInput(self,ch)
417         if ch==97:
418             self.addCategory()
419             self.refreshCategories()
420             self.refresh()
421         if ch ==10:
422             self.viewCategory()
423             self.refresh()
424         if ch==100:
425             count=0
426             for s in self.selected[0:self.hl-1]:
427                 if s:
428                     count+=1
429             self.delSelected()
430             self.refreshCategories()
431             self.refresh()
432             self.scroll(-count)
433             self.mvHighlight(-count)
434         return ch
435
436 class categorySelector(browserWindow):
437     columnDefs = [('Category',100,None)]
438     cs = [(' a', 'add category'), (' c', 'commit')]
439     book = {'id':''}
440     original=[]
441
442
443     def refreshCategories(self):
444         self.entries = db.getCategories()
445         self.sortByColumn('category')
446         self.refreshSelected()
447
448     def refreshSelected(self):
449         self.original = map(lambda x:False, self.entries)
450         cats = db.getBookCategories(self.book)
451         cats.sort()
452         cats.sort(key=lambda k: k.get('category')) # key=dict.get(col))
453         i = 0
454         j = 0
455         for cat in self.entries:
456             if i == len(cats):
457                 break
458             if cat['id']==cats[i]['cat_id']:
459                 self.original[j] = True;
460                 i+=1
461             j+=1
462         self.selected = self.original[:]
463
464
465     def addCategory(self):
466         w = curses.newwin(1,1,10,10)
467         cf = categoryForm(w,self.hb)
468         self.centreChild(w)
469         cats = cf.eventLoop()
470         for c in cats:
471             db.addCategory(c)
472         cf.clear()
473
474     def updateCategories(self):
475         # first removed the deselected ones
476         uncats = []
477         cats = []
478         for old, new, category in zip(self.original, self.selected, self.entries):
479             if old and (not new):
480                 uncats.append(category)
481             if (not old) and new:
482                 cats.append(category)
483         db.uncategorizeBook(self.book, uncats)
484         # add the newly selected categories
485         db.categorizeBook(self.book, cats)
486
487
488     def handleInput(self,ch):
489         browserWindow.handleInput(self,ch)
490         if ch==97:
491             self.addCategory()
492             self.refreshCategories()
493             self.refresh()
494         if ch==99:
495             self.updateCategories()
496             return 113
497