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