Couple more checkout related functions
[public/library.git] / form.py
1 import curses
2 import sys
3
4
5 class TextEntry:
6
7     """A part of a window that handles text entry.
8     Properties:
9         value holds the string that was entered
10     
11     Public Methods:
12         set_geom(row,column,width)  Sets the geometry in the window
13         set_value(string)           Set the value and redraw
14         gain_focus()                Gives it focus, moving cursor and changing the drawing
15         lose_focus()                Takes focus, moving cursor to start, changing drawing
16         handle_input(ch)            Pass this the ncurses key, and it manages input
17         redraw()                    Redraw the text entry (should never need to do this
18         """
19
20     # Public
21     value = ""  # Use the set_value function to set, but retrieve with value
22
23     # Should be Private
24     cursor = 0
25     start = 0
26     focus = False
27     x = 0
28     y = 0
29     width = 10
30
31     # Public methods
32     def __init__(self, parent_window, value=""):
33         self.w = parent_window
34         self.value = value
35
36     def set_geom(self,y,x,width):
37         self.x = x
38         self.y = y
39         self.width = width
40
41     def set_value(self,v):
42         self.value=v
43         self.cursor=len(v)
44         self.redraw()
45
46     def gain_focus(self):
47         #sys.stderr.write('I have focus!\n')
48         self.focus = True
49         self._mv_cursor(+len(self.value))
50         self.start = max(0,self.cursor-self.width) 
51         self.redraw()
52
53     def lose_focus(self):
54         self.focus = False
55         self.cursor = 0
56         self.start = 0
57         self.redraw()
58     
59     def handle_input(self,ch):
60         if ch==curses.KEY_LEFT:
61             self._mv_cursor(-1)
62         elif ch==curses.KEY_HOME:
63             self._set_cursor(0)
64         elif ch==curses.KEY_RIGHT:
65             self._mv_cursor(+1)
66         elif ch==curses.KEY_END:
67             self._set_cursor(len(self.value))
68         elif ch>=32 and ch<=126:
69             self._insert(curses.keyname(ch).decode('utf-8'))
70         elif ch==curses.KEY_BACKSPACE:
71             self._backspace()
72         elif ch==curses.KEY_DC:
73             self._delete()
74
75     def redraw(self):
76         self.w.addnstr(self.y,self.x, self.value[self.start:]+" "*self.width, self.width)
77         if self.focus:
78             self.w.chgat(self.y, self.x, self.width, curses.A_UNDERLINE)
79             curses.curs_set(1)
80
81     # Private functions
82     def _mv_cursor(self,delta):
83         self._set_cursor(self.cursor + delta)
84     
85     def _set_cursor(self, new_c):
86         self.cursor = max(0, min(len(self.value), new_c))
87         self.start = max(0,self.cursor-self.width+1) 
88         self.redraw()
89         # Place the drawn cursor in the correct spot
90         col = self.x + self.cursor - self.start
91         self.w.move(self.y,col)
92
93     def _insert(self,ch):
94         c = self.cursor
95         self.value = self.value[:c] +ch+  self.value[c:]
96         self._mv_cursor(+1)
97
98     def _backspace(self):
99         if self.cursor>0:
100             c = self.cursor
101             self.value=self.value[:c-1] + self.value[c:]
102             self._mv_cursor(-1)
103
104     def _delete(self):
105         c = self.cursor
106         self.value = self.value[:c] + self.value[c+1:]
107         self._mv_cursor(0)
108
109
110
111 class FormWindow:
112
113     """General class for a Form Window.
114     
115     To use, make the window for it, call the constructor, then call event_loop.
116     """
117
118     # Private variables
119     mx = my = 0
120     hl = 0
121     bt = -1
122     left = 0
123     top = 2
124     row = 2
125     caption = "Form"
126     blabel = "Done"
127     labels = ["label1"]
128
129     commands = [('pU', 'top'),('pD', 'bottom'),('Es', 'cancel')]
130
131
132     # Public functions
133     def __init__(self,window,helpbar,book={}, width=50):
134         self.w = window
135         self.w.resize(len(self.labels)+6,width)
136         self.hb = helpbar
137         self._make_entries()
138         self._update_geometry()
139         self._set_entries(book)
140
141     def clear(self):
142         self.w.erase()
143         self.w.refresh()
144
145     def event_loop(self):
146         self.w.keypad(1)
147         self.refresh()
148         self.hl=0;
149         self.entries[self.hl].gain_focus()
150
151         ch = self.w.getch()
152         while ch != 27:
153             #sys.stderr.write(curses.keyname(ch).decode('utf-8'))
154             self.handle_input(ch)
155             if ch==10 or ch==curses.KEY_ENTER:
156                 if self.bt==0:
157                     return {}
158                 elif self.bt==1:
159                     return self._return_values()
160                 else:
161                     self._mv_focus(+1)
162             self.w.refresh()
163             ch = self.w.getch()
164         curses.curs_set(0)
165         return {}
166
167     def _make_entries(self):
168         self.entries = []
169         for e in range(len(self.labels)):
170             self.entries.append(TextEntry(self.w))
171
172     def _update_geometry(self):
173         (self.my, self.mx) = self.w.getmaxyx()
174         self.left=0
175         for l in self.labels:
176             self.left = max(len(l),self.left)
177         self.left += 4
178         width = self.mx-self.left-2
179         self.top = 2
180         for r in range(len(self.entries)):
181             self.entries[r].set_geom(r+self.top, self.left, width)
182         # next, the buttons
183         self.brow = self.top+len(self.labels)+1
184         self.bcol = [self.mx-len(self.blabel)-14, self.mx-len(self.blabel)-4]
185         self.bwidth = [8,len(self.blabel)+2]
186
187     def _set_entries(self,book):
188         e = 0
189         for l in self.labels:
190             #sys.stderr.write('updating label: '+l+'\n')
191             if l.lower() in book:
192                 #sys.stderr.write('   '+l+' found\n')
193                 self.entries[e].value = str(book[l.lower()])
194             else:
195                 #sys.stderr.write('   '+l+' notfound\n')
196                 self.entries[e].value = ""
197             e += 1 
198
199     def redraw(self):
200         self.w.box()
201         self.w.addstr(0,(self.mx-len(self.caption))//2,self.caption)
202         r=0
203         for l in self.labels:
204             c = self.left-len(l)-2
205             self.w.addstr(r+self.top,c,l+":")
206             self.entries[r].redraw()
207             r+=1
208         self.w.addstr(self.brow,self.bcol[0], "<cancel>  <"+self.blabel+">")
209         self.w.refresh()
210
211     def refresh(self):
212         self.hb.commands = self.commands
213         self.hb.refresh()
214         self._update_geometry()
215         self.redraw()
216
217     def _highlight_button(self):
218         self.w.chgat(self.brow, self.bcol[self.bt], self.bwidth[self.bt], curses.A_REVERSE)
219         curses.curs_set(0)
220
221     def _unhighlight_button(self):
222         self.w.chgat(self.brow,1,self.mx-2,curses.A_NORMAL)
223
224     def _mv_focus(self,delta):
225         if self.bt==-1:
226             self.entries[self.hl].lose_focus()
227         else:
228             self._unhighlight_button()
229         new = self.hl+delta
230         new = max(0, min(len(self.labels), new))   # the extra is for the buttons
231         self.hl = new
232         if new == len(self.labels):
233             self.bt = 1
234             self.bt = min(self.bt,1)
235             self._highlight_button()
236         else:
237             self.bt=-1
238             self.entries[self.hl].gain_focus()
239
240
241     def _return_values(self):
242         book = {}
243         for k,e in zip(self.labels, self.entries):
244             if e!="" and k.lower()!="publish date":
245                 book[k.lower()]=e.value
246         return book
247
248
249     def handle_input(self,ch):
250         if ch==curses.KEY_UP:
251             self._mv_focus(-1)
252         elif ch==curses.KEY_PPAGE:
253             self._mv_focus(-len(self.labels))
254         elif ch==curses.KEY_DOWN:
255             self._mv_focus(+1)
256         elif ch==curses.KEY_NPAGE:
257             self._mv_focus(+len(self.labels))
258         elif ch==curses.KEY_LEFT:
259             if self.bt==-1:
260                 self.entries[self.hl].handle_input(ch)
261             else:
262                 self._unhighlight_button()
263                 self.bt=0
264                 self._highlight_button()
265         elif ch==curses.KEY_HOME:
266             if self.bt==-1:
267                 self._mv_cursor(-len(self.entries[self.hl]))
268         elif ch==curses.KEY_RIGHT:
269             if self.bt==-1:
270                 self.entries[self.hl].handle_input(ch)
271             else:
272                 self._unhighlight_button()
273                 self.bt=1
274                 self._highlight_button()
275         else:
276             if self.bt==-1:
277                 self.entries[self.hl].handle_input(ch)
278
279         
280         
281
282 class BookForm(FormWindow):
283     caption = "Add a Book"
284     blabel = "Add"
285     labels = ["ISBN", "LCCN", "Title", "Subtitle", "Authors", "Edition",
286               "Publisher", "Publish Date", "Publish Year", "Publish Month", "Publish location",
287               "Pages", "Pagination", "Weight"]
288     
289
290     # redefineable functions lookup is called when 'enter' is pressed on ISBN
291     # and returns the looked-up book. Default returns nothing
292     def lookup_isbn(self,isbn):
293         return {'isbn':isbn}
294     
295     def lookup_lccn(self,lccn):
296         return {'lccn':lccn}
297
298     def return_book(self):
299         return self._return_values()
300
301     def handle_input(self,ch):
302         if ch==10 or ch==curses.KEY_ENTER:
303             if self.hl==0:          # lookup by isbn
304                 book = self.lookup_isbn(self.entries[0].value)
305                 if book != {}:
306                     #sys.stderr.write('updating entries\n')
307                     self._set_entries(book)
308                 self.refresh()
309                 self._mv_focus(+7)
310             if self.hl==1:          # lookup by lccn
311                 book = self.lookup_lccn(self.entries[1].value)
312                 if book != {}:
313                     self._set_entries(book)
314                 self.refresh()
315                 self._mv_focus(+6)
316         else:
317             FormWindow.handle_input(self,ch)
318
319 class CategoryForm(FormWindow):
320     caption = "Add a Category"
321     blabel = "Add"
322     labels = ["Category"]
323
324     def _return_values(self):
325         return self.entries