symbian-qemu-0.9.1-12/python-win32-2.6.1/lib/distutils/command/bdist_msi.py
changeset 1 2fb8b9db1c86
equal deleted inserted replaced
0:ffa851df0825 1:2fb8b9db1c86
       
     1 # -*- coding: iso-8859-1 -*-
       
     2 # Copyright (C) 2005, 2006 Martin v. Löwis
       
     3 # Licensed to PSF under a Contributor Agreement.
       
     4 # The bdist_wininst command proper
       
     5 # based on bdist_wininst
       
     6 """
       
     7 Implements the bdist_msi command.
       
     8 """
       
     9 
       
    10 import sys, os
       
    11 from distutils.core import Command
       
    12 from distutils.dir_util import remove_tree
       
    13 from distutils.sysconfig import get_python_version
       
    14 from distutils.version import StrictVersion
       
    15 from distutils.errors import DistutilsOptionError
       
    16 from distutils.util import get_platform
       
    17 from distutils import log
       
    18 import msilib
       
    19 from msilib import schema, sequence, text
       
    20 from msilib import Directory, Feature, Dialog, add_data
       
    21 
       
    22 class PyDialog(Dialog):
       
    23     """Dialog class with a fixed layout: controls at the top, then a ruler,
       
    24     then a list of buttons: back, next, cancel. Optionally a bitmap at the
       
    25     left."""
       
    26     def __init__(self, *args, **kw):
       
    27         """Dialog(database, name, x, y, w, h, attributes, title, first,
       
    28         default, cancel, bitmap=true)"""
       
    29         Dialog.__init__(self, *args)
       
    30         ruler = self.h - 36
       
    31         bmwidth = 152*ruler/328
       
    32         #if kw.get("bitmap", True):
       
    33         #    self.bitmap("Bitmap", 0, 0, bmwidth, ruler, "PythonWin")
       
    34         self.line("BottomLine", 0, ruler, self.w, 0)
       
    35 
       
    36     def title(self, title):
       
    37         "Set the title text of the dialog at the top."
       
    38         # name, x, y, w, h, flags=Visible|Enabled|Transparent|NoPrefix,
       
    39         # text, in VerdanaBold10
       
    40         self.text("Title", 15, 10, 320, 60, 0x30003,
       
    41                   r"{\VerdanaBold10}%s" % title)
       
    42 
       
    43     def back(self, title, next, name = "Back", active = 1):
       
    44         """Add a back button with a given title, the tab-next button,
       
    45         its name in the Control table, possibly initially disabled.
       
    46 
       
    47         Return the button, so that events can be associated"""
       
    48         if active:
       
    49             flags = 3 # Visible|Enabled
       
    50         else:
       
    51             flags = 1 # Visible
       
    52         return self.pushbutton(name, 180, self.h-27 , 56, 17, flags, title, next)
       
    53 
       
    54     def cancel(self, title, next, name = "Cancel", active = 1):
       
    55         """Add a cancel button with a given title, the tab-next button,
       
    56         its name in the Control table, possibly initially disabled.
       
    57 
       
    58         Return the button, so that events can be associated"""
       
    59         if active:
       
    60             flags = 3 # Visible|Enabled
       
    61         else:
       
    62             flags = 1 # Visible
       
    63         return self.pushbutton(name, 304, self.h-27, 56, 17, flags, title, next)
       
    64 
       
    65     def next(self, title, next, name = "Next", active = 1):
       
    66         """Add a Next button with a given title, the tab-next button,
       
    67         its name in the Control table, possibly initially disabled.
       
    68 
       
    69         Return the button, so that events can be associated"""
       
    70         if active:
       
    71             flags = 3 # Visible|Enabled
       
    72         else:
       
    73             flags = 1 # Visible
       
    74         return self.pushbutton(name, 236, self.h-27, 56, 17, flags, title, next)
       
    75 
       
    76     def xbutton(self, name, title, next, xpos):
       
    77         """Add a button with a given title, the tab-next button,
       
    78         its name in the Control table, giving its x position; the
       
    79         y-position is aligned with the other buttons.
       
    80 
       
    81         Return the button, so that events can be associated"""
       
    82         return self.pushbutton(name, int(self.w*xpos - 28), self.h-27, 56, 17, 3, title, next)
       
    83 
       
    84 class bdist_msi (Command):
       
    85 
       
    86     description = "create a Microsoft Installer (.msi) binary distribution"
       
    87 
       
    88     user_options = [('bdist-dir=', None,
       
    89                      "temporary directory for creating the distribution"),
       
    90                     ('plat-name=', 'p',
       
    91                      "platform name to embed in generated filenames "
       
    92                      "(default: %s)" % get_platform()),
       
    93                     ('keep-temp', 'k',
       
    94                      "keep the pseudo-installation tree around after " +
       
    95                      "creating the distribution archive"),
       
    96                     ('target-version=', None,
       
    97                      "require a specific python version" +
       
    98                      " on the target system"),
       
    99                     ('no-target-compile', 'c',
       
   100                      "do not compile .py to .pyc on the target system"),
       
   101                     ('no-target-optimize', 'o',
       
   102                      "do not compile .py to .pyo (optimized)"
       
   103                      "on the target system"),
       
   104                     ('dist-dir=', 'd',
       
   105                      "directory to put final built distributions in"),
       
   106                     ('skip-build', None,
       
   107                      "skip rebuilding everything (for testing/debugging)"),
       
   108                     ('install-script=', None,
       
   109                      "basename of installation script to be run after"
       
   110                      "installation or before deinstallation"),
       
   111                     ('pre-install-script=', None,
       
   112                      "Fully qualified filename of a script to be run before "
       
   113                      "any files are installed.  This script need not be in the "
       
   114                      "distribution"),
       
   115                    ]
       
   116 
       
   117     boolean_options = ['keep-temp', 'no-target-compile', 'no-target-optimize',
       
   118                        'skip-build']
       
   119 
       
   120     def initialize_options (self):
       
   121         self.bdist_dir = None
       
   122         self.plat_name = None
       
   123         self.keep_temp = 0
       
   124         self.no_target_compile = 0
       
   125         self.no_target_optimize = 0
       
   126         self.target_version = None
       
   127         self.dist_dir = None
       
   128         self.skip_build = 0
       
   129         self.install_script = None
       
   130         self.pre_install_script = None
       
   131 
       
   132     def finalize_options (self):
       
   133         if self.bdist_dir is None:
       
   134             bdist_base = self.get_finalized_command('bdist').bdist_base
       
   135             self.bdist_dir = os.path.join(bdist_base, 'msi')
       
   136         short_version = get_python_version()
       
   137         if self.target_version:
       
   138             if not self.skip_build and self.distribution.has_ext_modules()\
       
   139                and self.target_version != short_version:
       
   140                 raise DistutilsOptionError, \
       
   141                       "target version can only be %s, or the '--skip_build'" \
       
   142                       " option must be specified" % (short_version,)
       
   143         else:
       
   144             self.target_version = short_version
       
   145 
       
   146         self.set_undefined_options('bdist',
       
   147                                    ('dist_dir', 'dist_dir'),
       
   148                                    ('plat_name', 'plat_name'),
       
   149                                    )
       
   150 
       
   151         if self.pre_install_script:
       
   152             raise DistutilsOptionError, "the pre-install-script feature is not yet implemented"
       
   153 
       
   154         if self.install_script:
       
   155             for script in self.distribution.scripts:
       
   156                 if self.install_script == os.path.basename(script):
       
   157                     break
       
   158             else:
       
   159                 raise DistutilsOptionError, \
       
   160                       "install_script '%s' not found in scripts" % \
       
   161                       self.install_script
       
   162         self.install_script_key = None
       
   163     # finalize_options()
       
   164 
       
   165 
       
   166     def run (self):
       
   167         if not self.skip_build:
       
   168             self.run_command('build')
       
   169 
       
   170         install = self.reinitialize_command('install', reinit_subcommands=1)
       
   171         install.prefix = self.bdist_dir
       
   172         install.skip_build = self.skip_build
       
   173         install.warn_dir = 0
       
   174 
       
   175         install_lib = self.reinitialize_command('install_lib')
       
   176         # we do not want to include pyc or pyo files
       
   177         install_lib.compile = 0
       
   178         install_lib.optimize = 0
       
   179 
       
   180         if self.distribution.has_ext_modules():
       
   181             # If we are building an installer for a Python version other
       
   182             # than the one we are currently running, then we need to ensure
       
   183             # our build_lib reflects the other Python version rather than ours.
       
   184             # Note that for target_version!=sys.version, we must have skipped the
       
   185             # build step, so there is no issue with enforcing the build of this
       
   186             # version.
       
   187             target_version = self.target_version
       
   188             if not target_version:
       
   189                 assert self.skip_build, "Should have already checked this"
       
   190                 target_version = sys.version[0:3]
       
   191             plat_specifier = ".%s-%s" % (self.plat_name, target_version)
       
   192             build = self.get_finalized_command('build')
       
   193             build.build_lib = os.path.join(build.build_base,
       
   194                                            'lib' + plat_specifier)
       
   195 
       
   196         log.info("installing to %s", self.bdist_dir)
       
   197         install.ensure_finalized()
       
   198 
       
   199         # avoid warning of 'install_lib' about installing
       
   200         # into a directory not in sys.path
       
   201         sys.path.insert(0, os.path.join(self.bdist_dir, 'PURELIB'))
       
   202 
       
   203         install.run()
       
   204 
       
   205         del sys.path[0]
       
   206 
       
   207         self.mkpath(self.dist_dir)
       
   208         fullname = self.distribution.get_fullname()
       
   209         installer_name = self.get_installer_filename(fullname)
       
   210         installer_name = os.path.abspath(installer_name)
       
   211         if os.path.exists(installer_name): os.unlink(installer_name)
       
   212 
       
   213         metadata = self.distribution.metadata
       
   214         author = metadata.author
       
   215         if not author:
       
   216             author = metadata.maintainer
       
   217         if not author:
       
   218             author = "UNKNOWN"
       
   219         version = metadata.get_version()
       
   220         # ProductVersion must be strictly numeric
       
   221         # XXX need to deal with prerelease versions
       
   222         sversion = "%d.%d.%d" % StrictVersion(version).version
       
   223         # Prefix ProductName with Python x.y, so that
       
   224         # it sorts together with the other Python packages
       
   225         # in Add-Remove-Programs (APR)
       
   226         product_name = "Python %s %s" % (self.target_version,
       
   227                        self.distribution.get_fullname())
       
   228         self.db = msilib.init_database(installer_name, schema,
       
   229                 product_name, msilib.gen_uuid(),
       
   230                 sversion, author)
       
   231         msilib.add_tables(self.db, sequence)
       
   232         props = [('DistVersion', version)]
       
   233         email = metadata.author_email or metadata.maintainer_email
       
   234         if email:
       
   235             props.append(("ARPCONTACT", email))
       
   236         if metadata.url:
       
   237             props.append(("ARPURLINFOABOUT", metadata.url))
       
   238         if props:
       
   239             add_data(self.db, 'Property', props)
       
   240 
       
   241         self.add_find_python()
       
   242         self.add_files()
       
   243         self.add_scripts()
       
   244         self.add_ui()
       
   245         self.db.Commit()
       
   246 
       
   247         if hasattr(self.distribution, 'dist_files'):
       
   248             self.distribution.dist_files.append(('bdist_msi', self.target_version, fullname))
       
   249 
       
   250         if not self.keep_temp:
       
   251             remove_tree(self.bdist_dir, dry_run=self.dry_run)
       
   252 
       
   253     def add_files(self):
       
   254         db = self.db
       
   255         cab = msilib.CAB("distfiles")
       
   256         f = Feature(db, "default", "Default Feature", "Everything", 1, directory="TARGETDIR")
       
   257         f.set_current()
       
   258         rootdir = os.path.abspath(self.bdist_dir)
       
   259         root = Directory(db, cab, None, rootdir, "TARGETDIR", "SourceDir")
       
   260         db.Commit()
       
   261         todo = [root]
       
   262         while todo:
       
   263             dir = todo.pop()
       
   264             for file in os.listdir(dir.absolute):
       
   265                 afile = os.path.join(dir.absolute, file)
       
   266                 if os.path.isdir(afile):
       
   267                     newdir = Directory(db, cab, dir, file, file, "%s|%s" % (dir.make_short(file), file))
       
   268                     todo.append(newdir)
       
   269                 else:
       
   270                     key = dir.add_file(file)
       
   271                     if file==self.install_script:
       
   272                         if self.install_script_key:
       
   273                             raise DistutilsOptionError, "Multiple files with name %s" % file
       
   274                         self.install_script_key = '[#%s]' % key
       
   275 
       
   276         cab.commit(db)
       
   277 
       
   278     def add_find_python(self):
       
   279         """Adds code to the installer to compute the location of Python.
       
   280         Properties PYTHON.MACHINE, PYTHON.USER, PYTHONDIR and PYTHON will be set
       
   281         in both the execute and UI sequences; PYTHONDIR will be set from
       
   282         PYTHON.USER if defined, else from PYTHON.MACHINE.
       
   283         PYTHON is PYTHONDIR\python.exe"""
       
   284         install_path = r"SOFTWARE\Python\PythonCore\%s\InstallPath" % self.target_version
       
   285         add_data(self.db, "RegLocator",
       
   286                 [("python.machine", 2, install_path, None, 2),
       
   287                  ("python.user", 1, install_path, None, 2)])
       
   288         add_data(self.db, "AppSearch",
       
   289                 [("PYTHON.MACHINE", "python.machine"),
       
   290                  ("PYTHON.USER", "python.user")])
       
   291         add_data(self.db, "CustomAction",
       
   292                 [("PythonFromMachine", 51+256, "PYTHONDIR", "[PYTHON.MACHINE]"),
       
   293                  ("PythonFromUser", 51+256, "PYTHONDIR", "[PYTHON.USER]"),
       
   294                  ("PythonExe", 51+256, "PYTHON", "[PYTHONDIR]\\python.exe"),
       
   295                  ("InitialTargetDir", 51+256, "TARGETDIR", "[PYTHONDIR]")])
       
   296         add_data(self.db, "InstallExecuteSequence",
       
   297                 [("PythonFromMachine", "PYTHON.MACHINE", 401),
       
   298                  ("PythonFromUser", "PYTHON.USER", 402),
       
   299                  ("PythonExe", None, 403),
       
   300                  ("InitialTargetDir", 'TARGETDIR=""', 404),
       
   301                 ])
       
   302         add_data(self.db, "InstallUISequence",
       
   303                 [("PythonFromMachine", "PYTHON.MACHINE", 401),
       
   304                  ("PythonFromUser", "PYTHON.USER", 402),
       
   305                  ("PythonExe", None, 403),
       
   306                  ("InitialTargetDir", 'TARGETDIR=""', 404),
       
   307                 ])
       
   308 
       
   309     def add_scripts(self):
       
   310         if self.install_script:
       
   311             add_data(self.db, "CustomAction",
       
   312                     [("install_script", 50, "PYTHON", self.install_script_key)])
       
   313             add_data(self.db, "InstallExecuteSequence",
       
   314                     [("install_script", "NOT Installed", 6800)])
       
   315         if self.pre_install_script:
       
   316             scriptfn = os.path.join(self.bdist_dir, "preinstall.bat")
       
   317             f = open(scriptfn, "w")
       
   318             # The batch file will be executed with [PYTHON], so that %1
       
   319             # is the path to the Python interpreter; %0 will be the path
       
   320             # of the batch file.
       
   321             # rem ="""
       
   322             # %1 %0
       
   323             # exit
       
   324             # """
       
   325             # <actual script>
       
   326             f.write('rem ="""\n%1 %0\nexit\n"""\n')
       
   327             f.write(open(self.pre_install_script).read())
       
   328             f.close()
       
   329             add_data(self.db, "Binary",
       
   330                 [("PreInstall", msilib.Binary(scriptfn))
       
   331                 ])
       
   332             add_data(self.db, "CustomAction",
       
   333                 [("PreInstall", 2, "PreInstall", None)
       
   334                 ])
       
   335             add_data(self.db, "InstallExecuteSequence",
       
   336                     [("PreInstall", "NOT Installed", 450)])
       
   337 
       
   338 
       
   339     def add_ui(self):
       
   340         db = self.db
       
   341         x = y = 50
       
   342         w = 370
       
   343         h = 300
       
   344         title = "[ProductName] Setup"
       
   345 
       
   346         # see "Dialog Style Bits"
       
   347         modal = 3      # visible | modal
       
   348         modeless = 1   # visible
       
   349         track_disk_space = 32
       
   350 
       
   351         # UI customization properties
       
   352         add_data(db, "Property",
       
   353                  # See "DefaultUIFont Property"
       
   354                  [("DefaultUIFont", "DlgFont8"),
       
   355                   # See "ErrorDialog Style Bit"
       
   356                   ("ErrorDialog", "ErrorDlg"),
       
   357                   ("Progress1", "Install"),   # modified in maintenance type dlg
       
   358                   ("Progress2", "installs"),
       
   359                   ("MaintenanceForm_Action", "Repair"),
       
   360                   # possible values: ALL, JUSTME
       
   361                   ("WhichUsers", "ALL")
       
   362                  ])
       
   363 
       
   364         # Fonts, see "TextStyle Table"
       
   365         add_data(db, "TextStyle",
       
   366                  [("DlgFont8", "Tahoma", 9, None, 0),
       
   367                   ("DlgFontBold8", "Tahoma", 8, None, 1), #bold
       
   368                   ("VerdanaBold10", "Verdana", 10, None, 1),
       
   369                   ("VerdanaRed9", "Verdana", 9, 255, 0),
       
   370                  ])
       
   371 
       
   372         # UI Sequences, see "InstallUISequence Table", "Using a Sequence Table"
       
   373         # Numbers indicate sequence; see sequence.py for how these action integrate
       
   374         add_data(db, "InstallUISequence",
       
   375                  [("PrepareDlg", "Not Privileged or Windows9x or Installed", 140),
       
   376                   ("WhichUsersDlg", "Privileged and not Windows9x and not Installed", 141),
       
   377                   # In the user interface, assume all-users installation if privileged.
       
   378                   ("SelectDirectoryDlg", "Not Installed", 1230),
       
   379                   # XXX no support for resume installations yet
       
   380                   #("ResumeDlg", "Installed AND (RESUME OR Preselected)", 1240),
       
   381                   ("MaintenanceTypeDlg", "Installed AND NOT RESUME AND NOT Preselected", 1250),
       
   382                   ("ProgressDlg", None, 1280)])
       
   383 
       
   384         add_data(db, 'ActionText', text.ActionText)
       
   385         add_data(db, 'UIText', text.UIText)
       
   386         #####################################################################
       
   387         # Standard dialogs: FatalError, UserExit, ExitDialog
       
   388         fatal=PyDialog(db, "FatalError", x, y, w, h, modal, title,
       
   389                      "Finish", "Finish", "Finish")
       
   390         fatal.title("[ProductName] Installer ended prematurely")
       
   391         fatal.back("< Back", "Finish", active = 0)
       
   392         fatal.cancel("Cancel", "Back", active = 0)
       
   393         fatal.text("Description1", 15, 70, 320, 80, 0x30003,
       
   394                    "[ProductName] setup ended prematurely because of an error.  Your system has not been modified.  To install this program at a later time, please run the installation again.")
       
   395         fatal.text("Description2", 15, 155, 320, 20, 0x30003,
       
   396                    "Click the Finish button to exit the Installer.")
       
   397         c=fatal.next("Finish", "Cancel", name="Finish")
       
   398         c.event("EndDialog", "Exit")
       
   399 
       
   400         user_exit=PyDialog(db, "UserExit", x, y, w, h, modal, title,
       
   401                      "Finish", "Finish", "Finish")
       
   402         user_exit.title("[ProductName] Installer was interrupted")
       
   403         user_exit.back("< Back", "Finish", active = 0)
       
   404         user_exit.cancel("Cancel", "Back", active = 0)
       
   405         user_exit.text("Description1", 15, 70, 320, 80, 0x30003,
       
   406                    "[ProductName] setup was interrupted.  Your system has not been modified.  "
       
   407                    "To install this program at a later time, please run the installation again.")
       
   408         user_exit.text("Description2", 15, 155, 320, 20, 0x30003,
       
   409                    "Click the Finish button to exit the Installer.")
       
   410         c = user_exit.next("Finish", "Cancel", name="Finish")
       
   411         c.event("EndDialog", "Exit")
       
   412 
       
   413         exit_dialog = PyDialog(db, "ExitDialog", x, y, w, h, modal, title,
       
   414                              "Finish", "Finish", "Finish")
       
   415         exit_dialog.title("Completing the [ProductName] Installer")
       
   416         exit_dialog.back("< Back", "Finish", active = 0)
       
   417         exit_dialog.cancel("Cancel", "Back", active = 0)
       
   418         exit_dialog.text("Description", 15, 235, 320, 20, 0x30003,
       
   419                    "Click the Finish button to exit the Installer.")
       
   420         c = exit_dialog.next("Finish", "Cancel", name="Finish")
       
   421         c.event("EndDialog", "Return")
       
   422 
       
   423         #####################################################################
       
   424         # Required dialog: FilesInUse, ErrorDlg
       
   425         inuse = PyDialog(db, "FilesInUse",
       
   426                          x, y, w, h,
       
   427                          19,                # KeepModeless|Modal|Visible
       
   428                          title,
       
   429                          "Retry", "Retry", "Retry", bitmap=False)
       
   430         inuse.text("Title", 15, 6, 200, 15, 0x30003,
       
   431                    r"{\DlgFontBold8}Files in Use")
       
   432         inuse.text("Description", 20, 23, 280, 20, 0x30003,
       
   433                "Some files that need to be updated are currently in use.")
       
   434         inuse.text("Text", 20, 55, 330, 50, 3,
       
   435                    "The following applications are using files that need to be updated by this setup. Close these applications and then click Retry to continue the installation or Cancel to exit it.")
       
   436         inuse.control("List", "ListBox", 20, 107, 330, 130, 7, "FileInUseProcess",
       
   437                       None, None, None)
       
   438         c=inuse.back("Exit", "Ignore", name="Exit")
       
   439         c.event("EndDialog", "Exit")
       
   440         c=inuse.next("Ignore", "Retry", name="Ignore")
       
   441         c.event("EndDialog", "Ignore")
       
   442         c=inuse.cancel("Retry", "Exit", name="Retry")
       
   443         c.event("EndDialog","Retry")
       
   444 
       
   445         # See "Error Dialog". See "ICE20" for the required names of the controls.
       
   446         error = Dialog(db, "ErrorDlg",
       
   447                        50, 10, 330, 101,
       
   448                        65543,       # Error|Minimize|Modal|Visible
       
   449                        title,
       
   450                        "ErrorText", None, None)
       
   451         error.text("ErrorText", 50,9,280,48,3, "")
       
   452         #error.control("ErrorIcon", "Icon", 15, 9, 24, 24, 5242881, None, "py.ico", None, None)
       
   453         error.pushbutton("N",120,72,81,21,3,"No",None).event("EndDialog","ErrorNo")
       
   454         error.pushbutton("Y",240,72,81,21,3,"Yes",None).event("EndDialog","ErrorYes")
       
   455         error.pushbutton("A",0,72,81,21,3,"Abort",None).event("EndDialog","ErrorAbort")
       
   456         error.pushbutton("C",42,72,81,21,3,"Cancel",None).event("EndDialog","ErrorCancel")
       
   457         error.pushbutton("I",81,72,81,21,3,"Ignore",None).event("EndDialog","ErrorIgnore")
       
   458         error.pushbutton("O",159,72,81,21,3,"Ok",None).event("EndDialog","ErrorOk")
       
   459         error.pushbutton("R",198,72,81,21,3,"Retry",None).event("EndDialog","ErrorRetry")
       
   460 
       
   461         #####################################################################
       
   462         # Global "Query Cancel" dialog
       
   463         cancel = Dialog(db, "CancelDlg", 50, 10, 260, 85, 3, title,
       
   464                         "No", "No", "No")
       
   465         cancel.text("Text", 48, 15, 194, 30, 3,
       
   466                     "Are you sure you want to cancel [ProductName] installation?")
       
   467         #cancel.control("Icon", "Icon", 15, 15, 24, 24, 5242881, None,
       
   468         #               "py.ico", None, None)
       
   469         c=cancel.pushbutton("Yes", 72, 57, 56, 17, 3, "Yes", "No")
       
   470         c.event("EndDialog", "Exit")
       
   471 
       
   472         c=cancel.pushbutton("No", 132, 57, 56, 17, 3, "No", "Yes")
       
   473         c.event("EndDialog", "Return")
       
   474 
       
   475         #####################################################################
       
   476         # Global "Wait for costing" dialog
       
   477         costing = Dialog(db, "WaitForCostingDlg", 50, 10, 260, 85, modal, title,
       
   478                          "Return", "Return", "Return")
       
   479         costing.text("Text", 48, 15, 194, 30, 3,
       
   480                      "Please wait while the installer finishes determining your disk space requirements.")
       
   481         c = costing.pushbutton("Return", 102, 57, 56, 17, 3, "Return", None)
       
   482         c.event("EndDialog", "Exit")
       
   483 
       
   484         #####################################################################
       
   485         # Preparation dialog: no user input except cancellation
       
   486         prep = PyDialog(db, "PrepareDlg", x, y, w, h, modeless, title,
       
   487                         "Cancel", "Cancel", "Cancel")
       
   488         prep.text("Description", 15, 70, 320, 40, 0x30003,
       
   489                   "Please wait while the Installer prepares to guide you through the installation.")
       
   490         prep.title("Welcome to the [ProductName] Installer")
       
   491         c=prep.text("ActionText", 15, 110, 320, 20, 0x30003, "Pondering...")
       
   492         c.mapping("ActionText", "Text")
       
   493         c=prep.text("ActionData", 15, 135, 320, 30, 0x30003, None)
       
   494         c.mapping("ActionData", "Text")
       
   495         prep.back("Back", None, active=0)
       
   496         prep.next("Next", None, active=0)
       
   497         c=prep.cancel("Cancel", None)
       
   498         c.event("SpawnDialog", "CancelDlg")
       
   499 
       
   500         #####################################################################
       
   501         # Target directory selection
       
   502         seldlg = PyDialog(db, "SelectDirectoryDlg", x, y, w, h, modal, title,
       
   503                         "Next", "Next", "Cancel")
       
   504         seldlg.title("Select Destination Directory")
       
   505 
       
   506         version = sys.version[:3]+" "
       
   507         seldlg.text("Hint", 15, 30, 300, 40, 3,
       
   508                 "The destination directory should contain a Python %sinstallation" % version)
       
   509 
       
   510         seldlg.back("< Back", None, active=0)
       
   511         c = seldlg.next("Next >", "Cancel")
       
   512         c.event("SetTargetPath", "TARGETDIR", ordering=1)
       
   513         c.event("SpawnWaitDialog", "WaitForCostingDlg", ordering=2)
       
   514         c.event("EndDialog", "Return", ordering=3)
       
   515 
       
   516         c = seldlg.cancel("Cancel", "DirectoryCombo")
       
   517         c.event("SpawnDialog", "CancelDlg")
       
   518 
       
   519         seldlg.control("DirectoryCombo", "DirectoryCombo", 15, 70, 272, 80, 393219,
       
   520                        "TARGETDIR", None, "DirectoryList", None)
       
   521         seldlg.control("DirectoryList", "DirectoryList", 15, 90, 308, 136, 3, "TARGETDIR",
       
   522                        None, "PathEdit", None)
       
   523         seldlg.control("PathEdit", "PathEdit", 15, 230, 306, 16, 3, "TARGETDIR", None, "Next", None)
       
   524         c = seldlg.pushbutton("Up", 306, 70, 18, 18, 3, "Up", None)
       
   525         c.event("DirectoryListUp", "0")
       
   526         c = seldlg.pushbutton("NewDir", 324, 70, 30, 18, 3, "New", None)
       
   527         c.event("DirectoryListNew", "0")
       
   528 
       
   529         #####################################################################
       
   530         # Disk cost
       
   531         cost = PyDialog(db, "DiskCostDlg", x, y, w, h, modal, title,
       
   532                         "OK", "OK", "OK", bitmap=False)
       
   533         cost.text("Title", 15, 6, 200, 15, 0x30003,
       
   534                   "{\DlgFontBold8}Disk Space Requirements")
       
   535         cost.text("Description", 20, 20, 280, 20, 0x30003,
       
   536                   "The disk space required for the installation of the selected features.")
       
   537         cost.text("Text", 20, 53, 330, 60, 3,
       
   538                   "The highlighted volumes (if any) do not have enough disk space "
       
   539               "available for the currently selected features.  You can either "
       
   540               "remove some files from the highlighted volumes, or choose to "
       
   541               "install less features onto local drive(s), or select different "
       
   542               "destination drive(s).")
       
   543         cost.control("VolumeList", "VolumeCostList", 20, 100, 330, 150, 393223,
       
   544                      None, "{120}{70}{70}{70}{70}", None, None)
       
   545         cost.xbutton("OK", "Ok", None, 0.5).event("EndDialog", "Return")
       
   546 
       
   547         #####################################################################
       
   548         # WhichUsers Dialog. Only available on NT, and for privileged users.
       
   549         # This must be run before FindRelatedProducts, because that will
       
   550         # take into account whether the previous installation was per-user
       
   551         # or per-machine. We currently don't support going back to this
       
   552         # dialog after "Next" was selected; to support this, we would need to
       
   553         # find how to reset the ALLUSERS property, and how to re-run
       
   554         # FindRelatedProducts.
       
   555         # On Windows9x, the ALLUSERS property is ignored on the command line
       
   556         # and in the Property table, but installer fails according to the documentation
       
   557         # if a dialog attempts to set ALLUSERS.
       
   558         whichusers = PyDialog(db, "WhichUsersDlg", x, y, w, h, modal, title,
       
   559                             "AdminInstall", "Next", "Cancel")
       
   560         whichusers.title("Select whether to install [ProductName] for all users of this computer.")
       
   561         # A radio group with two options: allusers, justme
       
   562         g = whichusers.radiogroup("AdminInstall", 15, 60, 260, 50, 3,
       
   563                                   "WhichUsers", "", "Next")
       
   564         g.add("ALL", 0, 5, 150, 20, "Install for all users")
       
   565         g.add("JUSTME", 0, 25, 150, 20, "Install just for me")
       
   566 
       
   567         whichusers.back("Back", None, active=0)
       
   568 
       
   569         c = whichusers.next("Next >", "Cancel")
       
   570         c.event("[ALLUSERS]", "1", 'WhichUsers="ALL"', 1)
       
   571         c.event("EndDialog", "Return", ordering = 2)
       
   572 
       
   573         c = whichusers.cancel("Cancel", "AdminInstall")
       
   574         c.event("SpawnDialog", "CancelDlg")
       
   575 
       
   576         #####################################################################
       
   577         # Installation Progress dialog (modeless)
       
   578         progress = PyDialog(db, "ProgressDlg", x, y, w, h, modeless, title,
       
   579                             "Cancel", "Cancel", "Cancel", bitmap=False)
       
   580         progress.text("Title", 20, 15, 200, 15, 0x30003,
       
   581                       "{\DlgFontBold8}[Progress1] [ProductName]")
       
   582         progress.text("Text", 35, 65, 300, 30, 3,
       
   583                       "Please wait while the Installer [Progress2] [ProductName]. "
       
   584                       "This may take several minutes.")
       
   585         progress.text("StatusLabel", 35, 100, 35, 20, 3, "Status:")
       
   586 
       
   587         c=progress.text("ActionText", 70, 100, w-70, 20, 3, "Pondering...")
       
   588         c.mapping("ActionText", "Text")
       
   589 
       
   590         #c=progress.text("ActionData", 35, 140, 300, 20, 3, None)
       
   591         #c.mapping("ActionData", "Text")
       
   592 
       
   593         c=progress.control("ProgressBar", "ProgressBar", 35, 120, 300, 10, 65537,
       
   594                            None, "Progress done", None, None)
       
   595         c.mapping("SetProgress", "Progress")
       
   596 
       
   597         progress.back("< Back", "Next", active=False)
       
   598         progress.next("Next >", "Cancel", active=False)
       
   599         progress.cancel("Cancel", "Back").event("SpawnDialog", "CancelDlg")
       
   600 
       
   601         ###################################################################
       
   602         # Maintenance type: repair/uninstall
       
   603         maint = PyDialog(db, "MaintenanceTypeDlg", x, y, w, h, modal, title,
       
   604                          "Next", "Next", "Cancel")
       
   605         maint.title("Welcome to the [ProductName] Setup Wizard")
       
   606         maint.text("BodyText", 15, 63, 330, 42, 3,
       
   607                    "Select whether you want to repair or remove [ProductName].")
       
   608         g=maint.radiogroup("RepairRadioGroup", 15, 108, 330, 60, 3,
       
   609                             "MaintenanceForm_Action", "", "Next")
       
   610         #g.add("Change", 0, 0, 200, 17, "&Change [ProductName]")
       
   611         g.add("Repair", 0, 18, 200, 17, "&Repair [ProductName]")
       
   612         g.add("Remove", 0, 36, 200, 17, "Re&move [ProductName]")
       
   613 
       
   614         maint.back("< Back", None, active=False)
       
   615         c=maint.next("Finish", "Cancel")
       
   616         # Change installation: Change progress dialog to "Change", then ask
       
   617         # for feature selection
       
   618         #c.event("[Progress1]", "Change", 'MaintenanceForm_Action="Change"', 1)
       
   619         #c.event("[Progress2]", "changes", 'MaintenanceForm_Action="Change"', 2)
       
   620 
       
   621         # Reinstall: Change progress dialog to "Repair", then invoke reinstall
       
   622         # Also set list of reinstalled features to "ALL"
       
   623         c.event("[REINSTALL]", "ALL", 'MaintenanceForm_Action="Repair"', 5)
       
   624         c.event("[Progress1]", "Repairing", 'MaintenanceForm_Action="Repair"', 6)
       
   625         c.event("[Progress2]", "repairs", 'MaintenanceForm_Action="Repair"', 7)
       
   626         c.event("Reinstall", "ALL", 'MaintenanceForm_Action="Repair"', 8)
       
   627 
       
   628         # Uninstall: Change progress to "Remove", then invoke uninstall
       
   629         # Also set list of removed features to "ALL"
       
   630         c.event("[REMOVE]", "ALL", 'MaintenanceForm_Action="Remove"', 11)
       
   631         c.event("[Progress1]", "Removing", 'MaintenanceForm_Action="Remove"', 12)
       
   632         c.event("[Progress2]", "removes", 'MaintenanceForm_Action="Remove"', 13)
       
   633         c.event("Remove", "ALL", 'MaintenanceForm_Action="Remove"', 14)
       
   634 
       
   635         # Close dialog when maintenance action scheduled
       
   636         c.event("EndDialog", "Return", 'MaintenanceForm_Action<>"Change"', 20)
       
   637         #c.event("NewDialog", "SelectFeaturesDlg", 'MaintenanceForm_Action="Change"', 21)
       
   638 
       
   639         maint.cancel("Cancel", "RepairRadioGroup").event("SpawnDialog", "CancelDlg")
       
   640 
       
   641     def get_installer_filename(self, fullname):
       
   642         # Factored out to allow overriding in subclasses
       
   643         base_name = "%s.%s-py%s.msi" % (fullname, self.plat_name,
       
   644                                         self.target_version)
       
   645         installer_name = os.path.join(self.dist_dir, base_name)
       
   646         return installer_name