forked from ajsteele/mmcalc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathui.py
313 lines (287 loc) · 9.06 KB
/
ui.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# coding=utf-8
import os
import sys
import numpy as np
import langen as lang
import config
width = config.console_width
write = sys.stdout.write
def clear():
os.system(lang.clearcommand)
def get_user_input(q):
return raw_input(q+' ')
#in defining get_menu_input, we try first some OS-specific keyboard polling character grabbing things, and resort to the default input method as a last resort
try: #first, try importing Unix modules
import sys, tty, termios
def get_menu_input():
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
write(ch+lang.newline)
return ch
except ImportError: #if that doesn't work, try Windows
try: #msvcrt is the Windows method of, amongst other things, polling the keyboard
import msvcrt
def get_menu_input():
write(lang.chooseoption+' ')
while msvcrt.kbhit():
msvcrt.getch()
ch = msvcrt.getch()
while ch in b'\x00\xe0':
msvcrt.getch()
ch = msvcrt.getch()
write(ch.decode()+lang.newline)
return ch.decode()
except ImportError:
#if neither of the above works, default to raw_input. This may happen on a Mac.
def get_menu_input():
return raw_input(lang.chooseoption_with_enter+' ')
#word-wrap functon
#cheers to http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061
def wrap(text, width):
return reduce(lambda line, word, width=width: '%s%s%s' %
(line,
' \n'[(len(line)-line.rfind('\n')-1
+ len(word.split('\n',1)[0]
) >= width)],
word),
text.split(' ')
)
def message(text):
write(wrap(text,width)+lang.newline)
return True
def quit(error=False):
# if there are no errors, then print bye bye...leave the error on-screen if not!
if not(error):
clear()
message(lang.goodbye)
exit()
def fatalerror(err):
print lang.red + lang.bold + 'FATAL ERROR!' + lang.reset + '\n' + wrap(err, width)
quit(error=True)
def title():
write(lang.bold + lang.underline + lang.gold + lang.bgblack + ' ' + lang.muon + ' ' + lang.reset +
lang.red + lang.bold + lang.underline + ' ' + lang.programname + ' ' + lang.reset + lang.red + lang.underline + lang.url + " "*(width-len(lang.muon+lang.programname+lang.url+lang.version)-5) + lang.reset +
lang.underline + lang.version + lang.reset + lang.newline*2)
def heading(text):
return lang.newline + "="*(int((width-len(text)-2)/2)) + ' ' + text + ' ' + "="*(int((width-len(text)-2)/2)) + lang.newline
def draw_menu(menu):
for [key,name,function,info] in menu:
menuitem = ' ('+key.upper() + ') ' + name
if info == '':
print menuitem
else:
padding = ' ('
ending = ')'
if len(info) > width - len(menuitem+padding+ending):
info = info[:(width-len(menuitem+padding)-len(lang.ellipsis_short)-2)]+lang.ellipsis_short+')'
write(menuitem + lang.grey + padding + info + lang.reset + lang.newline)
else:
write(menuitem + lang.grey + padding + info + lang.grey + ending + lang.reset + lang.newline)
write(lang.newline)
def get_user_choice(menu):
while 1:
a = get_menu_input()
for item in menu:
#if the key is the same as the one typed
if item[0]==a.lower(): #change to lower case so upper case values work
#return the relevant function
return item[2]
def get_user_option(menu,notblank):
while 1:
a = get_menu_input()
for item in menu:
#if the key is the same as the one typed
if item[0]==a:
#return the relevant value
return a
elif a == '' and not notblank:
return ''
def menu(menu,data=False):
clear()
title()
if data != False:
message(data)
draw_menu(menu)
return get_user_choice(menu)
def option(menu,notblank=False):
clear()
title()
draw_menu(menu)
return get_user_option(menu,notblank)
def message_screen(data):
clear()
title()
message(data)
#eqmin and eqmax are True if a value is allowed to be equal to the respective bound, and False if it must be within those bounds
def inputscreen(q,type='string',min=False,max=False,eqmin=True,eqmax=True,notblank=False,validate=False,text='',number=False,newscreen=True):
if newscreen:
clear()
title()
error=''
write (text+'\n')
while True:
write (lang.red+error+lang.reset+lang.newline)
error=''
a = get_user_input(q)
#if it's blank, just go to the previous menu
if a == '':
if notblank is False:
return False
else:
error='Please type something!'
else:
if type=='float':
try:
a = np.float(a)
except:
error = a + ' is not a number. Please enter a valid number, eg 2, 3.142, 6.63e-34…'
elif type=='int':
try:
a = np.int(a)
except:
error = a + ' is not an integer. Please enter a whole number!'
elif type=='complex':
try:
a = np.complex(a)
except:
error = a + ' is not a valid complex number, eg 1, 5.7 + 3.4j… (remember to use j for √-1)'
elif type=='float_or_string':
try:
a = np.float(a)
type='float'
except:
type='string' #don't worry if it doesn't work...it's just a string
elif type=='intlist':
a = a.split(',')
if number is not False:
if len(a) != number:
error = 'You must enter exactly '+str(number)+' values'
for i in range(len(a)):
try:
a[i] = np.int(a[i])
if min is not False and eqmin is True:
if a[i] < min:
error = 'Values must all be >= ' + str(min)
if min is not False and eqmin is False:
if a[i] <= min:
error = 'Values must all be > ' + str(min)
if max is not False and eqmax is True:
if a[i] > max:
error = 'Values must all be <= ' + str(max)
if max is not False and eqmax is False:
if a[i] >= max:
error = 'Values must all be < ' + str(max)
except:
error = 'Please enter a comma-separated list of integers, eg 1,3,4,-5,15'
elif type=='floatlist':
a = a.split(',')
if number is not False:
if len(a) != number:
error = 'You must enter exactly '+str(number)+' values'
for i in range(len(a)):
try:
a[i] = np.float(a[i])
if min is not False and eqmin is True:
if a[i] < min:
error = 'Values must all be >= ' + str(min)
if min is not False and eqmin is False:
if a[i] <= min:
error = 'Values must all be > ' + str(min)
if max is not False and eqmax is True:
if a[i] > max:
error = 'Values must all be <= ' + str(max)
if max is not False and eqmax is False:
if a[i] >= max:
error = 'Values must all be < ' + str(max)
except:
error = 'Please enter a comma-separated list of floats, eg 1.1,5,-6.666e19'
elif type=='yn':
if a[0] == 'y':
return 'yes'
elif a[0] == 'n':
return 'no'
else:
error = 'Please enter y or n, yes or no.'
if error == '' and type=='float' or type=='int':
if min is not False and eqmin is True:
if a < min:
error = 'Value must be >= ' + str(min)
if min is not False and eqmin is False:
if a <= min:
error = 'Value must be > ' + str(min)
if max is not False and eqmax is True:
if a > max:
error = 'Value must be <= ' + str(max)
if max is not False and eqmax is False:
if a >= max:
error = 'Value must be < ' + str(max)
if validate is not False:
valid,output = validate(a)
if valid:
return output
else:
error = output
#if it's a string, anything goes...so don't even test it
if error == '':
return a
def get_filename(directory,suffix,default_filename='',text='',file_description=''):
errtext = ''
write (text+'\n')
if file_description != '':
file_description = ' '+file_description
while True:
if default_filename != '':
filename = inputscreen('Please enter the filename'+file_description+' (blank for \''+default_filename+'\'):','str',notblank=False,text=errtext)
if filename == False:
filename = default_filename
else:
filename = inputscreen('Please enter the filename'+text+':','str',notblank=True)
if os.path.exists(directory+'/'+filename+suffix):
return filename
else:
errtext = "File '"+directory+'/'+filename+suffix+"' does not exist."
def info(info):
print lang.grey + wrap(info, width) + lang.reset
def table(data):
#make sure they're strings
#998 catch if data is a different width on the way down...
cellwidth = np.int((width-1)/len(data[0]))
table = ''
for row in data:
for cell in row:
if len(cell) >= cellwidth:
table += cell[:(cellwidth-len(lang.ellipsis_short)-1)]+lang.ellipsis_short+' '
else:
table += cell+' '*(cellwidth-len(cell))
table += lang.newline
return table
#http://stackoverflow.com/questions/775049/python-time-seconds-to-hms
def s_to_hms(seconds):
m,s = divmod(seconds,60)
h,m = divmod(m,60)
return '%d:%02d:%02d' % (h,m,s)
def charge_str(q):
if q == 0:
return str('0')
elif q < 0:
return str(abs(q))+'-'
else:
return str(q)+'+'
def complex_str(x):
#if it's just a real number
if np.imag(x) == 0:
return str(np.real(x))
elif np.real(x) == 0:
return str(np.imag(x))+'j'
else:
return str(np.real(x))+'+'+str(np.imag(x))+'j'
def list_str(l):
if len(l):
return str(l)[1:-1] #just chops off square brackets
else:
return lang.empty