Add experimental urwid-based GUI
This commit is contained in:
parent
299c25d610
commit
588f90b082
6
bin/ceo
6
bin/ceo
|
@ -15,7 +15,7 @@ os.environ['LESSSECURE'] = '1'
|
|||
os.environ['PATH'] = '/usr/sbin:/usr/bin:/sbin:/bin'
|
||||
|
||||
for pathent in sys.path[:]:
|
||||
if not pathent.find('/usr') == 0:
|
||||
if not pathent.find('/usr') == 0 and not pathent.find('/var') == 0:
|
||||
sys.path.remove(pathent)
|
||||
|
||||
euid = os.geteuid()
|
||||
|
@ -27,5 +27,5 @@ except OSError, e:
|
|||
print str(e)
|
||||
sys.exit(1)
|
||||
|
||||
import csc.apps.legacy.main
|
||||
csc.apps.legacy.main.run()
|
||||
import csc.apps.urwid.main
|
||||
csc.apps.urwid.main.start()
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
#!/usr/bin/python2.4 --
|
||||
"""CEO SUID Python Wrapper Script"""
|
||||
import os, sys
|
||||
|
||||
safe_environment = ['LOGNAME', 'USERNAME', 'USER', 'HOME', 'TERM', 'LANG'
|
||||
'LC_ALL', 'LC_COLLATE', 'LC_CTYPE', 'LC_MESSAGES', 'LC_MONETARY',
|
||||
'LC_NUMERIC', 'LC_TIME', 'UID', 'GID', 'SSH_CONNECTION', 'SSH_AUTH_SOCK',
|
||||
'SSH_CLIENT']
|
||||
|
||||
for key in os.environ.keys():
|
||||
if key not in safe_environment:
|
||||
del os.environ[key]
|
||||
|
||||
os.environ['LESSSECURE'] = '1'
|
||||
os.environ['PATH'] = '/usr/sbin:/usr/bin:/sbin:/bin'
|
||||
|
||||
for pathent in sys.path[:]:
|
||||
if not pathent.find('/usr') == 0:
|
||||
sys.path.remove(pathent)
|
||||
|
||||
euid = os.geteuid()
|
||||
egid = os.getegid()
|
||||
try:
|
||||
os.setreuid(euid, euid)
|
||||
os.setregid(egid, egid)
|
||||
except OSError, e:
|
||||
print str(e)
|
||||
sys.exit(1)
|
||||
|
||||
import csc.apps.legacy.main
|
||||
csc.apps.legacy.main.run()
|
|
@ -7,6 +7,7 @@ build: build-stamp
|
|||
build-stamp:
|
||||
mkdir build
|
||||
$(CC) -DFULL_PATH='"/usr/lib/csc/ceo"' -o build/ceo misc/setuid-prog.c
|
||||
$(CC) -DFULL_PATH='"/usr/lib/csc/ceo-old"' -o build/ceo-old misc/setuid-prog.c
|
||||
$(CC) -DFULL_PATH='"/usr/lib/csc/addhomedir"' -o build/addhomedir misc/setuid-prog.c
|
||||
$(CC) -DFULL_PATH='"/usr/lib/csc/ceoquery"' -o build/ceoquery misc/setuid-prog.c
|
||||
$(CC) -DFULL_PATH='"/usr/lib/csc/csc-chfn"' -o build/csc-chfn misc/setuid-prog.c
|
||||
|
@ -31,8 +32,8 @@ install: build
|
|||
dh_install etc/* etc/csc/
|
||||
dh_install sql/* usr/share/csc/
|
||||
|
||||
dh_install bin/ceo bin/addhomedir bin/ceoquery bin/csc-chsh bin/csc-chfn usr/lib/csc/
|
||||
dh_install build/ceo build/addhomedir build/ceoquery build/csc-chsh build/csc-chfn usr/bin/
|
||||
dh_install bin/ceo bin/ceo-old bin/addhomedir bin/ceoquery bin/csc-chsh bin/csc-chfn usr/lib/csc/
|
||||
dh_install build/ceo build/ceo-old build/addhomedir build/ceoquery build/csc-chsh build/csc-chfn usr/bin/
|
||||
dh_install misc/csc.schema etc/ldap/schema/
|
||||
|
||||
binary-arch: build install
|
||||
|
|
|
@ -2,7 +2,6 @@ TODO:
|
|||
|
||||
* Python bindings for libkadm5
|
||||
* Python bindings for quota?
|
||||
* New UI: urwid-based?
|
||||
* Logging via syslog
|
||||
* Try to recover and roll-back on error during account creation
|
||||
* Write manpages
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
"""
|
||||
Urwid User Interface
|
||||
"""
|
|
@ -0,0 +1,39 @@
|
|||
import urwid
|
||||
|
||||
from csc.apps.urwid.widgets import *
|
||||
from csc.apps.urwid.window import *
|
||||
|
||||
from csc.adm import accounts, members
|
||||
from csc.common.excep import InvalidArgument
|
||||
|
||||
class InfoPage(WizardPanel):
|
||||
def init_widgets(self):
|
||||
self.userid = urwid.Text("")
|
||||
self.name = urwid.Text("")
|
||||
self.terms = urwid.Text("")
|
||||
self.program = urwid.Text("")
|
||||
|
||||
self.widgets = [
|
||||
urwid.Text( "Member Details" ),
|
||||
urwid.Divider(),
|
||||
self.name,
|
||||
self.userid,
|
||||
self.program,
|
||||
urwid.Divider(),
|
||||
self.terms,
|
||||
]
|
||||
def focusable(self):
|
||||
return False
|
||||
def activate(self):
|
||||
member = self.state.get('member', {})
|
||||
name = member.get('cn', [''])[0]
|
||||
userid = self.state['userid']
|
||||
program = member.get('program', [''])[0]
|
||||
terms = member.get('term', [])
|
||||
|
||||
self.name.set_text("Name: %s" % name)
|
||||
self.userid.set_text("User: %s" % userid)
|
||||
self.program.set_text("Program: %s" % program)
|
||||
self.terms.set_text("Terms: %s" % ", ".join(terms))
|
||||
def check(self):
|
||||
pop_window()
|
|
@ -0,0 +1,122 @@
|
|||
import random, time
|
||||
import urwid, urwid.curses_display
|
||||
|
||||
from csc.apps.urwid.widgets import *
|
||||
from csc.apps.urwid.window import *
|
||||
import csc.apps.urwid.newmember as newmember
|
||||
import csc.apps.urwid.renew as renew
|
||||
import csc.apps.urwid.info as info
|
||||
import csc.apps.urwid.search as search
|
||||
|
||||
from csc.adm import accounts, members, terms
|
||||
from csc.common.excep import InvalidArgument
|
||||
|
||||
ui = urwid.curses_display.Screen()
|
||||
|
||||
ui.register_palette([
|
||||
# name, foreground, background, mono
|
||||
('banner', 'light gray', 'default', None),
|
||||
('menu', 'light gray', 'default', 'bold'),
|
||||
('selected', 'black', 'light gray', 'bold'),
|
||||
])
|
||||
|
||||
|
||||
def program_name():
|
||||
cwords = [ "CSC" ] * 20 + [ "Club" ] * 10 + [ "Campus" ] * 5 + \
|
||||
[ "Communist", "Canadian", "Celestial", "Cryptographic", "Calum's",
|
||||
"Canonical", "Capitalist", "Catastrophic", "Ceremonial", "Chaotic", "Civic",
|
||||
"City", "County", "Caffeinated" ]
|
||||
ewords = [ "Embellished", "Ergonomic", "Electric", "Eccentric", "European", "Economic",
|
||||
"Evil", "Egotistical", "Elliptic", "Emasculating", "Embalming",
|
||||
"Embryonic", "Emigrant", "Emissary's", "Emoting", "Employment", "Emulated",
|
||||
"Enabling", "Enamoring", "Encapsulated", "Enchanted", "Encoded", "Encrypted",
|
||||
"Encumbered", "Endemic", "Enhanced", "Enigmatic", "Enlightened", "Enormous",
|
||||
"Enrollment", "Enshrouded", "Ephermal", "Epidemic", "Episodic", "Epsilon",
|
||||
"Equitable", "Equestrian", "Equilateral", "Erroneous", "Erratic",
|
||||
"Espresso", "Essential", "Estate", "Esteemed", "Eternal", "Ethical", "Eucalyptus",
|
||||
"Euphemistic", "Envangelist", "Evasive", "Everyday", "Evidence", "Eviction", "Evildoer's",
|
||||
"Evolution", "Exacerbation", "Exalted", "Examiner's", "Excise", "Exciting", "Exclusion",
|
||||
"Exec", "Executioner's", "Exile", "Existential", "Expedient", "Expert", "Expletive",
|
||||
"Exploiter's", "Explosive", "Exponential", "Exposing", "Extortion", "Extraction",
|
||||
"Extraneous", "Extravaganza", "Extreme", "Extraterrestrial", "Extremist", "Eerie" ]
|
||||
owords = [ "Office" ] * 50 + [ "Outhouse", "Outpost" ]
|
||||
|
||||
cword = random.choice(cwords)
|
||||
eword = random.choice(ewords)
|
||||
oword = random.choice(owords)
|
||||
|
||||
return "%s %s %s" % (cword, eword, oword)
|
||||
|
||||
def menu_items(items):
|
||||
return [ urwid.AttrWrap( ButtonText( cb, txt ), 'menu', 'selected') for (txt, cb) in items ]
|
||||
|
||||
def main_menu():
|
||||
menu = [
|
||||
("New Member", new_member),
|
||||
("Renew Membership", renew_member),
|
||||
("Display Member", display_member),
|
||||
("Search", search_members),
|
||||
("Exit", raise_abort),
|
||||
]
|
||||
|
||||
listbox = urwid.ListBox( menu_items( menu ) )
|
||||
return listbox
|
||||
|
||||
def push_wizard(name, pages, dimensions=(50, 10)):
|
||||
state = {}
|
||||
wiz = Wizard()
|
||||
for page in pages:
|
||||
wiz.add_panel( page(state) )
|
||||
push_window( urwid.Filler( urwid.Padding(
|
||||
urwid.LineBox(wiz), 'center', dimensions[0]),
|
||||
'middle', dimensions[1] ), name )
|
||||
|
||||
def new_member(*args, **kwargs):
|
||||
push_wizard("New Member", [
|
||||
newmember.IntroPage,
|
||||
newmember.InfoPage,
|
||||
newmember.SignPage,
|
||||
newmember.PassPage,
|
||||
newmember.EndPage,
|
||||
])
|
||||
|
||||
def renew_member(*args, **kwargs):
|
||||
push_wizard("Renew Membership", [
|
||||
renew.IntroPage,
|
||||
renew.UserPage,
|
||||
renew.TermPage,
|
||||
renew.PayPage,
|
||||
renew.EndPage,
|
||||
])
|
||||
|
||||
def display_member(a):
|
||||
push_wizard("Display Member", [
|
||||
renew.UserPage,
|
||||
info.InfoPage,
|
||||
], (60, 15))
|
||||
|
||||
def search_members(a):
|
||||
menu = [
|
||||
("Members by term", search_term),
|
||||
("Members by name", search_name),
|
||||
("Back", raise_back),
|
||||
]
|
||||
|
||||
listbox = urwid.ListBox( menu_items( menu ) )
|
||||
push_window(listbox, "Search")
|
||||
|
||||
def search_name(a):
|
||||
push_wizard("By Name", [ search.NamePage ])
|
||||
|
||||
def search_term(a):
|
||||
push_wizard("By Term", [ search.TermPage ])
|
||||
|
||||
def run():
|
||||
push_window( main_menu(), program_name() )
|
||||
event_loop( ui )
|
||||
|
||||
def start():
|
||||
ui.run_wrapper( run )
|
||||
|
||||
if __name__ == '__main__':
|
||||
start()
|
|
@ -0,0 +1,134 @@
|
|||
import urwid
|
||||
from csc.apps.urwid.widgets import *
|
||||
from csc.apps.urwid.window import *
|
||||
|
||||
from csc.adm import accounts, members
|
||||
from csc.common.excep import InvalidArgument
|
||||
|
||||
class IntroPage(WizardPanel):
|
||||
def init_widgets(self):
|
||||
self.widgets = [
|
||||
urwid.Text( "Joining the Computer Science Club" ),
|
||||
urwid.Divider(),
|
||||
urwid.Text( "CSC membership is $2.00 for one term. Please ensure "
|
||||
"the fee is deposited into the safe before continuing." ),
|
||||
]
|
||||
def focusable(self):
|
||||
return False
|
||||
|
||||
class InfoPage(WizardPanel):
|
||||
def init_widgets(self):
|
||||
self.userid = WordEdit("UWdir ID: ")
|
||||
self.name = SingleEdit("Full name: ")
|
||||
self.program = SingleEdit("Program of Study: ")
|
||||
self.widgets = [
|
||||
urwid.Text( "Member Information - Please Check ID" ),
|
||||
urwid.Divider(),
|
||||
self.userid,
|
||||
self.name,
|
||||
self.program,
|
||||
]
|
||||
def check(self):
|
||||
self.state['userid'] = self.userid.get_edit_text()
|
||||
self.state['name'] = self.name.get_edit_text()
|
||||
self.state['program'] = self.program.get_edit_text()
|
||||
|
||||
if len( self.state['userid'] ) < 4:
|
||||
self.focus_widget( self.userid )
|
||||
set_status("Username is too short")
|
||||
return True
|
||||
elif len( self.state['name'] ) < 4:
|
||||
self.focus_widget( self.name )
|
||||
set_status("Name is too short")
|
||||
return True
|
||||
clear_status()
|
||||
|
||||
class SignPage(WizardPanel):
|
||||
def init_widgets(self):
|
||||
self.widgets = [
|
||||
urwid.Text( "Machine Usage Policy" ),
|
||||
urwid.Divider(),
|
||||
urwid.Text( "Ensure the new member has signed the "
|
||||
"Machine Usage Policy. Accounts of users who have not "
|
||||
"signed will be suspended if discovered." ),
|
||||
]
|
||||
def focusable(self):
|
||||
return False
|
||||
|
||||
class PassPage(WizardPanel):
|
||||
def init_widgets(self):
|
||||
self.password = PassEdit("Password: ")
|
||||
self.pwcheck = PassEdit("Re-enter: ")
|
||||
self.widgets = [
|
||||
urwid.Text( "Member Password" ),
|
||||
urwid.Divider(),
|
||||
self.password,
|
||||
self.pwcheck,
|
||||
]
|
||||
def focus_widget(self, widget):
|
||||
self.box.set_focus( self.widgets.index( widget ) )
|
||||
def clear_password(self):
|
||||
self.focus_widget( self.password )
|
||||
self.password.set_edit_text("")
|
||||
self.pwcheck.set_edit_text("")
|
||||
def check(self):
|
||||
self.state['password'] = self.password.get_edit_text()
|
||||
pwcheck = self.pwcheck.get_edit_text()
|
||||
|
||||
if self.state['password'] != pwcheck:
|
||||
self.clear_password()
|
||||
set_status("Passwords do not match")
|
||||
return True
|
||||
elif len(self.state['password']) < 5:
|
||||
self.clear_password()
|
||||
set_status("Password is too short")
|
||||
return True
|
||||
clear_status()
|
||||
|
||||
class EndPage(WizardPanel):
|
||||
def init_widgets(self):
|
||||
self.headtext = urwid.Text("")
|
||||
self.midtext = urwid.Text("")
|
||||
self.widgets = [
|
||||
self.headtext,
|
||||
urwid.Divider(),
|
||||
self.midtext,
|
||||
]
|
||||
def focusable(self):
|
||||
return False
|
||||
def check(self):
|
||||
pop_window()
|
||||
def activate(self):
|
||||
try:
|
||||
if not members.connected(): members.connect()
|
||||
members.new( self.state['userid'], self.state['name'], self.state['program'] )
|
||||
problem = None
|
||||
except members.InvalidRealName:
|
||||
problem = "Invalid real name"
|
||||
except InvalidArgument, e:
|
||||
if e.argname == 'uid' and e.explanation == 'duplicate uid':
|
||||
problem = 'Duplicate userid'
|
||||
else:
|
||||
raise
|
||||
if not problem:
|
||||
try:
|
||||
if not accounts.connected(): accounts.connect()
|
||||
accounts.create_member( self.state['userid'], self.state['password'], self.state['name'] )
|
||||
except accounts.NameConflict, e:
|
||||
problem = str(e)
|
||||
except accounts.NoAvailableIDs, e:
|
||||
problem = str(e)
|
||||
except accounts.InvalidArgument, e:
|
||||
problem = str(e)
|
||||
except accounts.LDAPException, e:
|
||||
problem = str(e)
|
||||
except accounts.KrbException, e:
|
||||
problem = str(e)
|
||||
if problem:
|
||||
self.headtext.set_text("Failed to add member")
|
||||
self.midtext.set_text("The error was: '%s'" % problem)
|
||||
else:
|
||||
self.headtext.set_text("Member Added")
|
||||
self.midtext.set_text("Congratulations, %s has been added "
|
||||
"successfully. Please run 'addhomedir %s'."
|
||||
% (self.state['userid'], self.state['userid']))
|
|
@ -0,0 +1,118 @@
|
|||
import urwid
|
||||
|
||||
from csc.apps.urwid.widgets import *
|
||||
from csc.apps.urwid.window import *
|
||||
|
||||
from csc.adm import members, terms
|
||||
|
||||
class IntroPage(WizardPanel):
|
||||
def init_widgets(self):
|
||||
self.widgets = [
|
||||
urwid.Text( "Renewing Membership" ),
|
||||
urwid.Divider(),
|
||||
urwid.Text( "CSC membership is $2.00 per term. You may pre-register "
|
||||
"for future terms if desired." )
|
||||
]
|
||||
def focusable(self):
|
||||
return False
|
||||
|
||||
class UserPage(WizardPanel):
|
||||
def init_widgets(self):
|
||||
self.userid = WordEdit("Username: ")
|
||||
|
||||
self.widgets = [
|
||||
urwid.Text( "Member Information" ),
|
||||
urwid.Divider(),
|
||||
self.userid,
|
||||
]
|
||||
def check(self):
|
||||
self.state['userid'] = self.userid.get_edit_text()
|
||||
self.state['member'] = None
|
||||
if self.state['userid']:
|
||||
if not members.connected(): members.connect()
|
||||
self.state['member'] = members.get(self.userid.get_edit_text())
|
||||
if not self.state['member']:
|
||||
set_status("Member not found")
|
||||
self.focus_widget(self.userid)
|
||||
return True
|
||||
|
||||
class TermPage(WizardPanel):
|
||||
def init_widgets(self):
|
||||
self.start = SingleEdit("Start: ")
|
||||
self.count = SingleIntEdit("Count: ")
|
||||
|
||||
self.widgets = [
|
||||
urwid.Text( "Terms to Register" ),
|
||||
urwid.Divider(),
|
||||
self.start,
|
||||
self.count,
|
||||
]
|
||||
def activate(self):
|
||||
if not self.start.get_edit_text():
|
||||
old_terms = []
|
||||
if 'term' in self.state['member']:
|
||||
old_terms = self.state['member']['term']
|
||||
self.start.set_edit_text( terms.next_unregistered( old_terms ) )
|
||||
self.count.set_edit_text( "1" )
|
||||
def check(self):
|
||||
try:
|
||||
self.state['terms'] = terms.interval( self.start.get_edit_text(), self.count.value() )
|
||||
except e:
|
||||
self.focus_widget( self.start )
|
||||
set_status( "Invalid start term" )
|
||||
return True
|
||||
for term in self.state['terms']:
|
||||
if members.registered( self.state['userid'], term):
|
||||
self.focus_widget( self.start )
|
||||
set_status( "Already registered for " + term )
|
||||
return True
|
||||
if len(self.state['terms']) == 0:
|
||||
self.focus_widget(self.count)
|
||||
set_status( "Registering for zero terms?" )
|
||||
return True
|
||||
|
||||
class PayPage(WizardPanel):
|
||||
def init_widgets(self):
|
||||
self.midtext = urwid.Text("")
|
||||
|
||||
self.widgets = [
|
||||
urwid.Text("Membership Fee"),
|
||||
urwid.Divider(),
|
||||
self.midtext,
|
||||
]
|
||||
def focusable(self):
|
||||
return False
|
||||
def activate(self):
|
||||
regterms = self.state['terms']
|
||||
plural = "term"
|
||||
if len(self.state['terms']) > 1:
|
||||
plural = "terms"
|
||||
self.midtext.set_text("You are registering for %d %s, and owe the "
|
||||
"Computer Science Club $%d.00 in membership fees. "
|
||||
"Please deposit the money in the safe before "
|
||||
"continuing. " % ( len(regterms), plural, len(regterms * 2)))
|
||||
|
||||
class EndPage(WizardPanel):
|
||||
def init_widgets(self):
|
||||
self.headtext = urwid.Text("")
|
||||
self.midtext = urwid.Text("")
|
||||
|
||||
self.widgets = [
|
||||
self.headtext,
|
||||
urwid.Divider(),
|
||||
self.midtext,
|
||||
]
|
||||
def focusable(self):
|
||||
return False
|
||||
def activate(self):
|
||||
try:
|
||||
members.register( self.state['userid'], self.state['terms'] )
|
||||
self.headtext.set_text("Registration Succeeded")
|
||||
self.midtext.set_text("The member has been registered for the following "
|
||||
"terms: " + ", ".join(self.state['terms']) + ".")
|
||||
except Exception, e:
|
||||
self.headtext.set_text("Failed to Register")
|
||||
self.midtext.set_text("You may refund any fees paid or retry."
|
||||
"The error was: '%s'" % e)
|
||||
def check(self):
|
||||
pop_window()
|
|
@ -0,0 +1,69 @@
|
|||
import urwid
|
||||
|
||||
from csc.apps.urwid.widgets import *
|
||||
from csc.apps.urwid.window import *
|
||||
|
||||
from csc.adm import accounts, members, terms
|
||||
from csc.common.excep import InvalidArgument
|
||||
|
||||
class TermPage(WizardPanel):
|
||||
def init_widgets(self):
|
||||
self.term = SingleEdit("Term: ")
|
||||
|
||||
self.widgets = [
|
||||
urwid.Text( "Terms Members" ),
|
||||
urwid.Divider(),
|
||||
self.term,
|
||||
]
|
||||
def check(self):
|
||||
if not members.connected(): members.connect()
|
||||
try:
|
||||
self.state['term'] = self.term.get_edit_text()
|
||||
terms.parse( self.state['term'] )
|
||||
except:
|
||||
self.focus_widget( self.term )
|
||||
set_status( "Invalid term" )
|
||||
return True
|
||||
mlist = members.list_term( self.state['term'] ).values()
|
||||
pop_window()
|
||||
member_list( mlist )
|
||||
|
||||
class NamePage(WizardPanel):
|
||||
def init_widgets(self):
|
||||
self.name = SingleEdit("Name: ")
|
||||
|
||||
self.widgets = [
|
||||
urwid.Text( "Members by Name" ),
|
||||
urwid.Divider(),
|
||||
self.name,
|
||||
]
|
||||
def check(self):
|
||||
if not members.connected(): members.connect()
|
||||
self.state['name'] = self.name.get_edit_text()
|
||||
if not self.state['name']:
|
||||
self.focus_widget( self.term )
|
||||
set_status( "Invalid name" )
|
||||
return True
|
||||
mlist = members.list_name( self.state['name'] ).values()
|
||||
pop_window()
|
||||
member_list( mlist )
|
||||
|
||||
def member_list(mlist):
|
||||
mlist = list(mlist)
|
||||
mlist.sort( lambda x, y: cmp(x['uid'], y['uid']) )
|
||||
buf = ''
|
||||
for member in mlist:
|
||||
if 'uid' in member:
|
||||
uid = member['uid'][0]
|
||||
else:
|
||||
uid = None
|
||||
if 'program' in member:
|
||||
program = member['program'][0]
|
||||
else:
|
||||
program = None
|
||||
attrs = ( uid, member['cn'][0], program )
|
||||
buf += "%10s %30s\n%41s\n\n" % attrs
|
||||
set_status("Press escape to return to the menu")
|
||||
push_window(urwid.ListBox([urwid.Text(buf)]))
|
||||
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
import urwid
|
||||
|
||||
class ButtonText(urwid.Text):
|
||||
def __init__(self, callback, *args, **kwargs):
|
||||
self.callback = callback
|
||||
urwid.Text.__init__(self, *args, **kwargs)
|
||||
def selectable(self):
|
||||
return True
|
||||
def keypress(self, size, key):
|
||||
if key == 'enter':
|
||||
self.callback(self.get_text())
|
||||
else:
|
||||
return key
|
||||
|
||||
class SingleEdit(urwid.Edit):
|
||||
def keypress(self, size, key):
|
||||
if key == 'enter':
|
||||
return urwid.Edit.keypress(self, size, 'down')
|
||||
else:
|
||||
return urwid.Edit.keypress(self, size, key)
|
||||
|
||||
class SingleIntEdit(urwid.IntEdit):
|
||||
def keypress(self, size, key):
|
||||
if key == 'enter':
|
||||
return urwid.Edit.keypress(self, size, 'down')
|
||||
else:
|
||||
return urwid.Edit.keypress(self, size, key)
|
||||
|
||||
class WordEdit(SingleEdit):
|
||||
def valid_char(self, ch):
|
||||
return urwid.Edit.valid_char(self, ch) and ch != ' '
|
||||
|
||||
class PassEdit(SingleEdit):
|
||||
def get_text(self):
|
||||
text = urwid.Edit.get_text(self)
|
||||
return (self.caption + " " * len(self.get_edit_text()), text[1])
|
||||
|
||||
class Wizard(urwid.WidgetWrap):
|
||||
def __init__(self):
|
||||
self.selected = None
|
||||
self.panels = []
|
||||
|
||||
self.panelwrap = urwid.WidgetWrap( urwid.SolidFill() )
|
||||
self.back = urwid.Button("Back", self.back)
|
||||
self.next = urwid.Button("Next", self.next)
|
||||
self.buttons = urwid.Columns( [ self.back, self.next ], dividechars=3, focus_column=1 )
|
||||
pad = urwid.Padding( self.buttons, ('fixed right', 2), 19 )
|
||||
self.pile = urwid.Pile( [self.panelwrap, ('flow', pad)], 0 )
|
||||
urwid.WidgetWrap.__init__(self, self.pile)
|
||||
|
||||
def add_panel(self, panel):
|
||||
self.panels.append( panel )
|
||||
if len(self.panels) == 1:
|
||||
self.select(0)
|
||||
|
||||
def select(self, panelno, set_focus=True):
|
||||
if 0 <= panelno < len(self.panels):
|
||||
self.selected = panelno
|
||||
self.panelwrap.set_w( self.panels[panelno] )
|
||||
self.panels[panelno].activate()
|
||||
|
||||
if set_focus:
|
||||
if self.panels[panelno].focusable():
|
||||
self.pile.set_focus( 0 )
|
||||
else:
|
||||
self.pile.set_focus( 1 )
|
||||
|
||||
def next(self, *args, **kwargs):
|
||||
if self.panels[self.selected].check():
|
||||
self.select( self.selected )
|
||||
return
|
||||
self.select(self.selected + 1)
|
||||
|
||||
def back(self, *args, **kwargs):
|
||||
self.select(self.selected - 1, False)
|
||||
|
||||
class WizardPanel(urwid.WidgetWrap):
|
||||
def __init__(self, state):
|
||||
self.state = state
|
||||
self.init_widgets()
|
||||
self.box = urwid.ListBox( urwid.SimpleListWalker( self.widgets ) )
|
||||
urwid.WidgetWrap.__init__( self, self.box )
|
||||
def init_widgets(self):
|
||||
self.widgets = []
|
||||
def focus_widget(self, widget):
|
||||
self.box.set_focus( self.widgets.index( widget ) )
|
||||
def focusable(self):
|
||||
return True
|
||||
def check(self):
|
||||
return
|
||||
def activate(self):
|
||||
return
|
|
@ -0,0 +1,65 @@
|
|||
import urwid
|
||||
|
||||
window_stack = []
|
||||
window_names = []
|
||||
|
||||
header = urwid.Text( "" )
|
||||
footer = urwid.Text( "" )
|
||||
top = urwid.Frame( urwid.SolidFill(), header, footer )
|
||||
|
||||
def push_window( frame, name=None ):
|
||||
window_stack.append( frame )
|
||||
window_names.append( name )
|
||||
update_top()
|
||||
|
||||
def pop_window():
|
||||
if len(window_stack) == 1:
|
||||
return False
|
||||
window_stack.pop()
|
||||
window_names.pop()
|
||||
update_top()
|
||||
return True
|
||||
|
||||
def update_top():
|
||||
names = [ n for n in window_names if n ]
|
||||
header.set_text(" - ".join( names ) + "\n")
|
||||
top.set_body( window_stack[-1] )
|
||||
|
||||
def set_status(message):
|
||||
footer.set_text(message)
|
||||
|
||||
def clear_status():
|
||||
footer.set_text("")
|
||||
|
||||
class Abort(Exception):
|
||||
pass
|
||||
|
||||
class Back(Exception):
|
||||
pass
|
||||
|
||||
def raise_abort(*args, **kwargs):
|
||||
raise Abort()
|
||||
|
||||
def raise_back(*args, **kwarg):
|
||||
raise Back()
|
||||
|
||||
def event_loop(ui):
|
||||
while True:
|
||||
try:
|
||||
cols, rows = ui.get_cols_rows()
|
||||
canvas = top.render( (cols, rows), focus=True )
|
||||
ui.draw_screen( (cols, rows), canvas )
|
||||
|
||||
keys = ui.get_input()
|
||||
for k in keys:
|
||||
if k == "esc":
|
||||
if not pop_window():
|
||||
break
|
||||
elif k == "window resize":
|
||||
(cols, rows) = ui.get_cols_rows()
|
||||
else:
|
||||
top.keypress( (cols, rows), k )
|
||||
except Back:
|
||||
pop_window()
|
||||
except (Abort, KeyboardInterrupt):
|
||||
return
|
Loading…
Reference in New Issue