blob: 27131c7237d3fdf8275c1a5826c5f2e28efcbedd [file] [log] [blame]
mblighbe2ac852006-09-28 04:44:30 +00001# dialog.py --- A python interface to the Linux "dialog" utility
2# Copyright (C) 2000 Robb Shecter, Sultanbek Tezadov
3# Copyright (C) 2002, 2003, 2004 Florent Rougon
4#
5# This library is free software; you can redistribute it and/or
6# modify it under the terms of the GNU Lesser General Public
7# License as published by the Free Software Foundation; either
8# version 2.1 of the License, or (at your option) any later version.
9#
10# This library 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 GNU
13# Lesser General Public License for more details.
14#
15# You should have received a copy of the GNU Lesser General Public
16# License along with this library; if not, write to the Free Software
17# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
19"""Python interface to dialog-like programs.
20
21This module provides a Python interface to dialog-like programs such
22as `dialog', `Xdialog' and `whiptail'.
23
24It provides a Dialog class that retains some parameters such as the
25program name and path as well as the values to pass as DIALOG*
26environment variables to the chosen program.
27
28For a quick start, you should look at the demo.py file that comes
29with pythondialog. It demonstrates a simple use of each widget
30offered by the Dialog class.
31
32See the Dialog class documentation for general usage information,
33list of available widgets and ways to pass options to dialog.
34
35
36Notable exceptions
37------------------
38
39Here is the hierarchy of notable exceptions raised by this module:
40
41 error
42 ExecutableNotFound
43 BadPythonDialogUsage
44 PythonDialogSystemError
45 PythonDialogIOError
46 PythonDialogOSError
47 PythonDialogErrorBeforeExecInChildProcess
48 PythonDialogReModuleError
49 UnexpectedDialogOutput
50 DialogTerminatedBySignal
51 DialogError
52 UnableToCreateTemporaryDirectory
53 PythonDialogBug
54 ProbablyPythonBug
55
56As you can see, every exception `exc' among them verifies:
57
58 issubclass(exc, error)
59
60so if you don't need fine-grained error handling, simply catch
61`error' (which will probably be accessible as dialog.error from your
62program) and you should be safe.
63
64"""
65
66from __future__ import nested_scopes
67import sys, os, tempfile, random, string, re, types
68
69
70# Python < 2.3 compatibility
71if sys.hexversion < 0x02030000:
72 # The assignments would work with Python >= 2.3 but then, pydoc
73 # shows them in the DATA section of the module...
74 True = 0 == 0
75 False = 0 == 1
76
77
78# Exceptions raised by this module
79#
80# When adding, suppressing, renaming exceptions or changing their
81# hierarchy, don't forget to update the module's docstring.
82class error(Exception):
83 """Base class for exceptions in pythondialog."""
84 def __init__(self, message=None):
85 self.message = message
86 def __str__(self):
87 return "<%s: %s>" % (self.__class__.__name__, self.message)
88 def complete_message(self):
89 if self.message:
90 return "%s: %s" % (self.ExceptionShortDescription, self.message)
91 else:
92 return "%s" % self.ExceptionShortDescription
93 ExceptionShortDescription = "pythondialog generic exception"
94
95# For backward-compatibility
96#
97# Note: this exception was not documented (only the specific ones were), so
98# the backward-compatibility binding could be removed relatively easily.
99PythonDialogException = error
100
101class ExecutableNotFound(error):
102 """Exception raised when the dialog executable can't be found."""
103 ExceptionShortDescription = "Executable not found"
104
105class PythonDialogBug(error):
106 """Exception raised when pythondialog finds a bug in his own code."""
107 ExceptionShortDescription = "Bug in pythondialog"
108
109# Yeah, the "Probably" makes it look a bit ugly, but:
110# - this is more accurate
111# - this avoids a potential clash with an eventual PythonBug built-in
112# exception in the Python interpreter...
113class ProbablyPythonBug(error):
114 """Exception raised when pythondialog behaves in a way that seems to \
115indicate a Python bug."""
116 ExceptionShortDescription = "Bug in python, probably"
117
118class BadPythonDialogUsage(error):
119 """Exception raised when pythondialog is used in an incorrect way."""
120 ExceptionShortDescription = "Invalid use of pythondialog"
121
122class PythonDialogSystemError(error):
123 """Exception raised when pythondialog cannot perform a "system \
124operation" (e.g., a system call) that should work in "normal" situations.
125
126 This is a convenience exception: PythonDialogIOError, PythonDialogOSError
127 and PythonDialogErrorBeforeExecInChildProcess all derive from this
128 exception. As a consequence, watching for PythonDialogSystemError instead
129 of the aformentioned exceptions is enough if you don't need precise
130 details about these kinds of errors.
131
132 Don't confuse this exception with Python's builtin SystemError
133 exception.
134
135 """
136 ExceptionShortDescription = "System error"
jadmanski0afbb632008-06-06 21:10:57 +0000137
mblighbe2ac852006-09-28 04:44:30 +0000138class PythonDialogIOError(PythonDialogSystemError):
139 """Exception raised when pythondialog catches an IOError exception that \
140should be passed to the calling program."""
141 ExceptionShortDescription = "IO error"
142
143class PythonDialogOSError(PythonDialogSystemError):
144 """Exception raised when pythondialog catches an OSError exception that \
145should be passed to the calling program."""
146 ExceptionShortDescription = "OS error"
147
148class PythonDialogErrorBeforeExecInChildProcess(PythonDialogSystemError):
149 """Exception raised when an exception is caught in a child process \
150before the exec sytem call (included).
151
152 This can happen in uncomfortable situations like when the system is out
153 of memory or when the maximum number of open file descriptors has been
154 reached. This can also happen if the dialog-like program was removed
155 (or if it is has been made non-executable) between the time we found it
156 with _find_in_path and the time the exec system call attempted to
157 execute it...
158
159 """
160 ExceptionShortDescription = "Error in a child process before the exec " \
161 "system call"
162
163class PythonDialogReModuleError(PythonDialogSystemError):
164 """Exception raised when pythondialog catches a re.error exception."""
165 ExceptionShortDescription = "'re' module error"
166
167class UnexpectedDialogOutput(error):
168 """Exception raised when the dialog-like program returns something not \
169expected by pythondialog."""
170 ExceptionShortDescription = "Unexpected dialog output"
171
172class DialogTerminatedBySignal(error):
173 """Exception raised when the dialog-like program is terminated by a \
174signal."""
175 ExceptionShortDescription = "dialog-like terminated by a signal"
176
177class DialogError(error):
178 """Exception raised when the dialog-like program exits with the \
179code indicating an error."""
180 ExceptionShortDescription = "dialog-like terminated due to an error"
181
182class UnableToCreateTemporaryDirectory(error):
183 """Exception raised when we cannot create a temporary directory."""
184 ExceptionShortDescription = "unable to create a temporary directory"
185
186# Values accepted for checklists
187try:
188 _on_rec = re.compile(r"on", re.IGNORECASE)
189 _off_rec = re.compile(r"off", re.IGNORECASE)
190
191 _calendar_date_rec = re.compile(
192 r"(?P<day>\d\d)/(?P<month>\d\d)/(?P<year>\d\d\d\d)$")
193 _timebox_time_rec = re.compile(
194 r"(?P<hour>\d\d):(?P<minute>\d\d):(?P<second>\d\d)$")
195except re.error, v:
196 raise PythonDialogReModuleError(v)
197
198
199# This dictionary allows us to write the dialog common options in a Pythonic
200# way (e.g. dialog_instance.checklist(args, ..., title="Foo", no_shadow=1)).
201#
202# Options such as --separate-output should obviously not be set by the user
203# since they affect the parsing of dialog's output:
204_common_args_syntax = {
205 "aspect": lambda ratio: ("--aspect", str(ratio)),
206 "backtitle": lambda backtitle: ("--backtitle", backtitle),
207 "beep": lambda enable: _simple_option("--beep", enable),
208 "beep_after": lambda enable: _simple_option("--beep-after", enable),
209 # Warning: order = y, x!
210 "begin": lambda coords: ("--begin", str(coords[0]), str(coords[1])),
211 "cancel": lambda string: ("--cancel-label", string),
212 "clear": lambda enable: _simple_option("--clear", enable),
213 "cr_wrap": lambda enable: _simple_option("--cr-wrap", enable),
214 "create_rc": lambda file: ("--create-rc", file),
215 "defaultno": lambda enable: _simple_option("--defaultno", enable),
216 "default_item": lambda string: ("--default-item", string),
217 "help": lambda enable: _simple_option("--help", enable),
218 "help_button": lambda enable: _simple_option("--help-button", enable),
219 "help_label": lambda string: ("--help-label", string),
220 "ignore": lambda enable: _simple_option("--ignore", enable),
221 "item_help": lambda enable: _simple_option("--item-help", enable),
222 "max_input": lambda size: ("--max-input", str(size)),
223 "no_kill": lambda enable: _simple_option("--no-kill", enable),
224 "no_cancel": lambda enable: _simple_option("--no-cancel", enable),
225 "nocancel": lambda enable: _simple_option("--nocancel", enable),
226 "no_shadow": lambda enable: _simple_option("--no-shadow", enable),
227 "ok_label": lambda string: ("--ok-label", string),
228 "print_maxsize": lambda enable: _simple_option("--print-maxsize",
229 enable),
230 "print_size": lambda enable: _simple_option("--print-size", enable),
231 "print_version": lambda enable: _simple_option("--print-version",
232 enable),
233 "separate_output": lambda enable: _simple_option("--separate-output",
234 enable),
235 "separate_widget": lambda string: ("--separate-widget", string),
236 "shadow": lambda enable: _simple_option("--shadow", enable),
237 "size_err": lambda enable: _simple_option("--size-err", enable),
238 "sleep": lambda secs: ("--sleep", str(secs)),
239 "stderr": lambda enable: _simple_option("--stderr", enable),
240 "stdout": lambda enable: _simple_option("--stdout", enable),
241 "tab_correct": lambda enable: _simple_option("--tab-correct", enable),
242 "tab_len": lambda n: ("--tab-len", str(n)),
243 "timeout": lambda secs: ("--timeout", str(secs)),
244 "title": lambda title: ("--title", title),
245 "trim": lambda enable: _simple_option("--trim", enable),
246 "version": lambda enable: _simple_option("--version", enable)}
jadmanski0afbb632008-06-06 21:10:57 +0000247
mblighbe2ac852006-09-28 04:44:30 +0000248
249def _simple_option(option, enable):
250 """Turn on or off the simplest dialog Common Options."""
251 if enable:
252 return (option,)
253 else:
254 # This will not add any argument to the command line
255 return ()
256
257
258def _find_in_path(prog_name):
259 """Search an executable in the PATH.
260
261 If PATH is not defined, the default path ":/bin:/usr/bin" is
262 used.
263
264 Return a path to the file or None if no readable and executable
265 file is found.
266
267 Notable exception: PythonDialogOSError
268
269 """
270 try:
271 # Note that the leading empty component in the default value for PATH
272 # could lead to the returned path not being absolute.
273 PATH = os.getenv("PATH", ":/bin:/usr/bin") # see the execvp(3) man page
274 for dir in string.split(PATH, ":"):
275 file_path = os.path.join(dir, prog_name)
276 if os.path.isfile(file_path) \
277 and os.access(file_path, os.R_OK | os.X_OK):
278 return file_path
279 return None
280 except os.error, v:
281 raise PythonDialogOSError(v.strerror)
282
283
284def _path_to_executable(f):
285 """Find a path to an executable.
286
287 Find a path to an executable, using the same rules as the POSIX
288 exec*p functions (see execvp(3) for instance).
289
290 If `f' contains a '/', it is assumed to be a path and is simply
291 checked for read and write permissions; otherwise, it is looked
292 for according to the contents of the PATH environment variable,
293 which defaults to ":/bin:/usr/bin" if unset.
294
295 The returned path is not necessarily absolute.
296
297 Notable exceptions:
298
299 ExecutableNotFound
300 PythonDialogOSError
jadmanski0afbb632008-06-06 21:10:57 +0000301
mblighbe2ac852006-09-28 04:44:30 +0000302 """
303 try:
304 if '/' in f:
305 if os.path.isfile(f) and \
306 os.access(f, os.R_OK | os.X_OK):
307 res = f
308 else:
309 raise ExecutableNotFound("%s cannot be read and executed" % f)
310 else:
311 res = _find_in_path(f)
312 if res is None:
313 raise ExecutableNotFound(
314 "can't find the executable for the dialog-like "
315 "program")
316 except os.error, v:
317 raise PythonDialogOSError(v.strerror)
318
319 return res
320
321
322def _to_onoff(val):
323 """Convert boolean expressions to "on" or "off"
324
325 This function converts every non-zero integer as well as "on",
326 "ON", "On" and "oN" to "on" and converts 0, "off", "OFF", etc. to
327 "off".
328
329 Notable exceptions:
330
331 PythonDialogReModuleError
332 BadPythonDialogUsage
333
334 """
335 if type(val) == types.IntType:
336 if val:
337 return "on"
338 else:
339 return "off"
340 elif type(val) == types.StringType:
341 try:
342 if _on_rec.match(val):
343 return "on"
344 elif _off_rec.match(val):
345 return "off"
346 except re.error, v:
347 raise PythonDialogReModuleError(v)
348 else:
349 raise BadPythonDialogUsage("invalid boolean value: %s" % val)
350
351
352def _compute_common_args(dict):
353 """Compute the list of arguments for dialog common options.
354
355 Compute a list of the command-line arguments to pass to dialog
356 from a keyword arguments dictionary for options listed as "common
357 options" in the manual page for dialog. These are the options
358 that are not tied to a particular widget.
359
360 This allows to specify these options in a pythonic way, such as:
361
362 d.checklist(<usual arguments for a checklist>,
363 title="...",
364 backtitle="...")
365
366 instead of having to pass them with strings like "--title foo" or
367 "--backtitle bar".
368
369 Notable exceptions: None
370
371 """
372 args = []
373 for key in dict.keys():
374 args.extend(_common_args_syntax[key](dict[key]))
375 return args
376
377
378def _create_temporary_directory():
379 """Create a temporary directory (securely).
380
381 Return the directory path.
382
383 Notable exceptions:
384 - UnableToCreateTemporaryDirectory
385 - PythonDialogOSError
386 - exceptions raised by the tempfile module (which are
387 unfortunately not mentioned in its documentation, at
388 least in Python 2.3.3...)
389
390 """
391 find_temporary_nb_attempts = 5
392 for i in range(find_temporary_nb_attempts):
393 try:
394 # Using something >= 2**31 causes an error in Python 2.2...
395 tmp_dir = os.path.join(tempfile.gettempdir(),
396 "%s-%u" \
397 % ("pythondialog",
398 random.randint(0, 2**30-1)))
399 except os.error, v:
400 raise PythonDialogOSError(v.strerror)
401
402 try:
403 os.mkdir(tmp_dir, 0700)
404 except os.error:
405 continue
406 else:
407 break
408 else:
409 raise UnableToCreateTemporaryDirectory(
410 "somebody may be trying to attack us")
411
412 return tmp_dir
413
414
415# DIALOG_OK, DIALOG_CANCEL, etc. are environment variables controlling
416# dialog's exit status in the corresponding situation.
417#
418# Note:
419# - 127 must not be used for any of the DIALOG_* values. It is used
420# when a failure occurs in the child process before it exec()s
421# dialog (where "before" includes a potential exec() failure).
422# - 126 is also used (although in presumably rare situations).
423_dialog_exit_status_vars = { "OK": 0,
424 "CANCEL": 1,
425 "ESC": 2,
426 "ERROR": 3,
427 "EXTRA": 4,
428 "HELP": 5 }
429
430
431# Main class of the module
432class Dialog:
433
434 """Class providing bindings for dialog-compatible programs.
435
436 This class allows you to invoke dialog or a compatible program in
437 a pythonic way to build quicky and easily simple but nice text
438 interfaces.
439
440 An application typically creates one instance of the Dialog class
441 and uses it for all its widgets, but it is possible to use
442 concurrently several instances of this class with different
443 parameters (such as the background title) if you have the need
444 for this.
445
446 The exit code (exit status) returned by dialog is to be
447 compared with the DIALOG_OK, DIALOG_CANCEL, DIALOG_ESC,
448 DIALOG_ERROR, DIALOG_EXTRA and DIALOG_HELP attributes of the
449 Dialog instance (they are integers).
450
451 Note: although this class does all it can to allow the caller to
452 differentiate between the various reasons that caused a
453 dialog box to be closed, its backend, dialog 0.9a-20020309a
454 for my tests, doesn't always return DIALOG_ESC when the
455 user presses the ESC key, but often returns DIALOG_ERROR
456 instead. The exit codes returned by the corresponding
457 Dialog methods are of course just as wrong in these cases.
458 You've been warned.
459
460
461 Public methods of the Dialog class (mainly widgets)
462 ---------------------------------------------------
463
464 The Dialog class has the following methods:
465
466 add_persistent_args
467 calendar
468 checklist
469 fselect
470
471 gauge_start
472 gauge_update
473 gauge_stop
474
475 infobox
476 inputbox
477 menu
478 msgbox
479 passwordbox
480 radiolist
481 scrollbox
482 tailbox
483 textbox
484 timebox
485 yesno
486
487 clear (obsolete)
488 setBackgroundTitle (obsolete)
489
490
491 Passing dialog "Common Options"
492 -------------------------------
493
494 Every widget method has a **kwargs argument allowing you to pass
495 dialog so-called Common Options (see the dialog(1) manual page)
496 to dialog for this widget call. For instance, if `d' is a Dialog
497 instance, you can write:
498
499 d.checklist(args, ..., title="A Great Title", no_shadow=1)
500
501 The no_shadow option is worth looking at:
502
503 1. It is an option that takes no argument as far as dialog is
504 concerned (unlike the "--title" option, for instance). When
505 you list it as a keyword argument, the option is really
506 passed to dialog only if the value you gave it evaluates to
507 true, e.g. "no_shadow=1" will cause "--no-shadow" to be
508 passed to dialog whereas "no_shadow=0" will cause this
509 option not to be passed to dialog at all.
510
511 2. It is an option that has a hyphen (-) in its name, which you
512 must change into an underscore (_) to pass it as a Python
513 keyword argument. Therefore, "--no-shadow" is passed by
514 giving a "no_shadow=1" keyword argument to a Dialog method
515 (the leading two dashes are also consistently removed).
516
517
518 Exceptions
519 ----------
520
521 Please refer to the specific methods' docstrings or simply to the
522 module's docstring for a list of all exceptions that might be
523 raised by this class' methods.
524
525 """
526
527 def __init__(self, dialog="dialog", DIALOGRC=None, compat="dialog",
528 use_stdout=None):
529 """Constructor for Dialog instances.
530
531 dialog -- name of (or path to) the dialog-like program to
532 use; if it contains a '/', it is assumed to be a
533 path and is used as is; otherwise, it is looked
534 for according to the contents of the PATH
535 environment variable, which defaults to
536 ":/bin:/usr/bin" if unset.
537 DIALOGRC -- string to pass to the dialog-like program as the
538 DIALOGRC environment variable, or None if no
539 modification to the environment regarding this
540 variable should be done in the call to the
541 dialog-like program
542 compat -- compatibility mode (see below)
543
544 The officially supported dialog-like program in pythondialog
545 is the well-known dialog program written in C, based on the
546 ncurses library. It is also known as cdialog and its home
547 page is currently (2004-03-15) located at:
548
549 http://dickey.his.com/dialog/dialog.html
550
551 If you want to use a different program such as Xdialog, you
552 should indicate the executable file name with the `dialog'
553 argument *and* the compatibility type that you think it
554 conforms to with the `compat' argument. Currently, `compat'
555 can be either "dialog" (for dialog; this is the default) or
556 "Xdialog" (for, well, Xdialog).
557
558 The `compat' argument allows me to cope with minor
559 differences in behaviour between the various programs
560 implementing the dialog interface (not the text or graphical
561 interface, I mean the "API"). However, having to support
562 various APIs simultaneously is a bit ugly and I would really
563 prefer you to report bugs to the relevant maintainers when
564 you find incompatibilities with dialog. This is for the
565 benefit of pretty much everyone that relies on the dialog
566 interface.
567
568 Notable exceptions:
569
570 ExecutableNotFound
571 PythonDialogOSError
572
jadmanski0afbb632008-06-06 21:10:57 +0000573 """
mblighbe2ac852006-09-28 04:44:30 +0000574 # DIALOGRC differs from the other DIALOG* variables in that:
575 # 1. It should be a string if not None
576 # 2. We may very well want it to be unset
577 if DIALOGRC is not None:
578 self.DIALOGRC = DIALOGRC
579
580 # After reflexion, I think DIALOG_OK, DIALOG_CANCEL, etc.
581 # should never have been instance attributes (I cannot see a
582 # reason why the user would want to change their values or
583 # even read them), but it is a bit late, now. So, we set them
584 # based on the (global) _dialog_exit_status_vars.keys.
585 for var in _dialog_exit_status_vars.keys():
586 varname = "DIALOG_" + var
587 setattr(self, varname, _dialog_exit_status_vars[var])
588
589 self._dialog_prg = _path_to_executable(dialog)
590 self.compat = compat
591 self.dialog_persistent_arglist = []
592
593 # Use stderr or stdout?
594 if self.compat == "Xdialog":
595 # Default to stdout if Xdialog
596 self.use_stdout = True
597 else:
598 self.use_stdout = False
599 if use_stdout != None:
600 # Allow explicit setting
601 self.use_stdout = use_stdout
602 if self.use_stdout:
603 self.add_persistent_args(["--stdout"])
604
605 def add_persistent_args(self, arglist):
606 self.dialog_persistent_arglist.extend(arglist)
607
608 # For compatibility with the old dialog...
609 def setBackgroundTitle(self, text):
610 """Set the background title for dialog.
611
612 This method is obsolete. Please remove calls to it from your
613 programs.
614
jadmanski0afbb632008-06-06 21:10:57 +0000615 """
616 self.add_persistent_args(("--backtitle", text))
mblighbe2ac852006-09-28 04:44:30 +0000617
618 def _call_program(self, redirect_child_stdin, cmdargs, **kwargs):
jadmanski0afbb632008-06-06 21:10:57 +0000619 """Do the actual work of invoking the dialog-like program.
mblighbe2ac852006-09-28 04:44:30 +0000620
621 Communication with the dialog-like program is performed
622 through one or two pipes, depending on
623 `redirect_child_stdin'. There is always one pipe that is
624 created to allow the parent process to read what dialog
625 writes on its standard error stream.
jadmanski0afbb632008-06-06 21:10:57 +0000626
mblighbe2ac852006-09-28 04:44:30 +0000627 If `redirect_child_stdin' is True, an additional pipe is
628 created whose reading end is connected to dialog's standard
629 input. This is used by the gauge widget to feed data to
630 dialog.
631
632 Beware when interpreting the return value: the length of the
633 returned tuple depends on `redirect_child_stdin'.
634
635 Notable exception: PythonDialogOSError (if pipe() or close()
636 system calls fail...)
637
638 """
639 # We want to define DIALOG_OK, DIALOG_CANCEL, etc. in the
640 # environment of the child process so that we know (and
641 # even control) the possible dialog exit statuses.
642 new_environ = {}
643 new_environ.update(os.environ)
644 for var in _dialog_exit_status_vars:
645 varname = "DIALOG_" + var
646 new_environ[varname] = str(getattr(self, varname))
647 if hasattr(self, "DIALOGRC"):
648 new_environ["DIALOGRC"] = self.DIALOGRC
649
650 # Create:
651 # - a pipe so that the parent process can read dialog's output on
652 # stdout/stderr
653 # - a pipe so that the parent process can feed data to dialog's
654 # stdin (this is needed for the gauge widget) if
655 # redirect_child_stdin is True
656 try:
657 # rfd = File Descriptor for Reading
658 # wfd = File Descriptor for Writing
659 (child_rfd, child_wfd) = os.pipe()
660 if redirect_child_stdin:
661 (child_stdin_rfd, child_stdin_wfd) = os.pipe()
662 except os.error, v:
663 raise PythonDialogOSError(v.strerror)
664
665 child_pid = os.fork()
666 if child_pid == 0:
667 # We are in the child process. We MUST NOT raise any exception.
668 try:
669 # The child process doesn't need these file descriptors
670 os.close(child_rfd)
671 if redirect_child_stdin:
672 os.close(child_stdin_wfd)
673 # We want:
674 # - dialog's output on stderr/stdout to go to child_wfd
675 # - data written to child_stdin_wfd to go to dialog's stdin
676 # if redirect_child_stdin is True
677 if self.use_stdout:
678 os.dup2(child_wfd, 1)
679 else:
680 os.dup2(child_wfd, 2)
681 if redirect_child_stdin:
682 os.dup2(child_stdin_rfd, 0)
683
684 arglist = [self._dialog_prg] + \
685 self.dialog_persistent_arglist + \
686 _compute_common_args(kwargs) + \
687 cmdargs
688 # Insert here the contents of the DEBUGGING file if you want
689 # to obtain a handy string of the complete command line with
690 # arguments quoted for the shell and environment variables
691 # set.
692 os.execve(self._dialog_prg, arglist, new_environ)
693 except:
694 os._exit(127)
695
696 # Should not happen unless there is a bug in Python
697 os._exit(126)
698
699 # We are in the father process.
700 #
701 # It is essential to close child_wfd, otherwise we will never
702 # see EOF while reading on child_rfd and the parent process
703 # will block forever on the read() call.
704 # [ after the fork(), the "reference count" of child_wfd from
705 # the operating system's point of view is 2; after the child exits,
706 # it is 1 until the father closes it itself; then it is 0 and a read
707 # on child_rfd encounters EOF once all the remaining data in
708 # the pipe has been read. ]
709 try:
710 os.close(child_wfd)
711 if redirect_child_stdin:
712 os.close(child_stdin_rfd)
713 return (child_pid, child_rfd, child_stdin_wfd)
714 else:
715 return (child_pid, child_rfd)
716 except os.error, v:
717 raise PythonDialogOSError(v.strerror)
718
719 def _wait_for_program_termination(self, child_pid, child_rfd):
720 """Wait for a dialog-like process to terminate.
721
722 This function waits for the specified process to terminate,
723 raises the appropriate exceptions in case of abnormal
724 termination and returns the exit status and standard error
725 output of the process as a tuple: (exit_code, stderr_string).
726
727 `child_rfd' must be the file descriptor for the
728 reading end of the pipe created by self._call_program()
729 whose writing end was connected by self._call_program() to
730 the child process's standard error.
731
732 This function reads the process's output on standard error
733 from `child_rfd' and closes this file descriptor once
734 this is done.
735
736 Notable exceptions:
737
738 DialogTerminatedBySignal
739 DialogError
740 PythonDialogErrorBeforeExecInChildProcess
741 PythonDialogIOError
742 PythonDialogBug
743 ProbablyPythonBug
744
745 """
746 exit_info = os.waitpid(child_pid, 0)[1]
747 if os.WIFEXITED(exit_info):
748 exit_code = os.WEXITSTATUS(exit_info)
749 # As we wait()ed for the child process to terminate, there is no
750 # need to call os.WIFSTOPPED()
751 elif os.WIFSIGNALED(exit_info):
752 raise DialogTerminatedBySignal("the dialog-like program was "
753 "terminated by signal %u" %
754 os.WTERMSIG(exit_info))
755 else:
756 raise PythonDialogBug("please report this bug to the "
757 "pythondialog maintainers")
758
759 if exit_code == self.DIALOG_ERROR:
760 raise DialogError("the dialog-like program exited with "
761 "code %d (was passed to it as the DIALOG_ERROR "
762 "environment variable)" % exit_code)
763 elif exit_code == 127:
764 raise PythonDialogErrorBeforeExecInChildProcess(
765 "perhaps the dialog-like program could not be executed; "
766 "perhaps the system is out of memory; perhaps the maximum "
767 "number of open file descriptors has been reached")
768 elif exit_code == 126:
769 raise ProbablyPythonBug(
770 "a child process returned with exit status 126; this might "
771 "be the exit status of the dialog-like program, for some "
772 "unknown reason (-> probably a bug in the dialog-like "
773 "program); otherwise, we have probably found a python bug")
jadmanski0afbb632008-06-06 21:10:57 +0000774
mblighbe2ac852006-09-28 04:44:30 +0000775 # We might want to check here whether exit_code is really one of
776 # DIALOG_OK, DIALOG_CANCEL, etc. However, I prefer not doing it
777 # because it would break pythondialog for no strong reason when new
778 # exit codes are added to the dialog-like program.
779 #
780 # As it is now, if such a thing happens, the program using
781 # pythondialog may receive an exit_code it doesn't know about. OK, the
782 # programmer just has to tell the pythondialog maintainer about it and
783 # can temporarily set the appropriate DIALOG_* environment variable if
784 # he wants and assign the corresponding value to the Dialog instance's
785 # DIALOG_FOO attribute from his program. He doesn't even need to use a
786 # patched pythondialog before he upgrades to a version that knows
787 # about the new exit codes.
788 #
789 # The bad thing that might happen is a new DIALOG_FOO exit code being
790 # the same by default as one of those we chose for the other exit
791 # codes already known by pythondialog. But in this situation, the
792 # check that is being discussed wouldn't help at all.
793
794 # Read dialog's output on its stderr
795 try:
796 child_output = os.fdopen(child_rfd, "rb").read()
797 # Now, since the file object has no reference anymore, the
798 # standard IO stream behind it will be closed, causing the
799 # end of the the pipe we used to read dialog's output on its
800 # stderr to be closed (this is important, otherwise invoking
801 # dialog enough times will eventually exhaust the maximum number
802 # of open file descriptors).
803 except IOError, v:
804 raise PythonDialogIOError(v)
805
806 return (exit_code, child_output)
807
808 def _perform(self, cmdargs, **kwargs):
jadmanski0afbb632008-06-06 21:10:57 +0000809 """Perform a complete dialog-like program invocation.
mblighbe2ac852006-09-28 04:44:30 +0000810
811 This function invokes the dialog-like program, waits for its
812 termination and returns its exit status and whatever it wrote
813 on its standard error stream.
814
815 Notable exceptions:
816
817 any exception raised by self._call_program() or
818 self._wait_for_program_termination()
819
820 """
821 (child_pid, child_rfd) = \
822 self._call_program(False, *(cmdargs,), **kwargs)
823 (exit_code, output) = \
824 self._wait_for_program_termination(child_pid,
825 child_rfd)
jadmanski0afbb632008-06-06 21:10:57 +0000826 return (exit_code, output)
mblighbe2ac852006-09-28 04:44:30 +0000827
828 def _strip_xdialog_newline(self, output):
829 """Remove trailing newline (if any), if using Xdialog"""
830 if self.compat == "Xdialog" and output.endswith("\n"):
831 output = output[:-1]
832 return output
833
834 # This is for compatibility with the old dialog.py
835 def _perform_no_options(self, cmd):
jadmanski0afbb632008-06-06 21:10:57 +0000836 """Call dialog without passing any more options."""
837 return os.system(self._dialog_prg + ' ' + cmd)
mblighbe2ac852006-09-28 04:44:30 +0000838
839 # For compatibility with the old dialog.py
840 def clear(self):
jadmanski0afbb632008-06-06 21:10:57 +0000841 """Clear the screen. Equivalent to the dialog --clear option.
mblighbe2ac852006-09-28 04:44:30 +0000842
843 This method is obsolete. Please remove calls to it from your
844 programs.
845
jadmanski0afbb632008-06-06 21:10:57 +0000846 """
847 self._perform_no_options('--clear')
mblighbe2ac852006-09-28 04:44:30 +0000848
849 def calendar(self, text, height=6, width=0, day=0, month=0, year=0,
850 **kwargs):
851 """Display a calendar dialog box.
852
853 text -- text to display in the box
854 height -- height of the box (minus the calendar height)
855 width -- width of the box
856 day -- inititial day highlighted
857 month -- inititial month displayed
858 year -- inititial year selected (0 causes the current date
859 to be used as the initial date)
jadmanski0afbb632008-06-06 21:10:57 +0000860
mblighbe2ac852006-09-28 04:44:30 +0000861 A calendar box displays month, day and year in separately
862 adjustable windows. If the values for day, month or year are
863 missing or negative, the current date's corresponding values
864 are used. You can increment or decrement any of those using
865 the left, up, right and down arrows. Use tab or backtab to
866 move between windows. If the year is given as zero, the
867 current date is used as an initial value.
868
869 Return a tuple of the form (code, date) where `code' is the
870 exit status (an integer) of the dialog-like program and
871 `date' is a list of the form [day, month, year] (where `day',
872 `month' and `year' are integers corresponding to the date
873 chosen by the user) if the box was closed with OK, or None if
874 it was closed with the Cancel button.
875
876 Notable exceptions:
877 - any exception raised by self._perform()
878 - UnexpectedDialogOutput
879 - PythonDialogReModuleError
880
jadmanski0afbb632008-06-06 21:10:57 +0000881 """
882 (code, output) = self._perform(
mblighbe2ac852006-09-28 04:44:30 +0000883 *(["--calendar", text, str(height), str(width), str(day),
884 str(month), str(year)],),
885 **kwargs)
886 if code == self.DIALOG_OK:
887 try:
888 mo = _calendar_date_rec.match(output)
889 except re.error, v:
890 raise PythonDialogReModuleError(v)
jadmanski0afbb632008-06-06 21:10:57 +0000891
mblighbe2ac852006-09-28 04:44:30 +0000892 if mo is None:
893 raise UnexpectedDialogOutput(
894 "the dialog-like program returned the following "
895 "unexpected date with the calendar box: %s" % output)
896 date = map(int, mo.group("day", "month", "year"))
897 else:
898 date = None
899 return (code, date)
900
901 def checklist(self, text, height=15, width=54, list_height=7,
902 choices=[], **kwargs):
jadmanski0afbb632008-06-06 21:10:57 +0000903 """Display a checklist box.
mblighbe2ac852006-09-28 04:44:30 +0000904
905 text -- text to display in the box
906 height -- height of the box
907 width -- width of the box
908 list_height -- number of entries displayed in the box (which
909 can be scrolled) at a given time
910 choices -- a list of tuples (tag, item, status) where
911 `status' specifies the initial on/off state of
912 each entry; can be 0 or 1 (integers, 1 meaning
913 checked, i.e. "on"), or "on", "off" or any
914 uppercase variant of these two strings.
915
916 Return a tuple of the form (code, [tag, ...]) with the tags
917 for the entries that were selected by the user. `code' is the
918 exit status of the dialog-like program.
919
920 If the user exits with ESC or CANCEL, the returned tag list
921 is empty.
922
923 Notable exceptions:
924
925 any exception raised by self._perform() or _to_onoff()
926
927 """
928 cmd = ["--checklist", text, str(height), str(width), str(list_height)]
929 for t in choices:
930 cmd.extend(((t[0], t[1], _to_onoff(t[2]))))
931
932 # The dialog output cannot be parsed reliably (at least in dialog
933 # 0.9b-20040301) without --separate-output (because double quotes in
934 # tags are escaped with backslashes, but backslashes are not
935 # themselves escaped and you have a problem when a tag ends with a
936 # backslash--the output makes you think you've encountered an embedded
937 # double-quote).
938 kwargs["separate_output"] = True
939
jadmanski0afbb632008-06-06 21:10:57 +0000940 (code, output) = self._perform(*(cmd,), **kwargs)
mblighbe2ac852006-09-28 04:44:30 +0000941
942 # Since we used --separate-output, the tags are separated by a newline
943 # in the output. There is also a final newline after the last tag.
944 if output:
945 return (code, string.split(output, '\n')[:-1])
946 else: # empty selection
947 return (code, [])
948
949 def fselect(self, filepath, height, width, **kwargs):
950 """Display a file selection dialog box.
951
952 filepath -- initial file path
953 height -- height of the box
954 width -- width of the box
jadmanski0afbb632008-06-06 21:10:57 +0000955
mblighbe2ac852006-09-28 04:44:30 +0000956 The file-selection dialog displays a text-entry window in
957 which you can type a filename (or directory), and above that
958 two windows with directory names and filenames.
959
960 Here, filepath can be a file path in which case the file and
961 directory windows will display the contents of the path and
962 the text-entry window will contain the preselected filename.
963
964 Use tab or arrow keys to move between the windows. Within the
965 directory or filename windows, use the up/down arrow keys to
966 scroll the current selection. Use the space-bar to copy the
967 current selection into the text-entry window.
968
969 Typing any printable character switches focus to the
970 text-entry window, entering that character as well as
971 scrolling the directory and filename windows to the closest
972 match.
973
974 Use a carriage return or the "OK" button to accept the
975 current value in the text-entry window, or the "Cancel"
976 button to cancel.
977
978 Return a tuple of the form (code, path) where `code' is the
979 exit status (an integer) of the dialog-like program and
980 `path' is the path chosen by the user (whose last element may
981 be a directory or a file).
jadmanski0afbb632008-06-06 21:10:57 +0000982
mblighbe2ac852006-09-28 04:44:30 +0000983 Notable exceptions:
984
985 any exception raised by self._perform()
986
jadmanski0afbb632008-06-06 21:10:57 +0000987 """
mblighbe2ac852006-09-28 04:44:30 +0000988 (code, output) = self._perform(
989 *(["--fselect", filepath, str(height), str(width)],),
990 **kwargs)
991
992 output = self._strip_xdialog_newline(output)
jadmanski0afbb632008-06-06 21:10:57 +0000993
994 return (code, output)
995
mblighbe2ac852006-09-28 04:44:30 +0000996 def gauge_start(self, text="", height=8, width=54, percent=0, **kwargs):
jadmanski0afbb632008-06-06 21:10:57 +0000997 """Display gauge box.
mblighbe2ac852006-09-28 04:44:30 +0000998
999 text -- text to display in the box
1000 height -- height of the box
1001 width -- width of the box
1002 percent -- initial percentage shown in the meter
1003
1004 A gauge box displays a meter along the bottom of the box. The
1005 meter indicates a percentage.
1006
1007 This function starts the dialog-like program telling it to
1008 display a gauge box with a text in it and an initial
1009 percentage in the meter.
1010
1011 Return value: undefined.
1012
1013
1014 Gauge typical usage
1015 -------------------
1016
1017 Gauge typical usage (assuming that `d' is an instance of the
jadmanski0afbb632008-06-06 21:10:57 +00001018 Dialog class) looks like this:
1019 d.gauge_start()
1020 # do something
1021 d.gauge_update(10) # 10% of the whole task is done
1022 # ...
1023 d.gauge_update(100, "any text here") # work is done
1024 exit_code = d.gauge_stop() # cleanup actions
mblighbe2ac852006-09-28 04:44:30 +00001025
1026
1027 Notable exceptions:
1028 - any exception raised by self._call_program()
1029 - PythonDialogOSError
1030
jadmanski0afbb632008-06-06 21:10:57 +00001031 """
mblighbe2ac852006-09-28 04:44:30 +00001032 (child_pid, child_rfd, child_stdin_wfd) = self._call_program(
1033 True,
1034 *(["--gauge", text, str(height), str(width), str(percent)],),
1035 **kwargs)
1036 try:
1037 self._gauge_process = {
1038 "pid": child_pid,
1039 "stdin": os.fdopen(child_stdin_wfd, "wb"),
1040 "child_rfd": child_rfd
1041 }
1042 except os.error, v:
1043 raise PythonDialogOSError(v.strerror)
jadmanski0afbb632008-06-06 21:10:57 +00001044
mblighbe2ac852006-09-28 04:44:30 +00001045 def gauge_update(self, percent, text="", update_text=0):
jadmanski0afbb632008-06-06 21:10:57 +00001046 """Update a running gauge box.
1047
mblighbe2ac852006-09-28 04:44:30 +00001048 percent -- new percentage to show in the gauge meter
1049 text -- new text to optionally display in the box
1050 update-text -- boolean indicating whether to update the
1051 text in the box
1052
1053 This function updates the percentage shown by the meter of a
1054 running gauge box (meaning `gauge_start' must have been
1055 called previously). If update_text is true (for instance, 1),
1056 the text displayed in the box is also updated.
1057
jadmanski0afbb632008-06-06 21:10:57 +00001058 See the `gauge_start' function's documentation for
1059 information about how to use a gauge.
mblighbe2ac852006-09-28 04:44:30 +00001060
1061 Return value: undefined.
1062
1063 Notable exception: PythonDialogIOError can be raised if there
1064 is an I/O error while writing to the pipe
1065 used to talk to the dialog-like program.
1066
jadmanski0afbb632008-06-06 21:10:57 +00001067 """
1068 if update_text:
1069 gauge_data = "%d\nXXX\n%s\nXXX\n" % (percent, text)
1070 else:
1071 gauge_data = "%d\n" % percent
1072 try:
mblighbe2ac852006-09-28 04:44:30 +00001073 self._gauge_process["stdin"].write(gauge_data)
1074 self._gauge_process["stdin"].flush()
1075 except IOError, v:
1076 raise PythonDialogIOError(v)
jadmanski0afbb632008-06-06 21:10:57 +00001077
mblighbe2ac852006-09-28 04:44:30 +00001078 # For "compatibility" with the old dialog.py...
1079 gauge_iterate = gauge_update
1080
1081 def gauge_stop(self):
jadmanski0afbb632008-06-06 21:10:57 +00001082 """Terminate a running gauge.
mblighbe2ac852006-09-28 04:44:30 +00001083
1084 This function performs the appropriate cleanup actions to
1085 terminate a running gauge (started with `gauge_start').
jadmanski0afbb632008-06-06 21:10:57 +00001086
1087 See the `gauge_start' function's documentation for
1088 information about how to use a gauge.
mblighbe2ac852006-09-28 04:44:30 +00001089
1090 Return value: undefined.
1091
1092 Notable exceptions:
1093 - any exception raised by
1094 self._wait_for_program_termination()
1095 - PythonDialogIOError can be raised if closing the pipe
1096 used to talk to the dialog-like program fails.
1097
jadmanski0afbb632008-06-06 21:10:57 +00001098 """
mblighbe2ac852006-09-28 04:44:30 +00001099 p = self._gauge_process
1100 # Close the pipe that we are using to feed dialog's stdin
1101 try:
1102 p["stdin"].close()
1103 except IOError, v:
1104 raise PythonDialogIOError(v)
1105 exit_code = \
1106 self._wait_for_program_termination(p["pid"],
1107 p["child_rfd"])[0]
1108 return exit_code
1109
1110 def infobox(self, text, height=10, width=30, **kwargs):
1111 """Display an information dialog box.
1112
1113 text -- text to display in the box
1114 height -- height of the box
1115 width -- width of the box
1116
1117 An info box is basically a message box. However, in this
1118 case, dialog will exit immediately after displaying the
1119 message to the user. The screen is not cleared when dialog
1120 exits, so that the message will remain on the screen until
1121 the calling shell script clears it later. This is useful
1122 when you want to inform the user that some operations are
1123 carrying on that may require some time to finish.
1124
1125 Return the exit status (an integer) of the dialog-like
1126 program.
1127
1128 Notable exceptions:
1129
1130 any exception raised by self._perform()
1131
jadmanski0afbb632008-06-06 21:10:57 +00001132 """
1133 return self._perform(
mblighbe2ac852006-09-28 04:44:30 +00001134 *(["--infobox", text, str(height), str(width)],),
1135 **kwargs)[0]
1136
1137 def inputbox(self, text, height=10, width=30, init='', **kwargs):
1138 """Display an input dialog box.
1139
1140 text -- text to display in the box
1141 height -- height of the box
1142 width -- width of the box
1143 init -- default input string
1144
1145 An input box is useful when you want to ask questions that
1146 require the user to input a string as the answer. If init is
1147 supplied it is used to initialize the input string. When
1148 entering the string, the BACKSPACE key can be used to
1149 correct typing errors. If the input string is longer than
1150 can fit in the dialog box, the input field will be scrolled.
1151
1152 Return a tuple of the form (code, string) where `code' is the
1153 exit status of the dialog-like program and `string' is the
1154 string entered by the user.
1155
1156 Notable exceptions:
1157
1158 any exception raised by self._perform()
1159
jadmanski0afbb632008-06-06 21:10:57 +00001160 """
mblighbe2ac852006-09-28 04:44:30 +00001161 (code, tag) = self._perform(
1162 *(["--inputbox", text, str(height), str(width), init],),
1163 **kwargs)
1164
1165 tag = self._strip_xdialog_newline(tag)
jadmanski0afbb632008-06-06 21:10:57 +00001166
1167 return (code, tag)
mblighbe2ac852006-09-28 04:44:30 +00001168
1169 def menu(self, text, height=15, width=54, menu_height=7, choices=[],
1170 **kwargs):
1171 """Display a menu dialog box.
1172
1173 text -- text to display in the box
1174 height -- height of the box
1175 width -- width of the box
1176 menu_height -- number of entries displayed in the box (which
1177 can be scrolled) at a given time
1178 choices -- a sequence of (tag, item) or (tag, item, help)
1179 tuples (the meaning of each `tag', `item' and
1180 `help' is explained below)
1181
1182
1183 Overview
1184 --------
1185
1186 As its name suggests, a menu box is a dialog box that can be
1187 used to present a list of choices in the form of a menu for
1188 the user to choose. Choices are displayed in the order given.
1189
1190 Each menu entry consists of a `tag' string and an `item'
1191 string. The tag gives the entry a name to distinguish it from
1192 the other entries in the menu. The item is a short
1193 description of the option that the entry represents.
1194
1195 The user can move between the menu entries by pressing the
1196 UP/DOWN keys, the first letter of the tag as a hot-key, or
1197 the number keys 1-9. There are menu-height entries displayed
1198 in the menu at one time, but the menu will be scrolled if
1199 there are more entries than that.
1200
1201
1202 Providing on-line help facilities
1203 ---------------------------------
1204
1205 If this function is called with item_help=1 (keyword
1206 argument), the option --item-help is passed to dialog and the
1207 tuples contained in `choices' must contain 3 elements each :
1208 (tag, item, help). The help string for the highlighted item
1209 is displayed in the bottom line of the screen and updated as
1210 the user highlights other items.
1211
1212 If item_help=0 or if this keyword argument is not passed to
1213 this function, the tuples contained in `choices' must contain
1214 2 elements each : (tag, item).
1215
1216 If this function is called with help_button=1, it must also
1217 be called with item_help=1 (this is a limitation of dialog),
1218 therefore the tuples contained in `choices' must contain 3
1219 elements each as explained in the previous paragraphs. This
1220 will cause a Help button to be added to the right of the
1221 Cancel button (by passing --help-button to dialog).
1222
1223
1224 Return value
1225 ------------
1226
1227 Return a tuple of the form (exit_info, string).
1228
1229 `exit_info' is either:
1230 - an integer, being the the exit status of the dialog-like
1231 program
1232 - or the string "help", meaning that help_button=1 was
1233 passed and that the user chose the Help button instead of
1234 OK or Cancel.
1235
1236 The meaning of `string' depends on the value of exit_info:
1237 - if `exit_info' is 0, `string' is the tag chosen by the
1238 user
1239 - if `exit_info' is "help", `string' is the `help' string
1240 from the `choices' argument corresponding to the item
1241 that was highlighted when the user chose the Help button
1242 - otherwise (the user chose Cancel or pressed Esc, or there
1243 was a dialog error), the value of `string' is undefined.
1244
1245 Notable exceptions:
1246
1247 any exception raised by self._perform()
1248
jadmanski0afbb632008-06-06 21:10:57 +00001249 """
mblighbe2ac852006-09-28 04:44:30 +00001250 cmd = ["--menu", text, str(height), str(width), str(menu_height)]
1251 for t in choices:
1252 cmd.extend(t)
jadmanski0afbb632008-06-06 21:10:57 +00001253 (code, output) = self._perform(*(cmd,), **kwargs)
mblighbe2ac852006-09-28 04:44:30 +00001254
1255 output = self._strip_xdialog_newline(output)
jadmanski0afbb632008-06-06 21:10:57 +00001256
mblighbe2ac852006-09-28 04:44:30 +00001257 if "help_button" in kwargs.keys() and output.startswith("HELP "):
1258 return ("help", output[5:])
1259 else:
1260 return (code, output)
1261
1262 def msgbox(self, text, height=10, width=30, **kwargs):
1263 """Display a message dialog box.
1264
1265 text -- text to display in the box
1266 height -- height of the box
1267 width -- width of the box
1268
1269 A message box is very similar to a yes/no box. The only
1270 difference between a message box and a yes/no box is that a
1271 message box has only a single OK button. You can use this
1272 dialog box to display any message you like. After reading
1273 the message, the user can press the ENTER key so that dialog
1274 will exit and the calling program can continue its
1275 operation.
1276
1277 Return the exit status (an integer) of the dialog-like
1278 program.
1279
1280 Notable exceptions:
1281
1282 any exception raised by self._perform()
1283
jadmanski0afbb632008-06-06 21:10:57 +00001284 """
1285 return self._perform(
mblighbe2ac852006-09-28 04:44:30 +00001286 *(["--msgbox", text, str(height), str(width)],),
1287 **kwargs)[0]
1288
1289 def passwordbox(self, text, height=10, width=60, init='', **kwargs):
1290 """Display an password input dialog box.
1291
1292 text -- text to display in the box
1293 height -- height of the box
1294 width -- width of the box
1295 init -- default input password
1296
1297 A password box is similar to an input box, except that the
1298 text the user enters is not displayed. This is useful when
1299 prompting for passwords or other sensitive information. Be
1300 aware that if anything is passed in "init", it will be
1301 visible in the system's process table to casual snoopers.
1302 Also, it is very confusing to the user to provide them with a
1303 default password they cannot see. For these reasons, using
1304 "init" is highly discouraged.
1305
1306 Return a tuple of the form (code, password) where `code' is
1307 the exit status of the dialog-like program and `password' is
1308 the password entered by the user.
1309
1310 Notable exceptions:
1311
1312 any exception raised by self._perform()
1313
jadmanski0afbb632008-06-06 21:10:57 +00001314 """
1315 (code, password) = self._perform(
mblighbe2ac852006-09-28 04:44:30 +00001316 *(["--passwordbox", text, str(height), str(width), init],),
1317 **kwargs)
1318
1319 password = self._strip_xdialog_newline(password)
1320
1321 return (code, password)
1322
1323 def radiolist(self, text, height=15, width=54, list_height=7,
1324 choices=[], **kwargs):
jadmanski0afbb632008-06-06 21:10:57 +00001325 """Display a radiolist box.
mblighbe2ac852006-09-28 04:44:30 +00001326
1327 text -- text to display in the box
1328 height -- height of the box
1329 width -- width of the box
1330 list_height -- number of entries displayed in the box (which
1331 can be scrolled) at a given time
1332 choices -- a list of tuples (tag, item, status) where
1333 `status' specifies the initial on/off state
1334 each entry; can be 0 or 1 (integers, 1 meaning
1335 checked, i.e. "on"), or "on", "off" or any
1336 uppercase variant of these two strings.
1337 No more than one entry should be set to on.
1338
1339 A radiolist box is similar to a menu box. The main difference
1340 is that you can indicate which entry is initially selected,
1341 by setting its status to on.
1342
1343 Return a tuple of the form (code, tag) with the tag for the
1344 entry that was chosen by the user. `code' is the exit status
1345 of the dialog-like program.
1346
1347 If the user exits with ESC or CANCEL, or if all entries were
1348 initially set to off and not altered before the user chose
1349 OK, the returned tag is the empty string.
1350
1351 Notable exceptions:
1352
1353 any exception raised by self._perform() or _to_onoff()
1354
jadmanski0afbb632008-06-06 21:10:57 +00001355 """
mblighbe2ac852006-09-28 04:44:30 +00001356 cmd = ["--radiolist", text, str(height), str(width), str(list_height)]
1357 for t in choices:
1358 cmd.extend(((t[0], t[1], _to_onoff(t[2]))))
1359
1360 (code, tag) = self._perform(*(cmd,), **kwargs)
1361
1362 tag = self._strip_xdialog_newline(tag)
jadmanski0afbb632008-06-06 21:10:57 +00001363
1364 return (code, tag)
mblighbe2ac852006-09-28 04:44:30 +00001365
1366 def scrollbox(self, text, height=20, width=78, **kwargs):
jadmanski0afbb632008-06-06 21:10:57 +00001367 """Display a string in a scrollable box.
mblighbe2ac852006-09-28 04:44:30 +00001368
1369 text -- text to display in the box
1370 height -- height of the box
1371 width -- width of the box
1372
1373 This method is a layer on top of textbox. The textbox option
1374 in dialog allows to display file contents only. This method
1375 allows you to display any text in a scrollable box. This is
1376 simply done by creating a temporary file, calling textbox and
1377 deleting the temporary file afterwards.
1378
1379 Return the dialog-like program's exit status.
1380
1381 Notable exceptions:
1382 - UnableToCreateTemporaryDirectory
1383 - PythonDialogIOError
1384 - PythonDialogOSError
1385 - exceptions raised by the tempfile module (which are
1386 unfortunately not mentioned in its documentation, at
1387 least in Python 2.3.3...)
1388
jadmanski0afbb632008-06-06 21:10:57 +00001389 """
mblighbe2ac852006-09-28 04:44:30 +00001390 # In Python < 2.3, the standard library does not have
1391 # tempfile.mkstemp(), and unfortunately, tempfile.mktemp() is
1392 # insecure. So, I create a non-world-writable temporary directory and
1393 # store the temporary file in this directory.
1394 try:
1395 # We want to ensure that f is already bound in the local
1396 # scope when the finally clause (see below) is executed
1397 f = 0
1398 tmp_dir = _create_temporary_directory()
1399 # If we are here, tmp_dir *is* created (no exception was raised),
1400 # so chances are great that os.rmdir(tmp_dir) will succeed (as
1401 # long as tmp_dir is empty).
1402 #
1403 # Don't move the _create_temporary_directory() call inside the
1404 # following try statement, otherwise the user will always see a
1405 # PythonDialogOSError instead of an
1406 # UnableToCreateTemporaryDirectory because whenever
1407 # UnableToCreateTemporaryDirectory is raised, the subsequent
1408 # os.rmdir(tmp_dir) is bound to fail.
1409 try:
1410 fName = os.path.join(tmp_dir, "text")
1411 # No race condition as with the deprecated tempfile.mktemp()
1412 # since tmp_dir is not world-writable.
1413 f = open(fName, "wb")
1414 f.write(text)
1415 f.close()
1416
1417 # Ask for an empty title unless otherwise specified
1418 if not "title" in kwargs.keys():
1419 kwargs["title"] = ""
1420
1421 return self._perform(
1422 *(["--textbox", fName, str(height), str(width)],),
1423 **kwargs)[0]
1424 finally:
1425 if type(f) == types.FileType:
1426 f.close() # Safe, even several times
1427 os.unlink(fName)
1428 os.rmdir(tmp_dir)
1429 except os.error, v:
1430 raise PythonDialogOSError(v.strerror)
1431 except IOError, v:
1432 raise PythonDialogIOError(v)
1433
1434 def tailbox(self, filename, height=20, width=60, **kwargs):
1435 """Display the contents of a file in a dialog box, as in "tail -f".
1436
1437 filename -- name of the file whose contents is to be
1438 displayed in the box
1439 height -- height of the box
1440 width -- width of the box
1441
1442 Display the contents of the specified file, updating the
1443 dialog box whenever the file grows, as with the "tail -f"
1444 command.
1445
1446 Return the exit status (an integer) of the dialog-like
1447 program.
1448
1449 Notable exceptions:
1450
1451 any exception raised by self._perform()
1452
jadmanski0afbb632008-06-06 21:10:57 +00001453 """
1454 return self._perform(
mblighbe2ac852006-09-28 04:44:30 +00001455 *(["--tailbox", filename, str(height), str(width)],),
1456 **kwargs)[0]
1457 # No tailboxbg widget, at least for now.
1458
1459 def textbox(self, filename, height=20, width=60, **kwargs):
1460 """Display the contents of a file in a dialog box.
1461
1462 filename -- name of the file whose contents is to be
1463 displayed in the box
1464 height -- height of the box
1465 width -- width of the box
1466
1467 A text box lets you display the contents of a text file in a
1468 dialog box. It is like a simple text file viewer. The user
1469 can move through the file by using the UP/DOWN, PGUP/PGDN
1470 and HOME/END keys available on most keyboards. If the lines
1471 are too long to be displayed in the box, the LEFT/RIGHT keys
1472 can be used to scroll the text region horizontally. For more
1473 convenience, forward and backward searching functions are
1474 also provided.
1475
1476 Return the exit status (an integer) of the dialog-like
1477 program.
1478
1479 Notable exceptions:
1480
1481 any exception raised by self._perform()
1482
jadmanski0afbb632008-06-06 21:10:57 +00001483 """
mblighbe2ac852006-09-28 04:44:30 +00001484 # This is for backward compatibility... not that it is
1485 # stupid, but I prefer explicit programming.
1486 if not "title" in kwargs.keys():
jadmanski0afbb632008-06-06 21:10:57 +00001487 kwargs["title"] = filename
1488 return self._perform(
mblighbe2ac852006-09-28 04:44:30 +00001489 *(["--textbox", filename, str(height), str(width)],),
1490 **kwargs)[0]
1491
1492 def timebox(self, text, height=3, width=30, hour=-1, minute=-1,
1493 second=-1, **kwargs):
1494 """Display a time dialog box.
1495
1496 text -- text to display in the box
1497 height -- height of the box
1498 width -- width of the box
1499 hour -- inititial hour selected
1500 minute -- inititial minute selected
1501 second -- inititial second selected
jadmanski0afbb632008-06-06 21:10:57 +00001502
mblighbe2ac852006-09-28 04:44:30 +00001503 A dialog is displayed which allows you to select hour, minute
1504 and second. If the values for hour, minute or second are
1505 negative (or not explicitely provided, as they default to
1506 -1), the current time's corresponding values are used. You
1507 can increment or decrement any of those using the left-, up-,
1508 right- and down-arrows. Use tab or backtab to move between
1509 windows.
1510
1511 Return a tuple of the form (code, time) where `code' is the
1512 exit status (an integer) of the dialog-like program and
1513 `time' is a list of the form [hour, minute, second] (where
1514 `hour', `minute' and `second' are integers corresponding to
1515 the time chosen by the user) if the box was closed with OK,
1516 or None if it was closed with the Cancel button.
1517
1518 Notable exceptions:
1519 - any exception raised by self._perform()
1520 - PythonDialogReModuleError
1521 - UnexpectedDialogOutput
1522
jadmanski0afbb632008-06-06 21:10:57 +00001523 """
1524 (code, output) = self._perform(
mblighbe2ac852006-09-28 04:44:30 +00001525 *(["--timebox", text, str(height), str(width),
1526 str(hour), str(minute), str(second)],),
1527 **kwargs)
1528 if code == self.DIALOG_OK:
1529 try:
1530 mo = _timebox_time_rec.match(output)
1531 if mo is None:
1532 raise UnexpectedDialogOutput(
1533 "the dialog-like program returned the following "
1534 "unexpected time with the --timebox option: %s" % output)
1535 time = map(int, mo.group("hour", "minute", "second"))
1536 except re.error, v:
1537 raise PythonDialogReModuleError(v)
1538 else:
1539 time = None
1540 return (code, time)
1541
1542 def yesno(self, text, height=10, width=30, **kwargs):
1543 """Display a yes/no dialog box.
1544
1545 text -- text to display in the box
1546 height -- height of the box
1547 width -- width of the box
1548
1549 A yes/no dialog box of size `height' rows by `width' columns
1550 will be displayed. The string specified by `text' is
1551 displayed inside the dialog box. If this string is too long
1552 to fit in one line, it will be automatically divided into
1553 multiple lines at appropriate places. The text string can
1554 also contain the sub-string "\\n" or newline characters to
1555 control line breaking explicitly. This dialog box is useful
1556 for asking questions that require the user to answer either
1557 yes or no. The dialog box has a Yes button and a No button,
1558 in which the user can switch between by pressing the TAB
1559 key.
1560
1561 Return the exit status (an integer) of the dialog-like
1562 program.
1563
1564 Notable exceptions:
1565
1566 any exception raised by self._perform()
1567
jadmanski0afbb632008-06-06 21:10:57 +00001568 """
1569 return self._perform(
mblighbe2ac852006-09-28 04:44:30 +00001570 *(["--yesno", text, str(height), str(width)],),
1571 **kwargs)[0]