class ExternalSystemShell(ExternalShellBase):
    """External Shell widget: execute Python script in a separate process"""
    SHELL_CLASS = TerminalWidget

    def __init__(self,
                 parent=None,
                 wdir=None,
                 path=[],
                 light_background=True,
                 menu_actions=None,
                 show_buttons_inside=True,
                 show_elapsed_time=True):
        ExternalShellBase.__init__(self,
                                   parent=parent,
                                   fname=None,
                                   wdir=wdir,
                                   history_filename='.history',
                                   light_background=light_background,
                                   menu_actions=menu_actions,
                                   show_buttons_inside=show_buttons_inside,
                                   show_elapsed_time=show_elapsed_time)

        # Additional python path list
        self.path = path

        # For compatibility with the other shells that can live in the external
        # console
        self.is_ipykernel = False
        self.connection_file = None

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

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

        self.process = QProcess(self)
        self.process.setProcessChannelMode(QProcess.MergedChannels)

        # PYTHONPATH (in case we use Python in this terminal, e.g. py2exe)
        env = [
            to_text_string(_path)
            for _path in self.process.systemEnvironment()
        ]
        add_pathlist_to_PYTHONPATH(env, self.path)
        self.process.setEnvironment(env)

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

        # Shell arguments
        if os.name == 'nt':
            p_args = ['/Q']
        else:
            p_args = ['-i']

        if self.arguments:
            p_args.extend(shell_split(self.arguments))

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

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

        if os.name == 'nt':
            self.process.start('cmd.exe', p_args)
        else:
            # Using bash:
            self.process.start('bash', p_args)
            self.send_to_process('PS1="\\u@\\h:\\w> "\n')

        running = self.process.waitForStarted()
        self.set_running_state(running)
        if not running:
            QMessageBox.critical(self, _("Error"),
                                 _("Process failed to start"))
        else:
            self.shell.setFocus()
            self.emit(SIGNAL('started()'))

        return self.process

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

    def transcode(self, qba):
        if os.name == 'nt':
            return to_text_string(CP850_CODEC.toUnicode(qba.data()))
        else:
            return ExternalShellBase.transcode(self, qba)

    def send_to_process(self, text):
        if not is_text_string(text):
            text = to_text_string(text)
        if text[:-1] in ["clear", "cls", "CLS"]:
            self.shell.clear()
            self.send_to_process(os.linesep)
            return
        if not text.endswith('\n'):
            text += '\n'
        if os.name == 'nt':
            self.process.write(text.encode('cp850'))
        else:
            self.process.write(LOCALE_CODEC.fromUnicode(text))
        self.process.waitForBytesWritten(-1)

    def keyboard_interrupt(self):
        # This does not work on Windows:
        # (unfortunately there is no easy way to send a Ctrl+C to cmd.exe)
        self.send_ctrl_to_process('c')
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._last_wdir = None
        self._last_args = None
        self._last_pythonpath = None
        
        self.filecombo = PythonModulesComboBox(self)
        
        self.start_button = create_toolbutton(self, icon=get_icon('run.png'),
                                    text=_("Profile"),
                                    tip=_("Run profiler"),
                                    triggered=self.start, text_beside_icon=True)
        self.stop_button = create_toolbutton(self,
                                    icon=get_icon('terminate.png'),
                                    text=_("Stop"),
                                    tip=_(
                                                  "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=_('Select Python script'),
                               triggered=self.select_file)

        self.datelabel = QLabel()

        self.log_button = create_toolbutton(self, icon=get_icon('log.png'),
                                            text=_("Output"),
                                            text_beside_icon=True,
                                            tip=_("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)
        self.start_button.setEnabled(False)
        
        if not is_profiler_installed():
            # This should happen only on certain GNU/Linux distributions 
            # or when this a home-made Python build because the Python 
            # profilers are included in the Python standard library
            for widget in (self.datatree, self.filecombo,
                           self.start_button, self.stop_button):
                widget.setDisabled(True)
            url = 'http://docs.python.org/library/profile.html'
            text = '%s <a href=%s>%s</a>' % (_('Please install'), url,
                                             _("the Python profiler modules"))
            self.datelabel.setText(text)
        else:
            pass # self.show_data()
        
    def analyze(self, filename, wdir=None, args=None, pythonpath=None):
        if not is_profiler_installed():
            return
        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():
            if wdir is None:
                wdir = osp.dirname(filename)
            self.start(wdir, args, pythonpath)
            
    def select_file(self):
        self.emit(SIGNAL('redirect_stdio(bool)'), False)
        filename, _selfilter = getopenfilename(self, _("Select Python script"),
                           getcwd(), _("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=_("Profiler output"),
                       readonly=True, size=(700, 500)).exec_()
    
    def show_errorlog(self):
        if self.error_output:
            TextEditor(self.error_output, title=_("Profiler output"),
                       readonly=True, size=(700, 500)).exec_()

    def start(self, wdir=None, args=None, pythonpath=None):
        filename = to_text_string(self.filecombo.currentText())
        if wdir is None:
            wdir = self._last_wdir
            if wdir is None:
                wdir = osp.basename(filename)
        if args is None:
            args = self._last_args
            if args is None:
                args = []
        if pythonpath is None:
            pythonpath = self._last_pythonpath
        self._last_wdir = wdir
        self._last_args = args
        self._last_pythonpath = pythonpath
        
        self.datelabel.setText(_('Profiling, please wait...'))
        
        self.process = QProcess(self)
        self.process.setProcessChannelMode(QProcess.SeparateChannels)
        self.process.setWorkingDirectory(wdir)
        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)

        if pythonpath is not None:
            env = [to_text_string(_pth)
                   for _pth in self.process.systemEnvironment()]
            baseshell.add_pathlist_to_PYTHONPATH(env, pythonpath)
            self.process.setEnvironment(env)
        
        self.output = ''
        self.error_output = ''
        
        p_args = ['-m', 'cProfile', '-o', self.DATAPATH]
        if os.name == 'nt':
            # On Windows, one has to replace backslashes by slashes to avoid 
            # confusion with escape characters (otherwise, for example, '\t' 
            # will be interpreted as a tabulation):
            p_args.append(osp.normpath(filename).replace(os.sep, '/'))
        else:
            p_args.append(filename)
        if args:
            p_args.extend(shell_split(args))
        executable = sys.executable
        if executable.endswith("spyder.exe"):
            # py2exe distribution
            executable = "python.exe"
        self.process.start(executable, p_args)
        
        running = self.process.waitForStarted()
        self.set_running_state(running)
        if not running:
            QMessageBox.critical(self, _("Error"),
                                 _("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)
        qba = QByteArray()
        while self.process.bytesAvailable():
            if error:
                qba += self.process.readAllStandardError()
            else:
                qba += self.process.readAllStandardOutput()
        text = to_text_string( locale_codec.toUnicode(qba.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 = to_text_string(self.filecombo.currentText())
        if not filename:
            return

        self.datatree.load_data(self.DATAPATH)
        self.datelabel.setText(_('Sorting data, please wait...'))
        QApplication.processEvents()
        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)
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,
                 umr_enabled=True, umr_namelist=[], umr_verbose=True,
                 pythonstartup=None, pythonexecutable=None,
                 monitor_enabled=True, mpl_backend=None, ets_backend='qt4',
                 qt_api=None, pyqt_api=0,
                 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.interact = interact
        self.is_ipykernel = ipykernel
        self.pythonstartup = pythonstartup
        self.pythonexecutable = pythonexecutable
        self.monitor_enabled = monitor_enabled
        self.mpl_backend = mpl_backend
        self.ets_backend = ets_backend
        self.qt_api = qt_api
        self.pyqt_api = pyqt_api
        self.ignore_sip_setapi_errors = ignore_sip_setapi_errors
        self.merge_output_channels = merge_output_channels
        self.colorize_sys_stderr = colorize_sys_stderr
        self.umr_enabled = umr_enabled
        self.umr_namelist = umr_namelist
        self.umr_verbose = umr_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 is_text_string(python_args)
            self.python_args = python_args
        
        assert is_text_string(arguments)
        self.arguments = arguments
        
        self.connection_file = None

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

        self.toggle_globals_explorer(False)
        self.interact_action.setChecked(self.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('stop.png'),
                  tip=_("Attempts to stop the process. The process\n"
                        "may not exit as a result of clicking this\n"
                        "button (it is given the chance to prompt\n"
                        "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.terminate_button, self.kill_button,
                    self.options_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(lambda data:
                           self.namespacebrowser.process_remote_view(data))
    
    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 = [to_text_string(_path)
               for _path in self.process.systemEnvironment()]
        if self.pythonstartup:
            env.append('PYTHONSTARTUP=%s' % self.pythonstartup)
        
        # Set standard input/output encoding for Python consoles
        # (IPython handles it on its own)
        # See http://stackoverflow.com/q/26312400/438386, specifically
        # the comments of Martijn Pieters
        if not self.is_ipykernel:
            env.append('PYTHONIOENCODING=UTF-8')
        
        # 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 spyderlib.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)
        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('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('UMR_ENABLED=%r' % self.umr_enabled)
            env.append('UMR_NAMELIST=%s' % ','.join(self.umr_namelist))
            env.append('UMR_VERBOSE=%r' % self.umr_verbose)
            env.append('MATPLOTLIB_ION=True')
        else:
            if self.interact:
                env.append('MATPLOTLIB_ION=True')
            else:
                env.append('MATPLOTLIB_ION=False')

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

        # Add sitecustomize path to path list 
        pathlist = []
        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. Set PYTHONPATH again but without grabbing entries defined in the
        #    environment (Fixes Issue 1321)
        # 3. Remove PYTHONOPTIMIZE from env so that we can have assert
        #    statements working with our interpreters (See Issue 1281)
        if running_in_mac_app():
            env.append('SPYDER_INTERPRETER=%s' % self.pythonexecutable)
            if MAC_APP_NAME not in self.pythonexecutable:
                env = [p for p in env if not (p.startswith('PYTHONPATH') or \
                                              p.startswith('PYTHONHOME'))] # 1.

                add_pathlist_to_PYTHONPATH(env, pathlist, drop_env=True)   # 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:
            if self.is_ipykernel:
                self.emit(SIGNAL("ipython_kernel_start_error(QString)"),
                          _("The kernel failed to start!! That's all we know... "
                            "Please close this console and open a new one."))
            else:
                QMessageBox.critical(self, _("Error"),
                                     _("A Python 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"""
        if self.is_ipykernel and exit_code == 1:
            self.emit(SIGNAL("ipython_kernel_start_error(QString)"),
                      self.shell.get_text_with_eol())
        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 is_text_string(text):
            text = to_text_string(text)
        if self.mpl_backend == 'Qt4Agg' and os.name == 'nt' 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 any([text == cmd for cmd in ['%ls', '%pwd', '%scientific']]) or \
          any([text.startswith(cmd) for cmd in ['%cd ', '%clear ']]):
            text = 'evalsc(r"%s")\n' % text
        if not text.endswith('\n'):
            text += '\n'
        self.process.write(to_binary_string(text, 'utf8'))
        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)
class ExternalSystemShell(ExternalShellBase):
    """External Shell widget: execute Python script in a separate process"""
    SHELL_CLASS = TerminalWidget
    def __init__(self, parent=None, wdir=None, path=[], light_background=True,
                 menu_actions=None, show_buttons_inside=True,
                 show_elapsed_time=True):
        ExternalShellBase.__init__(self, parent=parent, fname=None, wdir=wdir,
                                   history_filename='.history',
                                   light_background=light_background,
                                   menu_actions=menu_actions,
                                   show_buttons_inside=show_buttons_inside,
                                   show_elapsed_time=show_elapsed_time)
        
        # Additional python path list
        self.path = path
        
        # For compatibility with the other shells that can live in the external
        # console
        self.is_ipykernel = False
        self.connection_file = None

    def get_icon(self):
        return get_icon('cmdprompt.png')
    
    def create_process(self):
        self.shell.clear()
            
        self.process = QProcess(self)
        self.process.setProcessChannelMode(QProcess.MergedChannels)
        
        # PYTHONPATH (in case we use Python in this terminal, e.g. py2exe)
        env = [to_text_string(_path)
               for _path in self.process.systemEnvironment()]
        add_pathlist_to_PYTHONPATH(env, self.path)
        self.process.setEnvironment(env)
        
        # Working directory
        if self.wdir is not None:
            self.process.setWorkingDirectory(self.wdir)
            
        # Shell arguments
        if os.name == 'nt':
            p_args = ['/Q']
        else:
            p_args = ['-i']
            
        if self.arguments:
            p_args.extend( shell_split(self.arguments) )
                        
        self.connect(self.process, SIGNAL("readyReadStandardOutput()"),
                     self.write_output)
        self.connect(self.process, SIGNAL("finished(int,QProcess::ExitStatus)"),
                     self.finished)
        
        self.connect(self.kill_button, SIGNAL("clicked()"),
                     self.process.kill)
        
        if os.name == 'nt':
            self.process.start('cmd.exe', p_args)
        else:
            # Using bash:
            self.process.start('bash', p_args)
            self.send_to_process('PS1="\\u@\\h:\\w> "\n')
            
        running = self.process.waitForStarted()
        self.set_running_state(running)
        if not running:
            QMessageBox.critical(self, _("Error"),
                                 _("Process failed to start"))
        else:
            self.shell.setFocus()
            self.emit(SIGNAL('started()'))
            
        return self.process
    
#===============================================================================
#    Input/Output
#===============================================================================
    def transcode(self, qba):
        if os.name == 'nt':
            return to_text_string( CP850_CODEC.toUnicode(qba.data()) )
        else:
            return ExternalShellBase.transcode(self, qba)
    
    def send_to_process(self, text):
        if not is_text_string(text):
            text = to_text_string(text)
        if text[:-1] in ["clear", "cls", "CLS"]:
            self.shell.clear()
            self.send_to_process(os.linesep)
            return
        if not text.endswith('\n'):
            text += '\n'
        if os.name == 'nt':
            self.process.write(text.encode('cp850'))
        else:
            self.process.write(LOCALE_CODEC.fromUnicode(text))
        self.process.waitForBytesWritten(-1)
        
    def keyboard_interrupt(self):
        # This does not work on Windows:
        # (unfortunately there is no easy way to send a Ctrl+C to cmd.exe)
        self.send_ctrl_to_process('c')

#        # The following code will soon be removed:
#        # (last attempt to send a Ctrl+C on Windows)
#        if os.name == 'nt':
#            pid = int(self.process.pid())
#            import ctypes, win32api, win32con
#            class _PROCESS_INFORMATION(ctypes.Structure):
#                _fields_ = [("hProcess", ctypes.c_int),
#                            ("hThread", ctypes.c_int),
#                            ("dwProcessID", ctypes.c_int),
#                            ("dwThreadID", ctypes.c_int)]
#            x = ctypes.cast( ctypes.c_void_p(pid),
#                             ctypes.POINTER(_PROCESS_INFORMATION) )
#            win32api.GenerateConsoleCtrlEvent(win32con.CTRL_C_EVENT,
#                                              x.dwProcessID)
#        else:
#            self.send_ctrl_to_process('c')
                
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._last_wdir = None
        self._last_args = None
        self._last_pythonpath = None

        self.filecombo = PythonModulesComboBox(self)

        self.start_button = create_toolbutton(self,
                                              icon=get_icon('run.png'),
                                              text=_("Profile"),
                                              tip=_("Run profiler"),
                                              triggered=self.start,
                                              text_beside_icon=True)
        self.stop_button = create_toolbutton(self,
                                             icon=get_icon('stop.png'),
                                             text=_("Stop"),
                                             tip=_("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=_('Select Python script'),
                                          triggered=self.select_file)

        self.datelabel = QLabel()

        self.log_button = create_toolbutton(self,
                                            icon=get_icon('log.png'),
                                            text=_("Output"),
                                            text_beside_icon=True,
                                            tip=_("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)
        self.start_button.setEnabled(False)

        if not is_profiler_installed():
            # This should happen only on certain GNU/Linux distributions
            # or when this a home-made Python build because the Python
            # profilers are included in the Python standard library
            for widget in (self.datatree, self.filecombo, self.start_button,
                           self.stop_button):
                widget.setDisabled(True)
            url = 'http://docs.python.org/library/profile.html'
            text = '%s <a href=%s>%s</a>' % (_('Please install'), url,
                                             _("the Python profiler modules"))
            self.datelabel.setText(text)
        else:
            pass  # self.show_data()

    def analyze(self, filename, wdir=None, args=None, pythonpath=None):
        if not is_profiler_installed():
            return
        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():
            if wdir is None:
                wdir = osp.dirname(filename)
            self.start(wdir, args, pythonpath)

    def select_file(self):
        self.emit(SIGNAL('redirect_stdio(bool)'), False)
        filename, _selfilter = getopenfilename(
            self, _("Select Python script"), getcwd(),
            _("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=_("Profiler output"),
                       readonly=True,
                       size=(700, 500)).exec_()

    def show_errorlog(self):
        if self.error_output:
            TextEditor(self.error_output,
                       title=_("Profiler output"),
                       readonly=True,
                       size=(700, 500)).exec_()

    def start(self, wdir=None, args=None, pythonpath=None):
        filename = to_text_string(self.filecombo.currentText())
        if wdir is None:
            wdir = self._last_wdir
            if wdir is None:
                wdir = osp.basename(filename)
        if args is None:
            args = self._last_args
            if args is None:
                args = []
        if pythonpath is None:
            pythonpath = self._last_pythonpath
        self._last_wdir = wdir
        self._last_args = args
        self._last_pythonpath = pythonpath

        self.datelabel.setText(_('Profiling, please wait...'))

        self.process = QProcess(self)
        self.process.setProcessChannelMode(QProcess.SeparateChannels)
        self.process.setWorkingDirectory(wdir)
        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)

        if pythonpath is not None:
            env = [
                to_text_string(_pth)
                for _pth in self.process.systemEnvironment()
            ]
            baseshell.add_pathlist_to_PYTHONPATH(env, pythonpath)
            self.process.setEnvironment(env)

        self.output = ''
        self.error_output = ''

        p_args = ['-m', 'cProfile', '-o', self.DATAPATH]
        if os.name == 'nt':
            # On Windows, one has to replace backslashes by slashes to avoid
            # confusion with escape characters (otherwise, for example, '\t'
            # will be interpreted as a tabulation):
            p_args.append(osp.normpath(filename).replace(os.sep, '/'))
        else:
            p_args.append(filename)
        if args:
            p_args.extend(shell_split(args))
        executable = sys.executable
        if executable.endswith("spyder.exe"):
            # py2exe distribution
            executable = "python.exe"
        self.process.start(executable, p_args)

        running = self.process.waitForStarted()
        self.set_running_state(running)
        if not running:
            QMessageBox.critical(self, _("Error"),
                                 _("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)
        qba = QByteArray()
        while self.process.bytesAvailable():
            if error:
                qba += self.process.readAllStandardError()
            else:
                qba += self.process.readAllStandardOutput()
        text = to_text_string(locale_codec.toUnicode(qba.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 = to_text_string(self.filecombo.currentText())
        if not filename:
            return

        self.datatree.load_data(self.DATAPATH)
        self.datelabel.setText(_('Sorting data, please wait...'))
        QApplication.processEvents()
        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)
Beispiel #6
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,
                 umr_enabled=True, umr_namelist=[], umr_verbose=True,
                 pythonstartup=None, pythonexecutable=None,
                 monitor_enabled=True, mpl_backend=None, ets_backend='qt4',
                 qt_api=None, pyqt_api=0,
                 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.interact = interact
        self.is_ipykernel = ipykernel
        self.pythonstartup = pythonstartup
        self.pythonexecutable = pythonexecutable
        self.monitor_enabled = monitor_enabled
        self.mpl_backend = mpl_backend
        self.ets_backend = ets_backend
        self.qt_api = qt_api
        self.pyqt_api = pyqt_api
        self.ignore_sip_setapi_errors = ignore_sip_setapi_errors
        self.merge_output_channels = merge_output_channels
        self.colorize_sys_stderr = colorize_sys_stderr
        self.umr_enabled = umr_enabled
        self.umr_namelist = umr_namelist
        self.umr_verbose = umr_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 is_text_string(python_args)
            self.python_args = python_args
        
        assert is_text_string(arguments)
        self.arguments = arguments
        
        self.connection_file = None

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

        self.toggle_globals_explorer(False)
        self.interact_action.setChecked(self.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('stop.png'),
                  tip=_("Attempts to stop the process. The process\n"
                        "may not exit as a result of clicking this\n"
                        "button (it is given the chance to prompt\n"
                        "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.terminate_button, self.kill_button,
                    self.options_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(lambda data:
                           self.namespacebrowser.process_remote_view(data))
    
    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 = [to_text_string(_path)
               for _path in self.process.systemEnvironment()]
        if self.pythonstartup:
            env.append('PYTHONSTARTUP=%s' % self.pythonstartup)
        
        # Set standard input/output encoding for Python consoles
        # (IPython handles it on its own)
        # See http://stackoverflow.com/q/26312400/438386, specifically
        # the comments of Martijn Pieters
        if not self.is_ipykernel:
            env.append('PYTHONIOENCODING=UTF-8')
        
        # 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 spyderlib.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)
        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('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('UMR_ENABLED=%r' % self.umr_enabled)
            env.append('UMR_NAMELIST=%s' % ','.join(self.umr_namelist))
            env.append('UMR_VERBOSE=%r' % self.umr_verbose)
            env.append('MATPLOTLIB_ION=True')
        else:
            if self.interact:
                env.append('MATPLOTLIB_ION=True')
            else:
                env.append('MATPLOTLIB_ION=False')

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

        # Add sitecustomize path to path list 
        pathlist = []
        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. Set PYTHONPATH again but without grabbing entries defined in the
        #    environment (Fixes Issue 1321)
        # 3. Remove PYTHONOPTIMIZE from env so that we can have assert
        #    statements working with our interpreters (See Issue 1281)
        if running_in_mac_app():
            env.append('SPYDER_INTERPRETER=%s' % self.pythonexecutable)
            if MAC_APP_NAME not in self.pythonexecutable:
                env = [p for p in env if not (p.startswith('PYTHONPATH') or \
                                              p.startswith('PYTHONHOME'))] # 1.

                add_pathlist_to_PYTHONPATH(env, pathlist, drop_env=True)   # 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:
            if self.is_ipykernel:
                self.emit(SIGNAL("ipython_kernel_start_error(QString)"),
                          _("The kernel failed to start!! That's all we know... "
                            "Please close this console and open a new one."))
            else:
                QMessageBox.critical(self, _("Error"),
                                     _("A Python 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"""
        if self.is_ipykernel and exit_code == 1:
            self.emit(SIGNAL("ipython_kernel_start_error(QString)"),
                      self.shell.get_text_with_eol())
        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 is_text_string(text):
            text = to_text_string(text)
        if self.mpl_backend == 'Qt4Agg' and os.name == 'nt' 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 any([text == cmd for cmd in ['%ls', '%pwd', '%scientific']]) or \
          any([text.startswith(cmd) for cmd in ['%cd ', '%clear ']]):
            text = 'evalsc(r"%s")\n' % text
        if not text.endswith('\n'):
            text += '\n'
        self.process.write(to_binary_string(text, 'utf8'))
        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)
Beispiel #7
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='',
                 ipython_shell=False, ipython_kernel=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, 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)
        
        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.is_ipython_shell = ipython_shell
        self.is_ipython_kernel = ipython_kernel
        if self.is_ipython_shell or self.is_ipython_kernel:
            interact = False
            # Running our custom startup script for IPython sessions:
            # (see spyderlib/widgets/externalshell/startup.py)
            self.fname = get_module_source_path(
                            'spyderlib.widgets.externalshell', 'startup.py')
        else:
            self.fname = fname
        
        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
        
    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/IPython 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 or\
                                     self.is_ipython_shell))
        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 or self.is_ipython_shell:
            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'] + 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 spyderlib.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_frontend(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 related configuration
        if self.is_ipython_shell:
            env.append('IPYTHON=True')
            # Do not call msvcrt.getch in IPython.genutils.page_more:
            env.append('TERM=emacs')
        elif self.is_ipython_kernel:
            env.append('IPYTHON_KERNEL=True')
            
        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-------------------------------
                        
        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-------------------------------
        executable = self.pythonexecutable
        if executable is None:
            executable = get_python_executable()
        self.process.start(executable, p_args)
        #-------------------------Python specific-------------------------------
            
        running = self.process.waitForStarted()
        self.set_running_state(running)
        if not running:
            QMessageBox.critical(self, _("Error"),
                                 _("Process failed to start"))
        else:
            self.shell.setFocus()
            self.emit(SIGNAL('started()'))
            
        return self.process
    
#===============================================================================
#    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 isinstance(text, basestring):
            text = unicode(text)
        if self.install_qt_inputhook and not self.is_ipython_shell:
            # For now, the Spyder's input hook does not work with IPython:
            # with IPython v0.10 or non-Windows platforms, this is not a
            # problem. However, with IPython v0.11 on Windows, this will be
            # fixed by patching IPython to force it to use our inputhook.
            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 not self.is_ipython_shell and 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)