Re-build so debian.csclub include library as a dependency
[public/pyceo-broken.git] / ceo / urwid / widgets.py
1 import urwid, ldap, sys
2 from ceo.urwid.window import raise_back, push_window
3 import ceo.ldapi as ldapi
4
5 #Todo: kill ButtonText because no one uses it except one place and we can probably do that better anyway
6
7 csclub_uri = "ldap://ldap1.csclub.uwaterloo.ca/ ldap://ldap2.csclub.uwaterloo.ca"
8 csclub_base = "dc=csclub,dc=uwaterloo,dc=ca"
9
10 def make_menu(items):
11     items = [ urwid.AttrWrap( ButtonText( cb, data, txt ), 'menu', 'selected') for (txt, cb, data) in items ]
12     return ShortcutListBox(items)
13
14 def labelled_menu(itemses):
15     widgets = []
16     for label, items in itemses:
17         if label:
18             widgets.append(urwid.Text(label))
19         widgets += (urwid.AttrWrap(ButtonText(cb, data, txt), 'menu', 'selected') for (txt, cb, data) in items)
20         widgets.append(urwid.Divider())
21     widgets.pop()
22     return ShortcutListBox(widgets)
23
24 def push_wizard(name, pages, dimensions=(50, 10)):
25     state = {}
26     wiz = Wizard()
27     for page in pages:
28         if type(page) != tuple:
29             page = (page, )
30         wiz.add_panel( page[0](state, *page[1:]) )
31     push_window( urwid.Filler( urwid.Padding(
32         urwid.LineBox(wiz), 'center', dimensions[0]),
33         'middle', dimensions[1] ), name )
34
35 class ButtonText(urwid.Text):
36     def __init__(self, callback, data, *args, **kwargs):
37         self.callback = callback
38         self.data = data
39         urwid.Text.__init__(self, *args, **kwargs)
40     def selectable(self):
41         return True
42     def keypress(self, size, key):
43         if key == 'enter' and self.callback:
44             self.callback(self.data)
45         else:
46             return key
47
48 #DONTUSE
49 class CaptionedText(urwid.Text):
50     def __init__(self, caption, *args, **kwargs):
51         self.caption = caption
52         urwid.Text.__init__(self, *args, **kwargs)
53     def render(self, *args, **kwargs):
54         self.set_text(self.caption + self.get_text()[0])
55         urwid.Text.render(*args, **kwargs)
56
57 class SingleEdit(urwid.Edit):
58     def keypress(self, size, key):
59         key_mappings = {
60             'enter': 'down',
61             'tab': 'down',
62             'shift tab': 'up',
63             'ctrl a': 'home',
64             'ctrl e': 'end'
65         }
66         
67         if key in key_mappings:
68             return urwid.Edit.keypress(self, size, key_mappings[key])
69         else:
70             return urwid.Edit.keypress(self, size, key)
71
72 class SingleIntEdit(urwid.IntEdit):
73     def keypress(self, size, key):
74         if key == 'enter':
75             return urwid.Edit.keypress(self, size, 'down')
76         else:
77             return urwid.Edit.keypress(self, size, key)
78
79 class WordEdit(SingleEdit):
80     def valid_char(self, ch):
81         return urwid.Edit.valid_char(self, ch) and ch != ' '
82
83 class LdapWordEdit(WordEdit):
84     ldap = None
85     index = None
86
87     def __init__(self, uri, base, attr, *args):
88         try:
89             self.ldap = ldap.initialize(uri)
90             self.ldap.simple_bind_s("", "")
91         except ldap.LDAPError:
92             return WordEdit.__init__(self, *args)
93         self.base = base
94         self.attr = ldapi.escape(attr)
95         return WordEdit.__init__(self, *args)
96
97     def keypress(self, size, key):
98         if (key == 'tab' or key == 'shift tab') and self.ldap != None:
99             if self.index != None:
100                 if key == 'tab':
101                     self.index = (self.index + 1) % len(self.choices)
102                 elif key == 'shift tab':
103                     self.index = (self.index - 1) % len(self.choices)
104                 text = self.choices[self.index]
105                 self.set_edit_text(text)
106                 self.set_edit_pos(len(text))
107             else:
108                 try:
109                     text = self.get_edit_text()
110                     search = ldapi.escape(text)
111                     matches = self.ldap.search_s(self.base,
112                         ldap.SCOPE_SUBTREE, '(%s=%s*)' % (self.attr, search))
113                     self.choices = [ text ]
114                     for match in matches:
115                         (_, attrs) = match
116                         self.choices += attrs['uid']
117                     self.choices.sort()
118                     self.index = 0
119                     self.keypress(size, key)
120                 except ldap.LDAPError, e:
121                     pass
122         else:
123             self.index = None
124             return WordEdit.keypress(self, size, key)
125
126 class LdapFilterWordEdit(LdapWordEdit):
127     def __init__(self, uri, base, attr, map, *args):
128         LdapWordEdit.__init__(self, uri, base, attr, *args)
129         self.map = map
130     def keypress(self, size, key):
131         if self.ldap != None:
132             if key == 'enter' or key == 'down' or key == 'up':
133                 search = ldapi.escape(self.get_edit_text())
134                 try:
135                     matches = self.ldap.search_s(self.base,
136                         ldap.SCOPE_SUBTREE, '(%s=%s)' % (self.attr, search))
137                     if len(matches) > 0:
138                         (_, attrs) = matches[0]
139                         for (k, v) in self.map.items():
140                             if attrs.has_key(k) and len(attrs[k]) > 0:
141                                 v.set_edit_text(attrs[k][0])
142                 except ldap.LDAPError:
143                     pass
144         return LdapWordEdit.keypress(self, size, key)
145
146 class PassEdit(SingleEdit):
147     def get_text(self):
148         text = urwid.Edit.get_text(self)
149         return (self.caption + " " * len(self.get_edit_text()), text[1])
150
151 class EnhancedButton(urwid.Button):
152     def keypress(self, size, key):
153         if key == 'tab':
154             return urwid.Button.keypress(self, size, 'down')
155         elif key == 'shift tab':
156             return urwid.Button.keypress(self, size, 'up')
157         else:
158             return urwid.Button.keypress(self, size, key)
159
160 class DumbColumns(urwid.Columns):
161     """Dumb columns widget
162
163     The normal one tries to focus the "nearest" widget to the cursor.
164     This makes the Back button default instead of the Next button.
165     """
166     def move_cursor_to_coords(self, size, col, row):
167         pass
168
169 class Wizard(urwid.WidgetWrap):
170     def __init__(self):
171         self.selected = None
172         self.panels = []
173
174         self.panelwrap = urwid.WidgetWrap( urwid.SolidFill() )
175         self.back = EnhancedButton("Back", self.back)
176         self.next = EnhancedButton("Next", self.next)
177         self.buttons = DumbColumns( [ self.back, self.next ], dividechars=3, focus_column=1 )
178         pad = urwid.Padding( self.buttons, ('fixed right', 2), 19 )
179         self.pile = urwid.Pile( [self.panelwrap, ('flow', pad)], 0 )
180         urwid.WidgetWrap.__init__(self, self.pile)
181
182     def add_panel(self, panel):
183         self.panels.append( panel )
184         if len(self.panels) == 1:
185             self.select(0)
186
187     def select(self, panelno, set_focus=True):
188         if 0 <= panelno < len(self.panels):
189             self.selected = panelno
190             self.panelwrap._w = self.panels[panelno]
191             self.panelwrap._invalidate()
192             self.panels[panelno].activate()
193
194             if set_focus:
195                 if self.panels[panelno].focusable():
196                     self.pile.set_focus( 0 )
197                 else:
198                     self.pile.set_focus( 1 )
199
200     def next(self, *args, **kwargs):
201         if self.panels[self.selected].check():
202             self.select( self.selected )
203             return
204         self.select(self.selected + 1)
205
206     def back(self, *args, **kwargs):
207         if self.selected == 0:
208             raise_back()
209         self.select(self.selected - 1, False)
210
211 class WizardPanel(urwid.WidgetWrap):
212     def __init__(self, state):
213         self.state = state
214         self.init_widgets()
215         self.box = urwid.ListBox( urwid.SimpleListWalker( self.widgets ) )
216         urwid.WidgetWrap.__init__( self, self.box )
217     def init_widgets(self):
218         self.widgets = []
219     def focus_widget(self, widget):
220         self.box.set_focus( self.widgets.index( widget ) )
221     def focusable(self):
222         return True
223     def check(self):
224         return
225     def activate(self):
226         return
227
228 # assumes that a SimpleListWalker containing
229 # urwid.Text or subclass is used
230 class ShortcutListBox(urwid.ListBox):
231     def keypress(self, size, key):
232         # only process single letters; pass all else to super
233         if len(key) == 1 and key.isalpha():
234             next = self.get_focus()[1] + 1
235             shifted_contents = self.body.contents[next:] + self.body.contents[:next]
236
237             # find the next item matching the letter requested
238             try:
239                 new_focus = (i for i,w in enumerate(shifted_contents)
240                              if w.selectable() and w.text[0].upper() == key.upper()).next()
241                 new_focus = (new_focus + next) % len(self.body.contents)
242                 self.set_focus(new_focus)
243             except:
244                 # ring the bell if it isn't found
245                 sys.stdout.write('\a')
246         else:
247             urwid.ListBox.keypress(self, size, key)