Cleanup now that I have more Python experience.
[curses_check_for_keypress.git] / curses_check_for_keypress.py
1 #!/usr/bin/python
2 #
3 # curses_check_for_keypress - loop until user presses a key.
4 #
5 # Copyright (C) 2008-2011 William Trevor King
6 #
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Lesser General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 # Lesser General Public License for more details.
16 #
17 # You should have received a copy of the GNU Lesser General Public
18 # License along with this program.  If not, see
19 # <http://www.gnu.org/licenses/>.
20
21 """Enables looping until the user presses a key.
22
23 Uses the curses module to monitor for a single keypress, because other
24 methods of aquiring keyboard input generally require the user to press
25 enter.
26
27 Usual usage looks like the following, but leaving out the `test_mode`
28 option, which is used to get curses working with doctest's
29 stdin/stdout manipulations.
30
31 >>> c = CheckForKeypress('testing usual usage, press any key to stop loop...',
32 ...     test_mode=True)
33 testing usual usage, press any key to stop loop...
34
35 Use `quiet_sleep` or your own sleep function instead of `_test_sleep`
36 if you need to reduce the loop rate.
37
38 >>> try:
39 ...     while c.input() == None:
40 ...         c.output('sleeping\\n')
41 ...         _test_sleep()
42 ... finally:
43 ...     c.cleanup()  # doctest: +ELLIPSIS
44 sleeping
45 ...
46 sleeping
47
48 An example with error catching is
49
50 >>> c = CheckForKeypress('testing error catching, wait for the error...',
51 ...     test_mode=True)
52 testing error catching, wait for the error...
53 >>> i = 0
54 >>> class TestException (Exception):
55 ...     pass
56 >>> try:
57 ...     while c.input() == None:
58 ...         if i > 4:
59 ...             print >> _sys.stderr, 'testing error output'
60 ...             raise TestException, 'testing error exception'
61 ...         c.output('sleeping %d\\n' % i)
62 ...         _test_sleep()
63 ...         i += 1
64 ...     raise Exception, '_test_error_catching() failed!'
65 ... except TestException, e:
66 ...     print 'caught exception:', e
67 ... finally:
68 ...     c.cleanup()
69 sleeping 0
70 sleeping 1
71 sleeping 2
72 sleeping 3
73 sleeping 4
74 caught exception: testing error exception
75 """
76
77 import curses as _curses  # http://www.amk.ca/python/howto/curses/curses.html
78 import curses.ascii as _curses_ascii
79 from time import sleep as _sleep
80 import sys as _sys
81 import StringIO as _StringIO
82
83
84 __version__ = '0.2'
85
86
87 def quiet_sleep():
88     _sleep(.1)
89
90 def _test_sleep():
91     _sleep(.5)
92
93
94 class CheckForKeypress (object):
95     def __init__(self, prompt="Press any key to continue",
96                  timeout_ms=0, test_mode=False):
97         self.test_mode = test_mode
98         self.last = None  # last byte number read
99         self.lasta = None # last as an ASCII character
100         if test_mode == True:
101             print prompt
102             self.i = 0
103             return None
104         # redirect stderr to a file, because exiting curses mode clears
105         # any error messages that had been printed to the screen
106         _sys.stderr = _StringIO.StringIO()
107         # initialize raw curses mode
108         self._active = True
109         self.stdscr = _curses.initscr()
110         _curses.noecho()
111         _curses.cbreak()
112         self.stdscr.scrollok(1)
113         try:
114             self.stdscr.addstr(0,0,prompt+'\n')
115             if timeout_ms <= 0:
116                 self.stdscr.nodelay(True)
117             else:
118                 self.stdscr.halfdelay(timeout_ms)
119         except:
120             self.cleanup()
121
122     def __del__(self):
123         self.cleanup()
124
125     def cleanup(self):
126         if self.test_mode or not self._active:
127             return None
128         # return to standard terminal
129         while self.input() != None : # eat up the buffer
130             pass
131         self.stdscr.scrollok(0)
132         _curses.nocbreak()
133         _curses.echo()
134         _curses.endwin()
135         # print any errors and restore stderr
136         contents = _sys.stderr.getvalue()
137         _sys.stderr = _sys.__stderr__
138         if len(contents) > 0:
139             print >> _sys.stderr, contents
140         self._active = False
141
142     def input(self):
143         if self.test_mode == True:
144             if self.i < 10:
145                 self.i += 1
146                 return None
147             else:
148                 return "a"
149         c = self.stdscr.getch()
150         if c == _curses.ERR:
151             return None
152         else:
153             self.last = c
154             self.lasta = _curses_ascii.unctrl(c)
155             return c
156
157     def inputa(self):
158         c = self.input()
159         if c == None:
160             return None
161         else:
162             return self.lasta
163
164     def _output(self, string):
165         if self.test_mode == True:
166             print string,
167             return None
168         #y,x = self.stdscr.getyx()
169         self.stdscr.addstr(string)
170
171     def _flush(self):
172         if self.test_mode == True:
173             return None
174         self.stdscr.refresh()
175
176     def output(self, string):
177         self._output(string)
178         self._flush()
179
180
181 if __name__ == '__main__':
182     c = CheckForKeypress('testing...')
183     i = 0
184     i_max = 20
185     try:
186         while c.input() == None:
187             c.output('%d/%d (sleeping)\n' % (i, i_max))
188             _test_sleep()
189             if i >= i_max:
190                 raise Exception, "you didn't press a key!"
191             i += 1
192     finally:
193         c.cleanup()