blob: 93771c956682ce6857b246bfaacf23c6b1af17dc [file] [log] [blame]
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +00001"""curses.textpad
2
3"""
4
5import sys, curses, ascii
6
7def rectangle(win, uly, ulx, lry, lrx):
8 "Draw a rectangle."
9 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.
25 Ctrl-E Go to right edge (nospaces off) or end of line (nospaces on).
26 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.
31 Ctrl-L Refresh screen
32 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
51 def firstblank(self, y):
52 "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
71 # in the self.window.
72 try:
73 self.win.addch(ch)
74 except ERR:
75 pass
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +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:
84 self.win.move(y-1, self.firstblank(y-1))
85 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()
89 elif ch == ascii.EOT: # ^d
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000090 self.win.delch()
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +000091 elif ch == ascii.ENQ: # ^e
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +000092 if self.stripspaces:
93 self.win.move(y, self.firstblank(y, maxx))
94 else:
95 self.win.move(y, self.maxx)
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +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)
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +0000103 elif ch == ascii.BEL: # ^g
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000104 return 0
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +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)
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +0000110 elif ch == ascii.VT: # ^k
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000111 if x == 0 and self.firstblank(y) == 0:
112 self.win.deleteln()
113 else:
114 self.win.clrtoeol()
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +0000115 elif ch == ascii.FF: # ^l
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000116 self.win.refresh()
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +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)
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)
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000125 return 1
126
127 def gather(self):
128 "Collect and return the contents of the window."
129 result = ""
130 for y in range(self.maxy+1):
131 self.win.move(y, 0)
132 stop = self.firstblank(y)
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +0000133 #sys.stderr.write("y=%d, firstblank(y)=%d\n" % (y, stop))
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000134 if stop == 0 and self.stripspaces:
135 continue
136 for x in range(self.maxx+1):
137 if self.stripspaces and x == stop:
138 break
139 result = result + chr(ascii.ascii(self.win.inch(y, x)))
140 if self.maxy > 0:
141 result = result + "\n"
142 return result
143
144 def edit(self, validate=None):
145 "Edit in the widget window and collect the results."
146 while 1:
147 ch = self.win.getch()
148 if validate:
149 ch = validate(ch)
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +0000150 if not ch:
151 continue
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000152 if not self.do_command(ch):
153 break
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +0000154 self.win.refresh()
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000155 return self.gather()
156
157if __name__ == '__main__':
158 def test_editbox(stdscr):
159 win = curses.newwin(4, 9, 15, 20)
160 rectangle(stdscr, 14, 19, 19, 29)
161 stdscr.refresh()
Andrew M. Kuchlinge8d7dbf2000-06-27 00:53:12 +0000162 return Textbox(win).edit()
Andrew M. Kuchling2b9d0bc2000-06-26 23:55:42 +0000163
164 str = curses.wrapper(test_editbox)
165 print str