class IsoBuilder():
    def __init__(self, parent=None):
        self.parent = parent
        self.process = None

    def __parse_output(self):
        if not self.process:
            return

        output = QString(self.process.readAll())
        output = output.split("\n", QString.SkipEmptyParts)

        for line in output:
            line = common.qt_to_unicode(line)

            match = OUTPUT_RE.match(line)

            if match == None:
                continue

            percent = float(match.group(1))
            self.progress.setValue(percent)

    def build_iso(self, directory, iso_file):

        if self.process:
            return

        directory = os.path.abspath(directory)
        iso_file = os.path.abspath(iso_file)

        self.progress = QProgressDialog("Building ISO...", QtCore.QString(), 0,
                                        0, self.parent)
        self.progress.setWindowTitle("Building ISO")
        self.progress.setWindowModality(Qt.Qt.WindowModal)
        self.progress.setAutoClose(False)
        self.progress.setMinimumDuration(1000)

        self.progress.show()
        self.progress.setValue(0)
        self.progress.setMaximum(100)

        self.process = QProcess()
        self.process.finished.connect(self.__build_finished)
        self.process.setReadChannel(QProcess.StandardError)
        self.process.readyRead.connect(self.__parse_output)
        self.process.start("tools/mkisofs", [
            "-sort", "data/file_order.txt", "-iso-level", "4", "-xa", "-A",
            "PSP GAME", "-V", "DANGANRONPA", "-sysid", "PSP GAME", "-volset",
            "DANGANRONPA", "-p", "SPIKE", "-publisher", "SPIKE", "-o",
            iso_file, directory
        ])

    def __build_finished(self, code, status):
        self.progress.close()
        self.process = None
class IsoBuilder():
  def __init__(self, parent = None):
    self.parent = parent
    self.process = None
  
  def __parse_output(self):
    if not self.process:
      return
    
    output = QString(self.process.readAll())
    output = output.split("\n", QString.SkipEmptyParts)
    
    for line in output:
      line = common.qt_to_unicode(line)
      
      match = OUTPUT_RE.match(line)
      
      if match == None:
        continue
      
      percent = float(match.group(1))
      self.progress.setValue(percent)
  
  def build_iso(self, directory, iso_file):
    
    if self.process:
      return
    
    directory = os.path.abspath(directory)
    iso_file  = os.path.abspath(iso_file)
    
    self.progress = QProgressDialog("Building ISO...", QtCore.QString(), 0, 0, self.parent)
    self.progress.setWindowTitle("Building ISO")
    self.progress.setWindowModality(Qt.Qt.WindowModal)
    self.progress.setAutoClose(False)
    self.progress.setMinimumDuration(1000)
    
    self.progress.show()
    self.progress.setValue(0)
    self.progress.setMaximum(100)
    
    self.process = QProcess()
    self.process.finished.connect(self.__build_finished)
    self.process.setReadChannel(QProcess.StandardError)
    self.process.readyRead.connect(self.__parse_output)
    self.process.start("tools/mkisofs", ["-sort", "data/file_order.txt", "-iso-level", "4", "-xa", "-A", "PSP GAME", "-V", "DANGANRONPA", "-sysid", "PSP GAME", "-volset", "DANGANRONPA", "-p", "SPIKE", "-publisher", "SPIKE", "-o", iso_file, directory])
    
  def __build_finished(self, code, status):
    self.progress.close()
    self.process = None
Пример #3
0
class HostWindow(QMainWindow):
    # signals
    SIGTERM = pyqtSignal()
    SIGUSR1 = pyqtSignal()

    # --------------------------------------------------------------------------------------------------------

    def __init__(self):
        QMainWindow.__init__(self)
        self.ui = Ui_HostWindow()
        self.ui.setupUi(self)

        # ----------------------------------------------------------------------------------------------------
        # Internal stuff

        # Current mod-ui title
        #self.fCurrentBundle = ""
        self.fCurrentTitle = ""

        # Next bundle to load (done by startup arguments)
        self.fNextBundle = ""

        # first attempt of auto-start backend doesn't show an error
        self.fFirstBackendInit = True

        # need to call session reconnect after connecting the 1st time
        self.fNeedsSessionReconnect = False

        # Qt idle timer
        self.fIdleTimerId = 0

        # Qt web frame, used for evaluating javascript
        self.fWebFrame = None

        # to be filled with key-value pairs of current settings
        self.fSavedSettings = {}

        # List of pedalboards
        self.fPedalboards = get_all_pedalboards()

        # List of current-pedalboard presets
        self.fPresetMenuList = []

        # Process that runs the backend
        self.fProccessBackend = QProcess(self)
        self.fProccessBackend.setProcessChannelMode(QProcess.MergedChannels)
        self.fProccessBackend.setReadChannel(QProcess.StandardOutput)
        self.fStoppingBackend = False

        # Thread for managing the webserver
        self.fWebServerThread = WebServerThread(self)

        # ----------------------------------------------------------------------------------------------------
        # Set up GUI

        self.ui.webview = QWebView(self.ui.swp_webview)
        self.ui.webview.setMinimumWidth(980)
        self.ui.swp_webview.layout().addWidget(self.ui.webview)

        self.ui.webpage = HostWebPage(self)
        self.ui.webpage.setViewportSize(QSize(980, 600))
        self.ui.webview.setPage(self.ui.webpage)

        self.ui.webinspector = QWebInspector(None)
        self.ui.webinspector.resize(800, 600)
        self.ui.webinspector.setPage(self.ui.webpage)
        self.ui.webinspector.setVisible(False)

        self.ui.act_file_connect.setEnabled(False)
        self.ui.act_file_connect.setVisible(False)
        self.ui.act_file_disconnect.setEnabled(False)
        self.ui.act_file_disconnect.setVisible(False)

        self.ui.label_app.setText("MOD Application v%s" % config["version"])

        # disable file menu
        self.ui.act_file_refresh.setEnabled(False)
        self.ui.act_file_inspect.setEnabled(False)

        # disable pedalboard menu
        self.ui.act_pedalboard_new.setEnabled(False)
        self.ui.act_pedalboard_open.setEnabled(False)
        self.ui.act_pedalboard_save.setEnabled(False)
        self.ui.act_pedalboard_save_as.setEnabled(False)
        self.ui.act_pedalboard_share.setEnabled(False)
        self.ui.menu_Pedalboard.setEnabled(False)

        # disable presets menu
        self.ui.act_presets_new.setEnabled(False)
        self.ui.act_presets_save.setEnabled(False)
        self.ui.act_presets_save_as.setEnabled(False)
        self.ui.menu_Presets.setEnabled(False)

        # initial stopped state
        self.slot_backendFinished(-1, -1)

        # Qt needs this so it properly creates & resizes the webview
        self.ui.stackedwidget.setCurrentIndex(1)
        self.ui.stackedwidget.setCurrentIndex(0)

        # FIXME
        #self.ui.act_backend_stop.setVisible(False)
        #self.ui.act_backend_restart.setVisible(False)

        # ----------------------------------------------------------------------------------------------------
        # Set up GUI (special stuff for Mac OS)

        if MACOS:
            self.ui.act_file_quit.setMenuRole(QAction.QuitRole)
            self.ui.act_settings_configure.setMenuRole(QAction.PreferencesRole)
            self.ui.act_help_about.setMenuRole(QAction.AboutRole)
            #self.ui.menu_Settings.setTitle("Panels")
            #self.ui.menu_Help.hide()

        # ----------------------------------------------------------------------------------------------------
        # Load Settings

        self.loadSettings(True)

        # ----------------------------------------------------------------------------------------------------
        # Connect actions to functions

        self.SIGUSR1.connect(self.slot_handleSIGUSR1)
        self.SIGTERM.connect(self.slot_handleSIGTERM)

        self.fProccessBackend.error.connect(self.slot_backendError)
        self.fProccessBackend.started.connect(self.slot_backendStarted)
        self.fProccessBackend.finished.connect(self.slot_backendFinished)
        self.fProccessBackend.readyRead.connect(self.slot_backendRead)

        self.fWebServerThread.running.connect(self.slot_webServerRunning)
        self.fWebServerThread.finished.connect(self.slot_webServerFinished)

        self.ui.act_file_refresh.triggered.connect(self.slot_fileRefresh)
        self.ui.act_file_inspect.triggered.connect(self.slot_fileInspect)

        self.ui.act_settings_configure.triggered.connect(self.slot_configure)

        self.ui.act_help_about.triggered.connect(self.slot_about)
        self.ui.act_help_project.triggered.connect(self.slot_showProject)
        self.ui.act_help_website.triggered.connect(self.slot_showWebsite)

        self.ui.b_start.clicked.connect(self.slot_backendStart)
        self.ui.b_configure.clicked.connect(self.slot_configure)
        self.ui.b_about.clicked.connect(self.slot_about)

        # force our custom refresh
        webReloadAction = self.ui.webpage.action(QWebPage.Reload)
        webReloadAction.triggered.disconnect()
        webReloadAction.triggered.connect(self.slot_fileRefresh)

        # ----------------------------------------------------------------------------------------------------
        # Final setup

        self.setProperWindowTitle()
        SESSION.setupApp(self._pedal_changed_callback)

        if not "--no-autostart" in sys.argv:
            QTimer.singleShot(0, self.slot_backendStart)

        QTimer.singleShot(1, self.fixWebViewSize)

    def __del__(self):
        self.stopAndWaitForWebServer()
        self.stopAndWaitForBackend()

    def _pedal_changed_callback(self, ok, bundlepath, title):
        #self.fCurrentBundle = bundlepath
        self.fCurrentTitle = title or ""
        #self.updatePresetsMenu()
        self.setProperWindowTitle()

    # --------------------------------------------------------------------------------------------------------
    # Files (menu actions)

    @pyqtSlot()
    def slot_fileRefresh(self):
        if self.fWebFrame is None:
            return

        self.ui.label_progress.setText(self.tr("Refreshing UI..."))
        self.ui.stackedwidget.setCurrentIndex(0)
        QTimer.singleShot(0, self.ui.webview.reload)

    @pyqtSlot()
    def slot_fileInspect(self):
        self.ui.webinspector.show()

    # --------------------------------------------------------------------------------------------------------
    # Settings (menu actions)

    @pyqtSlot()
    def slot_configure(self):
        dialog = SettingsWindow(self, True)
        if not dialog.exec_():
            return

        self.loadSettings(False)

    # --------------------------------------------------------------------------------------------------------
    # About (menu actions)

    @pyqtSlot()
    def slot_about(self):
        QMessageBox.about(
            self, self.tr("About"),
            self.tr("""
            <b>MOD Desktop Application</b><br/>
            <br/>
            A software to have the complete MOD environment running in your desktop.<br/>
            (C) 2015-2016 - The MOD Team<br/>
            <br/>
            Publications, products, content or services referenced herein or on the website are the exclusive trademarks or servicemarks of MOD.<br/>
            Other product and company names mentioned in the site may be the trademarks of their respective owners.<br/>
            <br/>
            All software is available under the <a href="https://www.gnu.org/licenses/gpl-2.0.html">GPL license</a>.<br/>
        """))

    @pyqtSlot()
    def slot_showProject(self):
        QDesktopServices.openUrl(QUrl("https://github.com/moddevices/mod-app"))

    @pyqtSlot()
    def slot_showWebsite(self):
        QDesktopServices.openUrl(QUrl("http://moddevices.com/"))

    # --------------------------------------------------------------------------------------------------------
    # Backend (menu actions)

    @pyqtSlot()
    def slot_backendInformation(self):
        table = """
        <table><tr>
        <td> MOD-UI port:     <td></td> %s </td>
        </tr></table>
        """ % (config["port"], )
        QMessageBox.information(self, self.tr("information"), table)

    @pyqtSlot()
    def slot_backendStart(self):
        if self.fProccessBackend.state() != QProcess.NotRunning:
            print("slot_backendStart ignored")
            return

        print("slot_backendStart in progress...")

        hostPath = self.fSavedSettings[MOD_KEY_HOST_PATH]
        if hostPath.endswith("ingen"):
            hostPath = MOD_DEFAULT_HOST_PATH

        hostArgs = ["-p", "5555", "-f", "5556"]
        if self.fSavedSettings[MOD_KEY_HOST_VERBOSE]:
            hostArgs.append("-v")
        else:
            hostArgs.append("-n")

        self.fProccessBackend.start(hostPath, hostArgs)

    @pyqtSlot()
    def slot_backendStop(self):
        #if self.fPluginCount > 0:
        #if not forced:
        #ask = QMessageBox.question(self, self.tr("Warning"), self.tr("There are still some plugins loaded, you need to remove them to stop the engine.\n"
        #"Do you want to do this now?"),
        #QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        #if ask != QMessageBox.Yes:
        #return

        #self.removeAllPlugins()
        #self.host.set_engine_about_to_close()
        #self.host.remove_all_plugins()

        # testing red color for server stopped
        self.ui.webview.blockSignals(True)
        self.ui.webview.setHtml("<html><body bgcolor='green'></body></html>")
        self.ui.webview.blockSignals(False)

        self.stopAndWaitForWebServer()
        self.stopAndWaitForBackend()

    @pyqtSlot()
    def slot_backendRestart(self):
        self.slot_backendStop()
        self.slot_backendStart()

    # --------------------------------------------------------------------------------------------------------

    @pyqtSlot()
    def slot_backendStarted(self):
        self.ui.act_backend_start.setEnabled(False)
        self.ui.act_backend_stop.setEnabled(True)
        self.ui.act_backend_restart.setEnabled(True)
        self.ui.w_buttons.setEnabled(False)
        self.ui.label_progress.setText(self.tr("Loading backend..."))

    @pyqtSlot(int, QProcess.ExitStatus)
    def slot_backendFinished(self, exitCode, exitStatus):
        self.fFirstBackendInit = False
        self.fStoppingBackend = False
        self.ui.act_backend_start.setEnabled(True)
        self.ui.act_backend_stop.setEnabled(False)
        self.ui.act_backend_restart.setEnabled(False)
        self.ui.w_buttons.setEnabled(True)
        self.ui.label_progress.setText("")
        self.ui.stackedwidget.setCurrentIndex(0)

        # stop webserver
        self.stopAndWaitForWebServer()

    @pyqtSlot(QProcess.ProcessError)
    def slot_backendError(self, error):
        firstBackendInit = self.fFirstBackendInit
        self.fFirstBackendInit = False

        # stop webserver
        self.stopAndWaitForWebServer()

        # crashed while stopping, ignore
        if error == QProcess.Crashed and self.fStoppingBackend:
            return

        errorStr = self.tr("Could not start host backend.\n"
                           ) + self.getProcessErrorAsString(error)
        qWarning(errorStr)

        # don't show error if this is the first time starting the host or using live-iso
        if firstBackendInit:
            return

        # show the error message
        QMessageBox.critical(self, self.tr("Error"), errorStr)

    @pyqtSlot()
    def slot_backendRead(self):
        #if self.fProccessBackend.state() != QProcess.Running:
        #return

        for line in str(
                self.fProccessBackend.readAllStandardOutput().trimmed(),
                encoding="utf-8",
                errors="ignore").strip().split("\n"):
            line = line.replace("\x1b[0m",
                                "").replace("\x1b[0;31m",
                                            "").replace("\x1b[0;33m",
                                                        "").strip()
            if not line:
                continue

            if self.fSavedSettings[MOD_KEY_HOST_VERBOSE]:
                print("BACKEND:", line)

            if line == "mod-host ready!" or line == "mod-host is running.":
                QTimer.singleShot(0, self.slot_backendStartPhase2)
            #elif "Listening on socket " in line:
            #QTimer.singleShot(1000, self.slot_ingenStarted)
            ##elif "Activated Jack client " in line:
            ##QTimer.singleShot(1000, self.fWebServerThread.start)
            #elif "Failed to create UNIX socket" in line or "Could not activate Jack client" in line:
            ## need to wait for ingen to create sockets so it can delete them on termination
            #QTimer.singleShot(1000, self.slot_ingenStartError)

    @pyqtSlot()
    def slot_backendStartPhase2(self):
        if self.fProccessBackend.state() == QProcess.NotRunning:
            return

        if not self.fNeedsSessionReconnect:
            # we'll need it for next time
            self.fNeedsSessionReconnect = True
        else:
            # we need it now
            SESSION.reconnectApp()

        self.fWebServerThread.start()

    @pyqtSlot()
    def slot_backendStartError(self):
        self.stopAndWaitForBackend()
        self.slot_backendError(-2)

    # --------------------------------------------------------------------------------------------------------
    # Web Server

    @pyqtSlot()
    def slot_webServerRunning(self):
        self.ui.webview.loadStarted.connect(self.slot_webviewLoadStarted)
        self.ui.webview.loadProgress.connect(self.slot_webviewLoadProgress)
        self.ui.webview.loadFinished.connect(self.slot_webviewLoadFinished)

        print("webserver running with URL:", config["addr"])
        self.ui.webview.load(QUrl(config["addr"]))

    @pyqtSlot()
    def slot_webServerFinished(self):
        try:
            self.ui.webview.loadStarted.disconnect(
                self.slot_webviewLoadStarted)
            self.ui.webview.loadProgress.disconnect(
                self.slot_webviewLoadProgress)
            self.ui.webview.loadFinished.disconnect(
                self.slot_webviewLoadFinished)
        except:
            pass

        print("webserver finished")
        # testing red color for server finished
        self.ui.webview.blockSignals(True)
        self.ui.webview.setHtml("<html><body bgcolor='red'></body></html>")
        self.ui.webview.blockSignals(False)

    # --------------------------------------------------------------------------------------------------------
    # Web View

    @pyqtSlot()
    def slot_webviewLoadStarted(self):
        self.ui.label_progress.setText(self.tr("Loading UI..."))
        print("load started")

    @pyqtSlot(int)
    def slot_webviewLoadProgress(self, progress):
        self.ui.label_progress.setText(self.tr("Loading UI... %i%%" %
                                               progress))

    @pyqtSlot(bool)
    def slot_webviewLoadFinished(self, ok):
        self.ui.webview.loadStarted.disconnect(self.slot_webviewLoadStarted)
        self.ui.webview.loadProgress.disconnect(self.slot_webviewLoadProgress)
        self.ui.webview.loadFinished.disconnect(self.slot_webviewLoadFinished)

        if ok:
            # message
            self.ui.label_progress.setText(self.tr("Loading UI... finished!"))

            # enable file menu
            self.ui.act_file_refresh.setEnabled(True)
            self.ui.act_file_inspect.setEnabled(True)

            # for js evaulation
            self.fWebFrame = self.ui.webpage.currentFrame()

            # postpone app stuff
            QTimer.singleShot(100, self.slot_webviewPostFinished)

        else:
            # message
            self.ui.label_progress.setText(self.tr("Loading UI... failed!"))

            # disable file menu
            self.ui.act_file_refresh.setEnabled(False)
            self.ui.act_file_inspect.setEnabled(False)

            # disable pedalboard menu
            self.ui.act_pedalboard_new.setEnabled(False)
            self.ui.act_pedalboard_open.setEnabled(False)
            self.ui.act_pedalboard_save.setEnabled(False)
            self.ui.act_pedalboard_save_as.setEnabled(False)
            self.ui.act_pedalboard_share.setEnabled(False)
            self.ui.menu_Pedalboard.setEnabled(False)

            # stop js evaulation
            self.fWebFrame = None

            # stop backend&server
            self.stopAndWaitForWebServer()
            self.stopAndWaitForBackend()

        print("load finished")

    @pyqtSlot()
    def slot_webviewPostFinished(self):
        if self.fNextBundle:
            bundle = self.fNextBundle
            self.fNextBundle = ""
            self.fWebFrame.evaluateJavaScript(
                "desktop.loadPedalboard(\"%s\")" % bundle)

        QTimer.singleShot(0, self.slot_webviewPostFinished2)

    @pyqtSlot()
    def slot_webviewPostFinished2(self):
        self.ui.stackedwidget.setCurrentIndex(1)

    # --------------------------------------------------------------------------------------------------------
    # Settings

    def saveSettings(self):
        settings = QSettings()
        settings.setValue("Geometry", self.saveGeometry())

    def loadSettings(self, firstTime):
        qsettings = QSettings()
        websettings = self.ui.webview.settings()

        self.fSavedSettings = {
            # Main
            MOD_KEY_MAIN_PROJECT_FOLDER:
            qsettings.value(MOD_KEY_MAIN_PROJECT_FOLDER,
                            MOD_DEFAULT_MAIN_PROJECT_FOLDER,
                            type=str),
            MOD_KEY_MAIN_REFRESH_INTERVAL:
            qsettings.value(MOD_KEY_MAIN_REFRESH_INTERVAL,
                            MOD_DEFAULT_MAIN_REFRESH_INTERVAL,
                            type=int),
            # Host
            MOD_KEY_HOST_VERBOSE:
            qsettings.value(MOD_KEY_HOST_VERBOSE,
                            MOD_DEFAULT_HOST_VERBOSE,
                            type=bool),
            MOD_KEY_HOST_PATH:
            qsettings.value(MOD_KEY_HOST_PATH, MOD_DEFAULT_HOST_PATH,
                            type=str),
            # WebView
            MOD_KEY_WEBVIEW_INSPECTOR:
            qsettings.value(MOD_KEY_WEBVIEW_INSPECTOR,
                            MOD_DEFAULT_WEBVIEW_INSPECTOR,
                            type=bool),
            MOD_KEY_WEBVIEW_VERBOSE:
            qsettings.value(MOD_KEY_WEBVIEW_VERBOSE,
                            MOD_DEFAULT_WEBVIEW_VERBOSE,
                            type=bool),
            MOD_KEY_WEBVIEW_SHOW_INSPECTOR:
            qsettings.value(MOD_KEY_WEBVIEW_SHOW_INSPECTOR,
                            MOD_DEFAULT_WEBVIEW_SHOW_INSPECTOR,
                            type=bool)
        }

        inspectorEnabled = self.fSavedSettings[MOD_KEY_WEBVIEW_INSPECTOR]

        websettings.setAttribute(QWebSettings.DeveloperExtrasEnabled,
                                 inspectorEnabled)

        if firstTime:
            if qsettings.contains("Geometry"):
                self.restoreGeometry(qsettings.value("Geometry", ""))
            else:
                self.setWindowState(self.windowState() | Qt.WindowMaximized)

            if inspectorEnabled and self.fSavedSettings[
                    MOD_KEY_WEBVIEW_SHOW_INSPECTOR]:
                QTimer.singleShot(1000, self.ui.webinspector.show)

        self.ui.act_file_inspect.setVisible(inspectorEnabled)

        if self.fIdleTimerId != 0:
            self.killTimer(self.fIdleTimerId)

        self.fIdleTimerId = self.startTimer(
            self.fSavedSettings[MOD_KEY_MAIN_REFRESH_INTERVAL])

    # --------------------------------------------------------------------------------------------------------
    # Misc

    @pyqtSlot()
    def slot_handleSIGUSR1(self):
        print("Got SIGUSR1 -> Saving project now")
        self.slot_pedalboardSave()

    @pyqtSlot()
    def slot_handleSIGTERM(self):
        print("Got SIGTERM -> Closing now")
        self.close()

    # --------------------------------------------------------------------------------------------------------
    # Qt events

    def closeEvent(self, event):
        if self.fIdleTimerId != 0:
            self.killTimer(self.fIdleTimerId)
            self.fIdleTimerId = 0

        self.saveSettings()
        self.slot_backendStop()

        QMainWindow.closeEvent(self, event)

        # Needed in case the web inspector is still alive
        #self.ui.webinspector.close()
        QApplication.instance().quit()

    def timerEvent(self, event):
        if event.timerId() == self.fIdleTimerId:
            pass

        QMainWindow.timerEvent(self, event)

    def resizeEvent(self, event):
        QMainWindow.resizeEvent(self, event)
        self.fixWebViewSize()

    # --------------------------------------------------------------------------------------------------------
    # Internal stuff

    def getProcessErrorAsString(self, error):
        if error == -2:
            return self.tr("Ingen failed to create UNIX socket.")
        if error == QProcess.FailedToStart:
            return self.tr("Process failed to start.")
        if error == QProcess.Crashed:
            return self.tr("Process crashed.")
        if error == QProcess.Timedout:
            return self.tr("Process timed out.")
        if error == QProcess.WriteError:
            return self.tr("Process write error.")
        return self.tr("Unkown error.")

    def fixWebViewSize(self):
        if self.ui.stackedwidget.currentIndex() == 1:
            return

        size = self.ui.swp_intro.size()
        self.ui.swp_webview.resize(size)
        self.ui.webview.resize(size)
        self.ui.webpage.setViewportSize(size)

    def stopAndWaitForBackend(self):
        SESSION.host.close_jack()

        if self.fProccessBackend.state() == QProcess.NotRunning:
            return

        self.fStoppingBackend = True
        self.fProccessBackend.terminate()
        if not self.fProccessBackend.waitForFinished(2000):
            qWarning("Backend failed top stop cleanly, forced kill")
            self.fProccessBackend.kill()

    def stopAndWaitForWebServer(self):
        if not self.fWebServerThread.isRunning():
            return

        if not self.fWebServerThread.stopWait():
            qWarning(
                "WebServer Thread failed top stop cleanly, forced terminate")
            self.fWebServerThread.terminate()

    def setProperWindowTitle(self):
        title = "MOD Application"

        if self.fCurrentTitle:
            title += " - %s" % self.fCurrentTitle

        self.setWindowTitle(title)
Пример #4
0
class ExternalPythonShell(ExternalShellBase):
    """External Shell widget: execute Python script in a separate process"""
    SHELL_CLASS = ExtPythonShellWidget
    def __init__(self, parent=None, fname=None, wdir=None, commands=[],
                 interact=False, debug=False, path=[],
                 ipython=False, arguments=None, stand_alone=True,
                 umd_enabled=True, umd_namelist=[], umd_verbose=True):
        self.namespacebrowser = None # namespace browser widget!
        
        self.fname = startup.__file__ if fname is None else fname
        
        self.stand_alone = stand_alone
        
        self.umd_enabled = umd_enabled
        self.umd_namelist = umd_namelist
        self.umd_verbose = umd_verbose
        
        self.namespacebrowser_button = None
        self.cwd_button = None
        self.terminate_button = None
        
        ExternalShellBase.__init__(self, parent, wdir,
                                   history_filename='.history.py')

        self.nsb_timer = QTimer(self) # Namespace browser auto-refresh timer
        self.nsb_timer.setInterval(3000)
        
        if arguments is not None:
            assert isinstance(arguments, basestring)
            self.arguments = arguments
        
        self.ipython = ipython
        if self.ipython:
            interact = False
        
        self.shell.set_externalshell(self)

        self.toggle_globals_explorer(False)
        self.interact_action.setChecked(interact)
        self.debug_action.setChecked(debug)
        
        self.monitor_socket = None
        self.interpreter = fname is None
        
        if self.interpreter:
            self.terminate_button.hide()
        
        self.commands = ["import sys", "sys.path.insert(0, '')"] + commands
        
        # Additional python path list
        self.path = path
        
    def closeEvent(self, event):
        ExternalShellBase.closeEvent(self, event)
        self.disconnect(self.nsb_timer, SIGNAL("timeout()"),
                        self.namespacebrowser.refresh_table)
        
    def get_toolbar_buttons(self):
        ExternalShellBase.get_toolbar_buttons(self)
        if self.namespacebrowser_button is None and self.stand_alone:
            self.namespacebrowser_button = create_toolbutton(self,
                          get_icon('dictedit.png'), self.tr("Variables"),
                          tip=self.tr("Show/hide global variables explorer"),
                          toggled=self.toggle_globals_explorer)
        if self.cwd_button is None:
            self.cwd_button = create_toolbutton(self,
                          get_std_icon('DirOpenIcon'), self.tr("Working directory"),
                          tip=self.tr("Set current working directory"),
                          triggered=self.set_current_working_directory)
        if self.terminate_button is None:
            self.terminate_button = create_toolbutton(self,
                          get_icon('terminate.png'), self.tr("Terminate"),
                          tip=self.tr("Attempts to terminate the process.\n"
                                      "The process may not exit as a result of "
                                      "clicking this button\n"
                                      "(it is given the chance to prompt "
                                      "the user for any unsaved files, etc)."))        
        buttons = [self.cwd_button]
        if self.namespacebrowser_button is not None:
            buttons.append(self.namespacebrowser_button)
        buttons += [self.run_button, self.options_button,
                    self.terminate_button, self.kill_button]
        return buttons

    def get_options_menu(self):
        self.interact_action = create_action(self, self.tr("Interact"))
        self.interact_action.setCheckable(True)
        self.debug_action = create_action(self, self.tr("Debug"))
        self.debug_action.setCheckable(True)
        self.args_action = create_action(self, self.tr("Arguments..."),
                                         triggered=self.get_arguments)
        return [self.interact_action, self.debug_action, self.args_action]

    def is_interpreter(self):
        """Return True if shellwidget is a Python/IPython interpreter"""
        return self.interpreter
        
    def set_namespacebrowser(self, namespacebrowser):
        """Set namespace browser *widget*"""
        self.namespacebrowser = namespacebrowser
        
    def get_shell_widget(self):
        if self.stand_alone:
            self.namespacebrowser = NamespaceBrowser(self)
            self.namespacebrowser.set_shellwidget(self)
            self.connect(self.namespacebrowser, SIGNAL('collapse()'),
                         lambda: self.toggle_globals_explorer(False))
            # Shell splitter
            self.splitter = splitter = QSplitter(Qt.Vertical, self)
            self.connect(self.splitter, SIGNAL('splitterMoved(int, int)'),
                         self.splitter_moved)
            splitter.addWidget(self.shell)
            splitter.setCollapsible(0, False)
            splitter.addWidget(self.namespacebrowser)
            splitter.setStretchFactor(0, 1)
            splitter.setStretchFactor(1, 0)
            splitter.setHandleWidth(5)
            splitter.setSizes([2, 1])
            return splitter
        else:
            return self.shell
    
    def get_icon(self):
        return get_icon('python.png')

    def set_buttons_runnning_state(self, state):
        ExternalShellBase.set_buttons_runnning_state(self, state)
        self.interact_action.setEnabled(not state and not self.interpreter)
        self.debug_action.setEnabled(not state and not self.interpreter)
        self.args_action.setEnabled(not state and
                                    (not self.interpreter or self.ipython))
        if state:
            if self.arguments:
                argstr = self.tr("Arguments: %1").arg(self.arguments)
            else:
                argstr = self.tr("No argument")
        else:
            argstr = self.tr("Arguments...")
        self.args_action.setText(argstr)
        self.terminate_button.setEnabled(state)
        if not state:
            self.toggle_globals_explorer(False)
        self.cwd_button.setEnabled(state)
        if self.namespacebrowser_button is not None:
            self.namespacebrowser_button.setEnabled(state)
    
    def create_process(self):
        self.shell.clear()
            
        self.process = QProcess(self)
        if self.ipython:
            self.process.setProcessChannelMode(QProcess.MergedChannels)
        else:
            self.process.setProcessChannelMode(QProcess.SeparateChannels)
        self.connect(self.shell, SIGNAL("wait_for_ready_read()"),
                     lambda: self.process.waitForReadyRead(250))
        
        # Working directory
        if self.wdir is not None:
            self.process.setWorkingDirectory(self.wdir)

        #-------------------------Python specific-------------------------------
        # Python arguments
        p_args = ['-u']
        if self.interact_action.isChecked():
            p_args.append('-i')
        if self.debug_action.isChecked():
            p_args.extend(['-m', 'pdb'])
        if os.name == 'nt':
            # When calling pdb on Windows, one has to double the backslashes 
            # to help Python escaping these characters (otherwise, for example,
            # '\t' will be interpreted as a tabulation):
            p_args.append(self.fname.replace(os.sep, os.sep*2))
        else:
            p_args.append(self.fname)
        
        env = self.process.systemEnvironment()
        
        # Monitor
        env.append('SHELL_ID=%s' % id(self))
        from spyderlib.widgets.externalshell.monitor import start_server
        server, port = start_server()
        self.notification_thread = server.register(str(id(self)), self)
        self.connect(self.notification_thread, SIGNAL('refresh()'),
                     self.namespacebrowser.refresh_table)
        env.append('SPYDER_PORT=%d' % port)
        
        # Python init commands (interpreter only)
        if self.commands and self.interpreter:
            env.append('PYTHONINITCOMMANDS=%s' % ';'.join(self.commands))
            self.process.setEnvironment(env)
        
        # User Module Deleter
        if self.interpreter:
            env.append('UMD_ENABLED=%r' % self.umd_enabled)
            env.append('UMD_NAMELIST=%s' % ','.join(self.umd_namelist))
            env.append('UMD_VERBOSE=%r' % self.umd_verbose)
        
        # IPython related configuration
        if self.ipython:
            env.append('IPYTHON=True')
            # Do not call msvcrt.getch in IPython.genutils.page_more:
            env.append('TERM=emacs')
            
        pathlist = []

        # Fix encoding with custom "sitecustomize.py"
        scpath = osp.dirname(osp.abspath(__file__))
        pathlist.append(scpath)
        
        # Adding Spyder path
        pathlist += self.path
        
        # Adding path list to PYTHONPATH environment variable
        add_pathlist_to_PYTHONPATH(env, pathlist)
        
        self.process.setEnvironment(env)
        #-------------------------Python specific-------------------------------
            
        if self.arguments:
            p_args.extend( self.arguments.split(' ') )
                        
        self.connect(self.process, SIGNAL("readyReadStandardOutput()"),
                     self.write_output)
        self.connect(self.process, SIGNAL("readyReadStandardError()"),
                     self.write_error)
        self.connect(self.process, SIGNAL("finished(int,QProcess::ExitStatus)"),
                     self.finished)

        self.connect(self.terminate_button, SIGNAL("clicked()"),
                     self.process.terminate)
        self.connect(self.kill_button, SIGNAL("clicked()"),
                     self.process.kill)
        
        #-------------------------Python specific-------------------------------
        self.process.start(sys.executable, p_args)
        #-------------------------Python specific-------------------------------
            
        running = self.process.waitForStarted()
        self.set_running_state(running)
        if not running:
            QMessageBox.critical(self, self.tr("Error"),
                                 self.tr("Process failed to start"))
        else:
            self.shell.setFocus()
            self.emit(SIGNAL('started()'))
            self.connect(self.nsb_timer, SIGNAL("timeout()"),
                         self.namespacebrowser.auto_refresh)
            self.nsb_timer.start()
            
        return self.process
    
#===============================================================================
#    Input/Output
#===============================================================================
    def write_error(self):
        #---This is apparently necessary only on Windows (not sure though):
        #   emptying standard output buffer before writing error output
        self.process.setReadChannel(QProcess.StandardOutput)
        if self.process.waitForReadyRead(1):
            self.write_output()
            
        self.shell.write_error(self.get_stderr())
        QApplication.processEvents()
        
    def send_to_process(self, qstr):
        if not isinstance(qstr, QString):
            qstr = QString(qstr)
        if not qstr.endsWith('\n'):
            qstr.append('\n')
        self.process.write(qstr.toLocal8Bit())
        self.process.waitForBytesWritten(-1)
        
        # Eventually write prompt faster (when hitting Enter continuously)
        # -- necessary/working on Windows only:
        if os.name == 'nt':
            self.write_error()
        
    def keyboard_interrupt(self):
        communicate(self.monitor_socket, "thread.interrupt_main()")
            
#===============================================================================
#    Globals explorer
#===============================================================================
    def toggle_globals_explorer(self, state):
        if self.stand_alone:
            self.splitter.setSizes([1, 1 if state else 0])
            self.namespacebrowser_button.setChecked(state)
            if state:
                self.namespacebrowser.refresh_table()
        
    def splitter_moved(self, pos, index):
        self.namespacebrowser_button.setChecked( self.splitter.sizes()[1] )

#===============================================================================
#    Current working directory
#===============================================================================
    def set_current_working_directory(self):
        cwd = self.shell.get_cwd()
        self.emit(SIGNAL('redirect_stdio(bool)'), False)
        directory = QFileDialog.getExistingDirectory(self,
                                             self.tr("Select directory"), cwd)
        if not directory.isEmpty():
            self.shell.set_cwd(unicode(directory))
        self.emit(SIGNAL('redirect_stdio(bool)'), True)
Пример #5
0
class HostWindow(QMainWindow):
    # signals
    SIGTERM = pyqtSignal()
    SIGUSR1 = pyqtSignal()

    # --------------------------------------------------------------------------------------------------------

    def __init__(self):
        QMainWindow.__init__(self)
        self.ui = Ui_HostWindow()
        self.ui.setupUi(self)

        # ----------------------------------------------------------------------------------------------------
        # Internal stuff

        # Current mod-ui title
        #self.fCurrentBundle = ""
        self.fCurrentTitle  = ""

        # Next bundle to load (done by startup arguments)
        self.fNextBundle = ""

        # first attempt of auto-start backend doesn't show an error
        self.fFirstBackendInit = True

        # need to call session reconnect after connecting the 1st time
        self.fNeedsSessionReconnect = False

        # Qt idle timer
        self.fIdleTimerId = 0

        # Qt web frame, used for evaluating javascript
        self.fWebFrame = None

        # to be filled with key-value pairs of current settings
        self.fSavedSettings = {}

        # List of pedalboards
        self.fPedalboards = get_all_pedalboards()

        # List of current-pedalboard presets
        self.fPresetMenuList = []

        # Process that runs the backend
        self.fProccessBackend = QProcess(self)
        self.fProccessBackend.setProcessChannelMode(QProcess.MergedChannels)
        self.fProccessBackend.setReadChannel(QProcess.StandardOutput)
        self.fStoppingBackend = False

        # Thread for managing the webserver
        self.fWebServerThread = WebServerThread(self)

        # ----------------------------------------------------------------------------------------------------
        # Set up GUI

        self.ui.webview = QWebView(self.ui.swp_webview)
        self.ui.webview.setMinimumWidth(980)
        self.ui.swp_webview.layout().addWidget(self.ui.webview)

        self.ui.webpage = HostWebPage(self)
        self.ui.webpage.setViewportSize(QSize(980, 600))
        self.ui.webview.setPage(self.ui.webpage)

        self.ui.webinspector = QWebInspector(None)
        self.ui.webinspector.resize(800, 600)
        self.ui.webinspector.setPage(self.ui.webpage)
        self.ui.webinspector.setVisible(False)

        self.ui.act_file_connect.setEnabled(False)
        self.ui.act_file_connect.setVisible(False)
        self.ui.act_file_disconnect.setEnabled(False)
        self.ui.act_file_disconnect.setVisible(False)

        self.ui.label_app.setText("MOD Application v%s" % config["version"])

        # disable file menu
        self.ui.act_file_refresh.setEnabled(False)
        self.ui.act_file_inspect.setEnabled(False)

        # disable pedalboard menu
        self.ui.act_pedalboard_new.setEnabled(False)
        self.ui.act_pedalboard_open.setEnabled(False)
        self.ui.act_pedalboard_save.setEnabled(False)
        self.ui.act_pedalboard_save_as.setEnabled(False)
        self.ui.act_pedalboard_share.setEnabled(False)
        self.ui.menu_Pedalboard.setEnabled(False)

        # disable presets menu
        self.ui.act_presets_new.setEnabled(False)
        self.ui.act_presets_save.setEnabled(False)
        self.ui.act_presets_save_as.setEnabled(False)
        self.ui.menu_Presets.setEnabled(False)

        # initial stopped state
        self.slot_backendFinished(-1, -1)

        # Qt needs this so it properly creates & resizes the webview
        self.ui.stackedwidget.setCurrentIndex(1)
        self.ui.stackedwidget.setCurrentIndex(0)

        # FIXME
        #self.ui.act_backend_stop.setVisible(False)
        #self.ui.act_backend_restart.setVisible(False)

        # ----------------------------------------------------------------------------------------------------
        # Set up GUI (special stuff for Mac OS)

        if MACOS:
            self.ui.act_file_quit.setMenuRole(QAction.QuitRole)
            self.ui.act_settings_configure.setMenuRole(QAction.PreferencesRole)
            self.ui.act_help_about.setMenuRole(QAction.AboutRole)
            #self.ui.menu_Settings.setTitle("Panels")
            #self.ui.menu_Help.hide()

        # ----------------------------------------------------------------------------------------------------
        # Set up GUI (special stuff for Live-MOD ISO)

        if USING_LIVE_ISO:
            self.ui.menubar.hide()
            self.ui.b_start.hide()
            self.ui.b_configure.hide()
            self.ui.b_about.hide()

        # ----------------------------------------------------------------------------------------------------
        # Load Settings

        self.loadSettings(True)

        # ----------------------------------------------------------------------------------------------------
        # Connect actions to functions

        self.SIGUSR1.connect(self.slot_handleSIGUSR1)
        self.SIGTERM.connect(self.slot_handleSIGTERM)

        self.fProccessBackend.error.connect(self.slot_backendError)
        self.fProccessBackend.started.connect(self.slot_backendStarted)
        self.fProccessBackend.finished.connect(self.slot_backendFinished)
        self.fProccessBackend.readyRead.connect(self.slot_backendRead)

        self.fWebServerThread.running.connect(self.slot_webServerRunning)
        self.fWebServerThread.finished.connect(self.slot_webServerFinished)

        self.ui.menu_Pedalboard.aboutToShow.connect(self.slot_pedalboardCheckOnline)

        self.ui.act_file_refresh.triggered.connect(self.slot_fileRefresh)
        self.ui.act_file_inspect.triggered.connect(self.slot_fileInspect)

        self.ui.act_backend_information.triggered.connect(self.slot_backendInformation)
        self.ui.act_backend_start.triggered.connect(self.slot_backendStart)
        self.ui.act_backend_stop.triggered.connect(self.slot_backendStop)
        self.ui.act_backend_restart.triggered.connect(self.slot_backendRestart)

        self.ui.act_pedalboard_new.triggered.connect(self.slot_pedalboardNew)
        self.ui.act_pedalboard_open.triggered.connect(self.slot_pedalboardOpen)
        self.ui.act_pedalboard_save.triggered.connect(self.slot_pedalboardSave)
        self.ui.act_pedalboard_save_as.triggered.connect(self.slot_pedalboardSaveAs)
        self.ui.act_pedalboard_share.triggered.connect(self.slot_pedalboardShare)

        self.ui.act_settings_configure.triggered.connect(self.slot_configure)

        self.ui.act_help_about.triggered.connect(self.slot_about)
        self.ui.act_help_project.triggered.connect(self.slot_showProject)
        self.ui.act_help_website.triggered.connect(self.slot_showWebsite)

        self.ui.b_start.clicked.connect(self.slot_backendStart)
        self.ui.b_configure.clicked.connect(self.slot_configure)
        self.ui.b_about.clicked.connect(self.slot_about)

        # force our custom refresh
        webReloadAction = self.ui.webpage.action(QWebPage.Reload)
        webReloadAction.triggered.disconnect()
        webReloadAction.triggered.connect(self.slot_fileRefresh)

        # ----------------------------------------------------------------------------------------------------
        # Final setup

        self.setProperWindowTitle()
        SESSION.setupApp(self._pedal_changed_callback)

        if not "--no-autostart" in sys.argv:
            QTimer.singleShot(0, self.slot_backendStart)

        QTimer.singleShot(1, self.fixWebViewSize)

    def __del__(self):
        self.stopAndWaitForWebServer()
        self.stopAndWaitForBackend()

    def _pedal_changed_callback(self, ok, bundlepath, title):
        #self.fCurrentBundle = bundlepath
        self.fCurrentTitle = title or ""
        #self.updatePresetsMenu()
        self.setProperWindowTitle()

    # --------------------------------------------------------------------------------------------------------
    # Files (menu actions)

    @pyqtSlot()
    def slot_fileRefresh(self):
        if self.fWebFrame is None:
            return

        self.ui.label_progress.setText(self.tr("Refreshing UI..."))
        self.ui.stackedwidget.setCurrentIndex(0)
        QTimer.singleShot(0, self.slot_fileRefreshPost)

    @pyqtSlot()
    def slot_fileRefreshPost(self):
        self.ui.webview.loadStarted.connect(self.slot_webviewLoadStarted)
        self.ui.webview.loadProgress.connect(self.slot_webviewLoadProgress)
        self.ui.webview.loadFinished.connect(self.slot_webviewLoadFinished)
        self.ui.webview.reload()

    @pyqtSlot()
    def slot_fileInspect(self):
        self.ui.webinspector.show()

    # --------------------------------------------------------------------------------------------------------
    # Pedalboard (menu actions)

    @pyqtSlot()
    def slot_pedalboardCheckOnline(self):
        if self.fWebFrame is None:
            return
        isOnline = self.fWebFrame.evaluateJavaScript("$('#mod-cloud').hasClass('logged')")
        if isOnline is None:
            return print("isOnline is None")
        self.ui.act_pedalboard_share.setEnabled(isOnline)

    @pyqtSlot()
    def slot_pedalboardNew(self):
        if self.fWebFrame is None:
            return

        self.fWebFrame.evaluateJavaScript("desktop.reset()")

    # --------------------------------------------------------------------------------------------------------

    @pyqtSlot()
    def slot_pedalboardOpen(self):
        if len(self.fPedalboards) == 0:
            return QMessageBox.information(self, self.tr("information"), "No pedalboards found")

        dialog = OpenPedalboardWindow(self, self.fPedalboards)

        if not dialog.exec_():
            return

        pedalboard = dialog.getSelectedURI()

        if not pedalboard:
            return QMessageBox.information(self, self.tr("information"), "Invalid pedalboard selected")

        try:
            bundle = get_bundle_dirname(pedalboard)
        except:
            return

        if self.fWebFrame is None:
            return

        self.fWebFrame.evaluateJavaScript("desktop.loadPedalboard(\"%s\")" % bundle)

    def openPedalboardLater(self, filename):
        try:
            self.fNextBundle   = QFileInfo(filename).absoluteFilePath()
            self.fCurrentTitle = get_pedalboard_info(self.fNextBundle)['name']
        except:
            self.fNextBundle   = ""
            self.fCurrentTitle = ""

    # --------------------------------------------------------------------------------------------------------

    @pyqtSlot()
    def slot_pedalboardSave(self, saveAs=False):
        if self.fWebFrame is None:
            return

        self.fWebFrame.evaluateJavaScript("desktop.saveCurrentPedalboard(%s)" % ("true" if saveAs else "false"))

    @pyqtSlot()
    def slot_pedalboardSaveAs(self):
        self.slot_pedalboardSave(True)

    # --------------------------------------------------------------------------------------------------------

    @pyqtSlot()
    def slot_pedalboardShare(self):
        if self.fWebFrame is None:
            return

        self.fWebFrame.evaluateJavaScript("desktop.shareCurrentPedalboard()")

    # --------------------------------------------------------------------------------------------------------
    # Presets (menu actions)

    @pyqtSlot()
    def slot_presetClicked(self):
        print(self.sender().data())

    # --------------------------------------------------------------------------------------------------------
    # Settings (menu actions)

    @pyqtSlot()
    def slot_configure(self):
        dialog = SettingsWindow(self, True)
        if not dialog.exec_():
            return

        self.loadSettings(False)

    # --------------------------------------------------------------------------------------------------------
    # About (menu actions)

    @pyqtSlot()
    def slot_about(self):
        QMessageBox.about(self, self.tr("About"), self.tr("""
            <b>MOD Desktop Application</b><br/>
            <br/>
            A software to have the complete MOD environment running in your desktop.<br/>
            (C) 2015-2016 - The MOD Team<br/>
            <br/>
            Publications, products, content or services referenced herein or on the website are the exclusive trademarks or servicemarks of MOD.<br/>
            Other product and company names mentioned in the site may be the trademarks of their respective owners.<br/>
            <br/>
            All software is available under the <a href="https://www.gnu.org/licenses/gpl-2.0.html">GPL license</a>.<br/>
        """))

    @pyqtSlot()
    def slot_showProject(self):
        QDesktopServices.openUrl(QUrl("https://github.com/moddevices/mod-app"))

    @pyqtSlot()
    def slot_showWebsite(self):
        QDesktopServices.openUrl(QUrl("http://moddevices.com/"))

    # --------------------------------------------------------------------------------------------------------
    # Backend (menu actions)

    @pyqtSlot()
    def slot_backendInformation(self):
        table = """
        <table><tr>
        <td> MOD-UI port:     <td></td> %s </td>
        </tr><tr>
        <td> Backend address: <td></td> %s </td>
        </tr></table>
        """ % (config["port"], "unix:///tmp/mod-app-%s.sock" % config["port"])
        QMessageBox.information(self, self.tr("information"), table)

    @pyqtSlot()
    def slot_backendStart(self):
        if self.fProccessBackend.state() != QProcess.NotRunning:
            print("slot_backendStart ignored")
            return

        print("slot_backendStart in progress...")

        if USING_LIVE_ISO:
            os.system("jack_wait -w")
            os.system("jack_load mod-monitor")

            hostPath = "jack_load"
            hostArgs = ["-w", "-a", "mod-host"]

        else:
            hostPath = self.fSavedSettings[MOD_KEY_HOST_PATH]
            if hostPath.endswith("ingen"):
                hostPath = MOD_DEFAULT_HOST_PATH

            hostArgs = ["-n"]

        self.fProccessBackend.start(hostPath, hostArgs)

    @pyqtSlot()
    def slot_backendStop(self, forced = False):
        #if self.fPluginCount > 0:
            #if not forced:
                #ask = QMessageBox.question(self, self.tr("Warning"), self.tr("There are still some plugins loaded, you need to remove them to stop the engine.\n"
                                                                            #"Do you want to do this now?"),
                                                                            #QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
                #if ask != QMessageBox.Yes:
                    #return

            #self.removeAllPlugins()
            #self.host.set_engine_about_to_close()
            #self.host.remove_all_plugins()

        # testing red color for server stopped
        self.ui.webview.blockSignals(True)
        self.ui.webview.setHtml("<html><body bgcolor='green'></body></html>")
        self.ui.webview.blockSignals(False)

        self.stopAndWaitForWebServer()
        self.stopAndWaitForBackend()

    @pyqtSlot()
    def slot_backendRestart(self):
        #self.ui.stackedwidget.setCurrentIndex(0)
        self.slot_backendStop()
        #QApplication.instance().processEvents()
        self.slot_backendStart()

    # --------------------------------------------------------------------------------------------------------

    @pyqtSlot()
    def slot_backendStarted(self):
        self.ui.act_backend_start.setEnabled(False)
        self.ui.act_backend_stop.setEnabled(True)
        self.ui.act_backend_restart.setEnabled(True)
        self.ui.w_buttons.setEnabled(False)
        self.ui.label_progress.setText(self.tr("Loading backend..."))

    @pyqtSlot(int, QProcess.ExitStatus)
    def slot_backendFinished(self, exitCode, exitStatus):
        self.fFirstBackendInit = False
        self.fStoppingBackend = False
        self.ui.act_backend_start.setEnabled(True)
        self.ui.act_backend_stop.setEnabled(False)
        self.ui.act_backend_restart.setEnabled(False)
        self.ui.w_buttons.setEnabled(True)
        self.ui.label_progress.setText("")
        self.ui.stackedwidget.setCurrentIndex(0)

        # stop webserver
        self.stopAndWaitForWebServer()

    @pyqtSlot(QProcess.ProcessError)
    def slot_backendError(self, error):
        firstBackendInit = self.fFirstBackendInit
        self.fFirstBackendInit = False

        # stop webserver
        self.stopAndWaitForWebServer()

        # crashed while stopping, ignore
        if error == QProcess.Crashed and self.fStoppingBackend:
            return

        errorStr = self.tr("Could not start host backend.\n") + self.getProcessErrorAsString(error)
        qWarning(errorStr)

        # don't show error if this is the first time starting the host or using live-iso
        if firstBackendInit or USING_LIVE_ISO:
            return

        # show the error message
        QMessageBox.critical(self, self.tr("Error"), errorStr)

    @pyqtSlot()
    def slot_backendRead(self):
        #if self.fProccessBackend.state() != QProcess.Running:
            #return

        for line in str(self.fProccessBackend.readAllStandardOutput().trimmed(), encoding="utf-8", errors="ignore").strip().split("\n"):
            line = line.replace("\x1b[0m","").replace("\x1b[0;31m","").replace("\x1b[0;33m","").strip()
            if not line:
                continue

            if self.fSavedSettings[MOD_KEY_HOST_VERBOSE]:
                print("BACKEND:", line)

            if line == "mod-host ready!" or line == "mod-host is running.":
                QTimer.singleShot(0, self.slot_backendStartPhase2)
            #elif "Listening on socket " in line:
                #QTimer.singleShot(1000, self.slot_ingenStarted)
            ##elif "Activated Jack client " in line:
                ##QTimer.singleShot(1000, self.fWebServerThread.start)
            #elif "Failed to create UNIX socket" in line or "Could not activate Jack client" in line:
                ## need to wait for ingen to create sockets so it can delete them on termination
                #QTimer.singleShot(1000, self.slot_ingenStartError)

    @pyqtSlot()
    def slot_backendStartPhase2(self):
        if self.fProccessBackend.state() == QProcess.NotRunning:
            return

        if not self.fNeedsSessionReconnect:
            # we'll need it for next time
            self.fNeedsSessionReconnect = True
        else:
            # we need it now
            SESSION.reconnectApp()

        self.fWebServerThread.start()

    @pyqtSlot()
    def slot_backendStartError(self):
        self.stopAndWaitForBackend()
        self.slot_backendError(-2)

    # --------------------------------------------------------------------------------------------------------
    # Web Server

    @pyqtSlot()
    def slot_webServerRunning(self):
        try:
            self.ui.webview.loadStarted.connect(self.slot_webviewLoadStarted)
            self.ui.webview.loadProgress.connect(self.slot_webviewLoadProgress)
            self.ui.webview.loadFinished.connect(self.slot_webviewLoadFinished)
        except:
            pass

        print("webserver running")
        self.ui.webview.load(QUrl(config["addr"]))

    @pyqtSlot()
    def slot_webServerFinished(self):
        try:
            self.ui.webview.loadStarted.connect(self.slot_webviewLoadStarted)
            self.ui.webview.loadProgress.connect(self.slot_webviewLoadProgress)
            self.ui.webview.loadFinished.connect(self.slot_webviewLoadFinished)
        except:
            pass

        print("webserver finished")
        # testing red color for server finished
        self.ui.webview.blockSignals(True)
        self.ui.webview.setHtml("<html><body bgcolor='red'></body></html>")
        self.ui.webview.blockSignals(False)

    # --------------------------------------------------------------------------------------------------------
    # Web View

    @pyqtSlot()
    def slot_webviewLoadStarted(self):
        self.ui.label_progress.setText(self.tr("Loading UI..."))
        print("load started")

    @pyqtSlot(int)
    def slot_webviewLoadProgress(self, progress):
        self.ui.label_progress.setText(self.tr("Loading UI... %i%%" % progress))

    @pyqtSlot(bool)
    def slot_webviewLoadFinished(self, ok):
        self.ui.webview.loadStarted.disconnect(self.slot_webviewLoadStarted)
        self.ui.webview.loadProgress.disconnect(self.slot_webviewLoadProgress)
        self.ui.webview.loadFinished.disconnect(self.slot_webviewLoadFinished)

        if ok:
            # message
            self.ui.label_progress.setText(self.tr("Loading UI... finished!"))

            # enable file menu
            self.ui.act_file_refresh.setEnabled(True)
            self.ui.act_file_inspect.setEnabled(True)

            # for js evaulation
            self.fWebFrame = self.ui.webpage.currentFrame()

            # postpone app stuff
            QTimer.singleShot(100, self.slot_webviewPostFinished)

        else:
            # message
            self.ui.label_progress.setText(self.tr("Loading UI... failed!"))

            # disable file menu
            self.ui.act_file_refresh.setEnabled(False)
            self.ui.act_file_inspect.setEnabled(False)

            # disable pedalboard menu
            self.ui.act_pedalboard_new.setEnabled(False)
            self.ui.act_pedalboard_open.setEnabled(False)
            self.ui.act_pedalboard_save.setEnabled(False)
            self.ui.act_pedalboard_save_as.setEnabled(False)
            self.ui.act_pedalboard_share.setEnabled(False)
            self.ui.menu_Pedalboard.setEnabled(False)

            # stop js evaulation
            self.fWebFrame = None

            # stop backend&server
            self.stopAndWaitForWebServer()
            self.stopAndWaitForBackend()

        print("load finished")

    @pyqtSlot()
    def slot_webviewPostFinished(self):
        if self.fNextBundle:
            bundle = self.fNextBundle
            self.fNextBundle = ""
            self.fWebFrame.evaluateJavaScript("desktop.loadPedalboard(\"%s\")" % bundle)

        QTimer.singleShot(0, self.slot_webviewPostFinished2)

    @pyqtSlot()
    def slot_webviewPostFinished2(self):
        self.ui.stackedwidget.setCurrentIndex(1)

    # --------------------------------------------------------------------------------------------------------
    # Settings

    def saveSettings(self):
        settings = QSettings()
        settings.setValue("Geometry", self.saveGeometry())

    def loadSettings(self, firstTime):
        qsettings   = QSettings()
        websettings = self.ui.webview.settings()

        self.fSavedSettings = {
            # Main
            MOD_KEY_MAIN_PROJECT_FOLDER:    qsettings.value(MOD_KEY_MAIN_PROJECT_FOLDER,    MOD_DEFAULT_MAIN_PROJECT_FOLDER,    type=str),
            MOD_KEY_MAIN_REFRESH_INTERVAL:  qsettings.value(MOD_KEY_MAIN_REFRESH_INTERVAL,  MOD_DEFAULT_MAIN_REFRESH_INTERVAL,  type=int),
            # Host
            MOD_KEY_HOST_VERBOSE:           qsettings.value(MOD_KEY_HOST_VERBOSE,           MOD_DEFAULT_HOST_VERBOSE,           type=bool),
            MOD_KEY_HOST_PATH:              qsettings.value(MOD_KEY_HOST_PATH,              MOD_DEFAULT_HOST_PATH,              type=str),
            # WebView
            MOD_KEY_WEBVIEW_INSPECTOR:      qsettings.value(MOD_KEY_WEBVIEW_INSPECTOR,      MOD_DEFAULT_WEBVIEW_INSPECTOR,      type=bool),
            MOD_KEY_WEBVIEW_VERBOSE:        qsettings.value(MOD_KEY_WEBVIEW_VERBOSE,        MOD_DEFAULT_WEBVIEW_VERBOSE,        type=bool),
            MOD_KEY_WEBVIEW_SHOW_INSPECTOR: qsettings.value(MOD_KEY_WEBVIEW_SHOW_INSPECTOR, MOD_DEFAULT_WEBVIEW_SHOW_INSPECTOR, type=bool)
        }

        inspectorEnabled = self.fSavedSettings[MOD_KEY_WEBVIEW_INSPECTOR] and not USING_LIVE_ISO

        websettings.setAttribute(QWebSettings.DeveloperExtrasEnabled, inspectorEnabled)

        if firstTime:
            if qsettings.contains("Geometry"):
                self.restoreGeometry(qsettings.value("Geometry", ""))
            else:
                self.setWindowState(self.windowState() | Qt.WindowMaximized)

            if inspectorEnabled and self.fSavedSettings[MOD_KEY_WEBVIEW_SHOW_INSPECTOR]:
                QTimer.singleShot(1000, self.ui.webinspector.show)

        self.ui.act_file_inspect.setVisible(inspectorEnabled)

        if self.fIdleTimerId != 0:
            self.killTimer(self.fIdleTimerId)

        self.fIdleTimerId = self.startTimer(self.fSavedSettings[MOD_KEY_MAIN_REFRESH_INTERVAL])

    # --------------------------------------------------------------------------------------------------------
    # Misc

    @pyqtSlot()
    def slot_handleSIGUSR1(self):
        print("Got SIGUSR1 -> Saving project now")
        self.slot_pedalboardSave()

    @pyqtSlot()
    def slot_handleSIGTERM(self):
        print("Got SIGTERM -> Closing now")
        self.close()

    # --------------------------------------------------------------------------------------------------------
    # Qt events

    def closeEvent(self, event):
        if self.fIdleTimerId != 0:
            self.killTimer(self.fIdleTimerId)
            self.fIdleTimerId = 0

        self.saveSettings()
        self.slot_backendStop(True)

        QMainWindow.closeEvent(self, event)

        # Needed in case the web inspector is still alive
        #self.ui.webinspector.close()
        QApplication.instance().quit()

    def timerEvent(self, event):
        if event.timerId() == self.fIdleTimerId:
            pass

        QMainWindow.timerEvent(self, event)

    def resizeEvent(self, event):
        QMainWindow.resizeEvent(self, event)
        self.fixWebViewSize()

    # --------------------------------------------------------------------------------------------------------
    # Internal stuff

    def getProcessErrorAsString(self, error):
        if error == -2:
            return self.tr("Ingen failed to create UNIX socket.")
        if error == QProcess.FailedToStart:
            return self.tr("Process failed to start.")
        if error == QProcess.Crashed:
            return self.tr("Process crashed.")
        if error == QProcess.Timedout:
            return self.tr("Process timed out.")
        if error == QProcess.WriteError:
            return self.tr("Process write error.")
        return self.tr("Unkown error.")

    def fixWebViewSize(self):
        if self.ui.stackedwidget.currentIndex() == 1:
            return

        size = self.ui.swp_intro.size()
        self.ui.swp_webview.resize(size)
        self.ui.webview.resize(size)
        self.ui.webpage.setViewportSize(size)

    def stopAndWaitForBackend(self):
        if self.fProccessBackend.state() == QProcess.NotRunning:
            return

        self.fStoppingBackend = True
        self.fProccessBackend.terminate()
        if not self.fProccessBackend.waitForFinished(2000):
            qWarning("Backend failed top stop cleanly, forced kill")
            self.fProccessBackend.kill()

    def stopAndWaitForWebServer(self):
        if not self.fWebServerThread.isRunning():
            return

        if not self.fWebServerThread.stopWait():
            qWarning("WebServer Thread failed top stop cleanly, forced terminate")
            self.fWebServerThread.terminate()

    def setProperWindowTitle(self):
        title = "MOD Application"

        if self.fCurrentTitle:
            title += " - %s" % self.fCurrentTitle

        self.setWindowTitle(title)
Пример #6
0
class PylintWidget(QWidget):
    """
    Pylint widget
    """
    DATAPATH = get_conf_path('.pylint.results')
    VERSION = '1.0.2'
    
    def __init__(self, parent, max_entries=100):
        QWidget.__init__(self, parent)
        
        self.output = None
        self.error_output = None
        
        self.max_entries = max_entries
        self.data = [self.VERSION]
        if osp.isfile(self.DATAPATH):
            try:
                data = cPickle.load(file(self.DATAPATH))
                if data[0] == self.VERSION:
                    self.data = data
            except EOFError:
                pass

        self.filecombo = PythonModulesComboBox(self)
        if self.data:
            self.remove_obsolete_items()
            self.filecombo.addItems(self.get_filenames())
        
        self.start_button = create_toolbutton(self, get_icon('run.png'),
                                    translate('Pylint', "Analyze"),
                                    tip=translate('Pylint', "Run analysis"),
                                    triggered=self.start)
        self.stop_button = create_toolbutton(self, get_icon('terminate.png'),
                                    translate('Pylint', "Stop"),
                                    tip=translate('Pylint',
                                                  "Stop current analysis"))
        self.connect(self.filecombo, SIGNAL('valid(bool)'),
                     self.start_button.setEnabled)
        self.connect(self.filecombo, SIGNAL('valid(bool)'), self.show_data)

        browse_button = create_toolbutton(self, get_icon('fileopen.png'),
                               tip=translate('Pylint', 'Select Python script'),
                               triggered=self.select_file)

        self.ratelabel = QLabel()
        self.datelabel = QLabel()
        self.log_button = create_toolbutton(self, get_icon('log.png'),
                                    translate('Pylint', "Output"),
                                    tip=translate('Pylint',
                                                  "Complete Pylint output"),
                                    triggered=self.show_log)
        self.treewidget = ResultsTree(self)
        
        hlayout1 = QHBoxLayout()
        hlayout1.addWidget(self.filecombo)
        hlayout1.addWidget(browse_button)
        hlayout1.addWidget(self.start_button)
        hlayout1.addWidget(self.stop_button)

        hlayout2 = QHBoxLayout()
        hlayout2.addWidget(self.ratelabel)
        hlayout2.addStretch()
        hlayout2.addWidget(self.datelabel)
        hlayout2.addStretch()
        hlayout2.addWidget(self.log_button)
        
        layout = QVBoxLayout()
        layout.addLayout(hlayout1)
        layout.addLayout(hlayout2)
        layout.addWidget(self.treewidget)
        self.setLayout(layout)
        
        self.process = None
        self.set_running_state(False)
        
        if not is_pylint_installed():
            for widget in (self.treewidget, self.filecombo,
                           self.start_button, self.stop_button):
                widget.setDisabled(True)
            text = translate('Pylint', 'Please install <b>pylint</b>:')
            url = 'http://www.logilab.fr'
            text += ' <a href=%s>%s</a>' % (url, url)
            self.ratelabel.setText(text)
        else:
            self.show_data()
        
    def analyze(self, filename):
        if not is_pylint_installed():
            return
        filename = unicode(filename) # filename is a QString instance
        self.kill_if_running()
        index, _data = self.get_data(filename)
        if index is None:
            self.filecombo.addItem(filename)
            self.filecombo.setCurrentIndex(self.filecombo.count()-1)
        else:
            self.filecombo.setCurrentIndex(index)
        self.filecombo.selected()
        if self.filecombo.is_valid():
            self.start()
            
    def select_file(self):
        self.emit(SIGNAL('redirect_stdio(bool)'), False)
        filename = QFileDialog.getOpenFileName(self,
                      translate('Pylint', "Select Python script"), os.getcwdu(),
                      translate('Pylint', "Python scripts")+" (*.py ; *.pyw)")
        self.emit(SIGNAL('redirect_stdio(bool)'), False)
        if filename:
            self.analyze(filename)
            
    def remove_obsolete_items(self):
        """Removing obsolete items"""
        self.data = [self.VERSION] + \
                    [(filename, data) for filename, data in self.data[1:]
                     if is_module_or_package(filename)]
        
    def get_filenames(self):
        return [filename for filename, _data in self.data[1:]]
    
    def get_data(self, filename):
        filename = osp.abspath(filename)
        for index, (fname, data) in enumerate(self.data[1:]):
            if fname == filename:
                return index, data
        else:
            return None, None
            
    def set_data(self, filename, data):
        filename = osp.abspath(filename)
        index, _data = self.get_data(filename)
        if index is not None:
            self.data.pop(index)
        self.data.append( (filename, data) )
        self.save()
        
    def set_max_entries(self, max_entries):
        self.max_entries = max_entries
        self.save()
        
    def save(self):
        while len(self.data) > self.max_entries+1:
            self.data.pop(1)
        cPickle.dump(self.data, file(self.DATAPATH, 'w'))
        
    def show_log(self):
        if self.output:
            TextEditor(self.output, title=translate('Pylint', "Pylint output"),
                       readonly=True, size=(700, 500)).exec_()
        
    def start(self):
        filename = unicode(self.filecombo.currentText())
        
        self.process = QProcess(self)
        self.process.setProcessChannelMode(QProcess.SeparateChannels)
        self.process.setWorkingDirectory(osp.dirname(filename))
        self.connect(self.process, SIGNAL("readyReadStandardOutput()"),
                     self.read_output)
        self.connect(self.process, SIGNAL("readyReadStandardError()"),
                     lambda: self.read_output(error=True))
        self.connect(self.process, SIGNAL("finished(int,QProcess::ExitStatus)"),
                     self.finished)
        self.connect(self.stop_button, SIGNAL("clicked()"),
                     self.process.kill)
        
        self.output = ''
        self.error_output = ''
        p_args = [osp.basename(filename)]
        self.process.start(PYLINT_PATH, p_args)
        
        running = self.process.waitForStarted()
        self.set_running_state(running)
        if not running:
            QMessageBox.critical(self, translate('Pylint', "Error"),
                                 translate('Pylint', "Process failed to start"))
    
    def set_running_state(self, state=True):
        self.start_button.setEnabled(not state)
        self.stop_button.setEnabled(state)
        
    def read_output(self, error=False):
        if error:
            self.process.setReadChannel(QProcess.StandardError)
        else:
            self.process.setReadChannel(QProcess.StandardOutput)
        bytes = QByteArray()
        while self.process.bytesAvailable():
            if error:
                bytes += self.process.readAllStandardError()
            else:
                bytes += self.process.readAllStandardOutput()
        text = unicode( QString.fromLocal8Bit(bytes.data()) )
        if error:
            self.error_output += text
        else:
            self.output += text
        
    def finished(self):
        self.set_running_state(False)
        if not self.output:
            return
        
        # Convention, Refactor, Warning, Error
        results = {'C:': [], 'R:': [], 'W:': [], 'E:': []}
        txt_module = '************* Module '
        
        module = '' # Should not be needed - just in case something goes wrong
        for line in self.output.splitlines():
            if line.startswith(txt_module):
                # New module
                module = line[len(txt_module):]
                continue
            for prefix in results:
                if line.startswith(prefix):
                    break
            else:
                continue
            i1 = line.find(':')
            if i1 == -1:
                continue
            i2 = line.find(':', i1+1)
            if i2 == -1:
                continue
            line_nb = line[i1+1:i2].strip()
            if not line_nb:
                continue
            line_nb = int(line_nb)
            message = line[i2+1:]
            item = (module, line_nb, message)
            results[line[:i1+1]].append(item)                
            
        # Rate
        rate = None
        txt_rate = 'Your code has been rated at '
        i_rate = self.output.find(txt_rate)
        if i_rate > 0:
            i_rate_end = self.output.find('/10', i_rate)
            if i_rate_end > 0:
                rate = self.output[i_rate+len(txt_rate):i_rate_end]
        
        # Previous run
        previous = ''
        if rate is not None:
            txt_prun = 'previous run: '
            i_prun = self.output.find(txt_prun, i_rate_end)
            if i_prun > 0:
                i_prun_end = self.output.find('/10', i_prun)
                previous = self.output[i_prun+len(txt_prun):i_prun_end]
            
        
        filename = unicode(self.filecombo.currentText())
        self.set_data(filename, (time.localtime(), rate, previous, results))
        self.output = self.error_output + self.output
        self.show_data(justanalyzed=True)
        
    def kill_if_running(self):
        if self.process is not None:
            if self.process.state() == QProcess.Running:
                self.process.kill()
                self.process.waitForFinished()
        
    def show_data(self, justanalyzed=False):
        if not justanalyzed:
            self.output = None
        self.log_button.setEnabled(self.output is not None \
                                   and len(self.output) > 0)
        self.kill_if_running()
        filename = unicode(self.filecombo.currentText())
        if not filename:
            return
        
        _index, data = self.get_data(filename)
        if data is None:
            text = translate('Pylint', 'Source code has not been rated yet.')
            self.treewidget.clear()
            date_text = ''
        else:
            datetime, rate, previous_rate, results = data
            if rate is None:
                text = translate('Pylint', 'Analysis did not succeed '
                                           '(see output for more details).')
                self.treewidget.clear()
                date_text = ''
            else:
                text_style = "<span style=\'color: #444444\'><b>%s </b></span>"
                rate_style = "<span style=\'color: %s\'><b>%s</b></span>"
                prevrate_style = "<span style=\'color: #666666\'>%s</span>"
                color = "#FF0000"
                if float(rate) > 5.:
                    color = "#22AA22"
                elif float(rate) > 3.:
                    color = "#EE5500"
                text = translate('Pylint', 'Global evaluation:')
                text = (text_style % text)+(rate_style % (color,
                                                          ('%s/10' % rate)))
                if previous_rate:
                    text_prun = translate('Pylint', 'previous run:')
                    text_prun = ' (%s %s/10)' % (text_prun, previous_rate)
                    text += prevrate_style % text_prun
                self.treewidget.set_results(filename, results)
                date_text = text_style % time.strftime("%d %b %Y %H:%M",
                                                       datetime)
            
        self.ratelabel.setText(text)
        self.datelabel.setText(date_text)
Пример #7
0
class PylintWidget(QWidget):
    """
    Pylint widget
    """
    DATAPATH = get_conf_path('.pylint.results')
    VERSION = '1.0.2'

    def __init__(self, parent, max_entries=100):
        QWidget.__init__(self, parent)

        self.output = None
        self.error_output = None

        self.max_entries = max_entries
        self.data = [self.VERSION]
        if osp.isfile(self.DATAPATH):
            try:
                data = cPickle.load(file(self.DATAPATH))
                if data[0] == self.VERSION:
                    self.data = data
            except EOFError:
                pass

        self.filecombo = PythonModulesComboBox(self)
        if self.data:
            self.remove_obsolete_items()
            self.filecombo.addItems(self.get_filenames())

        self.start_button = create_toolbutton(self,
                                              get_icon('run.png'),
                                              translate('Pylint', "Analyze"),
                                              tip=translate(
                                                  'Pylint', "Run analysis"),
                                              triggered=self.start)
        self.stop_button = create_toolbutton(self,
                                             get_icon('terminate.png'),
                                             translate('Pylint', "Stop"),
                                             tip=translate(
                                                 'Pylint',
                                                 "Stop current analysis"))
        self.connect(self.filecombo, SIGNAL('valid(bool)'),
                     self.start_button.setEnabled)
        self.connect(self.filecombo, SIGNAL('valid(bool)'), self.show_data)

        browse_button = create_toolbutton(self,
                                          get_icon('fileopen.png'),
                                          tip=translate(
                                              'Pylint',
                                              'Select Python script'),
                                          triggered=self.select_file)

        self.ratelabel = QLabel()
        self.datelabel = QLabel()
        self.log_button = create_toolbutton(self,
                                            get_icon('log.png'),
                                            translate('Pylint', "Output"),
                                            tip=translate(
                                                'Pylint',
                                                "Complete Pylint output"),
                                            triggered=self.show_log)
        self.treewidget = ResultsTree(self)

        hlayout1 = QHBoxLayout()
        hlayout1.addWidget(self.filecombo)
        hlayout1.addWidget(browse_button)
        hlayout1.addWidget(self.start_button)
        hlayout1.addWidget(self.stop_button)

        hlayout2 = QHBoxLayout()
        hlayout2.addWidget(self.ratelabel)
        hlayout2.addStretch()
        hlayout2.addWidget(self.datelabel)
        hlayout2.addStretch()
        hlayout2.addWidget(self.log_button)

        layout = QVBoxLayout()
        layout.addLayout(hlayout1)
        layout.addLayout(hlayout2)
        layout.addWidget(self.treewidget)
        self.setLayout(layout)

        self.process = None
        self.set_running_state(False)

        if not is_pylint_installed():
            for widget in (self.treewidget, self.filecombo, self.start_button,
                           self.stop_button):
                widget.setDisabled(True)
            text = translate('Pylint', 'Please install <b>pylint</b>:')
            url = 'http://www.logilab.fr'
            text += ' <a href=%s>%s</a>' % (url, url)
            self.ratelabel.setText(text)
        else:
            self.show_data()

    def analyze(self, filename):
        if not is_pylint_installed():
            return
        filename = unicode(filename)  # filename is a QString instance
        self.kill_if_running()
        index, _data = self.get_data(filename)
        if index is None:
            self.filecombo.addItem(filename)
            self.filecombo.setCurrentIndex(self.filecombo.count() - 1)
        else:
            self.filecombo.setCurrentIndex(index)
        self.filecombo.selected()
        if self.filecombo.is_valid():
            self.start()

    def select_file(self):
        self.emit(SIGNAL('redirect_stdio(bool)'), False)
        filename = QFileDialog.getOpenFileName(
            self, translate('Pylint', "Select Python script"), os.getcwdu(),
            translate('Pylint', "Python scripts") + " (*.py ; *.pyw)")
        self.emit(SIGNAL('redirect_stdio(bool)'), False)
        if filename:
            self.analyze(filename)

    def remove_obsolete_items(self):
        """Removing obsolete items"""
        self.data = [self.VERSION] + \
                    [(filename, data) for filename, data in self.data[1:]
                     if is_module_or_package(filename)]

    def get_filenames(self):
        return [filename for filename, _data in self.data[1:]]

    def get_data(self, filename):
        filename = osp.abspath(filename)
        for index, (fname, data) in enumerate(self.data[1:]):
            if fname == filename:
                return index, data
        else:
            return None, None

    def set_data(self, filename, data):
        filename = osp.abspath(filename)
        index, _data = self.get_data(filename)
        if index is not None:
            self.data.pop(index)
        self.data.append((filename, data))
        self.save()

    def set_max_entries(self, max_entries):
        self.max_entries = max_entries
        self.save()

    def save(self):
        while len(self.data) > self.max_entries + 1:
            self.data.pop(1)
        cPickle.dump(self.data, file(self.DATAPATH, 'w'))

    def show_log(self):
        if self.output:
            TextEditor(self.output,
                       title=translate('Pylint', "Pylint output"),
                       readonly=True,
                       size=(700, 500)).exec_()

    def start(self):
        filename = unicode(self.filecombo.currentText())

        self.process = QProcess(self)
        self.process.setProcessChannelMode(QProcess.SeparateChannels)
        self.process.setWorkingDirectory(osp.dirname(filename))
        self.connect(self.process, SIGNAL("readyReadStandardOutput()"),
                     self.read_output)
        self.connect(self.process, SIGNAL("readyReadStandardError()"),
                     lambda: self.read_output(error=True))
        self.connect(self.process,
                     SIGNAL("finished(int,QProcess::ExitStatus)"),
                     self.finished)
        self.connect(self.stop_button, SIGNAL("clicked()"), self.process.kill)

        self.output = ''
        self.error_output = ''
        p_args = [osp.basename(filename)]
        self.process.start(PYLINT_PATH, p_args)

        running = self.process.waitForStarted()
        self.set_running_state(running)
        if not running:
            QMessageBox.critical(
                self, translate('Pylint', "Error"),
                translate('Pylint', "Process failed to start"))

    def set_running_state(self, state=True):
        self.start_button.setEnabled(not state)
        self.stop_button.setEnabled(state)

    def read_output(self, error=False):
        if error:
            self.process.setReadChannel(QProcess.StandardError)
        else:
            self.process.setReadChannel(QProcess.StandardOutput)
        bytes = QByteArray()
        while self.process.bytesAvailable():
            if error:
                bytes += self.process.readAllStandardError()
            else:
                bytes += self.process.readAllStandardOutput()
        text = unicode(QString.fromLocal8Bit(bytes.data()))
        if error:
            self.error_output += text
        else:
            self.output += text

    def finished(self):
        self.set_running_state(False)
        if not self.output:
            return

        # Convention, Refactor, Warning, Error
        results = {'C:': [], 'R:': [], 'W:': [], 'E:': []}
        txt_module = '************* Module '

        module = ''  # Should not be needed - just in case something goes wrong
        for line in self.output.splitlines():
            if line.startswith(txt_module):
                # New module
                module = line[len(txt_module):]
                continue
            for prefix in results:
                if line.startswith(prefix):
                    break
            else:
                continue
            i1 = line.find(':')
            if i1 == -1:
                continue
            i2 = line.find(':', i1 + 1)
            if i2 == -1:
                continue
            line_nb = line[i1 + 1:i2].strip()
            if not line_nb:
                continue
            line_nb = int(line_nb)
            message = line[i2 + 1:]
            item = (module, line_nb, message)
            results[line[:i1 + 1]].append(item)

        # Rate
        rate = None
        txt_rate = 'Your code has been rated at '
        i_rate = self.output.find(txt_rate)
        if i_rate > 0:
            i_rate_end = self.output.find('/10', i_rate)
            if i_rate_end > 0:
                rate = self.output[i_rate + len(txt_rate):i_rate_end]

        # Previous run
        previous = ''
        if rate is not None:
            txt_prun = 'previous run: '
            i_prun = self.output.find(txt_prun, i_rate_end)
            if i_prun > 0:
                i_prun_end = self.output.find('/10', i_prun)
                previous = self.output[i_prun + len(txt_prun):i_prun_end]

        filename = unicode(self.filecombo.currentText())
        self.set_data(filename, (time.localtime(), rate, previous, results))
        self.output = self.error_output + self.output
        self.show_data(justanalyzed=True)

    def kill_if_running(self):
        if self.process is not None:
            if self.process.state() == QProcess.Running:
                self.process.kill()
                self.process.waitForFinished()

    def show_data(self, justanalyzed=False):
        if not justanalyzed:
            self.output = None
        self.log_button.setEnabled(self.output is not None \
                                   and len(self.output) > 0)
        self.kill_if_running()
        filename = unicode(self.filecombo.currentText())
        if not filename:
            return

        _index, data = self.get_data(filename)
        if data is None:
            text = translate('Pylint', 'Source code has not been rated yet.')
            self.treewidget.clear()
            date_text = ''
        else:
            datetime, rate, previous_rate, results = data
            if rate is None:
                text = translate(
                    'Pylint', 'Analysis did not succeed '
                    '(see output for more details).')
                self.treewidget.clear()
                date_text = ''
            else:
                text_style = "<span style=\'color: #444444\'><b>%s </b></span>"
                rate_style = "<span style=\'color: %s\'><b>%s</b></span>"
                prevrate_style = "<span style=\'color: #666666\'>%s</span>"
                color = "#FF0000"
                if float(rate) > 5.:
                    color = "#22AA22"
                elif float(rate) > 3.:
                    color = "#EE5500"
                text = translate('Pylint', 'Global evaluation:')
                text = (text_style % text) + (rate_style % (color,
                                                            ('%s/10' % rate)))
                if previous_rate:
                    text_prun = translate('Pylint', 'previous run:')
                    text_prun = ' (%s %s/10)' % (text_prun, previous_rate)
                    text += prevrate_style % text_prun
                self.treewidget.set_results(filename, results)
                date_text = text_style % time.strftime("%d %b %Y %H:%M",
                                                       datetime)

        self.ratelabel.setText(text)
        self.datelabel.setText(date_text)
Пример #8
0
class ExternalPythonShell(ExternalShellBase):
    """External Shell widget: execute Python script in a separate process"""
    SHELL_CLASS = ExtPythonShellWidget
    def __init__(self, parent=None, fname=None, wdir=None,
                 interact=False, debug=False, path=[], python_args='',
                 ipykernel=False, arguments='', stand_alone=None,
                 umd_enabled=True, umd_namelist=[], umd_verbose=True,
                 pythonstartup=None, pythonexecutable=None,
                 monitor_enabled=True, mpl_patch_enabled=True,
                 mpl_backend=None, ets_backend='qt4', qt_api=None, pyqt_api=0,
                 install_qt_inputhook=True, ignore_sip_setapi_errors=False,
                 merge_output_channels=False, colorize_sys_stderr=False,
                 autorefresh_timeout=3000, autorefresh_state=True,
                 light_background=True, menu_actions=None,
                 show_buttons_inside=True, show_elapsed_time=True):

        assert qt_api in (None, 'pyqt', 'pyside')

        self.namespacebrowser = None # namespace browser widget!
        
        self.dialog_manager = DialogManager()
        
        self.stand_alone = stand_alone # stand alone settings (None: plugin)
        self.pythonstartup = pythonstartup
        self.pythonexecutable = pythonexecutable
        self.monitor_enabled = monitor_enabled
        self.mpl_patch_enabled = mpl_patch_enabled
        self.mpl_backend = mpl_backend
        self.ets_backend = ets_backend
        self.qt_api = qt_api
        self.pyqt_api = pyqt_api
        self.install_qt_inputhook = install_qt_inputhook
        self.ignore_sip_setapi_errors = ignore_sip_setapi_errors
        self.merge_output_channels = merge_output_channels
        self.colorize_sys_stderr = colorize_sys_stderr
        self.umd_enabled = umd_enabled
        self.umd_namelist = umd_namelist
        self.umd_verbose = umd_verbose
        self.autorefresh_timeout = autorefresh_timeout
        self.autorefresh_state = autorefresh_state
                
        self.namespacebrowser_button = None
        self.cwd_button = None
        self.env_button = None
        self.syspath_button = None
        self.terminate_button = None

        self.notification_thread = None
        
        ExternalShellBase.__init__(self, parent=parent, fname=fname, wdir=wdir,
                                   history_filename='.history.py',
                                   light_background=light_background,
                                   menu_actions=menu_actions,
                                   show_buttons_inside=show_buttons_inside,
                                   show_elapsed_time=show_elapsed_time)

        if self.pythonexecutable is None:
            self.pythonexecutable = get_python_executable()

        self.python_args = None
        if python_args:
            assert isinstance(python_args, basestring)
            self.python_args = python_args
        
        assert isinstance(arguments, basestring)
        self.arguments = arguments
        
        self.connection_file = None
        self.is_ipykernel = ipykernel
        if self.is_ipykernel:
            interact = False
            # Running our custom startup script for IPython kernels:
            # (see spyderlib/widgets/externalshell/start_ipython_kernel.py)
            self.fname = get_module_source_path(
                'SMlib.widgets.externalshell', 'start_ipython_kernel.py')
        
        self.shell.set_externalshell(self)

        self.toggle_globals_explorer(False)
        self.interact_action.setChecked(interact)
        self.debug_action.setChecked(debug)
        
        self.introspection_socket = None
        self.is_interpreter = fname is None
        
        if self.is_interpreter:
            self.terminate_button.hide()
        
        # Additional python path list
        self.path = path
        self.shell.path = path
        
    def set_introspection_socket(self, introspection_socket):
        self.introspection_socket = introspection_socket
        if self.namespacebrowser is not None:
            settings = self.namespacebrowser.get_view_settings()
            communicate(introspection_socket,
                        'set_remote_view_settings()', settings=[settings])
        
    def set_autorefresh_timeout(self, interval):
        if self.introspection_socket is not None:
            try:
                communicate(self.introspection_socket,
                            "set_monitor_timeout(%d)" % interval)
            except socket.error:
                pass
        
    def closeEvent(self, event):
        self.quit_monitor()
        ExternalShellBase.closeEvent(self, event)
        
    def get_toolbar_buttons(self):
        ExternalShellBase.get_toolbar_buttons(self)
        if self.namespacebrowser_button is None \
           and self.stand_alone is not None:
            self.namespacebrowser_button = create_toolbutton(self,
                  text=_("Variables"), icon=get_icon('dictedit.png'),
                  tip=_("Show/hide global variables explorer"),
                  toggled=self.toggle_globals_explorer, text_beside_icon=True)
        if self.terminate_button is None:
            self.terminate_button = create_toolbutton(self,
                  text=_("Terminate"), icon=get_icon('terminate.png'),
                  tip=_("""Attempts to terminate the process.
The process may not exit as a result of clicking this button
(it is given the chance to prompt the user for any unsaved files, etc)."""))
        buttons = []
        if self.namespacebrowser_button is not None:
            buttons.append(self.namespacebrowser_button)
        buttons += [self.run_button, self.options_button,
                    self.terminate_button, self.kill_button]
        return buttons

    def get_options_menu(self):
        ExternalShellBase.get_options_menu(self)
        self.interact_action = create_action(self, _("Interact"))
        self.interact_action.setCheckable(True)
        self.debug_action = create_action(self, _("Debug"))
        self.debug_action.setCheckable(True)
        self.args_action = create_action(self, _("Arguments..."),
                                         triggered=self.get_arguments)
        run_settings_menu = QMenu(_("Run settings"), self)
        add_actions(run_settings_menu,
                    (self.interact_action, self.debug_action, self.args_action))
        self.cwd_button = create_action(self, _("Working directory"),
                                icon=get_std_icon('DirOpenIcon'),
                                tip=_("Set current working directory"),
                                triggered=self.set_current_working_directory)
        self.env_button = create_action(self, _("Environment variables"),
                                        icon=get_icon('environ.png'),
                                        triggered=self.show_env)
        self.syspath_button = create_action(self,
                                            _("Show sys.path contents"),
                                            icon=get_icon('syspath.png'),
                                            triggered=self.show_syspath)
        actions = [run_settings_menu, self.show_time_action, None,
                   self.cwd_button, self.env_button, self.syspath_button]
        if self.menu_actions is not None:
            actions += [None]+self.menu_actions
        return actions

    def is_interpreter(self):
        """Return True if shellwidget is a Python interpreter"""
        return self.is_interpreter
        
    def get_shell_widget(self):
        if self.stand_alone is None:
            return self.shell
        else:
            self.namespacebrowser = NamespaceBrowser(self)
            settings = self.stand_alone
            self.namespacebrowser.set_shellwidget(self)
            self.namespacebrowser.setup(**settings)
            self.connect(self.namespacebrowser, SIGNAL('collapse()'),
                         lambda: self.toggle_globals_explorer(False))
            # Shell splitter
            self.splitter = splitter = QSplitter(Qt.Vertical, self)
            self.connect(self.splitter, SIGNAL('splitterMoved(int, int)'),
                         self.splitter_moved)
            splitter.addWidget(self.shell)
            splitter.setCollapsible(0, False)
            splitter.addWidget(self.namespacebrowser)
            splitter.setStretchFactor(0, 1)
            splitter.setStretchFactor(1, 0)
            splitter.setHandleWidth(5)
            splitter.setSizes([2, 1])
            return splitter
    
    def get_icon(self):
        return get_icon('python.png')

    def set_buttons_runnning_state(self, state):
        ExternalShellBase.set_buttons_runnning_state(self, state)
        self.interact_action.setEnabled(not state and not self.is_interpreter)
        self.debug_action.setEnabled(not state and not self.is_interpreter)
        self.args_action.setEnabled(not state and not self.is_interpreter)
        if state:
            if self.arguments:
                argstr = _("Arguments: %s") % self.arguments
            else:
                argstr = _("No argument")
        else:
            argstr = _("Arguments...")
        self.args_action.setText(argstr)
        self.terminate_button.setVisible(not self.is_interpreter and state)
        if not state:
            self.toggle_globals_explorer(False)
        for btn in (self.cwd_button, self.env_button, self.syspath_button):
            btn.setEnabled(state and self.monitor_enabled)
        if self.namespacebrowser_button is not None:
            self.namespacebrowser_button.setEnabled(state)
    
    def set_namespacebrowser(self, namespacebrowser):
        """
        Set namespace browser *widget*
        Note: this method is not used in stand alone mode
        """
        self.namespacebrowser = namespacebrowser
        self.configure_namespacebrowser()
        
    def configure_namespacebrowser(self):
        """Connect the namespace browser to the notification thread"""
        if self.notification_thread is not None:
            self.connect(self.notification_thread,
                         SIGNAL('refresh_namespace_browser()'),
                         self.namespacebrowser.refresh_table)
            signal = self.notification_thread.sig_process_remote_view
            signal.connect(self.namespacebrowser.process_remote_view)
    
    def create_process(self):
        self.shell.clear()
            
        self.process = QProcess(self)
        if self.merge_output_channels:
            self.process.setProcessChannelMode(QProcess.MergedChannels)
        else:
            self.process.setProcessChannelMode(QProcess.SeparateChannels)
        self.connect(self.shell, SIGNAL("wait_for_ready_read()"),
                     lambda: self.process.waitForReadyRead(250))
        
        # Working directory
        if self.wdir is not None:
            self.process.setWorkingDirectory(self.wdir)

        #-------------------------Python specific-------------------------------
        # Python arguments
        p_args = ['-u']
        if DEBUG >= 3:
            p_args += ['-v']
        p_args += get_python_args(self.fname, self.python_args,
                                  self.interact_action.isChecked(),
                                  self.debug_action.isChecked(),
                                  self.arguments)
        
        env = [unicode(_path) for _path in self.process.systemEnvironment()]

        if self.pythonstartup:
            env.append('PYTHONSTARTUP=%s' % self.pythonstartup)
        
        # Monitor
        if self.monitor_enabled:
            env.append('SPYDER_SHELL_ID=%s' % id(self))
            env.append('SPYDER_AR_TIMEOUT=%d' % self.autorefresh_timeout)
            env.append('SPYDER_AR_STATE=%r' % self.autorefresh_state)
            from SMlib.widgets.externalshell import introspection
            introspection_server = introspection.start_introspection_server()
            introspection_server.register(self)
            notification_server = introspection.start_notification_server()
            self.notification_thread = notification_server.register(self)
            self.connect(self.notification_thread, SIGNAL('pdb(QString,int)'),
                         lambda fname, lineno:
                         self.emit(SIGNAL('pdb(QString,int)'), fname, lineno))
            self.connect(self.notification_thread,
                         SIGNAL('new_ipython_kernel(QString)'),
                         lambda args:
                         self.emit(SIGNAL('create_ipython_client(QString)'),
                         args))
            self.connect(self.notification_thread,
                         SIGNAL('open_file(QString,int)'),
                         lambda fname, lineno:
                         self.emit(SIGNAL('open_file(QString,int)'),
                                   fname, lineno))
            if self.namespacebrowser is not None:
                self.configure_namespacebrowser()
            env.append('SPYDER_I_PORT=%d' % introspection_server.port)
            env.append('SPYDER_N_PORT=%d' % notification_server.port)
        
        # External modules options
        env.append('ETS_TOOLKIT=%s' % self.ets_backend)
        env.append('MATPLOTLIB_PATCH=%r' % self.mpl_patch_enabled)
        if self.mpl_backend:
            env.append('MATPLOTLIB_BACKEND=%s' % self.mpl_backend)
        if self.qt_api:
            env.append('QT_API=%s' % self.qt_api)
        env.append('INSTALL_QT_INPUTHOOK=%s' % self.install_qt_inputhook)
        env.append('COLORIZE_SYS_STDERR=%s' % self.colorize_sys_stderr)
#        # Socket-based alternative (see input hook in sitecustomize.py):
#        if self.install_qt_inputhook:
#            from PyQt4.QtNetwork import QLocalServer
#            self.local_server = QLocalServer()
#            self.local_server.listen(str(id(self)))
        if self.pyqt_api:
            env.append('PYQT_API=%d' % self.pyqt_api)
        env.append('IGNORE_SIP_SETAPI_ERRORS=%s'
                   % self.ignore_sip_setapi_errors)
        
        # User Module Deleter
        if self.is_interpreter:
            env.append('UMD_ENABLED=%r' % self.umd_enabled)
            env.append('UMD_NAMELIST=%s' % ','.join(self.umd_namelist))
            env.append('UMD_VERBOSE=%r' % self.umd_verbose)

        # IPython kernel
        env.append('IPYTHON_KERNEL=%r' % self.is_ipykernel)
            
        pathlist = []

        # Fix encoding with custom "sitecustomize.py"
        scpath = osp.dirname(osp.abspath(__file__))
        pathlist.append(scpath)
        
        # Adding Spyder path
        pathlist += self.path
        
        # Adding path list to PYTHONPATH environment variable
        add_pathlist_to_PYTHONPATH(env, pathlist)

        #-------------------------Python specific-------------------------------
                        
        self.connect(self.process, SIGNAL("readyReadStandardOutput()"),
                     self.write_output)
        self.connect(self.process, SIGNAL("readyReadStandardError()"),
                     self.write_error)
        self.connect(self.process, SIGNAL("finished(int,QProcess::ExitStatus)"),
                     self.finished)
                     
        self.connect(self, SIGNAL('finished()'), self.dialog_manager.close_all)

        self.connect(self.terminate_button, SIGNAL("clicked()"),
                     self.process.terminate)
        self.connect(self.kill_button, SIGNAL("clicked()"),
                     self.process.kill)
        
        #-------------------------Python specific-------------------------------
        # Fixes for our Mac app:
        # 1. PYTHONPATH and PYTHONHOME are set while bootstrapping the app,
        #    but their values are messing sys.path for external interpreters
        #    (e.g. EPD) so we need to remove them from the environment.
        # 2. Add this file's dir to PYTHONPATH. This will make every external
        #    interpreter to use our sitecustomize script.
        # 3. Remove PYTHONOPTIMIZE from env so that we can have assert
        #    statements working with our interpreters (See Issue 1281)
        if sys.platform == 'darwin' and 'Spyder.app' in __file__:
            env.append('SPYDER_INTERPRETER=%s' % self.pythonexecutable)
            if 'Spyder.app' not in self.pythonexecutable:
                env = [p for p in env if not (p.startswith('PYTHONPATH') or \
                                              p.startswith('PYTHONHOME'))] # 1.

                env.append('PYTHONPATH=%s' % osp.dirname(__file__))        # 2.

            env = [p for p in env if not p.startswith('PYTHONOPTIMIZE')]   # 3.

        self.process.setEnvironment(env)
        self.process.start(self.pythonexecutable, p_args)
        #-------------------------Python specific-------------------------------
            
        running = self.process.waitForStarted(3000)
        self.set_running_state(running)
        if not running:
            QMessageBox.critical(self, _("Error"),
                             _("A Python or IPython Console failed to start!"))
        else:
            self.shell.setFocus()
            self.emit(SIGNAL('started()'))
            
        return self.process

    def finished(self, exit_code, exit_status):
        """Reimplement ExternalShellBase method"""
        ExternalShellBase.finished(self, exit_code, exit_status)
        self.introspection_socket = None

    
#===============================================================================
#    Input/Output
#===============================================================================
    def write_error(self):
        if os.name == 'nt':
            #---This is apparently necessary only on Windows (not sure though):
            #   emptying standard output buffer before writing error output
            self.process.setReadChannel(QProcess.StandardOutput)
            if self.process.waitForReadyRead(1):
                self.write_output()
        self.shell.write_error(self.get_stderr())
        QApplication.processEvents()
        
    def send_to_process(self, text):
        if not self.is_running():
            return
            
        if not isinstance(text, basestring):
            text = unicode(text)
        if self.install_qt_inputhook and self.introspection_socket is not None:
            communicate(self.introspection_socket,
                        "toggle_inputhook_flag(True)")
#            # Socket-based alternative (see input hook in sitecustomize.py):
#            while self.local_server.hasPendingConnections():
#                self.local_server.nextPendingConnection().write('go!')
        if text.startswith(('%', '!')):
            text = 'evalsc(r"%s")\n' % text
        if not text.endswith('\n'):
            text += '\n'
        self.process.write(locale_codec.fromUnicode(text))
        self.process.waitForBytesWritten(-1)
        
        # Eventually write prompt faster (when hitting Enter continuously)
        # -- necessary/working on Windows only:
        if os.name == 'nt':
            self.write_error()
        
    def keyboard_interrupt(self):
        if self.introspection_socket is not None:
            communicate(self.introspection_socket, "thread.interrupt_main()")
        
    def quit_monitor(self):
        if self.introspection_socket is not None:
            try:
                write_packet(self.introspection_socket, "thread.exit()")
            except socket.error:
                pass
            
#===============================================================================
#    Globals explorer
#===============================================================================
    def toggle_globals_explorer(self, state):
        if self.stand_alone is not None:
            self.splitter.setSizes([1, 1 if state else 0])
            self.namespacebrowser_button.setChecked(state)
            if state and self.namespacebrowser is not None:
                self.namespacebrowser.refresh_table()
        
    def splitter_moved(self, pos, index):
        self.namespacebrowser_button.setChecked( self.splitter.sizes()[1] )

#===============================================================================
#    Misc.
#===============================================================================
    def set_current_working_directory(self):
        """Set current working directory"""
        cwd = self.shell.get_cwd()
        self.emit(SIGNAL('redirect_stdio(bool)'), False)
        directory = getexistingdirectory(self, _("Select directory"), cwd)
        if directory:
            self.shell.set_cwd(directory)
        self.emit(SIGNAL('redirect_stdio(bool)'), True)

    def show_env(self):
        """Show environment variables"""
        get_func = self.shell.get_env
        set_func = self.shell.set_env
        self.dialog_manager.show(RemoteEnvDialog(get_func, set_func))
        
    def show_syspath(self):
        """Show sys.path contents"""
        editor = DictEditor()
        editor.setup(self.shell.get_syspath(), title="sys.path", readonly=True,
                     width=600, icon='syspath.png')
        self.dialog_manager.show(editor)
Пример #9
0
class PyLint(Plugin, QObject):
    """ PyLint Plugin """
    capabilities = ['toolbarHook']
    __version__ = '0.5'
    thread = None

    def do_toolbarHook(self, widget):
        """ Hook to install the pylint toolbar button"""
        widget.addAction('PyLint', self.do_pylint)

    @pyqtSlot()
    def do_pylint(self):
        """ Launch the lint process and create the result window """
        print 'do_pylint'

        self.pylint_pross = QProcess()
        self.pylint_pross.setProcessChannelMode(QProcess.MergedChannels)
        self.pylint_pross.setWorkingDirectory( \
            os.path.dirname(str(self.parent.editor.filename)))
        self.pylint_pross.setReadChannel(QProcess.StandardOutput)

        self.connect(self.pylint_pross, \
            SIGNAL('finished(int)'), \
            self.finished)
        self.connect(self.pylint_pross, \
            SIGNAL('readyReadStandardOutput()'), \
            self.handle_stdout)
        self.connect(self.pylint_pross, \
            SIGNAL('readyReadStandardError()'), \
            self.handle_stderr)
        if (self.pylint_pross.start("pylint", \
                [self.parent.editor.filename,])):
            print 'Cannot start process'

        self.win = ResultWin()
        self.win.setWindowTitle("PyLint Results :" \
            + os.path.basename(str(self.parent.editor.filename)))
        self.win.show()
        if isMAEMO:
            self.win.setAttribute(Qt.WA_Maemo5ShowProgressIndicator, True)

        self.win.connect(self.win.list_view, \
            SIGNAL('doubleClicked(const QModelIndex&)'), \
            self.goto_line)

        self.pylint_pross.waitForStarted()

    def finished(self, _):
        """ Call back called when lint end """
        if isMAEMO:
            self.win.setAttribute(Qt.WA_Maemo5ShowProgressIndicator, False)

    def handle_stdout(self):
        """
        Private slot to handle the readyReadStdout
        signal of the pylint process.
        """
        result_list = []
        #regex = QRegExp('(\w)\S*:\S*(\d*):.*: (.*)')
        regex = QRegExp('(\w)\s*:\s*(\d*):(.*)')
        regex_score = \
            QRegExp('.*at.(\d.\d*)/10.*')
        while self.pylint_pross and self.pylint_pross.canReadLine():
            result = unicode(self.pylint_pross.readLine())
            if result != None:
                pos = 0
                while True:
                    pos = regex.indexIn(result, pos)
                    if pos < 0:
                        if regex_score.indexIn(result, 0) >= 0:
                            self.win.setWindowTitle( \
                                "PyLint Results :" \
                                + str(regex_score.cap(1)) \
                                + ':'
                                + os.path.basename(str(self.parent.editor.filename)))
                        break
                    result_list.append(
                        (regex.cap(1), regex.cap(2), regex.cap(3)))
                    #print 'Append : ',(regex.cap(1), regex.cap(2), regex.cap(3))
                    pos = pos + regex.matchedLength()

        if len(result_list) > 0:
            self.win.append_results(result_list)

    def goto_line(self, index):
        """ Callback called when a lint result is double clicked """
        line = int(self.win.list_model.items[index.row()][1])
        self.parent.do_gotoLine(line)

    def handle_stderr(self):
        """
        Private slot to handle the readyReadStderr
        signal of the pylint process. Currently not
        managed
        """
        print 'error stderr'
class SvnChangeListsDialog(QDialog, Ui_SvnChangeListsDialog):
    """
    Class implementing a dialog to browse the change lists.
    """
    def __init__(self, vcs, parent=None):
        """
        Constructor
        
        @param vcs reference to the vcs object
        @param parent parent widget (QWidget)
        """
        QDialog.__init__(self, parent)
        self.setupUi(self)
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
        
        self.process = None
        self.vcs = vcs
        
        self.rx_status = \
            QRegExp('(.{8,9})\\s+([0-9-]+)\\s+([0-9?]+)\\s+(\\S+)\\s+(.+)\\s*')
            # flags (8 or 9 anything), revision, changed rev, author, path
        self.rx_status2 = \
            QRegExp('(.{8,9})\\s+(.+)\\s*')
            # flags (8 or 9 anything), path
        self.rx_changelist = \
            QRegExp('--- \\S+ .([\\w\\s]+).:\\s+')
            # three dashes, Changelist (translated), quote,
            # changelist name, quote, :
    
    @pyqtSignature("QListWidgetItem*, QListWidgetItem*")
    def on_changeLists_currentItemChanged(self, current, previous):
        """
        Private slot to handle the selection of a new item.
        
        @param current current item (QListWidgetItem)
        @param previous previous current item (QListWidgetItem)
        """
        self.filesList.clear()
        if current is not None:
            changelist = unicode(current.text())
            if changelist in self.changeListsDict:
                self.filesList.addItems(sorted(self.changeListsDict[changelist]))
    
    def start(self, path):
        """
        Public slot to populate the data.
        
        @param path directory name to show change lists for (string)
        """
        self.changeListsDict = {}
        
        self.filesLabel.setText(self.trUtf8("Files (relative to %1):").arg(path))
        
        self.errorGroup.hide()
        self.intercept = False
        
        self.path = path
        self.currentChangelist = ""
        
        self.process = QProcess()
        self.process.finished.connect(self.__procFinished)
        self.process.readyReadStandardOutput.connect(self.__readStdout)
        self.process.readyReadStandardError.connect(self.__readStderr)
        
        args = []
        args.append('status')
        self.vcs.addArguments(args, self.vcs.options['global'])
        self.vcs.addArguments(args, self.vcs.options['status'])
        if '--verbose' not in self.vcs.options['global'] and \
           '--verbose' not in self.vcs.options['status']:
            args.append('--verbose')
        if isinstance(path, list):
            self.dname, fnames = self.vcs.splitPathList(path)
            self.vcs.addArguments(args, fnames)
        else:
            self.dname, fname = self.vcs.splitPath(path)
            args.append(fname)
        
        self.process.setWorkingDirectory(self.dname)
        
        self.process.start('svn', args)
        procStarted = self.process.waitForStarted()
        if not procStarted:
            self.inputGroup.setEnabled(False)
            self.inputGroup.hide()
            KQMessageBox.critical(self,
                self.trUtf8('Process Generation Error'),
                self.trUtf8(
                    'The process %1 could not be started. '
                    'Ensure, that it is in the search path.'
                ).arg('svn'))
        else:
            self.inputGroup.setEnabled(True)
            self.inputGroup.show()
    
    def __finish(self):
        """
        Private slot called when the process finished or the user pressed
        the button.
        """
        if self.process is not None and \
           self.process.state() != QProcess.NotRunning:
            self.process.terminate()
            QTimer.singleShot(2000, self.process.kill)
            self.process.waitForFinished(3000)
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        
        self.inputGroup.setEnabled(False)
        self.inputGroup.hide()
        
        if len(self.changeListsDict) == 0:
            self.changeLists.addItem(self.trUtf8("No changelists found"))
            self.buttonBox.button(QDialogButtonBox.Close).setFocus(Qt.OtherFocusReason)
        else:
            self.changeLists.addItems(sorted(self.changeListsDict.keys()))
            self.changeLists.setCurrentRow(0)
            self.changeLists.setFocus(Qt.OtherFocusReason)
    
    def on_buttonBox_clicked(self, button):
        """
        Private slot called by a button of the button box clicked.
        
        @param button button that was clicked (QAbstractButton)
        """
        if button == self.buttonBox.button(QDialogButtonBox.Close):
            self.close()
        elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
            self.__finish()
        
    def __procFinished(self, exitCode, exitStatus):
        """
        Private slot connected to the finished signal.
        
        @param exitCode exit code of the process (integer)
        @param exitStatus exit status of the process (QProcess.ExitStatus)
        """
        self.__finish()
        
    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.
        """
        if self.process is not None:
            self.process.setReadChannel(QProcess.StandardOutput)
            
            while self.process.canReadLine():
                s = unicode(self.process.readLine(),
                            Preferences.getSystem("IOEncoding"),
                            'replace')
                if self.currentChangelist != "" and self.rx_status.exactMatch(s):
                    file = unicode(self.rx_status.cap(5)).strip()
                    filename = file.replace(self.path + os.sep, "")
                    if filename not in self.changeListsDict[self.currentChangelist]:
                        self.changeListsDict[self.currentChangelist].append(filename)
                elif self.currentChangelist != "" and self.rx_status2.exactMatch(s):
                    file = unicode(self.rx_status2.cap(2)).strip()
                    filename = file.replace(self.path + os.sep, "")
                    if filename not in self.changeListsDict[self.currentChangelist]:
                        self.changeListsDict[self.currentChangelist].append(filename)
                elif self.rx_changelist.exactMatch(s):
                    self.currentChangelist = unicode(self.rx_changelist.cap(1))
                    if self.currentChangelist not in self.changeListsDict:
                        self.changeListsDict[self.currentChangelist] = []
        
    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.
        """
        if self.process is not None:
            self.errorGroup.show()
            s = QString(self.process.readAllStandardError())
            self.errors.insertPlainText(s)
            self.errors.ensureCursorVisible()
        
    def on_passwordCheckBox_toggled(self, isOn):
        """
        Private slot to handle the password checkbox toggled.
        
        @param isOn flag indicating the status of the check box (boolean)
        """
        if isOn:
            self.input.setEchoMode(QLineEdit.Password)
        else:
            self.input.setEchoMode(QLineEdit.Normal)
        
    @pyqtSignature("")
    def on_sendButton_clicked(self):
        """
        Private slot to send the input to the subversion process.
        """
        input = self.input.text()
        input += os.linesep
        
        if self.passwordCheckBox.isChecked():
            self.errors.insertPlainText(os.linesep)
            self.errors.ensureCursorVisible()
        else:
            self.errors.insertPlainText(input)
            self.errors.ensureCursorVisible()
        
        self.process.write(input)
        
        self.passwordCheckBox.setChecked(False)
        self.input.clear()
        
    def on_input_returnPressed(self):
        """
        Private slot to handle the press of the return key in the input field.
        """
        self.intercept = True
        self.on_sendButton_clicked()
        
    def keyPressEvent(self, evt):
        """
        Protected slot to handle a key press event.
        
        @param evt the key press event (QKeyEvent)
        """
        if self.intercept:
            self.intercept = False
            evt.accept()
            return
        QDialog.keyPressEvent(self, evt)
Пример #11
0
class PyLint(Plugin, QObject):
    """ PyLint Plugin """
    capabilities = ['toolbarHook']
    __version__ = '0.5'
    thread = None
    
    def do_toolbarHook(self, widget):
        """ Hook to install the pylint toolbar button"""
        widget.addAction('PyLint', self.do_pylint)
        
        
    @pyqtSlot()
    def do_pylint(self):
        """ Launch the lint process and create the result window """
        print 'do_pylint'

        self.pylint_pross = QProcess()
        self.pylint_pross.setProcessChannelMode(QProcess.MergedChannels)
        self.pylint_pross.setWorkingDirectory( \
            os.path.dirname(str(self.parent.editor.filename)))
        self.pylint_pross.setReadChannel(QProcess.StandardOutput)

        self.connect(self.pylint_pross, \
            SIGNAL('finished(int)'), \
            self.finished)
        self.connect(self.pylint_pross, \
            SIGNAL('readyReadStandardOutput()'), \
            self.handle_stdout)
        self.connect(self.pylint_pross, \
            SIGNAL('readyReadStandardError()'), \
            self.handle_stderr)
        if (self.pylint_pross.start("pylint", \
                [self.parent.editor.filename,])):
            print 'Cannot start process'

        self.win = ResultWin()
        self.win.setWindowTitle("PyLint Results :" \
            + os.path.basename(str(self.parent.editor.filename)))
        self.win.show()
        if isMAEMO:
            self.win.setAttribute(Qt.WA_Maemo5ShowProgressIndicator, True)
            
        self.win.connect(self.win.list_view, \
            SIGNAL('doubleClicked(const QModelIndex&)'), \
            self.goto_line)

        self.pylint_pross.waitForStarted()
        
    def finished(self, _):
        """ Call back called when lint end """
        if isMAEMO:
            self.win.setAttribute(Qt.WA_Maemo5ShowProgressIndicator, False)
            
    def handle_stdout(self):
        """
        Private slot to handle the readyReadStdout
        signal of the pylint process.
        """
        result_list = []
        #regex = QRegExp('(\w)\S*:\S*(\d*):.*: (.*)')
        regex = QRegExp('(\w)\s*:\s*(\d*):(.*)')
        regex_score = \
            QRegExp('.*at.(\d.\d*)/10.*')
        while self.pylint_pross and self.pylint_pross.canReadLine():
            result = unicode(self.pylint_pross.readLine())
            if result != None:
                pos = 0
                while True:
                    pos = regex.indexIn(result, pos)
                    if pos < 0:
                        if regex_score.indexIn(result, 0) >= 0:
                            self.win.setWindowTitle( \
                                "PyLint Results :" \
                                + str(regex_score.cap(1)) \
                                + ':'                                
                                + os.path.basename(str(self.parent.editor.filename)))
                        break
                    result_list.append((regex.cap(1), regex.cap(2), regex.cap(3)))
                    #print 'Append : ',(regex.cap(1), regex.cap(2), regex.cap(3))
                    pos = pos + regex.matchedLength()
                    
        if len(result_list)>0:
            self.win.append_results(result_list)
        
    def goto_line(self, index):
        """ Callback called when a lint result is double clicked """
        line = int(self.win.list_model.items[index.row()][1])
        self.parent.do_gotoLine(line)
        
    def handle_stderr(self):
        """
        Private slot to handle the readyReadStderr
        signal of the pylint process. Currently not
        managed
        """
        print 'error stderr'
Пример #12
0
class ExternalPythonShell(ExternalShellBase):
    """External Shell widget: execute Python script in a separate process"""
    SHELL_CLASS = ExtPythonShellWidget

    def __init__(self,
                 parent=None,
                 fname=None,
                 wdir=None,
                 interact=False,
                 debug=False,
                 path=[],
                 python_args='',
                 ipykernel=False,
                 arguments='',
                 stand_alone=None,
                 umd_enabled=True,
                 umd_namelist=[],
                 umd_verbose=True,
                 pythonstartup=None,
                 pythonexecutable=None,
                 monitor_enabled=True,
                 mpl_patch_enabled=True,
                 mpl_backend=None,
                 ets_backend='qt4',
                 qt_api=None,
                 pyqt_api=0,
                 install_qt_inputhook=True,
                 ignore_sip_setapi_errors=False,
                 merge_output_channels=False,
                 colorize_sys_stderr=False,
                 autorefresh_timeout=3000,
                 autorefresh_state=True,
                 light_background=True,
                 menu_actions=None,
                 show_buttons_inside=True,
                 show_elapsed_time=True):

        assert qt_api in (None, 'pyqt', 'pyside')

        self.namespacebrowser = None  # namespace browser widget!

        self.dialog_manager = DialogManager()

        self.stand_alone = stand_alone  # stand alone settings (None: plugin)
        self.pythonstartup = pythonstartup
        self.pythonexecutable = pythonexecutable
        self.monitor_enabled = monitor_enabled
        self.mpl_patch_enabled = mpl_patch_enabled
        self.mpl_backend = mpl_backend
        self.ets_backend = ets_backend
        self.qt_api = qt_api
        self.pyqt_api = pyqt_api
        self.install_qt_inputhook = install_qt_inputhook
        self.ignore_sip_setapi_errors = ignore_sip_setapi_errors
        self.merge_output_channels = merge_output_channels
        self.colorize_sys_stderr = colorize_sys_stderr
        self.umd_enabled = umd_enabled
        self.umd_namelist = umd_namelist
        self.umd_verbose = umd_verbose
        self.autorefresh_timeout = autorefresh_timeout
        self.autorefresh_state = autorefresh_state

        self.namespacebrowser_button = None
        self.cwd_button = None
        self.env_button = None
        self.syspath_button = None
        self.terminate_button = None

        self.notification_thread = None

        ExternalShellBase.__init__(self,
                                   parent=parent,
                                   fname=fname,
                                   wdir=wdir,
                                   history_filename='.history.py',
                                   light_background=light_background,
                                   menu_actions=menu_actions,
                                   show_buttons_inside=show_buttons_inside,
                                   show_elapsed_time=show_elapsed_time)

        if self.pythonexecutable is None:
            self.pythonexecutable = get_python_executable()

        self.python_args = None
        if python_args:
            assert isinstance(python_args, basestring)
            self.python_args = python_args

        assert isinstance(arguments, basestring)
        self.arguments = arguments

        self.connection_file = None
        self.is_ipykernel = ipykernel
        if self.is_ipykernel:
            interact = False
            # Running our custom startup script for IPython kernels:
            # (see spyderlib/widgets/externalshell/start_ipython_kernel.py)
            self.fname = get_module_source_path('SMlib.widgets.externalshell',
                                                'start_ipython_kernel.py')

        self.shell.set_externalshell(self)

        self.toggle_globals_explorer(False)
        self.interact_action.setChecked(interact)
        self.debug_action.setChecked(debug)

        self.introspection_socket = None
        self.is_interpreter = fname is None

        if self.is_interpreter:
            self.terminate_button.hide()

        # Additional python path list
        self.path = path
        self.shell.path = path

    def set_introspection_socket(self, introspection_socket):
        self.introspection_socket = introspection_socket
        if self.namespacebrowser is not None:
            settings = self.namespacebrowser.get_view_settings()
            communicate(introspection_socket,
                        'set_remote_view_settings()',
                        settings=[settings])

    def set_autorefresh_timeout(self, interval):
        if self.introspection_socket is not None:
            try:
                communicate(self.introspection_socket,
                            "set_monitor_timeout(%d)" % interval)
            except socket.error:
                pass

    def closeEvent(self, event):
        self.quit_monitor()
        ExternalShellBase.closeEvent(self, event)

    def get_toolbar_buttons(self):
        ExternalShellBase.get_toolbar_buttons(self)
        if self.namespacebrowser_button is None \
           and self.stand_alone is not None:
            self.namespacebrowser_button = create_toolbutton(
                self,
                text=_("Variables"),
                icon=get_icon('dictedit.png'),
                tip=_("Show/hide global variables explorer"),
                toggled=self.toggle_globals_explorer,
                text_beside_icon=True)
        if self.terminate_button is None:
            self.terminate_button = create_toolbutton(
                self,
                text=_("Terminate"),
                icon=get_icon('terminate.png'),
                tip=_("""Attempts to terminate the process.
The process may not exit as a result of clicking this button
(it is given the chance to prompt the user for any unsaved files, etc)."""))
        buttons = []
        if self.namespacebrowser_button is not None:
            buttons.append(self.namespacebrowser_button)
        buttons += [
            self.run_button, self.options_button, self.terminate_button,
            self.kill_button
        ]
        return buttons

    def get_options_menu(self):
        ExternalShellBase.get_options_menu(self)
        self.interact_action = create_action(self, _("Interact"))
        self.interact_action.setCheckable(True)
        self.debug_action = create_action(self, _("Debug"))
        self.debug_action.setCheckable(True)
        self.args_action = create_action(self,
                                         _("Arguments..."),
                                         triggered=self.get_arguments)
        run_settings_menu = QMenu(_("Run settings"), self)
        add_actions(
            run_settings_menu,
            (self.interact_action, self.debug_action, self.args_action))
        self.cwd_button = create_action(
            self,
            _("Working directory"),
            icon=get_std_icon('DirOpenIcon'),
            tip=_("Set current working directory"),
            triggered=self.set_current_working_directory)
        self.env_button = create_action(self,
                                        _("Environment variables"),
                                        icon=get_icon('environ.png'),
                                        triggered=self.show_env)
        self.syspath_button = create_action(self,
                                            _("Show sys.path contents"),
                                            icon=get_icon('syspath.png'),
                                            triggered=self.show_syspath)
        actions = [
            run_settings_menu, self.show_time_action, None, self.cwd_button,
            self.env_button, self.syspath_button
        ]
        if self.menu_actions is not None:
            actions += [None] + self.menu_actions
        return actions

    def is_interpreter(self):
        """Return True if shellwidget is a Python interpreter"""
        return self.is_interpreter

    def get_shell_widget(self):
        if self.stand_alone is None:
            return self.shell
        else:
            self.namespacebrowser = NamespaceBrowser(self)
            settings = self.stand_alone
            self.namespacebrowser.set_shellwidget(self)
            self.namespacebrowser.setup(**settings)
            self.connect(self.namespacebrowser, SIGNAL('collapse()'),
                         lambda: self.toggle_globals_explorer(False))
            # Shell splitter
            self.splitter = splitter = QSplitter(Qt.Vertical, self)
            self.connect(self.splitter, SIGNAL('splitterMoved(int, int)'),
                         self.splitter_moved)
            splitter.addWidget(self.shell)
            splitter.setCollapsible(0, False)
            splitter.addWidget(self.namespacebrowser)
            splitter.setStretchFactor(0, 1)
            splitter.setStretchFactor(1, 0)
            splitter.setHandleWidth(5)
            splitter.setSizes([2, 1])
            return splitter

    def get_icon(self):
        return get_icon('python.png')

    def set_buttons_runnning_state(self, state):
        ExternalShellBase.set_buttons_runnning_state(self, state)
        self.interact_action.setEnabled(not state and not self.is_interpreter)
        self.debug_action.setEnabled(not state and not self.is_interpreter)
        self.args_action.setEnabled(not state and not self.is_interpreter)
        if state:
            if self.arguments:
                argstr = _("Arguments: %s") % self.arguments
            else:
                argstr = _("No argument")
        else:
            argstr = _("Arguments...")
        self.args_action.setText(argstr)
        self.terminate_button.setVisible(not self.is_interpreter and state)
        if not state:
            self.toggle_globals_explorer(False)
        for btn in (self.cwd_button, self.env_button, self.syspath_button):
            btn.setEnabled(state and self.monitor_enabled)
        if self.namespacebrowser_button is not None:
            self.namespacebrowser_button.setEnabled(state)

    def set_namespacebrowser(self, namespacebrowser):
        """
        Set namespace browser *widget*
        Note: this method is not used in stand alone mode
        """
        self.namespacebrowser = namespacebrowser
        self.configure_namespacebrowser()

    def configure_namespacebrowser(self):
        """Connect the namespace browser to the notification thread"""
        if self.notification_thread is not None:
            self.connect(self.notification_thread,
                         SIGNAL('refresh_namespace_browser()'),
                         self.namespacebrowser.refresh_table)
            signal = self.notification_thread.sig_process_remote_view
            signal.connect(self.namespacebrowser.process_remote_view)

    def create_process(self):
        self.shell.clear()

        self.process = QProcess(self)
        if self.merge_output_channels:
            self.process.setProcessChannelMode(QProcess.MergedChannels)
        else:
            self.process.setProcessChannelMode(QProcess.SeparateChannels)
        self.connect(self.shell, SIGNAL("wait_for_ready_read()"),
                     lambda: self.process.waitForReadyRead(250))

        # Working directory
        if self.wdir is not None:
            self.process.setWorkingDirectory(self.wdir)

        #-------------------------Python specific-------------------------------
        # Python arguments
        p_args = ['-u']
        if DEBUG >= 3:
            p_args += ['-v']
        p_args += get_python_args(self.fname, self.python_args,
                                  self.interact_action.isChecked(),
                                  self.debug_action.isChecked(),
                                  self.arguments)

        env = [unicode(_path) for _path in self.process.systemEnvironment()]

        if self.pythonstartup:
            env.append('PYTHONSTARTUP=%s' % self.pythonstartup)

        # Monitor
        if self.monitor_enabled:
            env.append('SPYDER_SHELL_ID=%s' % id(self))
            env.append('SPYDER_AR_TIMEOUT=%d' % self.autorefresh_timeout)
            env.append('SPYDER_AR_STATE=%r' % self.autorefresh_state)
            from SMlib.widgets.externalshell import introspection
            introspection_server = introspection.start_introspection_server()
            introspection_server.register(self)
            notification_server = introspection.start_notification_server()
            self.notification_thread = notification_server.register(self)
            self.connect(
                self.notification_thread,
                SIGNAL('pdb(QString,int)'), lambda fname, lineno: self.emit(
                    SIGNAL('pdb(QString,int)'), fname, lineno))
            self.connect(
                self.notification_thread,
                SIGNAL('new_ipython_kernel(QString)'), lambda args: self.emit(
                    SIGNAL('create_ipython_client(QString)'), args))
            self.connect(
                self.notification_thread, SIGNAL('open_file(QString,int)'),
                lambda fname, lineno: self.emit(
                    SIGNAL('open_file(QString,int)'), fname, lineno))
            if self.namespacebrowser is not None:
                self.configure_namespacebrowser()
            env.append('SPYDER_I_PORT=%d' % introspection_server.port)
            env.append('SPYDER_N_PORT=%d' % notification_server.port)

        # External modules options
        env.append('ETS_TOOLKIT=%s' % self.ets_backend)
        env.append('MATPLOTLIB_PATCH=%r' % self.mpl_patch_enabled)
        if self.mpl_backend:
            env.append('MATPLOTLIB_BACKEND=%s' % self.mpl_backend)
        if self.qt_api:
            env.append('QT_API=%s' % self.qt_api)
        env.append('INSTALL_QT_INPUTHOOK=%s' % self.install_qt_inputhook)
        env.append('COLORIZE_SYS_STDERR=%s' % self.colorize_sys_stderr)
        #        # Socket-based alternative (see input hook in sitecustomize.py):
        #        if self.install_qt_inputhook:
        #            from PyQt4.QtNetwork import QLocalServer
        #            self.local_server = QLocalServer()
        #            self.local_server.listen(str(id(self)))
        if self.pyqt_api:
            env.append('PYQT_API=%d' % self.pyqt_api)
        env.append('IGNORE_SIP_SETAPI_ERRORS=%s' %
                   self.ignore_sip_setapi_errors)

        # User Module Deleter
        if self.is_interpreter:
            env.append('UMD_ENABLED=%r' % self.umd_enabled)
            env.append('UMD_NAMELIST=%s' % ','.join(self.umd_namelist))
            env.append('UMD_VERBOSE=%r' % self.umd_verbose)

        # IPython kernel
        env.append('IPYTHON_KERNEL=%r' % self.is_ipykernel)

        pathlist = []

        # Fix encoding with custom "sitecustomize.py"
        scpath = osp.dirname(osp.abspath(__file__))
        pathlist.append(scpath)

        # Adding Spyder path
        pathlist += self.path

        # Adding path list to PYTHONPATH environment variable
        add_pathlist_to_PYTHONPATH(env, pathlist)

        #-------------------------Python specific-------------------------------

        self.connect(self.process, SIGNAL("readyReadStandardOutput()"),
                     self.write_output)
        self.connect(self.process, SIGNAL("readyReadStandardError()"),
                     self.write_error)
        self.connect(self.process,
                     SIGNAL("finished(int,QProcess::ExitStatus)"),
                     self.finished)

        self.connect(self, SIGNAL('finished()'), self.dialog_manager.close_all)

        self.connect(self.terminate_button, SIGNAL("clicked()"),
                     self.process.terminate)
        self.connect(self.kill_button, SIGNAL("clicked()"), self.process.kill)

        #-------------------------Python specific-------------------------------
        # Fixes for our Mac app:
        # 1. PYTHONPATH and PYTHONHOME are set while bootstrapping the app,
        #    but their values are messing sys.path for external interpreters
        #    (e.g. EPD) so we need to remove them from the environment.
        # 2. Add this file's dir to PYTHONPATH. This will make every external
        #    interpreter to use our sitecustomize script.
        # 3. Remove PYTHONOPTIMIZE from env so that we can have assert
        #    statements working with our interpreters (See Issue 1281)
        if sys.platform == 'darwin' and 'Spyder.app' in __file__:
            env.append('SPYDER_INTERPRETER=%s' % self.pythonexecutable)
            if 'Spyder.app' not in self.pythonexecutable:
                env = [p for p in env if not (p.startswith('PYTHONPATH') or \
                                              p.startswith('PYTHONHOME'))] # 1.

                env.append('PYTHONPATH=%s' % osp.dirname(__file__))  # 2.

            env = [p for p in env if not p.startswith('PYTHONOPTIMIZE')]  # 3.

        self.process.setEnvironment(env)
        self.process.start(self.pythonexecutable, p_args)
        #-------------------------Python specific-------------------------------

        running = self.process.waitForStarted(3000)
        self.set_running_state(running)
        if not running:
            QMessageBox.critical(
                self, _("Error"),
                _("A Python or IPython Console failed to start!"))
        else:
            self.shell.setFocus()
            self.emit(SIGNAL('started()'))

        return self.process

    def finished(self, exit_code, exit_status):
        """Reimplement ExternalShellBase method"""
        ExternalShellBase.finished(self, exit_code, exit_status)
        self.introspection_socket = None

#===============================================================================
#    Input/Output
#===============================================================================

    def write_error(self):
        if os.name == 'nt':
            #---This is apparently necessary only on Windows (not sure though):
            #   emptying standard output buffer before writing error output
            self.process.setReadChannel(QProcess.StandardOutput)
            if self.process.waitForReadyRead(1):
                self.write_output()
        self.shell.write_error(self.get_stderr())
        QApplication.processEvents()

    def send_to_process(self, text):
        if not self.is_running():
            return

        if not isinstance(text, basestring):
            text = unicode(text)
        if self.install_qt_inputhook and self.introspection_socket is not None:
            communicate(self.introspection_socket,
                        "toggle_inputhook_flag(True)")
#            # Socket-based alternative (see input hook in sitecustomize.py):
#            while self.local_server.hasPendingConnections():
#                self.local_server.nextPendingConnection().write('go!')
        if text.startswith(('%', '!')):
            text = 'evalsc(r"%s")\n' % text
        if not text.endswith('\n'):
            text += '\n'
        self.process.write(locale_codec.fromUnicode(text))
        self.process.waitForBytesWritten(-1)

        # Eventually write prompt faster (when hitting Enter continuously)
        # -- necessary/working on Windows only:
        if os.name == 'nt':
            self.write_error()

    def keyboard_interrupt(self):
        if self.introspection_socket is not None:
            communicate(self.introspection_socket, "thread.interrupt_main()")

    def quit_monitor(self):
        if self.introspection_socket is not None:
            try:
                write_packet(self.introspection_socket, "thread.exit()")
            except socket.error:
                pass

#===============================================================================
#    Globals explorer
#===============================================================================

    def toggle_globals_explorer(self, state):
        if self.stand_alone is not None:
            self.splitter.setSizes([1, 1 if state else 0])
            self.namespacebrowser_button.setChecked(state)
            if state and self.namespacebrowser is not None:
                self.namespacebrowser.refresh_table()

    def splitter_moved(self, pos, index):
        self.namespacebrowser_button.setChecked(self.splitter.sizes()[1])

#===============================================================================
#    Misc.
#===============================================================================

    def set_current_working_directory(self):
        """Set current working directory"""
        cwd = self.shell.get_cwd()
        self.emit(SIGNAL('redirect_stdio(bool)'), False)
        directory = getexistingdirectory(self, _("Select directory"), cwd)
        if directory:
            self.shell.set_cwd(directory)
        self.emit(SIGNAL('redirect_stdio(bool)'), True)

    def show_env(self):
        """Show environment variables"""
        get_func = self.shell.get_env
        set_func = self.shell.set_env
        self.dialog_manager.show(RemoteEnvDialog(get_func, set_func))

    def show_syspath(self):
        """Show sys.path contents"""
        editor = DictEditor()
        editor.setup(self.shell.get_syspath(),
                     title="sys.path",
                     readonly=True,
                     width=600,
                     icon='syspath.png')
        self.dialog_manager.show(editor)
Пример #13
0
class ProfilerWidget(QWidget):
    """
    Profiler widget
    """
    DATAPATH = get_conf_path('.profiler.results')
    VERSION = '0.0.1'
    
    def __init__(self, parent, max_entries=100):
        QWidget.__init__(self, parent)
        
        self.setWindowTitle("Profiler")
        
        self.output = None
        self.error_output = None
        
        self.filecombo = PythonModulesComboBox(self)
        
        self.start_button = create_toolbutton(self, icon=get_icon('run.png'),
                                    text=translate('Profiler', "Profile"),
                                    tip=translate('Profiler', "Run profiler"),
                                    triggered=self.start, text_beside_icon=True)
        self.stop_button = create_toolbutton(self,
                                    icon=get_icon('terminate.png'),
                                    text=translate('Profiler', "Stop"),
                                    tip=translate('Profiler',
                                                  "Stop current profiling"),
                                    text_beside_icon=True)
        self.connect(self.filecombo, SIGNAL('valid(bool)'),
                     self.start_button.setEnabled)
        #self.connect(self.filecombo, SIGNAL('valid(bool)'), self.show_data)
        # FIXME: The combobox emits this signal on almost any event
        #        triggering show_data() too early, too often. 

        browse_button = create_toolbutton(self, icon=get_icon('fileopen.png'),
                               tip=translate('Profiler', 'Select Python script'),
                               triggered=self.select_file)

        self.datelabel = QLabel()

        self.log_button = create_toolbutton(self, icon=get_icon('log.png'),
                                    text=translate('Profiler', "Output"),
                                    text_beside_icon=True,
                                    tip=translate('Profiler',
                                                  "Show program's output"),
                                    triggered=self.show_log)

        self.datatree = ProfilerDataTree(self)

        self.collapse_button = create_toolbutton(self, icon=get_icon('collapse.png'),
                                    triggered=lambda dD=-1:self.datatree.change_view(dD),
                                    tip='Collapse one level up')
        self.expand_button = create_toolbutton(self, icon=get_icon('expand.png'),
                                    triggered=lambda dD=1:self.datatree.change_view(dD),
                                    tip='Expand one level down')

        
        hlayout1 = QHBoxLayout()
        hlayout1.addWidget(self.filecombo)
        hlayout1.addWidget(browse_button)
        hlayout1.addWidget(self.start_button)
        hlayout1.addWidget(self.stop_button)

        hlayout2 = QHBoxLayout()
        hlayout2.addWidget(self.collapse_button)
        hlayout2.addWidget(self.expand_button)
        hlayout2.addStretch()
        hlayout2.addWidget(self.datelabel)
        hlayout2.addStretch()
        hlayout2.addWidget(self.log_button)
        
        layout = QVBoxLayout()
        layout.addLayout(hlayout1)
        layout.addLayout(hlayout2)
        layout.addWidget(self.datatree)
        self.setLayout(layout)
        
        self.process = None
        self.set_running_state(False)
        
        if not is_profiler_installed():
            for widget in (self.datatree, self.filecombo,
                           self.start_button, self.stop_button):
                widget.setDisabled(True)
            if os.name == 'nt' \
               and programs.is_module_installed("profile"):
                # The following is a comment from the pylint plugin:
                # Module is installed but script is not in PATH
                # (AFAIK, could happen only on Windows)
                text = translate('Profiler',
                     'Profiler script was not found. Please add "%s" to PATH.')
                text = unicode(text) % os.path.join(sys.prefix, "Scripts")
            else:
                text = translate('Profiler',
                    ('Please install the modules '+
                     '<b>profile</b> and <b>pstats</b>:'))
                # FIXME: need the actual website
                url = 'http://www.python.org'
                text += ' <a href=%s>%s</a>' % (url, url)
            self.datelabel.setText(text)
        else:
            pass # self.show_data()
                
    def analyze(self, filename):
        if not is_profiler_installed():
            return
        filename = unicode(filename) # filename is a QString instance
        self.kill_if_running()
        #index, _data = self.get_data(filename)
        index=None # FIXME: storing data is not implemented yet
        if index is None:
            self.filecombo.addItem(filename)
            self.filecombo.setCurrentIndex(self.filecombo.count()-1)
        else:
            self.filecombo.setCurrentIndex(self.filecombo.findText(filename))
        self.filecombo.selected()
        if self.filecombo.is_valid():
            self.start()
            
    def select_file(self):
        self.emit(SIGNAL('redirect_stdio(bool)'), False)
        filename = QFileDialog.getOpenFileName(self,
                      translate('Profiler', "Select Python script"), os.getcwdu(),
                      translate('Profiler', "Python scripts")+" (*.py ; *.pyw)")
        self.emit(SIGNAL('redirect_stdio(bool)'), False)
        if filename:
            self.analyze(filename)
        
    def show_log(self):
        if self.output:
            TextEditor(self.output, title=translate('Profiler', "Profiler output"),
                       readonly=True, size=(700, 500)).exec_()
    
    def show_errorlog(self):
        if self.error_output:
            TextEditor(self.error_output, title=translate('Profiler', "Profiler output"),
                       readonly=True, size=(700, 500)).exec_()
        
    def start(self):
        self.datelabel.setText('Profiling, please wait...')
        filename = unicode(self.filecombo.currentText())
        
        self.process = QProcess(self)
        self.process.setProcessChannelMode(QProcess.SeparateChannels)
        self.process.setWorkingDirectory(os.path.dirname(filename))
        self.connect(self.process, SIGNAL("readyReadStandardOutput()"),
                     self.read_output)
        self.connect(self.process, SIGNAL("readyReadStandardError()"),
                     lambda: self.read_output(error=True))
        self.connect(self.process, SIGNAL("finished(int,QProcess::ExitStatus)"),
                     self.finished)
        self.connect(self.stop_button, SIGNAL("clicked()"),
                     self.process.kill)
        
        self.output = ''
        self.error_output = ''
        p_args = [os.path.basename(filename)]
        
        # FIXME: Use the system path to 'python' as opposed to hardwired
        p_args = ['-m', PROFILER_PATH, '-o', self.DATAPATH, os.path.basename(filename)]
        self.process.start('python', p_args)
        
        running = self.process.waitForStarted()
        self.set_running_state(running)
        if not running:
            QMessageBox.critical(self, translate('Profiler', "Error"),
                                 translate('Profiler', "Process failed to start"))
    
    def set_running_state(self, state=True):
        self.start_button.setEnabled(not state)
        self.stop_button.setEnabled(state)
        
    def read_output(self, error=False):
        if error:
            self.process.setReadChannel(QProcess.StandardError)
        else:
            self.process.setReadChannel(QProcess.StandardOutput)
        bytes = QByteArray()
        while self.process.bytesAvailable():
            if error:
                bytes += self.process.readAllStandardError()
            else:
                bytes += self.process.readAllStandardOutput()
        text = unicode( QString.fromLocal8Bit(bytes.data()) )
        if error:
            self.error_output += text
        else:
            self.output += text
        
    def finished(self):
        self.set_running_state(False)
        self.show_errorlog()  # If errors occurred, show them.
        self.output = self.error_output + self.output
        # FIXME: figure out if show_data should be called here or
        #        as a signal from the combobox
        self.show_data(justanalyzed=True)
                
    def kill_if_running(self):
        if self.process is not None:
            if self.process.state() == QProcess.Running:
                self.process.kill()
                self.process.waitForFinished()
        
    def show_data(self, justanalyzed=False):
        if not justanalyzed:
            self.output = None
        self.log_button.setEnabled(self.output is not None \
                                   and len(self.output) > 0)
        self.kill_if_running()
        filename = unicode(self.filecombo.currentText())
        if not filename:
            return

        self.datatree.load_data(self.DATAPATH)
        self.datatree.show_tree()
            
        text_style = "<span style=\'color: #444444\'><b>%s </b></span>"
        date_text = text_style % time.strftime("%d %b %Y %H:%M",time.localtime())
        self.datelabel.setText(date_text)
Пример #14
0
class CFDSTUDYGUI_QProcessDialog(QDialog, Ui_CFDSTUDYGUI_QProcessDialog):
    """
    Advanced dialog.
    """
    def __init__(self, parent, title, cmd_list, obj_directory="", start_directory=""):
        """
        Constructor
        """
        QDialog.__init__(self, parent)

        Ui_CFDSTUDYGUI_QProcessDialog.__init__(self)
        self.setupUi(self)
        self.setWindowTitle(title)
        self.pushButton.setEnabled(False)

        if start_directory != None and start_directory != "":
            os.chdir(start_directory)

        self.objBr = None
        if obj_directory != None and obj_directory != "":
            self.objBr = obj_directory

        self.proc = QProcess()
        #env = QProcessEnvironment().systemEnvironment()
        #self.proc.setProcessEnvironment(env)

        self.connect(self.proc, SIGNAL('readyReadStandardOutput()'), self.__readFromStdout)
        self.connect(self.proc, SIGNAL('readyReadStandardError()'),  self.__readFromStderr)
        self.procErrorFlag = False

        self.cmd_list = cmd_list
        self.cmd = self.cmd_list.pop(0)
        cursor = QCursor(Qt.BusyCursor)
        QApplication.setOverrideCursor(cursor)
        self.__process()


    def __process(self):
        if self.proc.exitStatus() == QProcess.NormalExit and not self.procErrorFlag:
            self.proc.start(self.cmd)
            if self.cmd_list:
                self.cmd = self.cmd_list.pop(0)
                self.connect(self.proc,
                             SIGNAL('finished(int, QProcess::ExitStatus)'),
                             self.__process)
            else:
                self.connect(self.proc,
                             SIGNAL('finished(int, QProcess::ExitStatus)'),
                             self.__finished)


    def __readFromStdout(self):
        """
        Private slot to handle the readyReadStandardOutput signal of the process.
        """
        if self.proc is None:
            return
        self.proc.setReadChannel(QProcess.StandardOutput)

        while self.proc and self.proc.canReadLine():
            ba = self.proc.readLine()
            if ba.isNull(): return
            str = QString()
            s = QString(str.fromUtf8(ba.data()))[:-1]
            self.logText.append(s)


    def __readFromStderr(self):
        """
        Private slot to handle the readyReadStandardError signal of the process.
        """
        if self.proc is None:
            return
        self.proc.setReadChannel(QProcess.StandardError)

        while self.proc and self.proc.canReadLine():
            ba = self.proc.readLine()
            if ba.isNull(): return
            str = QString()
            s = QString(str.fromUtf8(ba.data()))[:-1]
            self.logText.append(s.prepend('<font color="red">').append('</font>'))
            self.procErrorFlag = True


    def __finished(self):
        if self.objBr:
            CFDSTUDYGUI_DataModel.UpdateSubTree(self.objBr)
        QApplication.restoreOverrideCursor()
        self.pushButton.setEnabled(True)


    def event(self, e):
        if e.type() == 9999:
            return QDialog.event(self, QCloseEvent())

        if e.type() == 9998:
            return QDialog.event(self, QCloseEvent())
        if e.type() == QEvent.Close:
            e.ignore()
            return True

        return QDialog.event(self, e)