Continued implementing the PyInstaller interface (implemented the execution dialog).

Fri, 19 Jan 2018 17:08:15 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Fri, 19 Jan 2018 17:08:15 +0100
changeset 5
8c92d66d20e4
parent 4
52f0572b5908
child 6
0f0f1598fc4a

Continued implementing the PyInstaller interface (implemented the execution dialog).

PluginPyInstaller.e4p file | annotate | diff | comparison | revisions
PluginPyInstaller.py file | annotate | diff | comparison | revisions
PyInstaller/PyInstallerConfigDialog.py file | annotate | diff | comparison | revisions
PyInstaller/PyInstallerExecDialog.py file | annotate | diff | comparison | revisions
PyInstaller/PyInstallerExecDialog.ui file | annotate | diff | comparison | revisions
--- a/PluginPyInstaller.e4p	Thu Jan 18 14:30:06 2018 +0100
+++ b/PluginPyInstaller.e4p	Fri Jan 19 17:08:15 2018 +0100
@@ -17,12 +17,14 @@
     <Source>PluginPyInstaller.py</Source>
     <Source>PyInstaller/PyInstallerCleanupDialog.py</Source>
     <Source>PyInstaller/PyInstallerConfigDialog.py</Source>
+    <Source>PyInstaller/PyInstallerExecDialog.py</Source>
     <Source>PyInstaller/__init__.py</Source>
     <Source>__init__.py</Source>
   </Sources>
   <Forms>
     <Form>PyInstaller/PyInstallerCleanupDialog.ui</Form>
     <Form>PyInstaller/PyInstallerConfigDialog.ui</Form>
+    <Form>PyInstaller/PyInstallerExecDialog.ui</Form>
   </Forms>
   <Others>
     <Other>.hgignore</Other>
--- a/PluginPyInstaller.py	Thu Jan 18 14:30:06 2018 +0100
+++ b/PluginPyInstaller.py	Fri Jan 19 17:08:15 2018 +0100
@@ -420,18 +420,14 @@
         dlg = PyInstallerConfigDialog(project, executables, params,
                                       mode="installer")
         if dlg.exec_() == QDialog.Accepted:
-            args, params = dlg.generateParameters()
+            args, params, script = dlg.generateParameters()
             project.setData('PACKAGERSPARMS', "PYINSTALLER", params)
             
-            # TODO: implement pyinstaller
-            return
-            
             # now do the call
             from PyInstaller.PyInstallerExecDialog import PyInstallerExecDialog
             dia = PyInstallerExecDialog("pyinstaller")
             dia.show()
-            res = dia.start(args, params, project.getProjectPath(),
-                            project.getMainScript())
+            res = dia.start(args, params, project, script)
             if res:
                 dia.exec_()
     
--- a/PyInstaller/PyInstallerConfigDialog.py	Thu Jan 18 14:30:06 2018 +0100
+++ b/PyInstaller/PyInstallerConfigDialog.py	Fri Jan 19 17:08:15 2018 +0100
@@ -178,13 +178,13 @@
         Public method that generates the command line parameters.
         
         It generates a list of strings to be used to set the QProcess arguments
-        for the pyinstaller call and a list containing the non default
-        parameters. The second list can be passed back upon object generation
-        to overwrite the default settings.
+        for the pyinstaller/pyi-makespec call and a list containing the non
+        default parameters. The second list can be passed back upon object
+        generation to overwrite the default settings.
         
-        @return a tuple of the command line parameters and non default
-            parameters
-        @rtype tuple of (list of str, dict)
+        @return a tuple of the command line parameters, non default parameters
+            and the script path
+        @rtype tuple of (list of str, dict, str)
         """
         parms = {}
         args = []
@@ -257,13 +257,13 @@
         # 3. always add these arguments
         args.append("--noconfirm")  # don't ask the user
         
-        # finalize the arguments array
+        # determine the script to be processed
         if self.__parameters["mainscript"]:
-            args.append(self.__project.getMainScript())
+            script = self.__project.getMainScript()
         else:
-            args.append(self.__parameters["inputFile"])
+            script = self.__parameters["inputFile"]
         
-        return args, parms
+        return args, parms, script
 
     def accept(self):
         """
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PyInstaller/PyInstallerExecDialog.py	Fri Jan 19 17:08:15 2018 +0100
@@ -0,0 +1,202 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2018 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to show the output of the pyinstaller/pyi-makespec
+process.
+"""
+
+from __future__ import unicode_literals
+try:
+    str = unicode
+except NameError:
+    pass
+
+import os
+
+from PyQt5.QtCore import pyqtSlot, QProcess, QTimer
+from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QAbstractButton
+
+from E5Gui import E5MessageBox
+
+from .Ui_PyInstallerExecDialog import Ui_PyInstallerExecDialog
+
+import Preferences
+
+
+class PyInstallerExecDialog(QDialog, Ui_PyInstallerExecDialog):
+    """
+    Class implementing a dialog to show the output of the
+    pyinstaller/pyi-makespec process.
+    
+    This class starts a QProcess and displays a dialog that
+    shows the output of the packager command process.
+    """
+    def __init__(self, cmdname, parent=None):
+        """
+        Constructor
+        
+        @param cmdname name of the packager
+        @type str
+        @param parent reference to the parent widget
+        @type QWidget
+        """
+        super(PyInstallerExecDialog, self).__init__(parent)
+        self.setupUi(self)
+        
+        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
+        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
+        
+        self.__process = None
+        self.__cmdname = cmdname    # used for several outputs
+    
+    def start(self, args, parms, project, script):
+        """
+        Public slot to start the packager command.
+        
+        @param args command line arguments for packager program
+        @type list of str
+        @param parms parameters got from the config dialog
+        @type dict
+        @param project reference to the project object
+        @type Project
+        @param script script or spec file name to be processed by by the
+            packager
+        @type str
+        @return flag indicating the successful start of the process
+        @rtype bool
+        """
+        self.__project = project
+        
+        if not os.path.isabs(script):
+            # assume relative paths are relative to the project directory
+            script = os.path.join(self.__project.getProjectPath(), script)
+        dname = os.path.dirname(script)
+        self.__script = os.path.basename(script)
+        
+        self.contents.clear()
+        
+        args.append(self.__script)
+        
+        self.__process = QProcess()
+        self.__process.setWorkingDirectory(dname)
+        
+        self.__process.readyReadStandardOutput.connect(self.__readStdout)
+        self.__process.readyReadStandardError.connect(self.__readStderr)
+        self.__process.finished.connect(self.__finishedProcess)
+            
+        self.setWindowTitle(self.tr('{0} - {1}').format(
+            self.__cmdname, script))
+        self.contents.insertPlainText("{0} {1}\n\n".format(
+            ' '.join(args), os.path.join(dname, self.__script)))
+        self.contents.ensureCursorVisible()
+        
+        program = args.pop(0)
+        self.__process.start(program, args)
+        procStarted = self.__process.waitForStarted()
+        if not procStarted:
+            E5MessageBox.critical(
+                self,
+                self.tr('Process Generation Error'),
+                self.tr(
+                    'The process {0} could not be started. '
+                    'Ensure, that it is in the search path.'
+                ).format(program))
+        return procStarted
+    
+    @pyqtSlot(QAbstractButton)
+    def on_buttonBox_clicked(self, button):
+        """
+        Private slot called by a button of the button box clicked.
+        
+        @param button button that was clicked
+        @type QAbstractButton
+        """
+        if button == self.buttonBox.button(QDialogButtonBox.Close):
+            self.accept()
+        elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
+            self.__finish()
+    
+    def __finish(self):
+        """
+        Private slot called when the process was canceled by the user.
+        
+        It is called when the process finished or
+        the user pressed the cancel button.
+        """
+        if self.__process is not None:
+            self.__process.disconnect(self.__finishedProcess)
+            self.__process.terminate()
+            QTimer.singleShot(2000, self.__process.kill)
+            self.__process.waitForFinished(3000)
+        self.__process = None
+        
+        self.contents.insertPlainText(
+            self.tr('\n{0} aborted.\n').format(self.__cmdname))
+        
+        self.__enableButtons()
+    
+    def __finishedProcess(self):
+        """
+        Private slot called when the process finished.
+        
+        It is called when the process finished or
+        the user pressed the cancel button.
+        """
+        if self.__process.exitStatus() == QProcess.NormalExit:
+            # add the spec file to the project
+            self.__project.addToOthers(
+                os.path.splitext(self.__script)[0] + ".spec")
+        
+        self.__process = None
+
+        self.contents.insertPlainText(
+            self.tr('\n{0} finished.\n').format(self.__cmdname))
+        
+        self.__enableButtons()
+    
+    def __enableButtons(self):
+        """
+        Private slot called when all processes finished.
+        
+        It is called when the process finished or
+        the user pressed the cancel button.
+        """
+        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
+        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
+        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
+        self.contents.ensureCursorVisible()
+
+    def __readStdout(self):
+        """
+        Private slot to handle the readyReadStandardOutput signal.
+        
+        It reads the output of the process, formats it and inserts it into
+        the contents pane.
+        """
+        self.__process.setReadChannel(QProcess.StandardOutput)
+        
+        while self.__process.canReadLine():
+            s = str(self.__process.readAllStandardOutput(),
+                    Preferences.getSystem("IOEncoding"),
+                    'replace')
+            self.contents.insertPlainText(s)
+            self.contents.ensureCursorVisible()
+    
+    def __readStderr(self):
+        """
+        Private slot to handle the readyReadStandardError signal.
+        
+        It reads the error output of the process and inserts it into the
+        error pane.
+        """
+        self.__process.setReadChannel(QProcess.StandardError)
+        
+        while self.__process.canReadLine():
+            s = str(self.__process.readAllStandardError(),
+                    Preferences.getSystem("IOEncoding"),
+                    'replace')
+            self.contents.insertPlainText(s)
+            self.contents.ensureCursorVisible()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PyInstaller/PyInstallerExecDialog.ui	Fri Jan 19 17:08:15 2018 +0100
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PyInstallerExecDialog</class>
+ <widget class="QDialog" name="PyInstallerExecDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>750</width>
+    <height>600</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>PyInstaller</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout_3">
+   <item>
+    <widget class="QGroupBox" name="messagesGroup">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+       <horstretch>0</horstretch>
+       <verstretch>3</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="title">
+      <string>Messages</string>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout">
+      <item>
+       <widget class="QTextBrowser" name="contents">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+          <horstretch>0</horstretch>
+          <verstretch>3</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="whatsThis">
+         <string>&lt;b&gt;Packager Execution&lt;/b&gt;
+&lt;p&gt;This shows the output of the packager command.&lt;/p&gt;</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Close</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>PyInstallerExecDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>PyInstallerExecDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>

eric ide

mercurial