blob: cd4813bdfe0d91a81be63b8494133783fed46ee5 [file] [log] [blame]
Andrew M. Kuchlinge0d00902000-07-11 10:38:24 +00001"""Simple textbox editing widget with Emacs-like keybindings."""
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +00002
Martin v. Löwis9d51fc82002-02-23 22:31:53 +00003import curses, ascii
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +00004
5def rectangle(win, uly, ulx, lry, lrx):
Andrew M. Kuchlinga13ea552004-10-19 19:21:20 +00006 """Draw a rectangle with corners at the provided upper-left
7 and lower-right coordinates.
8 """
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +00009 win.vline(uly+1, ulx, curses.ACS_VLINE, lry - uly - 1)
10 win.hline(uly, ulx+1, curses.ACS_HLINE, lrx - ulx - 1)
11 win.hline(lry, ulx+1, curses.ACS_HLINE, lrx - ulx - 1)
12 win.vline(uly+1, lrx, curses.ACS_VLINE, lry - uly - 1)
13 win.addch(uly, ulx, curses.ACS_ULCORNER)
14 win.addch(uly, lrx, curses.ACS_URCORNER)
15 win.addch(lry, lrx, curses.ACS_LRCORNER)
16 win.addch(lry, ulx, curses.ACS_LLCORNER)
17
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +000018class Textbox:
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000019 """Editing widget using the interior of a window object.
20 Supports the following Emacs-like key bindings:
21
22 Ctrl-A Go to left edge of window.
23 Ctrl-B Cursor left, wrapping to previous line if appropriate.
24 Ctrl-D Delete character under cursor.
Eric S. Raymond5af256d2000-08-04 07:33:18 +000025 Ctrl-E Go to right edge (stripspaces off) or end of line (stripspaces on).
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000026 Ctrl-F Cursor right, wrapping to next line when appropriate.
27 Ctrl-G Terminate, returning the window contents.
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +000028 Ctrl-H Delete character backward.
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000029 Ctrl-J Terminate if the window is 1 line, otherwise insert newline.
30 Ctrl-K If line is blank, delete it, otherwise clear to end of line.
Eric S. Raymond5af256d2000-08-04 07:33:18 +000031 Ctrl-L Refresh screen.
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000032 Ctrl-N Cursor down; move down one line.
33 Ctrl-O Insert a blank line at cursor location.
34 Ctrl-P Cursor up; move up one line.
35
36 Move operations do nothing if the cursor is at an edge where the movement
37 is not possible. The following synonyms are supported where possible:
38
39 KEY_LEFT = Ctrl-B, KEY_RIGHT = Ctrl-F, KEY_UP = Ctrl-P, KEY_DOWN = Ctrl-N
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +000040 KEY_BACKSPACE = Ctrl-h
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000041 """
42 def __init__(self, win):
43 self.win = win
44 (self.maxy, self.maxx) = win.getmaxyx()
45 self.maxy = self.maxy - 1
46 self.maxx = self.maxx - 1
47 self.stripspaces = 1
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +000048 self.lastcmd = None
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000049 win.keypad(1)
50
Eric S. Raymond5af256d2000-08-04 07:33:18 +000051 def _end_of_line(self, y):
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000052 "Go to the location of the first blank on the given line."
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +000053 last = self.maxx
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000054 while 1:
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000055 if ascii.ascii(self.win.inch(y, last)) != ascii.SP:
56 last = last + 1
57 break
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +000058 elif last == 0:
59 break
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000060 last = last - 1
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000061 return last
62
63 def do_command(self, ch):
64 "Process a single editing command."
65 (y, x) = self.win.getyx()
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +000066 self.lastcmd = ch
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000067 if ascii.isprint(ch):
68 if y < self.maxy or x < self.maxx:
69 # The try-catch ignores the error we trigger from some curses
70 # versions by trying to write into the lowest-rightmost spot
Andrew M. Kuchlingfee31262001-08-13 13:47:23 +000071 # in the window.
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000072 try:
73 self.win.addch(ch)
Andrew M. Kuchlingfee31262001-08-13 13:47:23 +000074 except curses.error:
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000075 pass
Guido van Rossumbffa52f2002-09-29 00:25:51 +000076 elif ch == ascii.SOH: # ^a
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000077 self.win.move(y, 0)
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +000078 elif ch in (ascii.STX,curses.KEY_LEFT, ascii.BS,curses.KEY_BACKSPACE):
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000079 if x > 0:
80 self.win.move(y, x-1)
81 elif y == 0:
82 pass
83 elif self.stripspaces:
Eric S. Raymond5af256d2000-08-04 07:33:18 +000084 self.win.move(y-1, self._end_of_line(y-1))
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000085 else:
86 self.win.move(y-1, self.maxx)
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +000087 if ch in (ascii.BS, curses.KEY_BACKSPACE):
88 self.win.delch()
Guido van Rossumbffa52f2002-09-29 00:25:51 +000089 elif ch == ascii.EOT: # ^d
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000090 self.win.delch()
Guido van Rossumbffa52f2002-09-29 00:25:51 +000091 elif ch == ascii.ENQ: # ^e
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000092 if self.stripspaces:
Eric S. Raymond5af256d2000-08-04 07:33:18 +000093 self.win.move(y, self._end_of_line(y))
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000094 else:
95 self.win.move(y, self.maxx)
Guido van Rossumbffa52f2002-09-29 00:25:51 +000096 elif ch in (ascii.ACK, curses.KEY_RIGHT): # ^f
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000097 if x < self.maxx:
98 self.win.move(y, x+1)
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +000099 elif y == self.maxy:
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000100 pass
101 else:
102 self.win.move(y+1, 0)
Guido van Rossumbffa52f2002-09-29 00:25:51 +0000103 elif ch == ascii.BEL: # ^g
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000104 return 0
Guido van Rossumbffa52f2002-09-29 00:25:51 +0000105 elif ch == ascii.NL: # ^j
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000106 if self.maxy == 0:
107 return 0
108 elif y < self.maxy:
109 self.win.move(y+1, 0)
Guido van Rossumbffa52f2002-09-29 00:25:51 +0000110 elif ch == ascii.VT: # ^k
Eric S. Raymond5af256d2000-08-04 07:33:18 +0000111 if x == 0 and self._end_of_line(y) == 0:
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000112 self.win.deleteln()
113 else:
114 self.win.clrtoeol()
Guido van Rossumbffa52f2002-09-29 00:25:51 +0000115 elif ch == ascii.FF: # ^l
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000116 self.win.refresh()
Guido van Rossumbffa52f2002-09-29 00:25:51 +0000117 elif ch in (ascii.SO, curses.KEY_DOWN): # ^n
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000118 if y < self.maxy:
119 self.win.move(y+1, x)
Eric S. Raymond5af256d2000-08-04 07:33:18 +0000120 if x > self._end_of_line(y+1):
121 self.win.move(y+1, self._end_of_line(y+1))
Guido van Rossumbffa52f2002-09-29 00:25:51 +0000122 elif ch == ascii.SI: # ^o
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000123 self.win.insertln()
Guido van Rossumbffa52f2002-09-29 00:25:51 +0000124 elif ch in (ascii.DLE, curses.KEY_UP): # ^p
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000125 if y > 0:
126 self.win.move(y-1, x)
Eric S. Raymond5af256d2000-08-04 07:33:18 +0000127 if x > self._end_of_line(y-1):
128 self.win.move(y-1, self._end_of_line(y-1))
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000129 return 1
Guido van Rossumbffa52f2002-09-29 00:25:51 +0000130
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000131 def gather(self):
132 "Collect and return the contents of the window."
133 result = ""
134 for y in range(self.maxy+1):
135 self.win.move(y, 0)
Eric S. Raymond5af256d2000-08-04 07:33:18 +0000136 stop = self._end_of_line(y)
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000137 if stop == 0 and self.stripspaces:
138 continue
139 for x in range(self.maxx+1):
140 if self.stripspaces and x == stop:
141 break
142 result = result + chr(ascii.ascii(self.win.inch(y, x)))
143 if self.maxy > 0:
144 result = result + "\n"
145 return result
146
147 def edit(self, validate=None):
148 "Edit in the widget window and collect the results."
149 while 1:
150 ch = self.win.getch()
151 if validate:
152 ch = validate(ch)
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +0000153 if not ch:
154 continue
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000155 if not self.do_command(ch):
156 break
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +0000157 self.win.refresh()
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000158 return self.gather()
159
160if __name__ == '__main__':
161 def test_editbox(stdscr):
Andrew M. Kuchlinga13ea552004-10-19 19:21:20 +0000162 ncols, nlines = 9, 4
163 uly, ulx = 15, 20
164 win = curses.newwin(nlines, ncols, uly, ulx)
165 rectangle(stdscr, uly-1, ulx-1, uly + nlines, ulx + ncols)
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000166 stdscr.refresh()
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +0000167 return Textbox(win).edit()
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000168
169 str = curses.wrapper(test_editbox)
170 print str