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