PyXR

c:\python24\lib \ curses \ textpad.py



0001 """Simple textbox editing widget with Emacs-like keybindings."""
0002 
0003 import curses, ascii
0004 
0005 def rectangle(win, uly, ulx, lry, lrx):
0006     """Draw a rectangle with corners at the provided upper-left
0007     and lower-right coordinates.
0008     """
0009     win.vline(uly+1, ulx, curses.ACS_VLINE, lry - uly - 1)
0010     win.hline(uly, ulx+1, curses.ACS_HLINE, lrx - ulx - 1)
0011     win.hline(lry, ulx+1, curses.ACS_HLINE, lrx - ulx - 1)
0012     win.vline(uly+1, lrx, curses.ACS_VLINE, lry - uly - 1)
0013     win.addch(uly, ulx, curses.ACS_ULCORNER)
0014     win.addch(uly, lrx, curses.ACS_URCORNER)
0015     win.addch(lry, lrx, curses.ACS_LRCORNER)
0016     win.addch(lry, ulx, curses.ACS_LLCORNER)
0017 
0018 class Textbox:
0019     """Editing widget using the interior of a window object.
0020      Supports the following Emacs-like key bindings:
0021 
0022     Ctrl-A      Go to left edge of window.
0023     Ctrl-B      Cursor left, wrapping to previous line if appropriate.
0024     Ctrl-D      Delete character under cursor.
0025     Ctrl-E      Go to right edge (stripspaces off) or end of line (stripspaces on).
0026     Ctrl-F      Cursor right, wrapping to next line when appropriate.
0027     Ctrl-G      Terminate, returning the window contents.
0028     Ctrl-H      Delete character backward.
0029     Ctrl-J      Terminate if the window is 1 line, otherwise insert newline.
0030     Ctrl-K      If line is blank, delete it, otherwise clear to end of line.
0031     Ctrl-L      Refresh screen.
0032     Ctrl-N      Cursor down; move down one line.
0033     Ctrl-O      Insert a blank line at cursor location.
0034     Ctrl-P      Cursor up; move up one line.
0035 
0036     Move operations do nothing if the cursor is at an edge where the movement
0037     is not possible.  The following synonyms are supported where possible:
0038 
0039     KEY_LEFT = Ctrl-B, KEY_RIGHT = Ctrl-F, KEY_UP = Ctrl-P, KEY_DOWN = Ctrl-N
0040     KEY_BACKSPACE = Ctrl-h
0041     """
0042     def __init__(self, win):
0043         self.win = win
0044         (self.maxy, self.maxx) = win.getmaxyx()
0045         self.maxy = self.maxy - 1
0046         self.maxx = self.maxx - 1
0047         self.stripspaces = 1
0048         self.lastcmd = None
0049         win.keypad(1)
0050 
0051     def _end_of_line(self, y):
0052         "Go to the location of the first blank on the given line."
0053         last = self.maxx
0054         while 1:
0055             if ascii.ascii(self.win.inch(y, last)) != ascii.SP:
0056                 last = last + 1
0057                 break
0058             elif last == 0:
0059                 break
0060             last = last - 1
0061         return last
0062 
0063     def do_command(self, ch):
0064         "Process a single editing command."
0065         (y, x) = self.win.getyx()
0066         self.lastcmd = ch
0067         if ascii.isprint(ch):
0068             if y < self.maxy or x < self.maxx:
0069                 # The try-catch ignores the error we trigger from some curses
0070                 # versions by trying to write into the lowest-rightmost spot
0071                 # in the window.
0072                 try:
0073                     self.win.addch(ch)
0074                 except curses.error:
0075                     pass
0076         elif ch == ascii.SOH:                           # ^a
0077             self.win.move(y, 0)
0078         elif ch in (ascii.STX,curses.KEY_LEFT, ascii.BS,curses.KEY_BACKSPACE):
0079             if x > 0:
0080                 self.win.move(y, x-1)
0081             elif y == 0:
0082                 pass
0083             elif self.stripspaces:
0084                 self.win.move(y-1, self._end_of_line(y-1))
0085             else:
0086                 self.win.move(y-1, self.maxx)
0087             if ch in (ascii.BS, curses.KEY_BACKSPACE):
0088                 self.win.delch()
0089         elif ch == ascii.EOT:                           # ^d
0090             self.win.delch()
0091         elif ch == ascii.ENQ:                           # ^e
0092             if self.stripspaces:
0093                 self.win.move(y, self._end_of_line(y))
0094             else:
0095                 self.win.move(y, self.maxx)
0096         elif ch in (ascii.ACK, curses.KEY_RIGHT):       # ^f
0097             if x < self.maxx:
0098                 self.win.move(y, x+1)
0099             elif y == self.maxy:
0100                 pass
0101             else:
0102                 self.win.move(y+1, 0)
0103         elif ch == ascii.BEL:                           # ^g
0104             return 0
0105         elif ch == ascii.NL:                            # ^j
0106             if self.maxy == 0:
0107                 return 0
0108             elif y < self.maxy:
0109                 self.win.move(y+1, 0)
0110         elif ch == ascii.VT:                            # ^k
0111             if x == 0 and self._end_of_line(y) == 0:
0112                 self.win.deleteln()
0113             else:
0114                 # first undo the effect of self._end_of_line
0115                 self.win.move(y, x)
0116                 self.win.clrtoeol()
0117         elif ch == ascii.FF:                            # ^l
0118             self.win.refresh()
0119         elif ch in (ascii.SO, curses.KEY_DOWN):         # ^n
0120             if y < self.maxy:
0121                 self.win.move(y+1, x)
0122                 if x > self._end_of_line(y+1):
0123                     self.win.move(y+1, self._end_of_line(y+1))
0124         elif ch == ascii.SI:                            # ^o
0125             self.win.insertln()
0126         elif ch in (ascii.DLE, curses.KEY_UP):          # ^p
0127             if y > 0:
0128                 self.win.move(y-1, x)
0129                 if x > self._end_of_line(y-1):
0130                     self.win.move(y-1, self._end_of_line(y-1))
0131         return 1
0132 
0133     def gather(self):
0134         "Collect and return the contents of the window."
0135         result = ""
0136         for y in range(self.maxy+1):
0137             self.win.move(y, 0)
0138             stop = self._end_of_line(y)
0139             if stop == 0 and self.stripspaces:
0140                 continue
0141             for x in range(self.maxx+1):
0142                 if self.stripspaces and x == stop:
0143                     break
0144                 result = result + chr(ascii.ascii(self.win.inch(y, x)))
0145             if self.maxy > 0:
0146                 result = result + "\n"
0147         return result
0148 
0149     def edit(self, validate=None):
0150         "Edit in the widget window and collect the results."
0151         while 1:
0152             ch = self.win.getch()
0153             if validate:
0154                 ch = validate(ch)
0155             if not ch:
0156                 continue
0157             if not self.do_command(ch):
0158                 break
0159             self.win.refresh()
0160         return self.gather()
0161 
0162 if __name__ == '__main__':
0163     def test_editbox(stdscr):
0164         ncols, nlines = 9, 4
0165         uly, ulx = 15, 20
0166         stdscr.addstr(uly-2, ulx, "Use Ctrl-G to end editing.")
0167         win = curses.newwin(nlines, ncols, uly, ulx)
0168         rectangle(stdscr, uly-1, ulx-1, uly + nlines, ulx + ncols)
0169         stdscr.refresh()
0170         return Textbox(win).edit()
0171 
0172     str = curses.wrapper(test_editbox)
0173     print 'Contents of text box:', repr(str)
0174 

Generated by PyXR 0.9.4
SourceForge.net Logo