5d04e0bdcd11f7ba52e35b760f120e38ce969056
[mspang/pyceo.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         if key == 'enter':
60             return urwid.Edit.keypress(self, size, 'down')
61         else:
62             return urwid.Edit.keypress(self, size, key)
63
64 class SingleIntEdit(urwid.IntEdit):
65     def keypress(self, size, key):
66         if key == 'enter':
67             return urwid.Edit.keypress(self, size, 'down')
68         else:
69             return urwid.Edit.keypress(self, size, key)
70
71 class WordEdit(SingleEdit):
72     def valid_char(self, ch):
73         return urwid.Edit.valid_char(self, ch) and ch != ' '
74
75 class LdapWordEdit(WordEdit):
76     ldap = None
77     index = None
78
79     def __init__(self, uri, base, attr, *args):
80         try:
81             self.ldap = ldap.initialize(uri)
82             self.ldap.simple_bind_s("", "")
83         except ldap.LDAPError:
84             return WordEdit.__init__(self, *args)
85         self.base = base
86         self.attr = ldapi.escape(attr)
87         return WordEdit.__init__(self, *args)
88
89     def keypress(self, size, key):
90         if (key == 'tab' or key == 'shift tab') and self.ldap != None:
91             if self.index != None:
92                 if key == 'tab':
93                     self.index = (self.index + 1) % len(self.choices)
94                 elif key == 'shift tab':
95                     self.index = (self.index - 1) % len(self.choices)
96                 text = self.choices[self.index]
97                 self.set_edit_text(text)
98                 self.set_edit_pos(len(text))
99             else:
100                 try:
101                     text = self.get_edit_text()
102                     search = ldapi.escape(text)
103                     matches = self.ldap.search_s(self.base,
104                         ldap.SCOPE_SUBTREE, '(%s=%s*)' % (self.attr, search))
105                     self.choices = [ text ]
106                     for match in matches:
107                         (_, attrs) = match
108                         self.choices += attrs['uid']
109                     self.choices.sort()
110                     self.index = 0
111                     self.keypress(size, key)
112                 except ldap.LDAPError, e:
113                     pass
114         else:
115             self.index = None
116             return WordEdit.keypress(self, size, key)
117
118 class LdapFilterWordEdit(LdapWordEdit):
119     def __init__(self, uri, base, attr, map, *args):
120         LdapWordEdit.__init__(self, uri, base, attr, *args)
121         self.map = map
122     def keypress(self, size, key):
123         if self.ldap != None:
124             if key == 'enter' or key == 'down' or key == 'up':
125                 search = ldapi.escape(self.get_edit_text())
126                 try:
127                     matches = self.ldap.search_s(self.base,
128                         ldap.SCOPE_SUBTREE, '(%s=%s)' % (self.attr, search))
129                     if len(matches) > 0:
130                         (_, attrs) = matches[0]
131                         for (k, v) in self.map.items():
132                             if attrs.has_key(k) and len(attrs[k]) > 0:
133                                 v.set_edit_text(attrs[k][0])
134                 except ldap.LDAPError:
135                     pass
136         return LdapWordEdit.keypress(self, size, key)
137
138 class PassEdit(SingleEdit):
139     def get_text(self):
140         text = urwid.Edit.get_text(self)
141         return (self.caption + " " * len(self.get_edit_text()), text[1])
142
143 class Wizard(urwid.WidgetWrap):
144     def __init__(self):
145         self.selected = None
146         self.panels = []
147
148         self.panelwrap = urwid.WidgetWrap( urwid.SolidFill() )
149         self.back = urwid.Button("Back", self.back)
150         self.next = urwid.Button("Next", self.next)
151         self.buttons = urwid.Columns( [ self.back, self.next ], dividechars=3, focus_column=1 )
152         pad = urwid.Padding( self.buttons, ('fixed right', 2), 19 )
153         self.pile = urwid.Pile( [self.panelwrap, ('flow', pad)], 0 )
154         urwid.WidgetWrap.__init__(self, self.pile)
155
156     def add_panel(self, panel):
157         self.panels.append( panel )
158         if len(self.panels) == 1:
159             self.select(0)
160
161     def select(self, panelno, set_focus=True):
162         if 0 <= panelno < len(self.panels):
163             self.selected = panelno
164             self.panelwrap.set_w( self.panels[panelno] )
165             self.panels[panelno].activate()
166
167             if set_focus:
168                 if self.panels[panelno].focusable():
169                     self.pile.set_focus( 0 )
170                 else:
171                     self.pile.set_focus( 1 )
172
173     def next(self, *args, **kwargs):
174         if self.panels[self.selected].check():
175             self.select( self.selected )
176             return
177         self.select(self.selected + 1)
178
179     def back(self, *args, **kwargs):
180         if self.selected == 0:
181             raise_back()
182         self.select(self.selected - 1, False)
183
184 class WizardPanel(urwid.WidgetWrap):
185     def __init__(self, state):
186         self.state = state
187         self.init_widgets()
188         self.box = urwid.ListBox( urwid.SimpleListWalker( self.widgets ) )
189         urwid.WidgetWrap.__init__( self, self.box )
190     def init_widgets(self):
191         self.widgets = []
192     def focus_widget(self, widget):
193         self.box.set_focus( self.widgets.index( widget ) )
194     def focusable(self):
195         return True
196     def check(self):
197         return
198     def activate(self):
199         return
200
201 # assumes that a SimpleListWalker containing
202 # urwid.Text or subclass is used
203 class ShortcutListBox(urwid.ListBox):
204     def keypress(self, size, key):
205         # only process single letters; pass all else to super
206         if len(key) == 1 and key.isalpha():
207             next = self.get_focus()[1] + 1
208             shifted_contents = self.body.contents[next:] + self.body.contents[:next]
209
210             # find the next item matching the letter requested
211             try:
212                 new_focus = (i for i,w in enumerate(shifted_contents)
213                              if w.selectable() and w.text[0].upper() == key.upper()).next()
214                 new_focus = (new_focus + next) % len(self.body.contents)
215                 self.set_focus(new_focus)
216             except:
217                 # ring the bell if it isn't found
218                 sys.stdout.write('\a')
219         else:
220             urwid.ListBox.keypress(self, size, key)