| 1 | #
 | 
|---|
| 2 | # Copyright (c) 2009 Martin Decky
 | 
|---|
| 3 | # All rights reserved.
 | 
|---|
| 4 | #
 | 
|---|
| 5 | # Redistribution and use in source and binary forms, with or without
 | 
|---|
| 6 | # modification, are permitted provided that the following conditions
 | 
|---|
| 7 | # are met:
 | 
|---|
| 8 | #
 | 
|---|
| 9 | # - Redistributions of source code must retain the above copyright
 | 
|---|
| 10 | #   notice, this list of conditions and the following disclaimer.
 | 
|---|
| 11 | # - Redistributions in binary form must reproduce the above copyright
 | 
|---|
| 12 | #   notice, this list of conditions and the following disclaimer in the
 | 
|---|
| 13 | #   documentation and/or other materials provided with the distribution.
 | 
|---|
| 14 | # - The name of the author may not be used to endorse or promote products
 | 
|---|
| 15 | #   derived from this software without specific prior written permission.
 | 
|---|
| 16 | #
 | 
|---|
| 17 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 | 
|---|
| 18 | # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 | 
|---|
| 19 | # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 | 
|---|
| 20 | # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 | 
|---|
| 21 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 | 
|---|
| 22 | # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 | 
|---|
| 23 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 | 
|---|
| 24 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 | 
|---|
| 25 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 | 
|---|
| 26 | # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
|---|
| 27 | #
 | 
|---|
| 28 | """
 | 
|---|
| 29 | Text User Interface wrapper
 | 
|---|
| 30 | """
 | 
|---|
| 31 | 
 | 
|---|
| 32 | import sys
 | 
|---|
| 33 | import os
 | 
|---|
| 34 | 
 | 
|---|
| 35 | def call_dlg(dlgcmd, *args, **kw):
 | 
|---|
| 36 |         "Wrapper for calling 'dialog' program"
 | 
|---|
| 37 | 
 | 
|---|
| 38 |         indesc, outdesc = os.pipe()
 | 
|---|
| 39 |         pid = os.fork()
 | 
|---|
| 40 |         if (not pid):
 | 
|---|
| 41 |                 os.dup2(outdesc, 2)
 | 
|---|
| 42 |                 os.close(indesc)
 | 
|---|
| 43 | 
 | 
|---|
| 44 |                 dlgargs = [dlgcmd]
 | 
|---|
| 45 |                 for key, val in kw.items():
 | 
|---|
| 46 |                         dlgargs.append('--' + key)
 | 
|---|
| 47 |                         dlgargs.append(val)
 | 
|---|
| 48 | 
 | 
|---|
| 49 |                 dlgargs += args
 | 
|---|
| 50 |                 os.execlp(dlgcmd, *dlgargs)
 | 
|---|
| 51 | 
 | 
|---|
| 52 |         os.close(outdesc)
 | 
|---|
| 53 | 
 | 
|---|
| 54 |         try:
 | 
|---|
| 55 |                 errout = os.fdopen(indesc, 'r')
 | 
|---|
| 56 |                 data = errout.read()
 | 
|---|
| 57 |                 errout.close()
 | 
|---|
| 58 |                 pid, status = os.wait()
 | 
|---|
| 59 |         except:
 | 
|---|
| 60 |                 # Reset terminal
 | 
|---|
| 61 |                 os.system('reset')
 | 
|---|
| 62 |                 raise
 | 
|---|
| 63 | 
 | 
|---|
| 64 |         if (not os.WIFEXITED(status)):
 | 
|---|
| 65 |                 # Reset terminal
 | 
|---|
| 66 |                 os.system('reset')
 | 
|---|
| 67 |                 raise EOFError
 | 
|---|
| 68 | 
 | 
|---|
| 69 |         status = os.WEXITSTATUS(status)
 | 
|---|
| 70 |         if (status == 255):
 | 
|---|
| 71 |                 raise EOFError
 | 
|---|
| 72 | 
 | 
|---|
| 73 |         return (status, data)
 | 
|---|
| 74 | 
 | 
|---|
| 75 | try:
 | 
|---|
| 76 |         import snack
 | 
|---|
| 77 |         newt = True
 | 
|---|
| 78 |         dialog = False
 | 
|---|
| 79 | except ImportError:
 | 
|---|
| 80 |         newt = False
 | 
|---|
| 81 | 
 | 
|---|
| 82 |         dlgcmd = os.environ.get('DIALOG', 'dialog')
 | 
|---|
| 83 |         if (call_dlg(dlgcmd, '--print-maxsize')[0] != 0):
 | 
|---|
| 84 |                 dialog = False
 | 
|---|
| 85 |         else:
 | 
|---|
| 86 |                 dialog = True
 | 
|---|
| 87 | 
 | 
|---|
| 88 | width_extra = 13
 | 
|---|
| 89 | height_extra = 11
 | 
|---|
| 90 | 
 | 
|---|
| 91 | def width_fix(screen, width):
 | 
|---|
| 92 |         "Correct width to screen size"
 | 
|---|
| 93 | 
 | 
|---|
| 94 |         if (width + width_extra > screen.width):
 | 
|---|
| 95 |                 width = screen.width - width_extra
 | 
|---|
| 96 | 
 | 
|---|
| 97 |         if (width <= 0):
 | 
|---|
| 98 |                 width = screen.width
 | 
|---|
| 99 | 
 | 
|---|
| 100 |         return width
 | 
|---|
| 101 | 
 | 
|---|
| 102 | def height_fix(screen, height):
 | 
|---|
| 103 |         "Correct height to screen size"
 | 
|---|
| 104 | 
 | 
|---|
| 105 |         if (height + height_extra > screen.height):
 | 
|---|
| 106 |                 height = screen.height - height_extra
 | 
|---|
| 107 | 
 | 
|---|
| 108 |         if (height <= 0):
 | 
|---|
| 109 |                 height = screen.height
 | 
|---|
| 110 | 
 | 
|---|
| 111 |         return height
 | 
|---|
| 112 | 
 | 
|---|
| 113 | def screen_init():
 | 
|---|
| 114 |         "Initialize the screen"
 | 
|---|
| 115 | 
 | 
|---|
| 116 |         if (newt):
 | 
|---|
| 117 |                 return snack.SnackScreen()
 | 
|---|
| 118 | 
 | 
|---|
| 119 |         return None
 | 
|---|
| 120 | 
 | 
|---|
| 121 | def screen_done(screen):
 | 
|---|
| 122 |         "Cleanup the screen"
 | 
|---|
| 123 | 
 | 
|---|
| 124 |         if (newt):
 | 
|---|
| 125 |                 screen.finish()
 | 
|---|
| 126 | 
 | 
|---|
| 127 | def choice_window(screen, title, text, options, position):
 | 
|---|
| 128 |         "Create options menu"
 | 
|---|
| 129 | 
 | 
|---|
| 130 |         maxopt = 0
 | 
|---|
| 131 |         for option in options:
 | 
|---|
| 132 |                 length = len(option)
 | 
|---|
| 133 |                 if (length > maxopt):
 | 
|---|
| 134 |                         maxopt = length
 | 
|---|
| 135 | 
 | 
|---|
| 136 |         width = maxopt
 | 
|---|
| 137 |         height = len(options)
 | 
|---|
| 138 | 
 | 
|---|
| 139 |         if (newt):
 | 
|---|
| 140 |                 width = width_fix(screen, width + width_extra)
 | 
|---|
| 141 |                 height = height_fix(screen, height)
 | 
|---|
| 142 | 
 | 
|---|
| 143 |                 if (height > 3):
 | 
|---|
| 144 |                         large = True
 | 
|---|
| 145 |                 else:
 | 
|---|
| 146 |                         large = False
 | 
|---|
| 147 | 
 | 
|---|
| 148 |                 buttonbar = snack.ButtonBar(screen, ('Done', 'Cancel'))
 | 
|---|
| 149 |                 textbox = snack.TextboxReflowed(width, text)
 | 
|---|
| 150 |                 listbox = snack.Listbox(height, scroll = large, returnExit = 1)
 | 
|---|
| 151 | 
 | 
|---|
| 152 |                 cnt = 0
 | 
|---|
| 153 |                 for option in options:
 | 
|---|
| 154 |                         listbox.append(option, cnt)
 | 
|---|
| 155 |                         cnt += 1
 | 
|---|
| 156 | 
 | 
|---|
| 157 |                 if (position != None):
 | 
|---|
| 158 |                         listbox.setCurrent(position)
 | 
|---|
| 159 | 
 | 
|---|
| 160 |                 grid = snack.GridForm(screen, title, 1, 3)
 | 
|---|
| 161 |                 grid.add(textbox, 0, 0)
 | 
|---|
| 162 |                 grid.add(listbox, 0, 1, padding = (0, 1, 0, 1))
 | 
|---|
| 163 |                 grid.add(buttonbar, 0, 2, growx = 1)
 | 
|---|
| 164 | 
 | 
|---|
| 165 |                 retval = grid.runOnce()
 | 
|---|
| 166 | 
 | 
|---|
| 167 |                 return (buttonbar.buttonPressed(retval), listbox.current())
 | 
|---|
| 168 |         elif (dialog):
 | 
|---|
| 169 |                 if (width < 35):
 | 
|---|
| 170 |                         width = 35
 | 
|---|
| 171 | 
 | 
|---|
| 172 |                 args = []
 | 
|---|
| 173 |                 cnt = 0
 | 
|---|
| 174 |                 for option in options:
 | 
|---|
| 175 |                         args.append(str(cnt + 1))
 | 
|---|
| 176 |                         args.append(option)
 | 
|---|
| 177 | 
 | 
|---|
| 178 |                         cnt += 1
 | 
|---|
| 179 | 
 | 
|---|
| 180 |                 kw = {}
 | 
|---|
| 181 |                 if (position != None):
 | 
|---|
| 182 |                         kw['default-item'] = str(position + 1)
 | 
|---|
| 183 | 
 | 
|---|
| 184 |                 status, data = call_dlg(dlgcmd, '--title', title, '--extra-button', '--extra-label', 'Done', '--menu', text, str(height + height_extra), str(width + width_extra), str(cnt), *args, **kw)
 | 
|---|
| 185 | 
 | 
|---|
| 186 |                 if (status == 1):
 | 
|---|
| 187 |                         return ('cancel', None)
 | 
|---|
| 188 | 
 | 
|---|
| 189 |                 try:
 | 
|---|
| 190 |                         choice = int(data) - 1
 | 
|---|
| 191 |                 except ValueError:
 | 
|---|
| 192 |                         return ('cancel', None)
 | 
|---|
| 193 | 
 | 
|---|
| 194 |                 if (status == 0):
 | 
|---|
| 195 |                         return (None, choice)
 | 
|---|
| 196 | 
 | 
|---|
| 197 |                 return ('done', choice)
 | 
|---|
| 198 | 
 | 
|---|
| 199 |         sys.stdout.write("\n *** %s *** \n%s\n\n" % (title, text))
 | 
|---|
| 200 | 
 | 
|---|
| 201 |         maxcnt = len(str(len(options)))
 | 
|---|
| 202 |         cnt = 0
 | 
|---|
| 203 |         for option in options:
 | 
|---|
| 204 |                 sys.stdout.write("%*s. %s\n" % (maxcnt, cnt + 1, option))
 | 
|---|
| 205 |                 cnt += 1
 | 
|---|
| 206 | 
 | 
|---|
| 207 |         sys.stdout.write("\n%*s. Done\n" % (maxcnt, '0'))
 | 
|---|
| 208 |         sys.stdout.write("%*s. Quit\n\n" % (maxcnt, 'q'))
 | 
|---|
| 209 | 
 | 
|---|
| 210 |         while True:
 | 
|---|
| 211 |                 if (position != None):
 | 
|---|
| 212 |                         sys.stdout.write("Selection[%s]: " % str(position + 1))
 | 
|---|
| 213 |                 else:
 | 
|---|
| 214 |                         if (cnt > 0):
 | 
|---|
| 215 |                                 sys.stdout.write("Selection[1]: ")
 | 
|---|
| 216 |                         else:
 | 
|---|
| 217 |                                 sys.stdout.write("Selection[0]: ")
 | 
|---|
| 218 |                 inp = sys.stdin.readline()
 | 
|---|
| 219 | 
 | 
|---|
| 220 |                 if (not inp):
 | 
|---|
| 221 |                         raise EOFError
 | 
|---|
| 222 | 
 | 
|---|
| 223 |                 if (not inp.strip()):
 | 
|---|
| 224 |                         if (position != None):
 | 
|---|
| 225 |                                 return (None, position)
 | 
|---|
| 226 |                         else:
 | 
|---|
| 227 |                                 if (cnt > 0):
 | 
|---|
| 228 |                                         inp = '1'
 | 
|---|
| 229 |                                 else:
 | 
|---|
| 230 |                                         inp = '0'
 | 
|---|
| 231 | 
 | 
|---|
| 232 |                 if (inp.strip() == 'q'):
 | 
|---|
| 233 |                         return ('cancel', None)
 | 
|---|
| 234 | 
 | 
|---|
| 235 |                 try:
 | 
|---|
| 236 |                         choice = int(inp.strip())
 | 
|---|
| 237 |                 except ValueError:
 | 
|---|
| 238 |                         continue
 | 
|---|
| 239 | 
 | 
|---|
| 240 |                 if (choice == 0):
 | 
|---|
| 241 |                         return ('done', 0)
 | 
|---|
| 242 | 
 | 
|---|
| 243 |                 if (choice < 1) or (choice > len(options)):
 | 
|---|
| 244 |                         continue
 | 
|---|
| 245 | 
 | 
|---|
| 246 |                 return (None, choice - 1)
 | 
|---|
| 247 | 
 | 
|---|
| 248 | def error_dialog(screen, title, msg):
 | 
|---|
| 249 |         "Print error dialog"
 | 
|---|
| 250 | 
 | 
|---|
| 251 |         width = len(msg)
 | 
|---|
| 252 | 
 | 
|---|
| 253 |         if (newt):
 | 
|---|
| 254 |                 width = width_fix(screen, width)
 | 
|---|
| 255 | 
 | 
|---|
| 256 |                 buttonbar = snack.ButtonBar(screen, ['Ok'])
 | 
|---|
| 257 |                 textbox = snack.TextboxReflowed(width, msg)
 | 
|---|
| 258 | 
 | 
|---|
| 259 |                 grid = snack.GridForm(screen, title, 1, 2)
 | 
|---|
| 260 |                 grid.add(textbox, 0, 0, padding = (0, 0, 0, 1))
 | 
|---|
| 261 |                 grid.add(buttonbar, 0, 1, growx = 1)
 | 
|---|
| 262 |                 grid.runOnce()
 | 
|---|
| 263 |         elif (dialog):
 | 
|---|
| 264 |                 call_dlg(dlgcmd, '--title', title, '--msgbox', msg, '6', str(width + width_extra))
 | 
|---|
| 265 |         else:
 | 
|---|
| 266 |                 sys.stdout.write("\n%s: %s\n" % (title, msg))
 | 
|---|