1 """Simple textbox editing widget with Emacs-like keybindings."""
5 def rectangle(win
, uly
, ulx
, lry
, lrx
):
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
)
17 """Editing widget using the interior of a window object.
18 Supports the following Emacs-like key bindings:
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.
23 Ctrl-E Go to right edge (stripspaces off) or end of line (stripspaces on).
24 Ctrl-F Cursor right, wrapping to next line when appropriate.
25 Ctrl-G Terminate, returning the window contents.
26 Ctrl-H Delete character backward.
27 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.
29 Ctrl-L Refresh screen.
30 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.
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:
37 KEY_LEFT = Ctrl-B, KEY_RIGHT = Ctrl-F, KEY_UP = Ctrl-P, KEY_DOWN = Ctrl-N
38 KEY_BACKSPACE = Ctrl-h
40 def __init__(self
, win
):
42 (self
.maxy
, self
.maxx
) = win
.getmaxyx()
43 self
.maxy
= self
.maxy
- 1
44 self
.maxx
= self
.maxx
- 1
49 def _end_of_line(self
, y
):
50 "Go to the location of the first blank on the given line."
53 if ascii
.ascii(self
.win
.inch(y
, last
)) != ascii
.SP
:
61 def do_command(self
, ch
):
62 "Process a single editing command."
63 (y
, x
) = self
.win
.getyx()
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
74 elif ch
== ascii
.SOH
: # ^a
76 elif ch
in (ascii
.STX
,curses
.KEY_LEFT
, ascii
.BS
,curses
.KEY_BACKSPACE
):
81 elif self
.stripspaces
:
82 self
.win
.move(y
-1, self
._end
_of
_line
(y
-1))
84 self
.win
.move(y
-1, self
.maxx
)
85 if ch
in (ascii
.BS
, curses
.KEY_BACKSPACE
):
87 elif ch
== ascii
.EOT
: # ^d
89 elif ch
== ascii
.ENQ
: # ^e
91 self
.win
.move(y
, self
._end
_of
_line
(y
))
93 self
.win
.move(y
, self
.maxx
)
94 elif ch
in (ascii
.ACK
, curses
.KEY_RIGHT
): # ^f
100 self
.win
.move(y
+1, 0)
101 elif ch
== ascii
.BEL
: # ^g
103 elif ch
== ascii
.NL
: # ^j
107 self
.win
.move(y
+1, 0)
108 elif ch
== ascii
.VT
: # ^k
109 if x
== 0 and self
._end
_of
_line
(y
) == 0:
113 elif ch
== ascii
.FF
: # ^l
115 elif ch
in (ascii
.SO
, curses
.KEY_DOWN
): # ^n
117 self
.win
.move(y
+1, x
)
118 if x
> self
._end
_of
_line
(y
+1):
119 self
.win
.move(y
+1, self
._end
_of
_line
(y
+1))
120 elif ch
== ascii
.SI
: # ^o
122 elif ch
in (ascii
.DLE
, curses
.KEY_UP
): # ^p
124 self
.win
.move(y
-1, x
)
125 if x
> self
._end
_of
_line
(y
-1):
126 self
.win
.move(y
-1, self
._end
_of
_line
(y
-1))
130 "Collect and return the contents of the window."
132 for y
in range(self
.maxy
+1):
134 stop
= self
._end
_of
_line
(y
)
135 if stop
== 0 and self
.stripspaces
:
137 for x
in range(self
.maxx
+1):
138 if self
.stripspaces
and x
== stop
:
140 result
= result
+ chr(ascii
.ascii(self
.win
.inch(y
, x
)))
142 result
= result
+ "\n"
145 def edit(self
, validate
=None):
146 "Edit in the widget window and collect the results."
148 ch
= self
.win
.getch()
153 if not self
.do_command(ch
):
158 if __name__
== '__main__':
159 def test_editbox(stdscr
):
160 win
= curses
.newwin(4, 9, 15, 20)
161 rectangle(stdscr
, 14, 19, 19, 29)
163 return Textbox(win
).edit()
165 str = curses
.wrapper(test_editbox
)