blob: ece425ff05b3520ef012cf89b8412bdf289378ab [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):
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)
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000135 if stop == 0 and self.stripspaces:
136 continue
137 for x in range(self.maxx+1):
138 if self.stripspaces and x == stop:
139 break
140 result = result + chr(ascii.ascii(self.win.inch(y, x)))
141 if self.maxy > 0:
142 result = result + "\n"
143 return result
144
145 def edit(self, validate=None):
146 "Edit in the widget window and collect the results."
147 while 1:
148 ch = self.win.getch()
149 if validate:
150 ch = validate(ch)
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +0000151 if not ch:
152 continue
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000153 if not self.do_command(ch):
154 break
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +0000155 self.win.refresh()
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000156 return self.gather()
157
158if __name__ == '__main__':
159 def test_editbox(stdscr):
160 win = curses.newwin(4, 9, 15, 20)
161 rectangle(stdscr, 14, 19, 19, 29)
162 stdscr.refresh()
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +0000163 return Textbox(win).edit()
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000164
165 str = curses.wrapper(test_editbox)
166 print str