Tizen 2.0 Release
[platform/upstream/hplip.git] / base / tui.py
1 # -*- coding: utf-8 -*-
2 #
3 # (c) Copyright 2003-2009 Hewlett-Packard Development Company, L.P.
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18 #
19 # Author: Don Welch
20 #
21
22 # Std Lib
23 import sys
24 import re
25
26 # Local
27 from g import *
28 import pexpect
29 import utils
30
31
32 def enter_yes_no(question, default_value='y', choice_prompt=None):
33     if type(default_value) == type(""):
34         if default_value == 'y':
35             default_value = True
36         else:
37             default_value = False
38
39     #assert default_value in [True, False]
40
41     if choice_prompt is None:
42         if default_value:
43             question += " (y=yes*, n=no, q=quit) ? "
44         else:
45             question += " (y=yes, n=no*, q=quit) ? "
46     else:
47         question += choice_prompt
48
49     while True:
50         try:
51             user_input = raw_input(log.bold(question)).lower().strip()
52         except EOFError:
53             continue
54
55         if not user_input:
56             return True, default_value
57
58         if user_input == 'n':
59             return True, False
60
61         if user_input == 'y':
62             return True, True
63
64         if user_input in ('q', 'c'): # q -> quit, c -> cancel
65             return False, default_value
66
67         log.error("Please press <enter> or enter 'y', 'n', or 'q'.")
68
69
70 def enter_range(question, min_value, max_value, default_value=None):
71     while True:
72         try:
73             user_input = raw_input(log.bold(question)).lower().strip()
74         except EOFError:
75             continue
76
77         if not user_input:
78             if default_value is not None:
79                 return True, default_value
80
81         if user_input == 'q':
82             return False, default_value
83
84         try:
85             value_int = int(user_input)
86         except ValueError:
87             log.error('Please enter a number between %d and %d, or "q" to quit.' %
88                 (min_value, max_value))
89             continue
90
91         if value_int < min_value or value_int > max_value:
92             log.error('Please enter a number between %d and %d, or "q" to quit.' %
93                 (min_value, max_value))
94             continue
95
96         return True, value_int
97
98
99 def enter_choice(question, choices, default_value=None):
100     if 'q' not in choices:
101         choices.append('q')
102
103     while True:
104         try:
105             user_input = raw_input(log.bold(question)).lower().strip()
106         except EOFError:
107             continue
108
109
110         if (not user_input and default_value) or user_input == default_value:
111             if default_value == 'q':
112                 return False, default_value
113             else:
114                 return True, default_value
115
116         #print user_input
117         if user_input == 'q':
118             return False, user_input
119
120         if user_input in choices:
121             return True, user_input
122
123         log.error("Please enter %s or press <enter> for the default of '%s'." %
124             (', '.join(["'%s'" % x for x in choices]), default_value))
125
126
127 def title(text):
128     log.info("")
129     log.info("")
130     log.info(log.bold(text))
131     log.info(log.bold("-"*len(text)))
132
133
134 def header(text):
135     c = len(text)
136     log.info("")
137     log.info("-"*(c+4))
138     log.info("| "+text+" |")
139     log.info("-"*(c+4))
140     log.info("")
141
142
143 def load_paper_prompt():
144     return continue_prompt("A page will be printed.\nPlease load plain paper into the printer.")
145
146
147 def load_scanner_for_align_prompt():
148     return continue_prompt("Load the alignment page on the scanner bed and push the 'Scan' or 'Enter' button on the printer to complete the alignment.")
149
150 def load_photo_paper_prompt():
151     return continue_prompt("A page will be printed.\nPlease load HP Advanced Photo Paper - Glossy into the printer.")
152
153
154 def continue_prompt(prompt=''):
155     while True:
156         try:
157             x = raw_input(log.bold(prompt + " Press <enter> to continue or 'q' to quit: ")).lower().strip()
158         except EOFError:
159             continue
160
161         if not x:
162             return True
163
164         elif x == 'q':
165             return  False
166
167         log.error("Please press <enter> or enter 'q' to quit.")
168
169
170 def enter_regex(regex, prompt, pattern, default_value=None):
171     re_obj = re.compile(regex)
172     while True:
173         try:
174             x = raw_input(log.bold(prompt))
175         except EOFError:
176             continue
177
178         if not x and default_value is not None:
179             return default_value, x
180
181         elif x == 'q':
182             return False, default_value
183
184         match = re_obj.search(x)
185
186         if not match:
187             log.error("Incorrect input. Please enter correct input.")
188             continue
189
190         return True, x
191
192
193 def ttysize():
194     try:
195         import commands # TODO: Replace with subprocess (commands is deprecated in Python 3.0)
196         ln1 = commands.getoutput('stty -a').splitlines()[0]
197         vals = {'rows':None, 'columns':None}
198         for ph in ln1.split(';'):
199             x = ph.split()
200             if len(x) == 2:
201                 vals[x[0]] = x[1]
202                 vals[x[1]] = x[0]
203         return int(vals['rows']), int(vals['columns'])
204     except TypeError:
205         return 40, 64
206
207
208 class ProgressMeter(object):
209     def __init__(self, prompt="Progress:"):
210         self.progress = 0
211         self.prompt = prompt
212         self.prev_length = 0
213         self.spinner = "\|/-\|/-*"
214         self.spinner_pos = 0
215         self.max_size = ttysize()[1] - len(prompt) - 25
216         self.update(0)
217
218     def update(self, progress, msg=''): # progress in %
219         self.progress = progress
220
221         x = self.progress * self.max_size / 100
222         if x > self.max_size: x = self.max_size
223
224         if self.progress >= 100:
225             self.spinner_pos = 8
226             self.progress = 100
227
228         sys.stdout.write("\b" * self.prev_length)
229
230         y = "%s [%s%s%s] %d%%  %s   " % \
231             (self.prompt, '*'*(x-1), self.spinner[self.spinner_pos],
232              ' '*(self.max_size-x), self.progress, msg)
233
234         sys.stdout.write(y)
235
236         sys.stdout.flush()
237         self.prev_length = len(y)
238         self.spinner_pos = (self.spinner_pos + 1) % 8
239
240
241
242 class Formatter(object):
243     def __init__(self, margin=2, header=None, min_widths=None, max_widths=None):
244         self.margin = margin # int
245         self.header = header # tuple of strings
246         self.rows = [] # list of tuples
247         self.max_widths = max_widths # tuple of ints
248         self.min_widths = min_widths # tuple of ints
249
250
251     def add(self, row_data): # tuple of strings
252         self.rows.append(row_data)
253
254
255     def output(self):
256         if self.rows:
257             num_cols = len(self.rows[0])
258             for r in self.rows:
259                 if len(r) != num_cols:
260                     log.error("Invalid number of items in row: %s" % r)
261                     return
262
263             if len(self.header) != num_cols:
264                 log.error("Invalid number of items in header.")
265
266             min_calc_widths = []
267             for c in self.header:
268                 header_parts = c.split(' ')
269                 max_width = 0
270                 for x in header_parts:
271                     max_width = max(max_width, len(x))
272
273                 min_calc_widths.append(max_width)
274
275             max_calc_widths = []
276             for x, c in enumerate(self.header):
277                 max_width = 0
278                 for r in self.rows:
279                     max_width = max(max_width, len(r[x]))
280
281                 max_calc_widths.append(max_width)
282
283             max_screen_width = None
284
285             if self.max_widths is None:
286                 max_screen_width = ttysize()[1]
287                 def_max = 8*(max_screen_width/num_cols)/10
288                 self.max_widths = []
289                 for c in self.header:
290                     self.max_widths.append(def_max)
291             else:
292                 if len(self.max_widths) != num_cols:
293                     log.error("Invalid number of items in max col widths.")
294
295             if self.min_widths is None:
296                 if max_screen_width is None:
297                     max_screen_width = ttysize()[1]
298                 def_min = 4*(max_screen_width/num_cols)/10
299                 self.min_widths = []
300                 for c in self.header:
301                     self.min_widths.append(def_min)
302             else:
303                 if len(self.min_widths) != num_cols:
304                     log.error("Invalid number of items in min col widths.")
305
306             col_widths = []
307             formats = []
308             for m1, m2, m3, m4 in zip(self.min_widths, min_calc_widths,
309                                       self.max_widths, max_calc_widths):
310                 col_width = max(max(m1, m2), min(m3, m4))
311                 col_widths.append(col_width)
312                 formats.append({'width': col_width, 'margin': self.margin})
313
314             formatter = utils.TextFormatter(tuple(formats))
315
316             log.info(formatter.compose(self.header))
317
318             sep = []
319             for c in col_widths:
320                 sep.append('-'*c)
321
322             log.info(formatter.compose(tuple(sep)))
323
324             for r in self.rows:
325                 log.info(formatter.compose(r))
326
327         else:
328             log.error("No data rows")
329
330
331
332 ALIGN_LEFT = 0
333 ALIGN_CENTER = 1
334 ALIGN_RIGHT = 2
335
336
337 def align(line, width=70, alignment=ALIGN_LEFT):
338     space = width - len(line)
339
340     if alignment == ALIGN_CENTER:
341         return ' '*(space/2) + line + \
342                ' '*(space/2 + space%2)
343
344     elif alignment == ALIGN_RIGHT:
345         return ' '*space + line
346
347     else:
348         return line + ' '*space
349
350
351 def format_paragraph(paragraph, width=None, alignment=ALIGN_LEFT):
352     if width is None:
353         width = ttysize()[1]
354
355     result = []
356     words = paragraph.split()
357     try:
358         current, words = words[0], words[1:]
359     except IndexError:
360         return [paragraph]
361
362     for word in words:
363         increment = 1 + len(word)
364
365         if len(current) + increment > width:
366             result.append(align(current, width, alignment))
367             current = word
368
369         else:
370             current = current+" "+word
371
372     result.append(align(current, width, alignment))
373     return result
374
375
376 def printer_table(printers):
377     header("SELECT PRINTER")
378     last_used_printer_name = user_conf.get('last_used', 'printer_name')
379     ret = None
380
381     table = Formatter(header=('Num', 'CUPS Printer'),
382                               max_widths=(8, 100), min_widths=(8, 20))
383
384     default_index = None
385     for x, _ in enumerate(printers):
386         if last_used_printer_name == printers[x]:
387             table.add((str(x) + '*', printers[x]))
388             default_index = x
389         else:
390             table.add((str(x), printers[x]))
391
392     table.output()
393
394     if default_index is not None:
395         ok, i = enter_range("\nEnter number 0...%d for printer (q=quit, <enter>=default: *%d) ?" % (x, default_index),
396                                 0, x, default_index)
397     else:
398         ok, i = enter_range("\nEnter number 0...%d for printer (q=quit) ?" % x, 0, x)
399
400     if ok:
401         ret = printers[i]
402
403     return ret
404
405
406 def device_table(devices, scan_flag=False):
407     header("SELECT DEVICE")
408     last_used_device_uri = user_conf.get('last_used', 'device_uri')
409     ret = None
410
411     if scan_flag:
412         table = Formatter(header=('Num', 'Scan device URI'),
413                                  max_widths=(8, 100), min_widths=(8, 12))
414     else:
415         table = Formatter(header=('Num', 'Device URI', 'CUPS Printer(s)'),
416                                  max_widths=(8, 100, 100), min_widths=(8, 12, 12))
417
418     default_index = None
419     device_index = {}
420     for x, d in enumerate(devices):
421         device_index[x] = d
422         if last_used_device_uri == d:
423             if scan_flag:
424                 table.add((str(x) + "*", d))
425             else:
426                 table.add((str(x) + "*", d, ','.join(devices[d])))
427             default_index = x
428         else:
429             if scan_flag:
430                 table.add((str(x), d))
431             else:
432                 table.add((str(x), d, ','.join(devices[d])))
433
434     table.output()
435
436     if default_index is not None:
437         ok, i = enter_range("\nEnter number 0...%d for device (q=quit, <enter>=default: %d*) ?" % (x, default_index),
438                                 0, x, default_index)
439     else:
440         ok, i = enter_range("\nEnter number 0...%d for device (q=quit) ?" % x, 0, x)
441
442     if ok:
443         ret = device_index[i]
444
445     return ret
446
447
448 def connection_table():
449     ret, ios, x = None, {0: ('usb', "Universal Serial Bus (USB)") }, 1
450
451     if prop.net_build:
452         ios[x] = ('net', "Network/Ethernet/Wireless (direct connection or JetDirect)")
453         x += 1
454
455     if prop.par_build:
456         ios[x] = ('par', "Parallel Port (LPT:)")
457         x += 1
458
459     if len(ios) > 1:
460         header("SELECT CONNECTION (I/O) TYPE")
461
462         table = Formatter(header=('Num', 'Connection Type', 'Description'),
463                           max_widths=(8, 20, 80), min_widths=(8, 10, 40))
464
465         for x, data in ios.items():
466             if x == 0:
467                 table.add((str(x) + "*", data[0], data[1]))
468             else:
469                 table.add((str(x), data[0], data[1]))
470
471         table.output()
472
473         ok, val = enter_range("\nEnter number 0...%d for connection type (q=quit, enter=usb*) ? " % x,
474             0, x, 0)
475
476         if ok:
477             ret = [ios[val][0]]
478
479     else:
480         ret = ['usb']
481
482     return ret
483