tab support finally lands in ceo
[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         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 Wizard(urwid.WidgetWrap):
161     def __init__(self):
162         self.selected = None
163         self.panels = []
164
165         self.panelwrap = urwid.WidgetWrap( urwid.SolidFill() )
166         self.back = EnhancedButton("Back", self.back)
167         self.next = EnhancedButton("Next", self.next)
168         self.buttons = urwid.Columns( [ self.back, self.next ], dividechars=3, focus_column=1 )
169         pad = urwid.Padding( self.buttons, ('fixed right', 2), 19 )
170         self.pile = urwid.Pile( [self.panelwrap, ('flow', pad)], 0 )
171         urwid.WidgetWrap.__init__(self, self.pile)
172
173     def add_panel(self, panel):
174         self.panels.append( panel )
175         if len(self.panels) == 1:
176             self.select(0)
177
178     def select(self, panelno, set_focus=True):
179         if 0 <= panelno < len(self.panels):
180             self.selected = panelno
181             self.panelwrap.set_w( self.panels[panelno] )
182             self.panels[panelno].activate()
183
184             if set_focus:
185                 if self.panels[panelno].focusable():
186                     self.pile.set_focus( 0 )
187                 else:
188                     self.pile.set_focus( 1 )
189
190     def next(self, *args, **kwargs):
191         if self.panels[self.selected].check():
192             self.select( self.selected )
193             return
194         self.select(self.selected + 1)
195
196     def back(self, *args, **kwargs):
197         if self.selected == 0:
198             raise_back()
199         self.select(self.selected - 1, False)
200
201 class WizardPanel(urwid.WidgetWrap):
202     def __init__(self, state):
203         self.state = state
204         self.init_widgets()
205         self.box = urwid.ListBox( urwid.SimpleListWalker( self.widgets ) )
206         urwid.WidgetWrap.__init__( self, self.box )
207     def init_widgets(self):
208         self.widgets = []
209     def focus_widget(self, widget):
210         self.box.set_focus( self.widgets.index( widget ) )
211     def focusable(self):
212         return True
213     def check(self):
214         return
215     def activate(self):
216         return
217
218 # assumes that a SimpleListWalker containing
219 # urwid.Text or subclass is used
220 class ShortcutListBox(urwid.ListBox):
221     def keypress(self, size, key):
222         # only process single letters; pass all else to super
223         if len(key) == 1 and key.isalpha():
224             next = self.get_focus()[1] + 1
225             shifted_contents = self.body.contents[next:] + self.body.contents[:next]
226
227             # find the next item matching the letter requested
228             try:
229                 new_focus = (i for i,w in enumerate(shifted_contents)
230                              if w.selectable() and w.text[0].upper() == key.upper()).next()
231                 new_focus = (new_focus + next) % len(self.body.contents)
232                 self.set_focus(new_focus)
233             except:
234                 # ring the bell if it isn't found
235                 sys.stdout.write('\a')
236         else:
237             urwid.ListBox.keypress(self, size, key)