blob: cd68174845358b1ce9f45ec3bb7aa31cbcc5ddae [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
18class textbox:
19 """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.
28 Ctrl-J Terminate if the window is 1 line, otherwise insert newline.
29 Ctrl-K If line is blank, delete it, otherwise clear to end of line.
30 Ctrl-L Refresh screen
31 Ctrl-N Cursor down; move down one line.
32 Ctrl-O Insert a blank line at cursor location.
33 Ctrl-P Cursor up; move up one line.
34
35 Move operations do nothing if the cursor is at an edge where the movement
36 is not possible. The following synonyms are supported where possible:
37
38 KEY_LEFT = Ctrl-B, KEY_RIGHT = Ctrl-F, KEY_UP = Ctrl-P, KEY_DOWN = Ctrl-N
39 """
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
46 win.keypad(1)
47
48 def firstblank(self, y):
49 "Go to the location of the first blank on the given line."
50 (oldy, oldx) = self.win.getyx()
51 self.win.move(y, self.maxx-1)
52 last = self.maxx-1
53 while 1:
54 if last == 0:
55 break
56 if ascii.ascii(self.win.inch(y, last)) != ascii.SP:
57 last = last + 1
58 break
59 last = last - 1
60 self.win.move(oldy, oldx)
61 return last
62
63 def do_command(self, ch):
64 "Process a single editing command."
65 (y, x) = self.win.getyx()
66 if ascii.isprint(ch):
67 if y < self.maxy or x < self.maxx:
68 # The try-catch ignores the error we trigger from some curses
69 # versions by trying to write into the lowest-rightmost spot
70 # in the self.window.
71 try:
72 self.win.addch(ch)
73 except ERR:
74 pass
75 elif ch == ascii.SOH: # Ctrl-a
76 self.win.move(y, 0)
77 elif ch in (ascii.STX, curses.KEY_LEFT): # Ctrl-b
78 if x > 0:
79 self.win.move(y, x-1)
80 elif y == 0:
81 pass
82 elif self.stripspaces:
83 self.win.move(y-1, self.firstblank(y-1))
84 else:
85 self.win.move(y-1, self.maxx)
86 elif ch == ascii.EOT: # Ctrl-d
87 self.win.delch()
88 elif ch == ascii.ENQ: # Ctrl-e
89 if self.stripspaces:
90 self.win.move(y, self.firstblank(y, maxx))
91 else:
92 self.win.move(y, self.maxx)
93 elif ch in (ascii.ACK, curses.KEY_RIGHT): # Ctrl-f
94 if x < self.maxx:
95 self.win.move(y, x+1)
96 elif y == self.maxx:
97 pass
98 else:
99 self.win.move(y+1, 0)
100 elif ch == ascii.BEL: # Ctrl-g
101 return 0
102 elif ch == ascii.NL: # Ctrl-j
103 if self.maxy == 0:
104 return 0
105 elif y < self.maxy:
106 self.win.move(y+1, 0)
107 elif ch == ascii.VT: # Ctrl-k
108 if x == 0 and self.firstblank(y) == 0:
109 self.win.deleteln()
110 else:
111 self.win.clrtoeol()
112 elif ch == ascii.FF: # Ctrl-l
113 self.win.refresh()
114 elif ch in (ascii.SO, curses.KEY_DOWN): # Ctrl-n
115 if y < self.maxy:
116 self.win.move(y+1, x)
117 elif ch == ascii.SI: # Ctrl-o
118 self.win.insertln()
119 elif ch in (ascii.DLE, curses.KEY_UP): # Ctrl-p
120 if y > 0:
121 self.win.move(y-1, x)
122 self.win.refresh()
123 return 1
124
125 def gather(self):
126 "Collect and return the contents of the window."
127 result = ""
128 for y in range(self.maxy+1):
129 self.win.move(y, 0)
130 stop = self.firstblank(y)
131 if stop == 0 and self.stripspaces:
132 continue
133 for x in range(self.maxx+1):
134 if self.stripspaces and x == stop:
135 break
136 result = result + chr(ascii.ascii(self.win.inch(y, x)))
137 if self.maxy > 0:
138 result = result + "\n"
139 return result
140
141 def edit(self, validate=None):
142 "Edit in the widget window and collect the results."
143 while 1:
144 ch = self.win.getch()
145 if validate:
146 ch = validate(ch)
147 if not self.do_command(ch):
148 break
149 return self.gather()
150
151if __name__ == '__main__':
152 def test_editbox(stdscr):
153 win = curses.newwin(4, 9, 15, 20)
154 rectangle(stdscr, 14, 19, 19, 29)
155 stdscr.refresh()
156 return textbox(win).edit()
157
158 str = curses.wrapper(test_editbox)
159 print str