def add_shellwidget(self, shellwidget): shellwidget_id = id(shellwidget) # Add shell only once: this method may be called two times in a row # by the External console plugin (dev. convenience) from spyderlib.widgets.externalshell import systemshell if isinstance(shellwidget, systemshell.ExternalSystemShell): return if shellwidget_id not in self.shellwidgets: nsb = NamespaceBrowser(self) nsb.set_shellwidget(shellwidget) nsb.setup(**VariableExplorer.get_settings()) nsb.sig_option_changed.connect(self.sig_option_changed.emit) self.addWidget(nsb) self.shellwidgets[shellwidget_id] = nsb self.set_shellwidget(shellwidget) return nsb
def add_shellwidget(self, shellwidget): shellwidget_id = id(shellwidget) # Add shell only once: this method may be called two times in a row # by the External console plugin (dev. convenience) from spyderlib.widgets.externalshell import systemshell if isinstance(shellwidget, systemshell.ExternalSystemShell): return if shellwidget_id not in self.shellwidgets: nsb = NamespaceBrowser(self) nsb.set_shellwidget(shellwidget) nsb.setup(**VariableExplorer.get_settings()) nsb.sig_option_changed.connect(self.sig_option_changed.emit) self.add_widget(nsb) self.shellwidgets[shellwidget_id] = nsb self.set_shellwidget_from_id(shellwidget_id) return nsb
class ExternalPythonShell(ExternalShellBase): """External Shell widget: execute Python script in a separate process""" SHELL_CLASS = ExtPythonShellWidget sig_pdb = Signal(str, int) open_file = Signal(str, int) ipython_kernel_start_error = Signal(str) create_ipython_client = Signal(str) started = Signal() sig_finished = Signal() def __init__(self, parent=None, fname=None, wdir=None, interact=False, debug=False, post_mortem=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() self.post_mortem_action.setChecked(post_mortem and not self.is_interpreter) # 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) self.post_mortem_action = create_action(self, _("Post Mortem Debug")) self.post_mortem_action.setCheckable(True) run_settings_menu = QMenu(_("Run settings"), self) add_actions(run_settings_menu, (self.interact_action, self.debug_action, self.args_action, self.post_mortem_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.namespacebrowser.sig_collapse.connect( lambda: self.toggle_globals_explorer(False)) # Shell splitter self.splitter = splitter = QSplitter(Qt.Vertical, self) self.splitter.splitterMoved.connect(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) self.post_mortem_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.notification_thread.refresh_namespace_browser.connect( 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.shell.wait_for_ready_read.connect( 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) #-------------------------Python specific------------------------------- # Post mortem debugging if self.post_mortem_action.isChecked(): env.append('SPYDER_EXCEPTHOOK=True') # 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.notification_thread.sig_pdb.connect( lambda fname, lineno: self.sig_pdb.emit(fname, lineno)) self.notification_thread.new_ipython_kernel.connect( lambda args: self.create_ipython_client.emit(args)) self.notification_thread.open_file.connect( lambda fname, lineno: self.open_file.emit(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.process.readyReadStandardOutput.connect(self.write_output) self.process.readyReadStandardError.connect(self.write_error) self.process.finished.connect( lambda ec, es=QProcess.ExitStatus: self.finished(ec, es)) self.sig_finished.connect(self.dialog_manager.close_all) self.terminate_button.clicked.connect(self.process.terminate) self.kill_button.clicked.connect(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. processEnvironment = QProcessEnvironment() for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) self.process.setProcessEnvironment(processEnvironment) 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.ipython_kernel_start_error.emit( _("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.started.emit() return self.process def finished(self, exit_code, exit_status): """Reimplement ExternalShellBase method""" if self.is_ipykernel and exit_code == 1: self.ipython_kernel_start_error.emit( 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 #============================================================================== @Slot(bool) 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. #============================================================================== @Slot() def set_current_working_directory(self): """Set current working directory""" cwd = self.shell.get_cwd() self.redirect_stdio.emit(False) directory = getexistingdirectory(self, _("Select directory"), cwd) if directory: self.shell.set_cwd(directory) self.redirect_stdio.emit(True) @Slot() 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)) @Slot() 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 ExternalPythonShell(ExternalShellBase): """External Shell widget: execute Python script in a separate process""" SHELL_CLASS = ExtPythonShellWidget sig_pdb = Signal(str, int) open_file = Signal(str, int) ipython_kernel_start_error = Signal(str) create_ipython_client = Signal(str) started = Signal() sig_finished = Signal() def __init__(self, parent=None, fname=None, wdir=None, interact=False, debug=False, post_mortem=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() self.post_mortem_action.setChecked(post_mortem and not self.is_interpreter) # 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) self.post_mortem_action = create_action(self, _("Post Mortem Debug")) self.post_mortem_action.setCheckable(True) run_settings_menu = QMenu(_("Run settings"), self) add_actions(run_settings_menu, (self.interact_action, self.debug_action, self.args_action, self.post_mortem_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.namespacebrowser.sig_collapse.connect( lambda: self.toggle_globals_explorer(False)) # Shell splitter self.splitter = splitter = QSplitter(Qt.Vertical, self) self.splitter.splitterMoved.connect(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) self.post_mortem_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.notification_thread.refresh_namespace_browser.connect( 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.shell.wait_for_ready_read.connect( 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) #-------------------------Python specific------------------------------- # Post mortem debugging if self.post_mortem_action.isChecked(): env.append('SPYDER_EXCEPTHOOK=True') # 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.notification_thread.sig_pdb.connect( lambda fname, lineno: self.sig_pdb.emit(fname, lineno)) self.notification_thread.new_ipython_kernel.connect( lambda args: self.create_ipython_client.emit(args)) self.notification_thread.open_file.connect( lambda fname, lineno: self.open_file.emit(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.process.readyReadStandardOutput.connect(self.write_output) self.process.readyReadStandardError.connect(self.write_error) self.process.finished.connect(lambda ec, es=QProcess.ExitStatus: self.finished(ec, es)) self.sig_finished.connect(self.dialog_manager.close_all) self.terminate_button.clicked.connect(self.process.terminate) self.kill_button.clicked.connect(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. processEnvironment = QProcessEnvironment() for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) self.process.setProcessEnvironment(processEnvironment) 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.ipython_kernel_start_error.emit( _("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.started.emit() return self.process def finished(self, exit_code, exit_status): """Reimplement ExternalShellBase method""" if self.is_ipykernel and exit_code == 1: self.ipython_kernel_start_error.emit(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 #============================================================================== @Slot(bool) 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. #============================================================================== @Slot() def set_current_working_directory(self): """Set current working directory""" cwd = self.shell.get_cwd() self.redirect_stdio.emit(False) directory = getexistingdirectory(self, _("Select directory"), cwd) if directory: self.shell.set_cwd(directory) self.redirect_stdio.emit(True) @Slot() 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)) @Slot() 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 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)
class MainWindow(QMainWindow, Ui_MainWindow): """ The main window of SpikeViewer """ def __init__(self, parent = None): QMainWindow.__init__(self, parent) QCoreApplication.setOrganizationName('SpykeUtils') QCoreApplication.setApplicationName('Spyke Viewer') self.setupUi(self) self.dir = os.getcwd() self.console = None self.progress = ProgressIndicatorDialog(self) self.provider_factory = DataProvider self.selections = [] self.provider = None self.plugin_paths = [] # Drag and Drop for selections menu self.menuSelections.setAcceptDrops(True) self.menuSelections.paintEvent =\ self.on_menuSelections_paint self.menuSelections.mousePressEvent =\ self.on_menuSelections_mousePressed self.menuSelections.mouseMoveEvent =\ self.on_menuSelections_mouseMoved self.menuSelections.dragEnterEvent =\ self.on_menuSelections_dragEnter self.menuSelections.dragMoveEvent =\ self.on_menuSelections_dragMoved self.menuSelections.dropEvent =\ self.on_menuSelections_drop self.seldrag_start_pos = None self.seldrag_selection = None self.seldrag_target = None self.seldrag_target_upper = False # Docks self.setCentralWidget(None) # A lot of docks are only needed for the internal database mode self.navigationDBDock.setVisible(False) self.filterDBDock.setVisible(False) self.spikeSortingDock.setVisible(False) self.waveformsDock.setVisible(False) self.spikeTrainDock.setVisible(False) if not hasattr(self, 'internal_database_mode'): self.removeDockWidget(self.navigationDBDock) self.removeDockWidget(self.filterDBDock) self.removeDockWidget(self.spikeSortingDock) self.removeDockWidget(self.waveformsDock) self.removeDockWidget(self.spikeTrainDock) del self.navigationDBDock del self.filterDBDock del self.spikeSortingDock del self.waveformsDock del self.spikeTrainDock self.update_view_menu() if not hasattr(self, 'internal_database_mode'): self.restore_state() self.mode_switch() def update_view_menu(self): self.menuBar.clear() self.menuBar.addMenu(self.menuFile) self.menuBar.addMenu(self.menuSelections) self.menuView = self.createPopupMenu() self.menuView.setTitle('View') self.menuBar.addMenu(self.menuView) self.menuBar.addMenu(self.menuHelp) def restore_state(self): settings = QSettings() if not settings.contains('windowGeometry') or\ not settings.contains('windowState'): self.set_initial_layout() else: self.restoreGeometry(settings.value('windowGeometry')) self.restoreState(settings.value('windowState')) if not settings.contains('pluginPaths'): if hasattr(sys, 'frozen'): module_path = os.path.dirname(sys.executable) else: file_path = os.path.abspath(os.path.dirname(__file__)) module_path = os.path.dirname(os.path.dirname(file_path)) plugin_path = os.path.join(module_path, 'plugins') if os.path.isdir(plugin_path): self.plugin_paths.append(plugin_path) else: paths = settings.value('pluginPaths') if paths is None: self.plugin_paths = [] else: self.plugin_paths = paths if not settings.contains('selectionPath'): data_path = QDesktopServices.storageLocation( QDesktopServices.DataLocation) self.selection_path = os.path.join(data_path, 'selections') else: self.selection_path = settings.value('selectionPath') if not settings.contains('dataPath'): data_path = QDesktopServices.storageLocation( QDesktopServices.DataLocation) AnalysisPlugin.data_dir = os.path.join(data_path, 'data') else: AnalysisPlugin.data_dir = settings.value('dataPath') if not settings.contains('remoteScript'): if hasattr(sys, 'frozen'): path = os.path.dirname(sys.executable) else: file_path = os.path.abspath(os.path.dirname(__file__)) path = os.path.dirname(os.path.dirname(file_path)) path = os.path.join(path, 'bin') self.remote_script = os.path.join(path, 'spykeview_analysis') else: self.remote_script = settings.value('remoteScript') def set_initial_layout(self): self.setGeometry(self.x(), self.y(), 800, 750) self.navigationNeoDock.setVisible(True) self.removeDockWidget(self.filterDock) self.removeDockWidget(self.analysisDock) self.addDockWidget(Qt.RightDockWidgetArea, self.filterDock) self.addDockWidget(Qt.RightDockWidgetArea, self.analysisDock) self.tabifyDockWidget(self.filterDock, self.analysisDock) self.filterDock.setVisible(True) self.analysisDock.setVisible(True) self.consoleDock.setVisible(False) self.variableExplorerDock.setVisible(False) self.historyDock.setVisible(False) self.tabifyDockWidget(self.consoleDock, self.variableExplorerDock) self.tabifyDockWidget(self.variableExplorerDock, self.historyDock) def mode_switch(self): if self.is_db_mode(): self.switch_to_db_mode() if self.is_neo_mode(): self.switch_to_neo_mode() if self.console: self.console.interpreter.locals['current'] = self.provider def switch_to_db_mode(self): raise NotImplementedError() def switch_to_neo_mode(self): raise NotImplementedError() def _init_python_tab(self): # Console ns = {'current': self.provider, 'selections': self.selections} cmds = """ from __future__ import division import scipy as sp import numpy as np import matplotlib.pyplot as plt import guiqwt import guiqwt.pyplot as guiplt import guidata import spykeutils import spykeviewer plt.ion() """.split('\n') self.console = InternalShell(self.consoleDock, namespace=ns, multithreaded=False, commands=cmds, max_line_count=1000) self.consoleDock.setWidget(self.console) self.console.clear_terminal() # Variable browser self.browser = NamespaceBrowser(self.variableExplorerDock) self.browser.set_shellwidget(self.console) self.browser.setup(check_all=True, exclude_private=True, exclude_uppercase=False, exclude_capitalized=False, exclude_unsupported=True, excluded_names=[], truncate=False, minmax=False, collvalue=False, remote_editing=False, inplace=False, autorefresh=False) self.variableExplorerDock.setWidget(self.browser) # History self.history = CodeEditor(self.historyDock) self.history.setup_editor(linenumbers=False, language='py', scrollflagarea=False) self.history.setReadOnly(True) self.history.set_text('\n'.join(self.console.history)) self.history.set_cursor_position('eof') self.historyDock.setWidget(self.history) self.console.connect(self.console, SIGNAL("refresh()"), self._append_python_history) def _append_python_history(self): self.browser.refresh_table() self.history.append('\n' + self.console.history[-1]) self.history.set_cursor_position('eof') def is_neo_mode(self): return True def is_db_mode(self): return False @pyqtSignature("") def on_actionExit_triggered(self): self.close() def on_menuHelp_triggered(self): QMessageBox.about(self, 'How to navigate plots', 'Zoom:\tHold right mouse button' + '\nTranslate:\tHold middle mouse button' + '\nHome:\tClick middle mouse button' + '\n\nAxis synchronization does not work with some actions. ' + 'Zoom or translate changed plot to synchronize.') def on_menuSelections_mousePressed(self, event): if event.button() == Qt.LeftButton: action = self.menuSelections.actionAt(event.pos()) if action: selection = action.data() if selection: self.seldrag_start_pos = event.pos() self.seldrag_selection = selection else: self.seldrag_start_pos = None self.seldrag_selection = None self.seldrag_target = None QMenu.mousePressEvent(self.menuSelections, event) def on_menuSelections_mouseMoved(self, event): if event.buttons() & Qt.LeftButton and self.seldrag_start_pos: if ((event.pos() - self.seldrag_start_pos).manhattanLength() >= QApplication.startDragDistance()): drag = QDrag(self.menuSelections) data = QMimeData() data.setText(self.seldrag_selection.name) drag.setMimeData(data) drag.exec_() self.seldrag_start_pos = None self.seldrag_selection = None self.seldrag_target = None QMenu.mouseMoveEvent(self.menuSelections, event) def on_menuSelections_paint(self, event): QMenu.paintEvent(self.menuSelections, event) if self.seldrag_target: # Paint line where selection will be dropped p = QPainter() color = QPalette().color(self.menuSelections.foregroundRole()) pen = QPen(color, 2, Qt.SolidLine) p.begin(self.menuSelections) p.setPen(pen) rect = self.menuSelections.actionGeometry(self.seldrag_target) if self.seldrag_target_upper: p.drawLine(rect.topLeft(), rect.topRight()) else: p.drawLine(rect.bottomLeft(), rect.bottomRight()) p.end() def _menuSelections_pos_is_drop_target(self, pos): """ Return if selection can be dropped at this position and prepare information needed for drawing and dropping """ action = self.menuSelections.actionAt(pos) if not action or not action.data(): self.seldrag_target = None return False self.seldrag_target = action rect = self.menuSelections.actionGeometry(action) if pos.y() < rect.top() + rect.height() / 2: self.seldrag_target_upper = True else: self.seldrag_target_upper = False return True def on_menuSelections_dragEnter(self, event): event.setDropAction(Qt.MoveAction) if self._menuSelections_pos_is_drop_target(event.pos()): event.accept() else: event.ignore() QMenu.dragEnterEvent(self.menuSelections, event) def on_menuSelections_dragMoved(self, event): event.setDropAction(Qt.MoveAction) if self._menuSelections_pos_is_drop_target(event.pos()): event.accept() self.menuSelections.update() else: event.ignore() QMenu.dragMoveEvent(self.menuSelections, event) def on_menuSelections_drop(self, event): source = self.seldrag_selection target = self.seldrag_target.data() if source != target: self.selections.remove(source) target_index = self.selections.index(target) if not self.seldrag_target_upper: target_index += 1 self.selections.insert(target_index, source) self.populate_selection_menu() QMenu.dropEvent(self.menuSelections, event) def populate_selection_menu(self): self.menuSelections.clear() a = self.menuSelections.addAction('New') a.triggered.connect(self.on_selection_new) a = self.menuSelections.addAction('Clear') a.triggered.connect(self.on_selection_clear) self.menuSelections.addSeparator() for i, s in enumerate(self.selections): m = self.menuSelections.addMenu(s.name) m.menuAction().setData(s) a = m.addAction('Load') self.connect(a, SIGNAL('triggered()'), lambda sel=s:self.on_selection_load(sel)) a = m.addAction('Save') self.connect(a, SIGNAL('triggered()'), lambda sel=s:self.on_selection_save(sel)) a = m.addAction('Rename') self.connect(a, SIGNAL('triggered()'), lambda sel=s:self.on_selection_rename(sel)) a = m.addAction('Remove') self.connect(a, SIGNAL('triggered()'), lambda sel=s:self.on_selection_remove(sel)) def on_selection_load(self, selection): self.set_current_selection(selection.data_dict()) def on_selection_save(self, selection): i = self.selections.index(selection) self.selections[i] = self.provider_factory( self.selections[i].name, self) self.populate_selection_menu() def on_selection_clear(self): if QMessageBox.question(self, 'Confirmation', 'Do you really want to remove all selections?', QMessageBox.Yes | QMessageBox.No) == QMessageBox.No: return del self.selections[:] self.populate_selection_menu() def on_selection_rename(self, selection): (name, ok) = QInputDialog.getText(self, 'Edit selection name', 'New name:', QLineEdit.Normal, selection.name) if ok and name: selection.name = name self.populate_selection_menu() def on_selection_remove(self, selection): if QMessageBox.question(self, 'Confirmation', 'Do you really want to remove the selection "%s"?' % selection.name, QMessageBox.Yes | QMessageBox.No) == QMessageBox.No: return self.selections.remove(selection) self.populate_selection_menu() def on_selection_new(self): self.selections.append(self.provider_factory('Selection %d' % (len(self.selections) + 1), self)) self.populate_selection_menu() def serialize_selections(self): sl = list() # Selection list, current selection as first item sl.append(self.provider_factory('current', self).data_dict()) for s in self.selections: sl.append(s.data_dict()) return json.dumps(sl, sort_keys=True, indent=2) def save_selections_to_file(self, filename): f = open(filename, 'w') f.write(self.serialize_selections()) f.close() def load_selections_from_file(self, filename): try: f = open(filename, 'r') p = json.load(f) # First selection in file is current selection self.set_current_selection(p[0]) for s in p[1:]: self.add_selection(s) f.close() self.populate_selection_menu() except Exception, e: self.progress.done() QMessageBox.critical(self, 'Error loading selection', str(e).decode('utf8'))