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