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