Author: johannes Date: 2005-05-18 05:13:26 -0500 (Wed, 18 May 2005) New Revision: 7544
Modified: trunk/gnue-forms/src/uidrivers/curses/UILoginHandler.py trunk/gnue-forms/src/uidrivers/curses/dialogs.py Log: Added versatile input dialog, updated login handler Modified: trunk/gnue-forms/src/uidrivers/curses/UILoginHandler.py =================================================================== --- trunk/gnue-forms/src/uidrivers/curses/UILoginHandler.py 2005-05-17 15:31:08 UTC (rev 7543) +++ trunk/gnue-forms/src/uidrivers/curses/UILoginHandler.py 2005-05-18 10:13:26 UTC (rev 7544) @@ -24,6 +24,7 @@ import curses from gnue.common.datasources import GLoginHandler +from gnue.forms.uidrivers.curses import dialogs # ============================================================================= # This class implements a login handler for curses @@ -35,10 +36,11 @@ # Get input for all fields listed in loginData # --------------------------------------------------------------------------- - def getLogin (self, loginData, error = None): + def _askLogin (self, title, fields): - (connection, description, fields) = loginData + dlg = dialogs.InputDialog (title, fields, self.uiDriver.attr) - text = u_('Login required for "%s"') % (description or connection) + dlg.run () + return dlg.inputData - return self.uiDriver.askLogin (text, fields, error) + Modified: trunk/gnue-forms/src/uidrivers/curses/dialogs.py =================================================================== --- trunk/gnue-forms/src/uidrivers/curses/dialogs.py 2005-05-17 15:31:08 UTC (rev 7543) +++ trunk/gnue-forms/src/uidrivers/curses/dialogs.py 2005-05-18 10:13:26 UTC (rev 7544) @@ -22,7 +22,11 @@ # $Id$ import curses +import curses.ascii +import sys +from gnue.common.apps import i18n + # ============================================================================= # Dialog box for selection an option from a given options dictionary # ============================================================================= @@ -34,7 +38,7 @@ # --------------------------------------------------------------------------- def __init__ (self, title, options, attrs, quit = None, left = 0, top = 0, - right = 80, bottom = 24): + right = 80, bottom = 24, returnKeys = False): curses.noecho () curses.cbreak () @@ -44,10 +48,10 @@ except: self.__oldcursor = 1 - self.__data = ["%s" % item for item in options.values ()] + self.__data = [("%s" % val, key) for (key, val) in options.items ()] self.__data.sort () - maxString = max ([len (item) for item in self.__data]) + maxString = max ([len (item) for (item, key) in self.__data]) width = min (right - left, max ((maxString + 4), len (title) + 4)) height = min (bottom - top, len (options) + 4) @@ -75,6 +79,7 @@ self.__index = 0 self.__pIndex = 1 self.__page = height - 4 + self.__returnKeys = returnKeys self.__refresh () @@ -106,7 +111,7 @@ self.__move (+1) elif key == 10: - return self.__data [self.__index] + return self.__data [self.__index][self.__returnKeys] finally: try: @@ -148,7 +153,7 @@ def __refresh (self): lines = self.__data [self.__offset:self.__offset + self.__page] - for (line, value) in enumerate (lines): + for (line, (value, key)) in enumerate (lines): text = " %s " % value.ljust (self.__dwidth) [:self.__dwidth] if line == self.__pIndex - 1: attr = self.__reverse @@ -160,33 +165,342 @@ self.__window.move (3 + self.__pIndex - 1, 1) + # ============================================================================= +# Class implementing a versatile input dialog +# ============================================================================= + +class InputDialog: + + # --------------------------------------------------------------------------- + # Create a new input dialog + # --------------------------------------------------------------------------- + + def __init__ (self, title, fields, attrs, cancel = True, left = 0, top = 0, + right = 80, bottom = 24): + + curses.noecho () + curses.cbreak () + + self.attrs = attrs + self.fields = fields + self.__buildWindow (title, left, top, right, bottom) + + self.inputData = {} + + # A dictionary with the fieldname as key and a triple (master, allowed, + # current) as data items + self.lookups = {} + + y = 2 + self.__entries = [] + + for (label, name, ftype, default, master, elements) in fields: + if ftype == 'image': + continue + + y += 1 + + if ftype in ['label', 'warning']: + attr = [attrs ['background'], attrs ['warnmsg']][ftype == 'warning'] + cw = self.__width - 2 + lh = label.count ('\n') + 1 + + for item in label.splitlines (): + self.__window.addnstr (y, 1, o(item.center (cw)), cw, attr) + y += 1 + + if lh > 1: y -= 1 + + elif ftype in ['string', 'password', 'dropdown']: + self.__window.addnstr (y, 2, o(label), self.__leftcol) + + win = curses.newwin (1, self.__rightcol, + self.__wtop + y, self.__wleft + self.__leftcol + 2) + self.__entries.append ((win, name, default, ftype == 'password')) + + if ftype == 'dropdown': + values = elements [0][1] + self.lookups [name] = (master, values, + self.__getCurrent (master, values)) + dispValue = self.lookups [name][2].get (default, '') + else: + dispValue = default + + self.__display (win, dispValue, ftype == 'password') + + + + # --------------------------------------------------------------------------- + # Run the dialog + # --------------------------------------------------------------------------- + + def run (self): + + self.__window.refresh () + + index = 0 + while index < len (self.__entries): + (win, name, default, hidden) = self.__entries [index] + cvalue = self.inputData.get (name, default) + (value, code) = self.__accept (win, cvalue, name, hidden) + if value is None: + if name in self.inputData: + del self.inputData [name] + else: + self.inputData [name] = value + + if code == 27: + self.inputData = None + break + + elif code == curses.KEY_UP: + index = max (0, index - 1) + + else: + index += 1 + + + # --------------------------------------------------------------------------- + # Calculate and build the window + # --------------------------------------------------------------------------- + + def __buildWindow (self, title, left, top, right, bottom): + + leftcol = 0 + rightcol = 0 + width = 0 + height = 5 # box + title + spacer + bottom + + for (label, name, ftype, default, master, elements) in self.fields: + if ftype in ['label', 'warning']: + arr = ([width] + [len (part) + 2 for part in label.splitlines ()]) + width = max ([width] + [len (part) + 2 for part in label.splitlines ()]) + height += label.count ('\n') + 1 + + elif ftype in ['string', 'password']: + leftcol = max (leftcol, len (label) + 1) + rightcol = max (rightcol, 20) + height += 1 + + elif ftype == 'dropdown': + leftcol = max (leftcol, len (label) + 1) + height += 1 + + # We introspect only the first control since we do not provide them all + # for input + (label, allowed) = elements [0] + widest = 0 + if master is None: + data = [allowed] + else: + data = allowed.values () + + for vdict in data: + widest = max ([widest] + [len (i) for i in vdict.values ()]) + + rightcol = max (rightcol, widest) + + + self.__width = min (max (leftcol + rightcol + 4, width), right - left) + self.__height = min (height, bottom - top) + self.__wtop = (bottom - top - self.__height) / 2 + self.__wleft = (right - left - self.__width) / 2 + + self.__window = curses.newwin (self.__height, self.__width, + self.__wtop, self.__wleft) + self.__window.bkgd (' ', self.attrs ['background']) + self.__window.box () + self.__window.keypad (1) + + self.__window.addstr (1, 1, title.center (self.__width - 2)) + + self.__leftcol = leftcol + self.__rightcol = rightcol + + + + # --------------------------------------------------------------------------- + # Get input for a given field + # --------------------------------------------------------------------------- + + def __accept (self, win, default, name, hidden = False): + + if name in self.lookups: + (master, allowed, current) = self.lookups [name] + current = self.__getCurrent (master, allowed) + self.lookups [name] = (master, allowed, current) + + reverse = {} + for (k, v) in current.items (): + reverse [v] = k + + value = current.get (default, '') + else: + value = default or '' + current = None + revese = None + + win.keypad (1) + + while True: + self.__display (win, value, hidden, True) + + code = self.__window.getch () + + if code in [9, 10, 27, curses.KEY_DOWN, curses.KEY_UP]: + if code == 27: + rvalue = None + + elif code == 9: + code = curses.KEY_DOWN + + if code != 27 and name in self.lookups: + if reverse and not value in reverse: + curses.beep () + continue + else: + rvalue = reverse.get (value) + else: + rvalue = value + + break + + elif code == curses.KEY_BACKSPACE: + value = value [:-1] + + elif current and code == 23: + op = OptionsDialog ('Select Option', current, self.attrs, 23) + v = op.run () + self.__window.redrawwin () + curses.doupdate () + + if v is not None: + value = v + curses.ungetch (10) + + elif curses.ascii.isprint (code): + value += chr (code) + + self.__display (win, value, hidden, False) + + return (rvalue, code) + + + # --------------------------------------------------------------------------- + # Display the visible portion of a given value within a window + # --------------------------------------------------------------------------- + + def __display (self, win, value, hidden = False, edit = False): + + data = value or '' + attr = self.attrs [edit and 'focusentry' or 'entry'] + win.bkgd (' ', attr) + + length = win.getmaxyx () [1] - 1 + (top, left) = win.getbegyx () + (mtop, mleft) = self.__window.getbegyx () + top -= mtop + left -= mleft + + dvalue = [data, '*' * len (data)] [hidden] + offset = max (0, len (dvalue) - length) + pos = min (len (dvalue) - offset, length) + + out = dvalue [offset:offset + length].ljust (length + 1) + self.__window.addnstr (top, left, out, length + 1, attr) + + self.__window.move (top, (pos + left)) + + + # --------------------------------------------------------------------------- + # Get the current value dictionary for a given dropdown + # --------------------------------------------------------------------------- + + def __getCurrent (self, master, values): + + if master is None: + return values + else: + return values.get (self.inputData.get (master), {}) + + +# ============================================================================= # Module self test # ============================================================================= if __name__ == '__main__': curses.initscr () + curses.cbreak () curses.raw () curses.start_color () curses.init_pair (3, curses.COLOR_BLACK, curses.COLOR_CYAN) curses.init_pair (4, curses.COLOR_BLUE, curses.COLOR_WHITE) curses.init_pair (5, curses.COLOR_WHITE, curses.COLOR_RED) + curses.init_pair (6, curses.COLOR_BLACK, curses.COLOR_CYAN) + curses.init_pair (9, curses.COLOR_WHITE, curses.COLOR_RED) attrs = {'background': curses.color_pair (4), 'focusentry': curses.color_pair (5), - 'window 1': curses.color_pair (3)} + 'entry' : curses.color_pair (6), + 'window 1' : curses.color_pair (3), + 'warnmsg' : curses.color_pair (9) + curses.A_BOLD} opts = {'foo': 'Foobar', 'baz': 'Barbaz and the Gang!', 2: 'something', 3: 'quite', 4: 'interesting stuff', 5: 'hey ho', 7: 'number 7'} opts = {1:1, 2:2, 3:3, 4:4, 5:5, 6:6, 7:7, 8:8, 9:9, 10:10, 11:11, 12:12, - 13:13, 14:14, 15:15, 16:16, 17:17, 18:18, 19:19, 20:20} - d = OptionsDialog ('Foos', opts, attrs, quit = 23, top = 2, bottom = 22) - r = d.run () + 13:13, 14:14, "k15":15, 16:16, 17:17, 18:18, 19:19, 20:20} + #d = OptionsDialog ('Foos', opts, attrs, quit = 23, top = 2, bottom = 22, + #returnKeys = True) + #r = d.run () + #curses.endwin () + + + #print "RESULT:", r + + #sys.exit () + + # --------------------------------------------------------------------------- + + cname = {'c1': 'demoa', 'c2': 'demob'} + ckey = {'c1': 'ck-A', 'c2': 'ck-B'} + + wija = {'c1': {'04': '2004', '05': '2005'}, + 'c2': {'24': '2024', '25': '2025', '26': '2026'}} + + codes = {'24': {'241': 'c-24-1', '242': 'c-24-2'}, + '25': {'251': 'c-25-1'}} + + descr = 'Hier ist ein langer Text\nMit einem Zeilenumbruch' + fields = [('Foo!', '/home/johannes/gnue/share/gnue/images/gnue.png', 'image', + None, None, []), + ('Username', '_username', 'string', 'frodo', None, \ + [('Name of the user', None)]), + ('', None, 'label', None, None, []), + ('Password', '_password', 'password', 'foo', None, [('yeah',1)]), + ('Foobar', '_foobar', 'dropdown', 'frob', None, \ + [('single', {'trash': 'Da Trash', 'frob': 'Frob'})]), + ('Multi', '_multi', 'dropdown', '100', None, \ + [('name', {'50': 'A 50', '100': 'B 100', '9': 'C 9'}), + ('sepp', {'50': 'se 50', '100': 'se 100', '9': 'se 9'})]), + (descr, '_depp', 'label', 'furz', None, []), + ('Das ist jetzt ein Fehler', None, 'warning', None, None, []), + ('Firma', 'company', 'dropdown', 'c1', None, + [('Name', cname), ('Code', ckey)]), + ('Wirtschaftsjahr', 'wija', 'dropdown', '05', 'company', + [('Jahr', wija)]), + ('Codes', 'codes', 'dropdown', None, 'wija', + [('Code', codes)]), + (u"Dre\xf6ksau 'bl\xf6sepp'", None, 'warning', None, None, [])] + + dlg = InputDialog ('foobar', fields, attrs) + dlg.run () + curses.endwin () - print "RESULT:", r + print "RES:", dlg.inputData _______________________________________________ Commit-gnue mailing list Commit-gnue@gnu.org http://lists.gnu.org/mailman/listinfo/commit-gnue