class RichText(QWidget): """ WebView widget with find dialog """ def __init__(self, parent): QWidget.__init__(self, parent) self.webview = FrameWebView(self) self.find_widget = FindReplace(self) self.find_widget.set_editor(self.webview.web_widget) self.find_widget.hide() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.webview) layout.addWidget(self.find_widget) self.setLayout(layout) def set_font(self, font, fixed_font=None): """Set font""" self.webview.set_font(font, fixed_font=fixed_font) def set_html(self, html_text, base_url): """Set html text""" self.webview.setHtml(html_text, base_url) def clear(self): self.set_html('', self.webview.url())
class PlainText(QWidget): """ Read-only editor widget with find dialog """ # Signals focus_changed = Signal() def __init__(self, parent): QWidget.__init__(self, parent) self.editor = None # Read-only editor self.editor = codeeditor.CodeEditor(self) self.editor.setup_editor(linenumbers=False, language='py', scrollflagarea=False, edge_line=False) self.editor.focus_changed.connect(lambda: self.focus_changed.emit()) self.editor.setReadOnly(True) # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.set_editor(self.editor) self.find_widget.hide() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.editor) layout.addWidget(self.find_widget) self.setLayout(layout) def set_font(self, font, color_scheme=None): """Set font""" self.editor.set_font(font, color_scheme=color_scheme) def set_color_scheme(self, color_scheme): """Set color scheme""" self.editor.set_color_scheme(color_scheme) def set_text(self, text, is_code): self.editor.set_highlight_current_line(is_code) self.editor.set_occurrence_highlighting(is_code) if is_code: self.editor.set_language('py') else: self.editor.set_language(None) self.editor.set_text(text) self.editor.set_cursor_position('sof') def clear(self): self.editor.clear()
class Console(TRexPluginWidget): """ Console widget """ CONF_SECTION = 'internal_console' focus_changed = Signal() redirect_stdio = Signal(bool) edit_goto = Signal(str, int, str) def __init__(self, parent=None, namespace=None, commands=[], message=None, exitfunc=None, profile=False, multithreaded=False): if PYQT5: TRexPluginWidget.__init__(self, parent, main = parent) else: TRexPluginWidget.__init__(self, parent) debug_print(" ..internal console: initializing") self.dialog_manager = DialogManager() # Shell light_background = self.get_option('light_background') self.shell = InternalShell(parent, namespace, commands, message, self.get_option('max_line_count'), self.get_plugin_font(), exitfunc, profile, multithreaded, light_background=light_background) self.shell.status.connect(lambda msg: self.show_message.emit(msg, 0)) self.shell.go_to_error.connect(self.go_to_error) self.shell.focus_changed.connect(lambda: self.focus_changed.emit()) # Redirecting some signals: self.shell.redirect_stdio.connect(lambda state: self.redirect_stdio.emit(state)) # Initialize plugin self.initialize_plugin() # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.set_editor(self.shell) self.find_widget.hide() self.register_widget_shortcuts(self.find_widget) # Main layout layout = QVBoxLayout() layout.addWidget(self.shell) layout.addWidget(self.find_widget) self.setLayout(layout) # Parameters self.shell.toggle_wrap_mode(self.get_option('wrap')) # Accepting drops self.setAcceptDrops(True) #------ Private API -------------------------------------------------------- def set_historylog(self, historylog): """Bind historylog instance to this console Not used anymore since v2.0""" historylog.add_history(self.shell.history_filename) self.shell.append_to_history.connect(historylog.append_to_history) def set_help(self, help_plugin): """Bind help instance to this console""" self.shell.help = help_plugin #------ TRexPluginWidget API --------------------------------------------- def get_plugin_title(self): """Return widget title""" return _('Internal console') def get_focus_widget(self): """ Return the widget to give focus to when this plugin's dockwidget is raised on top-level """ return self.shell def update_font(self): """Update font from Preferences""" font = self.get_plugin_font() self.shell.set_font(font) def closing_plugin(self, cancelable=False): """Perform actions before parent main window is closed""" self.dialog_manager.close_all() self.shell.exit_interpreter() return True def refresh_plugin(self): pass def get_plugin_actions(self): """Return a list of actions related to plugin""" quit_action = create_action(self, _("&Quit"), icon=ima.icon('exit'), tip=_("Quit"), triggered=self.quit) self.register_shortcut(quit_action, "_", "Quit", "Ctrl+Q") run_action = create_action(self, _("&Run..."), None, ima.icon('run_small'), _("Run a Python script"), triggered=self.run_script) environ_action = create_action(self, _("Environment variables..."), icon=ima.icon('environ'), tip=_("Show and edit environment variables" " (for current session)"), triggered=self.show_env) syspath_action = create_action(self, _("Show sys.path contents..."), icon=ima.icon('syspath'), tip=_("Show (read-only) sys.path"), triggered=self.show_syspath) buffer_action = create_action(self, _("Buffer..."), None, tip=_("Set maximum line count"), triggered=self.change_max_line_count) exteditor_action = create_action(self, _("External editor path..."), None, None, _("Set external editor executable path"), triggered=self.change_exteditor) wrap_action = create_action(self, _("Wrap lines"), toggled=self.toggle_wrap_mode) wrap_action.setChecked(self.get_option('wrap')) calltips_action = create_action(self, _("Display balloon tips"), toggled=self.toggle_calltips) calltips_action.setChecked(self.get_option('calltips')) codecompletion_action = create_action(self, _("Automatic code completion"), toggled=self.toggle_codecompletion) codecompletion_action.setChecked(self.get_option('codecompletion/auto')) codecompenter_action = create_action(self, _("Enter key selects completion"), toggled=self.toggle_codecompletion_enter) codecompenter_action.setChecked(self.get_option( 'codecompletion/enter_key')) option_menu = QMenu(_('Internal console settings'), self) option_menu.setIcon(ima.icon('tooloptions')) add_actions(option_menu, (buffer_action, wrap_action, calltips_action, codecompletion_action, codecompenter_action, exteditor_action)) plugin_actions = [None, run_action, environ_action, syspath_action, option_menu, None, quit_action] # Add actions to context menu add_actions(self.shell.menu, plugin_actions) return plugin_actions def register_plugin(self): """Register plugin in TRex's main window""" self.focus_changed.connect(self.main.plugin_focus_changed) self.main.add_dockwidget(self) # Connecting the following signal once the dockwidget has been created: self.shell.traceback_available.connect(self.traceback_available) def traceback_available(self): """Traceback is available in the internal console: showing the internal console automatically to warn the user""" if CONF.get('main', 'show_internal_console_if_traceback', False): self.dockwidget.show() self.dockwidget.raise_() #------ Public API --------------------------------------------------------- @Slot() def quit(self): """Quit mainwindow""" self.main.close() @Slot() def show_env(self): """Show environment variables""" self.dialog_manager.show(EnvDialog()) @Slot() def show_syspath(self): """Show sys.path""" editor = CollectionsEditor() editor.setup(sys.path, title="sys.path", readonly=True, width=600, icon=ima.icon('syspath')) self.dialog_manager.show(editor) @Slot() def run_script(self, filename=None, silent=False, set_focus=False, args=None): """Run a Python script""" if filename is None: self.shell.interpreter.restore_stds() filename, _selfilter = getopenfilename(self, _("Run Python script"), getcwd(), _("Python scripts")+" (*.py ; *.pyw ; *.ipy)") self.shell.interpreter.redirect_stds() if filename: os.chdir( osp.dirname(filename) ) filename = osp.basename(filename) else: return debug_print(args) filename = osp.abspath(filename) rbs = remove_backslashes command = "runfile('%s', args='%s')" % (rbs(filename), rbs(args)) if set_focus: self.shell.setFocus() if self.dockwidget and not self.ismaximized: self.dockwidget.setVisible(True) self.dockwidget.raise_() self.shell.write(command+'\n') self.shell.run_command(command) def go_to_error(self, text): """Go to error if relevant""" match = get_error_match(to_text_string(text)) if match: fname, lnb = match.groups() self.edit_script(fname, int(lnb)) def edit_script(self, filename=None, goto=-1): """Edit script""" # Called from InternalShell if not hasattr(self, 'main') \ or not hasattr(self.main, 'editor'): self.shell.external_editor(filename, goto) return if filename is not None: self.edit_goto.emit(osp.abspath(filename), goto, '') def execute_lines(self, lines): """Execute lines and give focus to shell""" self.shell.execute_lines(to_text_string(lines)) self.shell.setFocus() @Slot() def change_max_line_count(self): "Change maximum line count""" mlc, valid = QInputDialog.getInt(self, _('Buffer'), _('Maximum line count'), self.get_option('max_line_count'), 0, 1000000) if valid: self.shell.setMaximumBlockCount(mlc) self.set_option('max_line_count', mlc) @Slot() def change_exteditor(self): """Change external editor path""" path, valid = QInputDialog.getText(self, _('External editor'), _('External editor executable path:'), QLineEdit.Normal, self.get_option('external_editor/path')) if valid: self.set_option('external_editor/path', to_text_string(path)) @Slot(bool) def toggle_wrap_mode(self, checked): """Toggle wrap mode""" self.shell.toggle_wrap_mode(checked) self.set_option('wrap', checked) @Slot(bool) def toggle_calltips(self, checked): """Toggle calltips""" self.shell.set_calltips(checked) self.set_option('calltips', checked) @Slot(bool) def toggle_codecompletion(self, checked): """Toggle automatic code completion""" self.shell.set_codecompletion_auto(checked) self.set_option('codecompletion/auto', checked) @Slot(bool) def toggle_codecompletion_enter(self, checked): """Toggle Enter key for code completion""" self.shell.set_codecompletion_enter(checked) self.set_option('codecompletion/enter_key', checked) #----Drag and drop def dragEnterEvent(self, event): """Reimplement Qt method Inform Qt about the types of data that the widget accepts""" source = event.mimeData() if source.hasUrls(): if mimedata2url(source): event.acceptProposedAction() else: event.ignore() elif source.hasText(): event.acceptProposedAction() def dropEvent(self, event): """Reimplement Qt method Unpack dropped data and handle it""" source = event.mimeData() if source.hasUrls(): pathlist = mimedata2url(source) self.shell.drop_pathlist(pathlist) elif source.hasText(): lines = to_text_string(source.text()) self.shell.set_cursor_position('eof') self.shell.execute_lines(lines) event.acceptProposedAction()
class HistoryLog(TRexPluginWidget): """ History log widget """ CONF_SECTION = 'historylog' CONFIGWIDGET_CLASS = HistoryConfigPage focus_changed = Signal() def __init__(self, parent): self.tabwidget = None self.menu_actions = None self.dockviewer = None self.wrap_action = None self.editors = [] self.filenames = [] if PYQT5: TRexPluginWidget.__init__(self, parent, main=parent) else: TRexPluginWidget.__init__(self, parent) # Initialize plugin self.initialize_plugin() layout = QVBoxLayout() self.tabwidget = Tabs(self, self.menu_actions) self.tabwidget.currentChanged.connect(self.refresh_plugin) self.tabwidget.move_data.connect(self.move_tab) if sys.platform == 'darwin': tab_container = QWidget() tab_container.setObjectName('tab-container') tab_layout = QHBoxLayout(tab_container) tab_layout.setContentsMargins(0, 0, 0, 0) tab_layout.addWidget(self.tabwidget) layout.addWidget(tab_container) else: layout.addWidget(self.tabwidget) # Menu as corner widget options_button = create_toolbutton(self, text=_('Options'), icon=ima.icon('tooloptions')) options_button.setPopupMode(QToolButton.InstantPopup) menu = QMenu(self) add_actions(menu, self.menu_actions) options_button.setMenu(menu) self.tabwidget.setCornerWidget(options_button) # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.hide() self.register_widget_shortcuts(self.find_widget) layout.addWidget(self.find_widget) self.setLayout(layout) #------ TRexPluginWidget API --------------------------------------------- def get_plugin_title(self): """Return widget title""" return _('History log') def get_plugin_icon(self): """Return widget icon""" return ima.icon('history') def get_focus_widget(self): """ Return the widget to give focus to when this plugin's dockwidget is raised on top-level """ return self.tabwidget.currentWidget() def closing_plugin(self, cancelable=False): """Perform actions before parent main window is closed""" return True def refresh_plugin(self): """Refresh tabwidget""" if self.tabwidget.count(): editor = self.tabwidget.currentWidget() else: editor = None self.find_widget.set_editor(editor) def get_plugin_actions(self): """Return a list of actions related to plugin""" history_action = create_action(self, _("History..."), None, ima.icon('history'), _("Set history maximum entries"), triggered=self.change_history_depth) self.wrap_action = create_action(self, _("Wrap lines"), toggled=self.toggle_wrap_mode) self.wrap_action.setChecked(self.get_option('wrap')) self.menu_actions = [history_action, self.wrap_action] return self.menu_actions def on_first_registration(self): """Action to be performed on first plugin registration""" self.main.tabify_plugins(self.main.extconsole, self) def register_plugin(self): """Register plugin in TRex's main window""" self.focus_changed.connect(self.main.plugin_focus_changed) self.main.add_dockwidget(self) # self.main.console.set_historylog(self) self.main.console.shell.refresh.connect(self.refresh_plugin) def update_font(self): """Update font from Preferences""" color_scheme = self.get_color_scheme() font = self.get_plugin_font() for editor in self.editors: editor.set_font(font, color_scheme) def apply_plugin_settings(self, options): """Apply configuration file's plugin settings""" color_scheme_n = 'color_scheme_name' color_scheme_o = self.get_color_scheme() font_n = 'plugin_font' font_o = self.get_plugin_font() wrap_n = 'wrap' wrap_o = self.get_option(wrap_n) self.wrap_action.setChecked(wrap_o) for editor in self.editors: if font_n in options: scs = color_scheme_o if color_scheme_n in options else None editor.set_font(font_o, scs) elif color_scheme_n in options: editor.set_color_scheme(color_scheme_o) if wrap_n in options: editor.toggle_wrap_mode(wrap_o) #------ Private API -------------------------------------------------------- def move_tab(self, index_from, index_to): """ Move tab (tabs themselves have already been moved by the tabwidget) """ filename = self.filenames.pop(index_from) editor = self.editors.pop(index_from) self.filenames.insert(index_to, filename) self.editors.insert(index_to, editor) #------ Public API --------------------------------------------------------- def add_history(self, filename): """ Add new history tab Slot for add_history signal emitted by shell instance """ filename = encoding.to_unicode_from_fs(filename) if filename in self.filenames: return editor = codeeditor.CodeEditor(self) if osp.splitext(filename)[1] == '.py': language = 'py' else: language = 'bat' editor.setup_editor(linenumbers=False, language=language, scrollflagarea=False) editor.focus_changed.connect(lambda: self.focus_changed.emit()) editor.setReadOnly(True) color_scheme = self.get_color_scheme() editor.set_font(self.get_plugin_font(), color_scheme) editor.toggle_wrap_mode(self.get_option('wrap')) text, _ = encoding.read(filename) editor.set_text(text) editor.set_cursor_position('eof') self.editors.append(editor) self.filenames.append(filename) index = self.tabwidget.addTab(editor, osp.basename(filename)) self.find_widget.set_editor(editor) self.tabwidget.setTabToolTip(index, filename) self.tabwidget.setCurrentIndex(index) def append_to_history(self, filename, command): """ Append an entry to history filename Slot for append_to_history signal emitted by shell instance """ if not is_text_string(filename): # filename is a QString filename = to_text_string(filename.toUtf8(), 'utf-8') command = to_text_string(command) index = self.filenames.index(filename) self.editors[index].append(command) if self.get_option('go_to_eof'): self.editors[index].set_cursor_position('eof') self.tabwidget.setCurrentIndex(index) @Slot() def change_history_depth(self): "Change history max entries" "" depth, valid = QInputDialog.getInt(self, _('History'), _('Maximum entries'), self.get_option('max_entries'), 10, 10000) if valid: self.set_option('max_entries', depth) @Slot(bool) def toggle_wrap_mode(self, checked): """Toggle wrap mode""" if self.tabwidget is None: return for editor in self.editors: editor.toggle_wrap_mode(checked) self.set_option('wrap', checked)
class WebBrowser(QWidget): """ Web browser widget """ def __init__(self, parent=None): QWidget.__init__(self, parent) self.home_url = None self.webview = WebView(self) self.webview.loadFinished.connect(self.load_finished) self.webview.titleChanged.connect(self.setWindowTitle) self.webview.urlChanged.connect(self.url_changed) home_button = create_toolbutton(self, icon=ima.icon('home'), tip=_("Home"), triggered=self.go_home) zoom_out_button = action2button(self.webview.zoom_out_action) zoom_in_button = action2button(self.webview.zoom_in_action) pageact2btn = lambda prop: action2button(self.webview.pageAction(prop), parent=self.webview) refresh_button = pageact2btn(QWebEnginePage.Reload) stop_button = pageact2btn(QWebEnginePage.Stop) previous_button = pageact2btn(QWebEnginePage.Back) next_button = pageact2btn(QWebEnginePage.Forward) stop_button.setEnabled(False) self.webview.loadStarted.connect(lambda: stop_button.setEnabled(True)) self.webview.loadFinished.connect( lambda: stop_button.setEnabled(False)) progressbar = QProgressBar(self) progressbar.setTextVisible(False) progressbar.hide() self.webview.loadStarted.connect(progressbar.show) self.webview.loadProgress.connect(progressbar.setValue) self.webview.loadFinished.connect(lambda _state: progressbar.hide()) label = QLabel(self.get_label()) self.url_combo = UrlComboBox(self) self.url_combo.valid.connect(self.url_combo_activated) if not WEBENGINE: self.webview.iconChanged.connect(self.icon_changed) self.find_widget = FindReplace(self) self.find_widget.set_editor(self.webview) self.find_widget.hide() find_button = create_toolbutton(self, icon=ima.icon('find'), tip=_("Find text"), toggled=self.toggle_find_widget) self.find_widget.visibility_changed.connect(find_button.setChecked) hlayout = QHBoxLayout() for widget in (previous_button, next_button, home_button, find_button, label, self.url_combo, zoom_out_button, zoom_in_button, refresh_button, progressbar, stop_button): hlayout.addWidget(widget) layout = QVBoxLayout() layout.addLayout(hlayout) layout.addWidget(self.webview) layout.addWidget(self.find_widget) self.setLayout(layout) def get_label(self): """Return address label text""" return _("Address:") def set_home_url(self, text): """Set home URL""" self.home_url = QUrl(text) def set_url(self, url): """Set current URL""" self.url_changed(url) self.go_to(url) def go_to(self, url_or_text): """Go to page *address*""" if is_text_string(url_or_text): url = QUrl(url_or_text) else: url = url_or_text self.webview.load(url) @Slot() def go_home(self): """Go to home page""" if self.home_url is not None: self.set_url(self.home_url) def text_to_url(self, text): """Convert text address into QUrl object""" return QUrl(text) def url_combo_activated(self, valid): """Load URL from combo box first item""" text = to_text_string(self.url_combo.currentText()) self.go_to(self.text_to_url(text)) def load_finished(self, ok): if not ok: self.webview.setHtml(_("Unable to load page")) def url_to_text(self, url): """Convert QUrl object to displayed text in combo box""" return url.toString() def url_changed(self, url): """Displayed URL has changed -> updating URL combo box""" self.url_combo.add_text(self.url_to_text(url)) def icon_changed(self): self.url_combo.setItemIcon(self.url_combo.currentIndex(), self.webview.icon()) self.setWindowIcon(self.webview.icon()) @Slot(bool) def toggle_find_widget(self, state): if state: self.find_widget.show() else: self.find_widget.hide()
class ExternalConsole(TRexPluginWidget): """ Console widget """ CONF_SECTION = 'console' CONFIGWIDGET_CLASS = ExternalConsoleConfigPage DISABLE_ACTIONS_WHEN_HIDDEN = False edit_goto = Signal((str, int, str), (str, int, str, bool)) focus_changed = Signal() redirect_stdio = Signal(bool) def __init__(self, parent): if PYQT5: TRexPluginWidget.__init__(self, parent, main=parent) else: TRexPluginWidget.__init__(self, parent) self.tabwidget = None self.menu_actions = None self.help = None # Help plugin self.historylog = None # History log plugin self.variableexplorer = None # Variable explorer plugin self.python_count = 0 self.terminal_count = 0 # Python startup file selection if not osp.isfile(self.get_option('pythonstartup', '')): self.set_option('pythonstartup', SCIENTIFIC_STARTUP) # default/custom settings are mutually exclusive: self.set_option('pythonstartup/custom', not self.get_option('pythonstartup/default')) self.shellwidgets = [] self.filenames = [] self.icons = [] self.runfile_args = "" # Initialize plugin self.initialize_plugin() layout = QVBoxLayout() self.tabwidget = Tabs(self, self.menu_actions) if hasattr(self.tabwidget, 'setDocumentMode')\ and not sys.platform == 'darwin': # Don't set document mode to true on OSX because it generates # a crash when the console is detached from the main window # Fixes Issue 561 self.tabwidget.setDocumentMode(True) self.tabwidget.currentChanged.connect(self.refresh_plugin) self.tabwidget.move_data.connect(self.move_tab) self.main.sig_pythonpath_changed.connect(self.set_path) self.tabwidget.set_close_function(self.close_console) if sys.platform == 'darwin': tab_container = QWidget() tab_container.setObjectName('tab-container') tab_layout = QHBoxLayout(tab_container) tab_layout.setContentsMargins(0, 0, 0, 0) tab_layout.addWidget(self.tabwidget) layout.addWidget(tab_container) else: layout.addWidget(self.tabwidget) # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.hide() self.register_widget_shortcuts(self.find_widget) layout.addWidget(self.find_widget) self.setLayout(layout) # Accepting drops self.setAcceptDrops(True) def move_tab(self, index_from, index_to): """ Move tab (tabs themselves have already been moved by the tabwidget) """ filename = self.filenames.pop(index_from) shell = self.shellwidgets.pop(index_from) icons = self.icons.pop(index_from) self.filenames.insert(index_to, filename) self.shellwidgets.insert(index_to, shell) self.icons.insert(index_to, icons) self.update_plugin_title.emit() def get_shell_index_from_id(self, shell_id): """Return shellwidget index from id""" for index, shell in enumerate(self.shellwidgets): if id(shell) == shell_id: return index def close_console(self, index=None): """Close console tab from index or widget (or close current tab)""" # Get tab index if not self.tabwidget.count(): return if index is None: index = self.tabwidget.currentIndex() # Closing logic self.tabwidget.widget(index).close() self.tabwidget.removeTab(index) self.filenames.pop(index) self.shellwidgets.pop(index) self.icons.pop(index) self.update_plugin_title.emit() self.update_tabs_text() def set_variableexplorer(self, variableexplorer): """Set variable explorer plugin""" self.variableexplorer = variableexplorer def set_path(self): """Set consoles PYTHONPATH if changed by the user""" from trex.widgets.externalshell import pythonshell for sw in self.shellwidgets: if isinstance(sw, pythonshell.ExternalPythonShell): if sw.is_interpreter and sw.is_running(): sw.path = self.main.get_trex_pythonpath() sw.shell.path = sw.path def __find_python_shell(self, interpreter_only=False): current_index = self.tabwidget.currentIndex() if current_index == -1: return from trex.widgets.externalshell import pythonshell for index in [current_index] + list(range(self.tabwidget.count())): shellwidget = self.tabwidget.widget(index) if isinstance(shellwidget, pythonshell.ExternalPythonShell): if interpreter_only and not shellwidget.is_interpreter: continue elif not shellwidget.is_running(): continue else: self.tabwidget.setCurrentIndex(index) return shellwidget def get_current_shell(self): """ Called by Help to retrieve the current shell instance """ shellwidget = self.__find_python_shell() return shellwidget.shell def get_running_python_shell(self): """ Called by Help to retrieve a running Python shell instance """ current_index = self.tabwidget.currentIndex() if current_index == -1: return from trex.widgets.externalshell import pythonshell shellwidgets = [ self.tabwidget.widget(index) for index in range(self.tabwidget.count()) ] shellwidgets = [_w for _w in shellwidgets if isinstance(_w, pythonshell.ExternalPythonShell) \ and _w.is_running()] if shellwidgets: # First, iterate on interpreters only: for shellwidget in shellwidgets: if shellwidget.is_interpreter: return shellwidget.shell else: return shellwidgets[0].shell def run_script_in_current_shell(self, filename, wdir, args, debug, post_mortem): """Run script in current shell, if any""" norm = lambda text: remove_backslashes(to_text_string(text)) line = "%s('%s'" % ('debugfile' if debug else 'runfile', norm(filename)) if args: line += ", args='%s'" % norm(args) if wdir: line += ", wdir='%s'" % norm(wdir) if post_mortem: line += ', post_mortem=True' line += ")" if not self.execute_code(line, interpreter_only=True): QMessageBox.warning( self, _('Warning'), _("No Python console is currently selected to run <b>%s</b>." "<br><br>Please select or open a new Python console " "and try again.") % osp.basename(norm(filename)), QMessageBox.Ok) else: self.visibility_changed(True) self.raise_() def set_current_shell_working_directory(self, directory): """Set current shell working directory""" shellwidget = self.__find_python_shell() if shellwidget is not None: directory = encoding.to_unicode_from_fs(directory) shellwidget.shell.set_cwd(directory) def execute_code(self, lines, interpreter_only=False): """Execute code in an already opened Python interpreter""" shellwidget = self.__find_python_shell( interpreter_only=interpreter_only) if shellwidget is not None: shellwidget.shell.execute_lines(to_text_string(lines)) self.activateWindow() shellwidget.shell.setFocus() return True else: return False def pdb_has_stopped(self, fname, lineno, shellwidget): """Python debugger has just stopped at frame (fname, lineno)""" # This is a unique form of the edit_goto signal that is intended to # prevent keyboard input from accidentally entering the editor # during repeated, rapid entry of debugging commands. self.edit_goto[str, int, str, bool].emit(fname, lineno, '', False) self.activateWindow() shellwidget.shell.setFocus() def set_trex_breakpoints(self): """Set all TRex breakpoints into all shells""" for shellwidget in self.shellwidgets: shellwidget.shell.set_trex_breakpoints() def start(self, fname, wdir=None, args='', interact=False, debug=False, python=True, python_args='', post_mortem=True): """ Start new console fname: string: filename of script to run None: open an interpreter wdir: working directory args: command line options of the Python script interact: inspect script interactively after its execution debug: run pdb python: True: Python interpreter, False: terminal python_args: additionnal Python interpreter command line options (option "-u" is mandatory, see widgets.externalshell package) """ # Note: fname is None <=> Python interpreter if fname is not None and not is_text_string(fname): fname = to_text_string(fname) if wdir is not None and not is_text_string(wdir): wdir = to_text_string(wdir) if fname is not None and fname in self.filenames: index = self.filenames.index(fname) if self.get_option('single_tab'): old_shell = self.shellwidgets[index] if old_shell.is_running(): runconfig = get_run_configuration(fname) if runconfig is None or runconfig.show_kill_warning: if PYQT5: answer = QMessageBox.question( self, self.get_plugin_title(), _("%s is already running in a separate process.\n" "Do you want to kill the process before starting " "a new one?") % osp.basename(fname), QMessageBox.Yes | QMessageBox.Cancel) else: mb = QMessageBox(self) answer = mb.question( mb, self.get_plugin_title(), _("%s is already running in a separate process.\n" "Do you want to kill the process before starting " "a new one?") % osp.basename(fname), QMessageBox.Yes | QMessageBox.Cancel) else: answer = QMessageBox.Yes if answer == QMessageBox.Yes: old_shell.process.kill() old_shell.process.waitForFinished() else: return self.close_console(index) else: index = self.tabwidget.count() # Creating a new external shell pythonpath = self.main.get_trex_pythonpath() light_background = self.get_option('light_background') show_elapsed_time = self.get_option('show_elapsed_time') if python: if CONF.get('main_interpreter', 'default'): pythonexecutable = get_python_executable() external_interpreter = False else: pythonexecutable = CONF.get('main_interpreter', 'executable') external_interpreter = True if self.get_option('pythonstartup/default'): pythonstartup = None else: pythonstartup = self.get_option('pythonstartup', None) monitor_enabled = self.get_option('monitor/enabled') mpl_backend = self.get_option('matplotlib/backend/value') ets_backend = self.get_option('ets_backend') qt_api = self.get_option('qt/api') if qt_api not in ('pyqt', 'pyside', 'pyqt5'): qt_api = None merge_output_channels = self.get_option('merge_output_channels') colorize_sys_stderr = self.get_option('colorize_sys_stderr') umr_enabled = CONF.get('main_interpreter', 'umr/enabled') umr_namelist = CONF.get('main_interpreter', 'umr/namelist') umr_verbose = CONF.get('main_interpreter', 'umr/verbose') ar_timeout = CONF.get('variable_explorer', 'autorefresh/timeout') ar_state = CONF.get('variable_explorer', 'autorefresh') sa_settings = None shellwidget = ExternalPythonShell( self, fname, wdir, interact, debug, post_mortem=post_mortem, path=pythonpath, python_args=python_args, arguments=args, stand_alone=sa_settings, pythonstartup=pythonstartup, pythonexecutable=pythonexecutable, external_interpreter=external_interpreter, umr_enabled=umr_enabled, umr_namelist=umr_namelist, umr_verbose=umr_verbose, ets_backend=ets_backend, monitor_enabled=monitor_enabled, mpl_backend=mpl_backend, qt_api=qt_api, merge_output_channels=merge_output_channels, colorize_sys_stderr=colorize_sys_stderr, autorefresh_timeout=ar_timeout, autorefresh_state=ar_state, light_background=light_background, menu_actions=self.menu_actions, show_buttons_inside=False, show_elapsed_time=show_elapsed_time) shellwidget.sig_pdb.connect( lambda fname, lineno, shellwidget=shellwidget: self. pdb_has_stopped(fname, lineno, shellwidget)) self.register_widget_shortcuts(shellwidget.shell) else: if os.name == 'posix': cmd = 'gnome-terminal' args = [] if programs.is_program_installed(cmd): if wdir: args.extend(['--working-directory=%s' % wdir]) programs.run_program(cmd, args) return cmd = 'konsole' if programs.is_program_installed(cmd): if wdir: args.extend(['--workdir', wdir]) programs.run_program(cmd, args) return shellwidget = ExternalSystemShell( self, wdir, path=pythonpath, light_background=light_background, menu_actions=self.menu_actions, show_buttons_inside=False, show_elapsed_time=show_elapsed_time) # Code completion / calltips shellwidget.shell.setMaximumBlockCount( self.get_option('max_line_count')) shellwidget.shell.set_font(self.get_plugin_font()) shellwidget.shell.toggle_wrap_mode(self.get_option('wrap')) shellwidget.shell.set_calltips(self.get_option('calltips')) shellwidget.shell.set_codecompletion_auto( self.get_option('codecompletion/auto')) shellwidget.shell.set_codecompletion_case( self.get_option('codecompletion/case_sensitive')) shellwidget.shell.set_codecompletion_enter( self.get_option('codecompletion/enter_key')) if python and self.help is not None: shellwidget.shell.set_help(self.help) shellwidget.shell.set_help_enabled( CONF.get('help', 'connect/python_console')) if self.historylog is not None: self.historylog.add_history(shellwidget.shell.history_filename) shellwidget.shell.append_to_history.connect( self.historylog.append_to_history) shellwidget.shell.go_to_error.connect(self.go_to_error) shellwidget.shell.focus_changed.connect( lambda: self.focus_changed.emit()) if python: if self.main.editor is not None: shellwidget.open_file.connect(self.open_file_in_trex) if fname is None: self.python_count += 1 tab_name = "Python %d" % self.python_count tab_icon1 = ima.icon('python') tab_icon2 = ima.icon('python_t') self.filenames.insert(index, fname) else: self.filenames.insert(index, fname) tab_name = self.get_tab_text(fname) self.update_tabs_text() tab_icon1 = ima.icon('run') tab_icon2 = ima.icon('terminated') else: fname = id(shellwidget) if os.name == 'nt': tab_name = _("Command Window") else: tab_name = _("Terminal") self.terminal_count += 1 tab_name += (" %d" % self.terminal_count) tab_icon1 = ima.icon('cmdprompt') tab_icon2 = ima.icon('cmdprompt_t') self.filenames.insert(index, fname) self.shellwidgets.insert(index, shellwidget) self.icons.insert(index, (tab_icon1, tab_icon2)) if index is None: index = self.tabwidget.addTab(shellwidget, tab_name) else: self.tabwidget.insertTab(index, shellwidget, tab_name) shellwidget.started.connect( lambda sid=id(shellwidget): self.process_started(sid)) shellwidget.sig_finished.connect( lambda sid=id(shellwidget): self.process_finished(sid)) self.find_widget.set_editor(shellwidget.shell) self.tabwidget.setTabToolTip(index, fname if wdir is None else wdir) self.tabwidget.setCurrentIndex(index) if self.dockwidget and not self.ismaximized: self.dockwidget.setVisible(True) self.dockwidget.raise_() shellwidget.set_icontext_visible(self.get_option('show_icontext')) # Start process and give focus to console shellwidget.start_shell() def open_file_in_trex(self, fname, lineno): """Open file in TRex's editor from remote process""" self.main.editor.activateWindow() self.main.editor.raise_() self.main.editor.load(fname, lineno) def get_tab_text(self, fname): """Get tab text without ambiguation.""" files_path_list = [ filename for filename in self.filenames if filename is not None ] return sourcecode.get_file_title(files_path_list, fname) def update_tabs_text(self): """Update the text from the tabs.""" for index, fname in enumerate(self.filenames): if fname is not None: self.tabwidget.setTabText(index, self.get_tab_text(fname)) #------ Private API ------------------------------------------------------- def process_started(self, shell_id): index = self.get_shell_index_from_id(shell_id) shell = self.shellwidgets[index] icon, _icon = self.icons[index] self.tabwidget.setTabIcon(index, icon) if self.help is not None: self.help.set_shell(shell.shell) if self.variableexplorer is not None: self.variableexplorer.add_shellwidget(shell) def process_finished(self, shell_id): index = self.get_shell_index_from_id(shell_id) if index is not None: # Not sure why it happens, but sometimes the shellwidget has # already been removed, so that's not bad if we can't change # the tab icon... _icon, icon = self.icons[index] self.tabwidget.setTabIcon(index, icon) if self.variableexplorer is not None: self.variableexplorer.remove_shellwidget(shell_id) #------ TRexPluginWidget API -------------------------------------------- def get_plugin_title(self): """Return widget title""" title = _('Python console') return title def get_plugin_icon(self): """Return widget icon.""" return ima.icon('console') def get_focus_widget(self): """Return the widget to give focus to.""" return self.tabwidget.currentWidget() def get_plugin_actions(self): """Return a list of actions related to plugin""" interpreter_action = create_action(self, _("Open a &Python console"), None, ima.icon('python'), triggered=self.open_interpreter) if os.name == 'nt': text = _("Open &command prompt") tip = _("Open a Windows command prompt") else: text = _("Open a &terminal") tip = _("Open a terminal window") terminal_action = create_action(self, text, None, None, tip, triggered=self.open_terminal) run_action = create_action(self, _("&Run..."), None, ima.icon('run_small'), _("Run a Python script"), triggered=self.run_script) consoles_menu_actions = [interpreter_action] tools_menu_actions = [terminal_action] self.menu_actions = [interpreter_action, terminal_action, run_action] self.main.consoles_menu_actions += consoles_menu_actions self.main.tools_menu_actions += tools_menu_actions return self.menu_actions + consoles_menu_actions + tools_menu_actions def register_plugin(self): """Register plugin in TRex's main window""" self.main.add_dockwidget(self) self.help = self.main.help self.historylog = self.main.historylog self.edit_goto.connect(self.main.editor.load) self.edit_goto[str, int, str, bool].connect( lambda fname, lineno, word, processevents: self.main.editor.load( fname, lineno, word, processevents=processevents)) self.main.editor.run_in_current_extconsole.connect( self.run_script_in_current_shell) self.main.editor.breakpoints_saved.connect(self.set_trex_breakpoints) self.main.editor.open_dir.connect( self.set_current_shell_working_directory) self.main.workingdirectory.set_current_console_wd.connect( self.set_current_shell_working_directory) self.focus_changed.connect(self.main.plugin_focus_changed) self.redirect_stdio.connect(self.main.redirect_internalshell_stdio) expl = self.main.explorer if expl is not None: expl.open_terminal.connect(self.open_terminal) expl.open_interpreter.connect(self.open_interpreter) pexpl = self.main.projects if pexpl is not None: pexpl.open_terminal.connect(self.open_terminal) pexpl.open_interpreter.connect(self.open_interpreter) def closing_plugin(self, cancelable=False): """Perform actions before parent main window is closed""" for shellwidget in self.shellwidgets: shellwidget.close() return True def restart(self): """ Restart the console This is needed when we switch project to update PYTHONPATH and the selected interpreter """ self.python_count = 0 for i in range(len(self.shellwidgets)): self.close_console() self.open_interpreter() def refresh_plugin(self): """Refresh tabwidget""" shellwidget = None if self.tabwidget.count(): shellwidget = self.tabwidget.currentWidget() editor = shellwidget.shell editor.setFocus() widgets = [shellwidget.create_time_label(), 5 ] + shellwidget.get_toolbar_buttons() + [5] else: editor = None widgets = [] self.find_widget.set_editor(editor) self.tabwidget.set_corner_widgets({Qt.TopRightCorner: widgets}) if shellwidget: shellwidget.update_time_label_visibility() self.variableexplorer.set_shellwidget_from_id(id(shellwidget)) self.help.set_shell(shellwidget.shell) self.main.last_console_plugin_focus_was_python = True self.update_plugin_title.emit() def update_font(self): """Update font from Preferences""" font = self.get_plugin_font() for shellwidget in self.shellwidgets: shellwidget.shell.set_font(font) completion_size = CONF.get('main', 'completion/size') comp_widget = shellwidget.shell.completion_widget comp_widget.setup_appearance(completion_size, font) def apply_plugin_settings(self, options): """Apply configuration file's plugin settings""" showtime_n = 'show_elapsed_time' showtime_o = self.get_option(showtime_n) icontext_n = 'show_icontext' icontext_o = self.get_option(icontext_n) calltips_n = 'calltips' calltips_o = self.get_option(calltips_n) help_n = 'connect_to_oi' help_o = CONF.get('help', 'connect/python_console') wrap_n = 'wrap' wrap_o = self.get_option(wrap_n) compauto_n = 'codecompletion/auto' compauto_o = self.get_option(compauto_n) case_comp_n = 'codecompletion/case_sensitive' case_comp_o = self.get_option(case_comp_n) compenter_n = 'codecompletion/enter_key' compenter_o = self.get_option(compenter_n) mlc_n = 'max_line_count' mlc_o = self.get_option(mlc_n) for shellwidget in self.shellwidgets: if showtime_n in options: shellwidget.set_elapsed_time_visible(showtime_o) if icontext_n in options: shellwidget.set_icontext_visible(icontext_o) if calltips_n in options: shellwidget.shell.set_calltips(calltips_o) if help_n in options: if isinstance(shellwidget, ExternalPythonShell): shellwidget.shell.set_help_enabled(help_o) if wrap_n in options: shellwidget.shell.toggle_wrap_mode(wrap_o) if compauto_n in options: shellwidget.shell.set_codecompletion_auto(compauto_o) if case_comp_n in options: shellwidget.shell.set_codecompletion_case(case_comp_o) if compenter_n in options: shellwidget.shell.set_codecompletion_enter(compenter_o) if mlc_n in options: shellwidget.shell.setMaximumBlockCount(mlc_o) #------ TRexPluginMixin API --------------------------------------------- def toggle_view(self, checked): """Toggle view""" if checked: self.dockwidget.show() self.dockwidget.raise_() # Start a console in case there are none shown from trex.widgets.externalshell import pythonshell consoles = None for sw in self.shellwidgets: if isinstance(sw, pythonshell.ExternalPythonShell): consoles = True break if not consoles: self.open_interpreter() else: self.dockwidget.hide() #------ Public API --------------------------------------------------------- @Slot(bool) @Slot(str) def open_interpreter(self, wdir=None): """Open interpreter""" if not wdir: wdir = getcwd() self.visibility_changed(True) self.start(fname=None, wdir=to_text_string(wdir), args='', interact=True, debug=False, python=True) @Slot(bool) @Slot(str) def open_terminal(self, wdir=None): """Open terminal""" if not wdir: wdir = getcwd() self.start(fname=None, wdir=to_text_string(wdir), args='', interact=True, debug=False, python=False) @Slot() def run_script(self): """Run a Python script""" self.redirect_stdio.emit(False) filename, _selfilter = getopenfilename( self, _("Run Python script"), getcwd(), _("Python scripts") + " (*.py ; *.pyw ; *.ipy)") self.redirect_stdio.emit(True) if filename: self.start(fname=filename, wdir=None, args='', interact=False, debug=False) def go_to_error(self, text): """Go to error if relevant""" match = get_error_match(to_text_string(text)) if match: fname, lnb = match.groups() self.edit_goto.emit(osp.abspath(fname), int(lnb), '') #----Drag and drop def dragEnterEvent(self, event): """Reimplement Qt method Inform Qt about the types of data that the widget accepts""" source = event.mimeData() if source.hasUrls(): if mimedata2url(source): pathlist = mimedata2url(source) shellwidget = self.tabwidget.currentWidget() if all([ is_python_script(to_text_string(qstr)) for qstr in pathlist ]): event.acceptProposedAction() elif shellwidget is None or not shellwidget.is_running(): event.ignore() else: event.acceptProposedAction() else: event.ignore() elif source.hasText(): event.acceptProposedAction() def dropEvent(self, event): """Reimplement Qt method Unpack dropped data and handle it""" source = event.mimeData() shellwidget = self.tabwidget.currentWidget() if source.hasText(): qstr = source.text() if is_python_script(to_text_string(qstr)): self.start(qstr) elif shellwidget: shellwidget.shell.insert_text(qstr) elif source.hasUrls(): pathlist = mimedata2url(source) if all( [is_python_script(to_text_string(qstr)) for qstr in pathlist]): for fname in pathlist: self.start(fname) elif shellwidget: shellwidget.shell.drop_pathlist(pathlist) event.acceptProposedAction()