source: contrib/scriptGerDistrib/dialog.py @ 3545

Revision 3545, 59.9 KB checked in by rafaelraymundo, 13 years ago (diff)

Ticket #1344 - Informar na tela de login a tag/branch e revisão do svn.

Line 
1# 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"
137   
138class 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)}
247   
248
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
301       
302    """
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
573        """       
574        # 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
615        """
616        self.add_persistent_args(("--backtitle", text))
617
618    def _call_program(self, redirect_child_stdin, cmdargs, **kwargs):
619        """Do the actual work of invoking the dialog-like program.
620
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.
626       
627        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")
774       
775        # 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):
809        """Perform a complete dialog-like program invocation.
810
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)
826        return (exit_code, output)
827
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):
836        """Call dialog without passing any more options."""
837        return os.system(self._dialog_prg + ' ' + cmd)
838
839    # For compatibility with the old dialog.py
840    def clear(self):
841        """Clear the screen. Equivalent to the dialog --clear option.
842
843        This method is obsolete. Please remove calls to it from your
844        programs.
845
846        """
847        self._perform_no_options('--clear')
848
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)
860       
861        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
881        """
882        (code, output) = self._perform(
883            *(["--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)
891           
892            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):
903        """Display a checklist box.
904
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
940        (code, output) = self._perform(*(cmd,), **kwargs)
941
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
955       
956        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).
982             
983        Notable exceptions:
984
985            any exception raised by self._perform()
986
987        """
988        (code, output) = self._perform(
989            *(["--fselect", filepath, str(height), str(width)],),
990            **kwargs)
991
992        output = self._strip_xdialog_newline(output)
993       
994        return (code, output)
995   
996    def gauge_start(self, text="", height=8, width=54, percent=0, **kwargs):
997        """Display gauge box.
998
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
1018        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
1025
1026
1027        Notable exceptions:
1028            - any exception raised by self._call_program()
1029            - PythonDialogOSError
1030
1031        """
1032        (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)
1044           
1045    def gauge_update(self, percent, text="", update_text=0):
1046        """Update a running gauge box.
1047       
1048        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
1058        See the `gauge_start' function's documentation for
1059        information about how to use a gauge.
1060
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
1067        """
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:
1073            self._gauge_process["stdin"].write(gauge_data)
1074            self._gauge_process["stdin"].flush()
1075        except IOError, v:
1076            raise PythonDialogIOError(v)
1077   
1078    # For "compatibility" with the old dialog.py...
1079    gauge_iterate = gauge_update
1080
1081    def gauge_stop(self):
1082        """Terminate a running gauge.
1083
1084        This function performs the appropriate cleanup actions to
1085        terminate a running gauge (started with `gauge_start').
1086       
1087        See the `gauge_start' function's documentation for
1088        information about how to use a gauge.
1089
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
1098        """
1099        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
1132        """
1133        return self._perform(
1134            *(["--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
1160        """
1161        (code, tag) = self._perform(
1162            *(["--inputbox", text, str(height), str(width), init],),
1163            **kwargs)
1164
1165        tag = self._strip_xdialog_newline(tag)
1166       
1167        return (code, tag)
1168
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
1249        """
1250        cmd = ["--menu", text, str(height), str(width), str(menu_height)]
1251        for t in choices:
1252            cmd.extend(t)
1253        (code, output) = self._perform(*(cmd,), **kwargs)
1254
1255        output = self._strip_xdialog_newline(output)
1256       
1257        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
1284        """
1285        return self._perform(
1286            *(["--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
1314        """
1315        (code, password) = self._perform(
1316            *(["--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=70, list_height=7,
1324                  choices=[], **kwargs):
1325        """Display a radiolist box.
1326
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
1355        """
1356        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)
1363           
1364        return (code, tag)
1365
1366    def scrollbox(self, text, height=20, width=78, **kwargs):
1367        """Display a string in a scrollable box.
1368
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
1389        """
1390        # 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
1453        """
1454        return self._perform(
1455            *(["--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
1483        """
1484        # This is for backward compatibility... not that it is
1485        # stupid, but I prefer explicit programming.
1486        if not "title" in kwargs.keys():
1487            kwargs["title"] = filename
1488        return self._perform(
1489            *(["--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
1502       
1503        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
1523        """
1524        (code, output) = self._perform(
1525            *(["--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
1568        """
1569        return self._perform(
1570            *(["--yesno", text, str(height), str(width)],),
1571            **kwargs)[0]
Note: See TracBrowser for help on using the repository browser.