blob: 2b036486285763c5cc56edeca3a7ea2cf5786945 [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
3import sys, curses, ascii
4
5def rectangle(win, uly, ulx, lry, lrx):
6 "Draw a rectangle."
7 win.vline(uly+1, ulx, curses.ACS_VLINE, lry - uly - 1)
8 win.hline(uly, ulx+1, curses.ACS_HLINE, lrx - ulx - 1)
9 win.hline(lry, ulx+1, curses.ACS_HLINE, lrx - ulx - 1)
10 win.vline(uly+1, lrx, curses.ACS_VLINE, lry - uly - 1)
11 win.addch(uly, ulx, curses.ACS_ULCORNER)
12 win.addch(uly, lrx, curses.ACS_URCORNER)
13 win.addch(lry, lrx, curses.ACS_LRCORNER)
14 win.addch(lry, ulx, curses.ACS_LLCORNER)
15
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +000016class Textbox:
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000017 """Editing widget using the interior of a window object.
18 Supports the following Emacs-like key bindings:
19
20 Ctrl-A Go to left edge of window.
21 Ctrl-B Cursor left, wrapping to previous line if appropriate.
22 Ctrl-D Delete character under cursor.
Eric S. Raymond5af256d2000-08-04 07:33:18 +000023 Ctrl-E Go to right edge (stripspaces off) or end of line (stripspaces on).
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000024 Ctrl-F Cursor right, wrapping to next line when appropriate.
25 Ctrl-G Terminate, returning the window contents.
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +000026 Ctrl-H Delete character backward.
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000027 Ctrl-J Terminate if the window is 1 line, otherwise insert newline.
28 Ctrl-K If line is blank, delete it, otherwise clear to end of line.
Eric S. Raymond5af256d2000-08-04 07:33:18 +000029 Ctrl-L Refresh screen.
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000030 Ctrl-N Cursor down; move down one line.
31 Ctrl-O Insert a blank line at cursor location.
32 Ctrl-P Cursor up; move up one line.
33
34 Move operations do nothing if the cursor is at an edge where the movement
35 is not possible. The following synonyms are supported where possible:
36
37 KEY_LEFT = Ctrl-B, KEY_RIGHT = Ctrl-F, KEY_UP = Ctrl-P, KEY_DOWN = Ctrl-N
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +000038 KEY_BACKSPACE = Ctrl-h
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000039 """
40 def __init__(self, win):
41 self.win = win
42 (self.maxy, self.maxx) = win.getmaxyx()
43 self.maxy = self.maxy - 1
44 self.maxx = self.maxx - 1
45 self.stripspaces = 1
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +000046 self.lastcmd = None
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000047 win.keypad(1)
48
Eric S. Raymond5af256d2000-08-04 07:33:18 +000049 def _end_of_line(self, y):
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000050 "Go to the location of the first blank on the given line."
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +000051 last = self.maxx
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000052 while 1:
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000053 if ascii.ascii(self.win.inch(y, last)) != ascii.SP:
54 last = last + 1
55 break
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +000056 elif last == 0:
57 break
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000058 last = last - 1
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000059 return last
60
61 def do_command(self, ch):
62 "Process a single editing command."
63 (y, x) = self.win.getyx()
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +000064 self.lastcmd = ch
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000065 if ascii.isprint(ch):
66 if y < self.maxy or x < self.maxx:
67 # The try-catch ignores the error we trigger from some curses
68 # versions by trying to write into the lowest-rightmost spot
Andrew M. Kuchlingfee31262001-08-13 13:47:23 +000069 # in the window.
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000070 try:
71 self.win.addch(ch)
Andrew M. Kuchlingfee31262001-08-13 13:47:23 +000072 except curses.error:
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000073 pass
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +000074 elif ch == ascii.SOH: # ^a
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000075 self.win.move(y, 0)
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +000076 elif ch in (ascii.STX,curses.KEY_LEFT, ascii.BS,curses.KEY_BACKSPACE):
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000077 if x > 0:
78 self.win.move(y, x-1)
79 elif y == 0:
80 pass
81 elif self.stripspaces:
Eric S. Raymond5af256d2000-08-04 07:33:18 +000082 self.win.move(y-1, self._end_of_line(y-1))
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000083 else:
84 self.win.move(y-1, self.maxx)
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +000085 if ch in (ascii.BS, curses.KEY_BACKSPACE):
86 self.win.delch()
87 elif ch == ascii.EOT: # ^d
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000088 self.win.delch()
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +000089 elif ch == ascii.ENQ: # ^e
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000090 if self.stripspaces:
Eric S. Raymond5af256d2000-08-04 07:33:18 +000091 self.win.move(y, self._end_of_line(y))
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000092 else:
93 self.win.move(y, self.maxx)
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +000094 elif ch in (ascii.ACK, curses.KEY_RIGHT): # ^f
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000095 if x < self.maxx:
96 self.win.move(y, x+1)
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +000097 elif y == self.maxy:
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000098 pass
99 else:
100 self.win.move(y+1, 0)
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +0000101 elif ch == ascii.BEL: # ^g
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000102 return 0
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +0000103 elif ch == ascii.NL: # ^j
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000104 if self.maxy == 0:
105 return 0
106 elif y < self.maxy:
107 self.win.move(y+1, 0)
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +0000108 elif ch == ascii.VT: # ^k
Eric S. Raymond5af256d2000-08-04 07:33:18 +0000109 if x == 0 and self._end_of_line(y) == 0:
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000110 self.win.deleteln()
111 else:
112 self.win.clrtoeol()
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +0000113 elif ch == ascii.FF: # ^l
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000114 self.win.refresh()
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +0000115 elif ch in (ascii.SO, curses.KEY_DOWN): # ^n
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000116 if y < self.maxy:
117 self.win.move(y+1, x)
Eric S. Raymond5af256d2000-08-04 07:33:18 +0000118 if x > self._end_of_line(y+1):
119 self.win.move(y+1, self._end_of_line(y+1))
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +0000120 elif ch == ascii.SI: # ^o
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000121 self.win.insertln()
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +0000122 elif ch in (ascii.DLE, curses.KEY_UP): # ^p
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000123 if y > 0:
124 self.win.move(y-1, x)
Eric S. Raymond5af256d2000-08-04 07:33:18 +0000125 if x > self._end_of_line(y-1):
126 self.win.move(y-1, self._end_of_line(y-1))
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000127 return 1
128
129 def gather(self):
130 "Collect and return the contents of the window."
131 result = ""
132 for y in range(self.maxy+1):
133 self.win.move(y, 0)
Eric S. Raymond5af256d2000-08-04 07:33:18 +0000134 stop = self._end_of_line(y)
135 #sys.stderr.write("y=%d, _end_of_line(y)=%d\n" % (y, stop))
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000136 if stop == 0 and self.stripspaces:
137 continue
138 for x in range(self.maxx+1):
139 if self.stripspaces and x == stop:
140 break
141 result = result + chr(ascii.ascii(self.win.inch(y, x)))
142 if self.maxy > 0:
143 result = result + "\n"
144 return result
145
146 def edit(self, validate=None):
147 "Edit in the widget window and collect the results."
148 while 1:
149 ch = self.win.getch()
150 if validate:
151 ch = validate(ch)
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +0000152 if not ch:
153 continue
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000154 if not self.do_command(ch):
155 break
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +0000156 self.win.refresh()
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000157 return self.gather()
158
159if __name__ == '__main__':
160 def test_editbox(stdscr):
161 win = curses.newwin(4, 9, 15, 20)
162 rectangle(stdscr, 14, 19, 19, 29)
163 stdscr.refresh()
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +0000164 return Textbox(win).edit()
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000165
166 str = curses.wrapper(test_editbox)
167 print str