def qapplication(translate=True, test_time=3): """ Return QApplication instance Creates it if it doesn't already exist test_time: Time to maintain open the application when testing. It's given in seconds """ if running_in_mac_app(): SpyderApplication = MacApplication else: SpyderApplication = QApplication app = SpyderApplication.instance() if app is None: # Set Application name for Gnome 3 # https://groups.google.com/forum/#!topic/pyside/24qxvwfrRDs app = SpyderApplication(['Spyder']) # Set application name for KDE (See issue 2207) app.setApplicationName('Spyder') if translate: install_translator(app) test_travis = os.environ.get('TEST_CI_WIDGETS', None) if test_travis is not None: timer_shutdown = QTimer(app) timer_shutdown.timeout.connect(app.quit) timer_shutdown.start(test_time*1000) return app
class RequestHandler(QObject): """Handle introspection request. """ introspection_complete = Signal() def __init__(self, code_info, plugins): super(RequestHandler, self).__init__() self.info = code_info self.timer = QTimer() self.timer.singleShot(LEAD_TIME_SEC * 1000, self._handle_timeout) self.waiting = True self.pending = {} self.result = None self.plugins = plugins self._start_time = time.time() self._threads = {} for plugin in plugins: self._make_async_call(plugin, code_info) def _handle_timeout(self): debug_print('got timeout: %s' % self.plugins) if self.pending: for plugin in self.plugins: if plugin.name in self.pending: self._finalize(plugin.name, self.pending[plugin.name]) return self.waiting = False def _handle_incoming(self, name): # coerce to a str in case it is a QString name = str(name) self._threads[name].wait() if self.result: return result = self._threads[name].result if name == self.plugins[0].name or not self.waiting: if result: self._finalize(name, result) else: debug_print('No valid responses acquired') self.introspection_complete.emit() else: self.pending[name] = result def _make_async_call(self, plugin, info): """Trigger an introspection job in a thread""" self._threads[str(plugin.name)] = thread = IntrospectionThread(plugin, info) thread.request_handled.connect(self._handle_incoming) thread.start() def _finalize(self, name, result): self.result = result self.waiting = False self.pending = None delta = time.time() - self._start_time debug_print('%s request from %s finished: "%s" in %.1f sec' % (self.info.name, name, str(result)[:100], delta)) self.introspection_complete.emit()
def focusOutEvent(self, event): """Handle focus out event restoring the last valid selected path.""" # Calling asynchronously the 'add_current_text' to avoid crash # https://groups.google.com/group/spyderlib/browse_thread/thread/2257abf530e210bd lineedit = self.lineEdit() QTimer.singleShot(50, lambda: lineedit.setText(self.selected_text)) hide_status = getattr(self.lineEdit(), 'hide_status_icon', None) if hide_status: hide_status() QComboBox.focusOutEvent(self, event)
def follow_directories_loaded(self, fname): """Follow directories loaded during startup""" if self._to_be_loaded is None: return path = osp.normpath(to_text_string(fname)) if path in self._to_be_loaded: self._to_be_loaded.remove(path) if self._to_be_loaded is not None and len(self._to_be_loaded) == 0 and not is_pyqt46: self.fsmodel.directoryLoaded.disconnect(self.follow_directories_loaded) if self._scrollbar_positions is not None: # The tree view need some time to render branches: QTimer.singleShot(50, self.restore_scrollbar_positions)
def follow_directories_loaded(self, fname): """Follow directories loaded during startup""" if self._to_be_loaded is None: return path = osp.normpath(unicode(fname)) if path in self._to_be_loaded: self._to_be_loaded.pop(self._to_be_loaded.index(path)) if self._to_be_loaded is not None and len(self._to_be_loaded) == 0: self.disconnect(self.fsmodel, SIGNAL('directoryLoaded(QString)'), self.follow_directories_loaded) if self._scrollbar_positions is not None: # The tree view need some time to render branches: QTimer.singleShot(50, self.restore_scrollbar_positions)
def setup(self, icon_painter, painter, rect): if self.parent_widget not in self.info: timer = QTimer() timer.timeout.connect(lambda: self._update(self.parent_widget)) self.info[self.parent_widget] = [timer, 0, self.step] timer.start(self.interval) else: timer, angle, self.step = self.info[self.parent_widget] x_center = rect.width() * 0.5 y_center = rect.height() * 0.5 painter.translate(x_center, y_center) painter.rotate(angle) painter.translate(-x_center, -y_center)
def __init__(self, parent, history_filename, profile=False): """ parent : specifies the parent widget """ ConsoleBaseWidget.__init__(self, parent) SaveHistoryMixin.__init__(self) # Prompt position: tuple (line, index) self.current_prompt_pos = None self.new_input_line = True # History self.histidx = None self.hist_wholeline = False assert is_text_string(history_filename) self.history_filename = history_filename self.history = self.load_history() # Session self.historylog_filename = CONF.get('main', 'historylog_filename', get_conf_path('history.log')) # Context menu self.menu = None self.setup_context_menu() # Simple profiling test self.profile = profile # Buffer to increase performance of write/flush operations self.__buffer = [] self.__timestamp = 0.0 self.__flushtimer = QTimer(self) self.__flushtimer.setSingleShot(True) self.__flushtimer.timeout.connect(self.flush) # Give focus to widget self.setFocus() # Completion completion_size = CONF.get('shell_appearance', 'completion/size') completion_font = get_font('console') self.completion_widget.setup_appearance(completion_size, completion_font) # Cursor width self.setCursorWidth(CONF.get('shell_appearance', 'cursor/width'))
class BaseTimerStatus(StatusBarWidget): TITLE = None TIP = None def __init__(self, parent, statusbar): StatusBarWidget.__init__(self, parent, statusbar) self.setToolTip(self.TIP) layout = self.layout() layout.addWidget(QLabel(self.TITLE)) self.label = QLabel() self.label.setFont(self.label_font) layout.addWidget(self.label) layout.addSpacing(20) if self.is_supported(): self.timer = QTimer() self.timer.timeout.connect(self.update_label) self.timer.start(2000) else: self.timer = None self.hide() def set_interval(self, interval): """Set timer interval (ms)""" if self.timer is not None: self.timer.setInterval(interval) def import_test(self): """Raise ImportError if feature is not supported""" raise NotImplementedError def is_supported(self): """Return True if feature is supported""" try: self.import_test() return True except ImportError: return False def get_value(self): """Return value (e.g. CPU or memory usage)""" raise NotImplementedError def update_label(self): """Update status label widget, if widget is visible""" if self.isVisible(): self.label.setText('%d %%' % self.get_value())
def __init__(self): super(Restarter, self).__init__() self.ellipsis = ['', '.', '..', '...', '..', '.'] # Widgets self.timer_ellipsis = QTimer(self) self.splash = QSplashScreen( QPixmap(get_image_path('splash.svg'), 'svg')) # Widget setup self.setVisible(False) font = self.splash.font() font.setPixelSize(10) self.splash.setFont(font) self.splash.show() self.timer_ellipsis.timeout.connect(self.animate_ellipsis)
def event(self, event): """Qt Override. Filter tab keys and process double tab keys. """ if (event.type() == QEvent.KeyPress) and (event.key() == Qt.Key_Tab): self.sig_tab_pressed.emit(True) self.numpress += 1 if self.numpress == 1: self.presstimer = QTimer.singleShot(400, self.handle_keypress) return True return QComboBox.event(self, event)
def __init__(self, parent, history_filename, debug=False, profile=False): """ parent : specifies the parent widget """ ConsoleBaseWidget.__init__(self, parent) # Prompt position: tuple (line, index) self.current_prompt_pos = None self.new_input_line = True # History self.histidx = None self.hist_wholeline = False assert isinstance(history_filename, (str, unicode)) self.history_filename = history_filename self.history = self.load_history() # Session self.historylog_filename = CONF.get('main', 'historylog_filename', get_conf_path('history.log')) # Context menu self.menu = None self.setup_context_menu() # Debug mode self.debug = debug # Simple profiling test self.profile = profile # Buffer to increase performance of write/flush operations self.__buffer = [] self.__timestamp = 0.0 self.__flushtimer = QTimer(self) self.__flushtimer.setSingleShot(True) self.connect(self.__flushtimer, SIGNAL('timeout()'), self.flush) # Give focus to widget self.setFocus() # Calltips calltip_size = CONF.get('shell_appearance', 'calltips/size') calltip_font = get_font('shell_appearance', 'calltips') self.setup_calltips(calltip_size, calltip_font) # Completion completion_size = CONF.get('shell_appearance', 'completion/size') completion_font = get_font('shell_appearance', 'completion') self.completion_widget.setup_appearance(completion_size, completion_font) # Cursor width self.setCursorWidth( CONF.get('shell_appearance', 'cursor/width') )
def __init__(self, code_info, plugins): super(RequestHandler, self).__init__() self.info = code_info self.timer = QTimer() self.timer.singleShot(LEAD_TIME_SEC * 1000, self._handle_timeout) self.waiting = True self.pending = {} self.result = None self.plugins = plugins self._start_time = time.time() self._threads = {} for plugin in plugins: self._make_async_call(plugin, code_info)
def __init__(self, executable): super(PluginManager, self).__init__() plugins = OrderedDict() for name in PLUGINS: try: plugin = PluginClient(name, executable) plugin.run() except Exception as e: debug_print('Introspection Plugin Failed: %s' % name) debug_print(str(e)) continue debug_print('Introspection Plugin Loaded: %s' % name) plugins[name] = plugin plugin.received.connect(self.handle_response) self.plugins = plugins self.timer = QTimer() self.desired = [] self.ids = dict() self.info = None self.request = None self.pending = None self.pending_request = None self.waiting = False
def __init__(self, parent, statusbar): StatusBarWidget.__init__(self, parent, statusbar) self.setToolTip(self.TIP) layout = self.layout() layout.addWidget(QLabel(self.TITLE)) self.label = QLabel() self.label.setFont(self.label_font) layout.addWidget(self.label) layout.addSpacing(20) if self.is_supported(): self.timer = QTimer() self.timer.timeout.connect(self.update_label) self.timer.start(2000) else: self.timer = None self.hide()
def __init__(self): super(Restarter, self).__init__() self.ellipsis = ['', '.', '..', '...', '..', '.'] # Widgets self.timer_ellipsis = QTimer(self) self.splash = QSplashScreen(QPixmap(get_image_path('splash.svg'), 'svg')) # Widget setup self.setVisible(False) font = self.splash.font() font.setPixelSize(10) self.splash.setFont(font) self.splash.show() self.timer_ellipsis.timeout.connect(self.animate_ellipsis)
def __init__(self, target, executable=None, name=None, extra_args=None, libs=None, cwd=None, env=None): super(AsyncClient, self).__init__() self.executable = executable or sys.executable self.extra_args = extra_args self.target = target self.name = name or self self.libs = libs self.cwd = cwd self.env = env self.is_initialized = False self.closing = False self.context = zmq.Context() QApplication.instance().aboutToQuit.connect(self.close) # Set up the heartbeat timer. self.timer = QTimer(self) self.timer.timeout.connect(self._heartbeat) self.timer.start(HEARTBEAT)
def __init__(self, parent, history_filename, profile=False): """ parent : specifies the parent widget """ ConsoleBaseWidget.__init__(self, parent) SaveHistoryMixin.__init__(self) # Prompt position: tuple (line, index) self.current_prompt_pos = None self.new_input_line = True # History self.histidx = None self.hist_wholeline = False assert is_text_string(history_filename) self.history_filename = history_filename self.history = self.load_history() # Session self.historylog_filename = CONF.get('main', 'historylog_filename', get_conf_path('history.log')) # Context menu self.menu = None self.setup_context_menu() # Simple profiling test self.profile = profile # Buffer to increase performance of write/flush operations self.__buffer = [] self.__timestamp = 0.0 self.__flushtimer = QTimer(self) self.__flushtimer.setSingleShot(True) self.__flushtimer.timeout.connect(self.flush) # Give focus to widget self.setFocus() # Cursor width self.setCursorWidth( CONF.get('main', 'cursor/width') )
def __init__(self, executable): super(PluginManager, self).__init__() plugins = OrderedDict() for name in PLUGINS: try: plugin = PluginClient(name, executable) except Exception as e: debug_print('Introspection Plugin Failed: %s' % name) debug_print(str(e)) continue debug_print('Introspection Plugin Loaded: %s' % name) plugins[name] = plugin plugin.request_handled.connect(self.handle_response) self.plugins = plugins self.timer = QTimer() self.desired = [] self.info = None self.request = None self.pending = None self.pending_request = None self.waiting = False
class FindReplace(QWidget): """Find widget""" STYLE = {False: "background-color:rgb(255, 175, 90);", True: ""} visibility_changed = Signal(bool) def __init__(self, parent, enable_replace=False): QWidget.__init__(self, parent) self.enable_replace = enable_replace self.editor = None self.is_code_editor = None glayout = QGridLayout() glayout.setContentsMargins(0, 0, 0, 0) self.setLayout(glayout) self.close_button = create_toolbutton( self, triggered=self.hide, icon=get_std_icon("DialogCloseButton")) glayout.addWidget(self.close_button, 0, 0) # Find layout self.search_text = PatternComboBox(self, tip=_("Search string"), adjust_to_minimum=False) self.search_text.valid.connect(lambda state: self.find( changed=False, forward=True, rehighlight=False)) self.search_text.lineEdit().textEdited.connect( self.text_has_been_edited) self.previous_button = create_toolbutton(self, triggered=self.find_previous, icon=get_std_icon("ArrowUp")) self.next_button = create_toolbutton(self, triggered=self.find_next, icon=get_std_icon("ArrowDown")) self.next_button.clicked.connect(self.update_search_combo) self.previous_button.clicked.connect(self.update_search_combo) self.re_button = create_toolbutton(self, icon=get_icon("advanced.png"), tip=_("Regular expression")) self.re_button.setCheckable(True) self.re_button.toggled.connect(lambda state: self.find()) self.case_button = create_toolbutton(self, icon=get_icon("upper_lower.png"), tip=_("Case Sensitive")) self.case_button.setCheckable(True) self.case_button.toggled.connect(lambda state: self.find()) self.words_button = create_toolbutton(self, icon=get_icon("whole_words.png"), tip=_("Whole words")) self.words_button.setCheckable(True) self.words_button.toggled.connect(lambda state: self.find()) self.highlight_button = create_toolbutton( self, icon=get_icon("highlight.png"), tip=_("Highlight matches")) self.highlight_button.setCheckable(True) self.highlight_button.toggled.connect(self.toggle_highlighting) hlayout = QHBoxLayout() self.widgets = [ self.close_button, self.search_text, self.previous_button, self.next_button, self.re_button, self.case_button, self.words_button, self.highlight_button ] for widget in self.widgets[1:]: hlayout.addWidget(widget) glayout.addLayout(hlayout, 0, 1) # Replace layout replace_with = QLabel(_("Replace with:")) self.replace_text = PatternComboBox(self, adjust_to_minimum=False, tip=_("Replace string")) self.replace_button = create_toolbutton( self, text=_("Replace/find"), icon=get_std_icon("DialogApplyButton"), triggered=self.replace_find, text_beside_icon=True) self.replace_button.clicked.connect(self.update_replace_combo) self.replace_button.clicked.connect(self.update_search_combo) self.all_check = QCheckBox(_("Replace all")) self.replace_layout = QHBoxLayout() widgets = [ replace_with, self.replace_text, self.replace_button, self.all_check ] for widget in widgets: self.replace_layout.addWidget(widget) glayout.addLayout(self.replace_layout, 1, 1) self.widgets.extend(widgets) self.replace_widgets = widgets self.hide_replace() self.search_text.setTabOrder(self.search_text, self.replace_text) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.shortcuts = self.create_shortcuts(parent) self.highlight_timer = QTimer(self) self.highlight_timer.setSingleShot(True) self.highlight_timer.setInterval(1000) self.highlight_timer.timeout.connect(self.highlight_matches) def create_shortcuts(self, parent): """Create shortcuts for this widget""" # Configurable findnext = create_shortcut(self.find_next, context='Editor', name='Find next', parent=parent) findprev = create_shortcut(self.find_previous, context='Editor', name='Find previous', parent=parent) togglefind = create_shortcut(self.show, context='Editor', name='Find text', parent=parent) togglereplace = create_shortcut(self.toggle_replace_widgets, context='Editor', name='Replace text', parent=parent) # Fixed new_shortcut("Escape", self, self.hide) return [findnext, findprev, togglefind, togglereplace] def get_shortcut_data(self): """ Returns shortcut data, a list of tuples (shortcut, text, default) shortcut (QShortcut or QAction instance) text (string): action/shortcut description default (string): default key sequence """ return [sc.data for sc in self.shortcuts] def update_search_combo(self): self.search_text.lineEdit().returnPressed.emit() def update_replace_combo(self): self.replace_text.lineEdit().returnPressed.emit() def toggle_replace_widgets(self): if self.enable_replace: # Toggle replace widgets if self.replace_widgets[0].isVisible(): self.hide_replace() self.hide() else: self.show_replace() self.replace_text.setFocus() @Slot(bool) def toggle_highlighting(self, state): """Toggle the 'highlight all results' feature""" if self.editor is not None: if state: self.highlight_matches() else: self.clear_matches() def show(self): """Overrides Qt Method""" QWidget.show(self) self.visibility_changed.emit(True) if self.editor is not None: text = self.editor.get_selected_text() if len(text) > 0: self.search_text.setEditText(text) self.search_text.lineEdit().selectAll() self.refresh() else: self.search_text.lineEdit().selectAll() self.search_text.setFocus() @Slot() def hide(self): """Overrides Qt Method""" for widget in self.replace_widgets: widget.hide() QWidget.hide(self) self.visibility_changed.emit(False) if self.editor is not None: self.editor.setFocus() self.clear_matches() def show_replace(self): """Show replace widgets""" self.show() for widget in self.replace_widgets: widget.show() def hide_replace(self): """Hide replace widgets""" for widget in self.replace_widgets: widget.hide() def refresh(self): """Refresh widget""" if self.isHidden(): if self.editor is not None: self.clear_matches() return state = self.editor is not None for widget in self.widgets: widget.setEnabled(state) if state: self.find() def set_editor(self, editor, refresh=True): """ Set associated editor/web page: codeeditor.base.TextEditBaseWidget browser.WebView """ self.editor = editor from spyderlib.qt.QtWebKit import QWebView self.words_button.setVisible(not isinstance(editor, QWebView)) self.re_button.setVisible(not isinstance(editor, QWebView)) from spyderlib.widgets.sourcecode.codeeditor import CodeEditor self.is_code_editor = isinstance(editor, CodeEditor) self.highlight_button.setVisible(self.is_code_editor) if refresh: self.refresh() if self.isHidden() and editor is not None: self.clear_matches() @Slot() def find_next(self): """Find next occurence""" state = self.find(changed=False, forward=True, rehighlight=False) self.editor.setFocus() self.search_text.add_current_text() return state @Slot() def find_previous(self): """Find previous occurence""" state = self.find(changed=False, forward=False, rehighlight=False) self.editor.setFocus() return state def text_has_been_edited(self, text): """Find text has been edited (this slot won't be triggered when setting the search pattern combo box text programmatically""" self.find(changed=True, forward=True, start_highlight_timer=True) def highlight_matches(self): """Highlight found results""" if self.is_code_editor and self.highlight_button.isChecked(): text = self.search_text.currentText() words = self.words_button.isChecked() regexp = self.re_button.isChecked() self.editor.highlight_found_results(text, words=words, regexp=regexp) def clear_matches(self): """Clear all highlighted matches""" if self.is_code_editor: self.editor.clear_found_results() def find(self, changed=True, forward=True, rehighlight=True, start_highlight_timer=False): """Call the find function""" text = self.search_text.currentText() if len(text) == 0: self.search_text.lineEdit().setStyleSheet("") return None else: case = self.case_button.isChecked() words = self.words_button.isChecked() regexp = self.re_button.isChecked() found = self.editor.find_text(text, changed, forward, case=case, words=words, regexp=regexp) self.search_text.lineEdit().setStyleSheet(self.STYLE[found]) if self.is_code_editor and found: if rehighlight or not self.editor.found_results: self.highlight_timer.stop() if start_highlight_timer: self.highlight_timer.start() else: self.highlight_matches() else: self.clear_matches() return found
def __init__(self, parent, enable_replace=False): QWidget.__init__(self, parent) self.enable_replace = enable_replace self.editor = None self.is_code_editor = None glayout = QGridLayout() glayout.setContentsMargins(0, 0, 0, 0) self.setLayout(glayout) self.close_button = create_toolbutton( self, triggered=self.hide, icon=get_std_icon("DialogCloseButton")) glayout.addWidget(self.close_button, 0, 0) # Find layout self.search_text = PatternComboBox(self, tip=_("Search string"), adjust_to_minimum=False) self.search_text.valid.connect(lambda state: self.find( changed=False, forward=True, rehighlight=False)) self.search_text.lineEdit().textEdited.connect( self.text_has_been_edited) self.previous_button = create_toolbutton(self, triggered=self.find_previous, icon=get_std_icon("ArrowUp")) self.next_button = create_toolbutton(self, triggered=self.find_next, icon=get_std_icon("ArrowDown")) self.next_button.clicked.connect(self.update_search_combo) self.previous_button.clicked.connect(self.update_search_combo) self.re_button = create_toolbutton(self, icon=get_icon("advanced.png"), tip=_("Regular expression")) self.re_button.setCheckable(True) self.re_button.toggled.connect(lambda state: self.find()) self.case_button = create_toolbutton(self, icon=get_icon("upper_lower.png"), tip=_("Case Sensitive")) self.case_button.setCheckable(True) self.case_button.toggled.connect(lambda state: self.find()) self.words_button = create_toolbutton(self, icon=get_icon("whole_words.png"), tip=_("Whole words")) self.words_button.setCheckable(True) self.words_button.toggled.connect(lambda state: self.find()) self.highlight_button = create_toolbutton( self, icon=get_icon("highlight.png"), tip=_("Highlight matches")) self.highlight_button.setCheckable(True) self.highlight_button.toggled.connect(self.toggle_highlighting) hlayout = QHBoxLayout() self.widgets = [ self.close_button, self.search_text, self.previous_button, self.next_button, self.re_button, self.case_button, self.words_button, self.highlight_button ] for widget in self.widgets[1:]: hlayout.addWidget(widget) glayout.addLayout(hlayout, 0, 1) # Replace layout replace_with = QLabel(_("Replace with:")) self.replace_text = PatternComboBox(self, adjust_to_minimum=False, tip=_("Replace string")) self.replace_button = create_toolbutton( self, text=_("Replace/find"), icon=get_std_icon("DialogApplyButton"), triggered=self.replace_find, text_beside_icon=True) self.replace_button.clicked.connect(self.update_replace_combo) self.replace_button.clicked.connect(self.update_search_combo) self.all_check = QCheckBox(_("Replace all")) self.replace_layout = QHBoxLayout() widgets = [ replace_with, self.replace_text, self.replace_button, self.all_check ] for widget in widgets: self.replace_layout.addWidget(widget) glayout.addLayout(self.replace_layout, 1, 1) self.widgets.extend(widgets) self.replace_widgets = widgets self.hide_replace() self.search_text.setTabOrder(self.search_text, self.replace_text) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.shortcuts = self.create_shortcuts(parent) self.highlight_timer = QTimer(self) self.highlight_timer.setSingleShot(True) self.highlight_timer.setInterval(1000) self.highlight_timer.timeout.connect(self.highlight_matches)
class ExternalShellBase(QWidget): """External Shell widget: execute Python script in a separate process""" SHELL_CLASS = None redirect_stdio = Signal(bool) sig_finished = Signal() def __init__( self, parent=None, fname=None, wdir=None, history_filename=None, show_icontext=True, light_background=True, menu_actions=None, show_buttons_inside=True, show_elapsed_time=True, ): QWidget.__init__(self, parent) self.menu_actions = menu_actions self.run_button = None self.kill_button = None self.options_button = None self.icontext_action = None self.show_elapsed_time = show_elapsed_time self.fname = fname if wdir is None: wdir = osp.dirname(osp.abspath(fname)) self.wdir = wdir if osp.isdir(wdir) else None self.arguments = "" self.shell = self.SHELL_CLASS(parent, get_conf_path(history_filename)) self.shell.set_light_background(light_background) self.shell.execute.connect(self.send_to_process) self.shell.sig_keyboard_interrupt.connect(self.keyboard_interrupt) # Redirecting some SIGNALs: self.shell.redirect_stdio.connect(lambda state: self.redirect_stdio.emit(state)) self.state_label = None self.time_label = None vlayout = QVBoxLayout() toolbar_buttons = self.get_toolbar_buttons() if show_buttons_inside: self.state_label = QLabel() hlayout = QHBoxLayout() hlayout.addWidget(self.state_label) hlayout.addStretch(0) hlayout.addWidget(self.create_time_label()) hlayout.addStretch(0) for button in toolbar_buttons: hlayout.addWidget(button) vlayout.addLayout(hlayout) else: vlayout.setContentsMargins(0, 0, 0, 0) vlayout.addWidget(self.get_shell_widget()) self.setLayout(vlayout) self.resize(640, 480) if parent is None: self.setWindowIcon(self.get_icon()) self.setWindowTitle(_("Console")) self.t0 = None self.timer = QTimer(self) self.process = None self.is_closing = False if show_buttons_inside: self.update_time_label_visibility() @Slot(bool) def set_elapsed_time_visible(self, state): self.show_elapsed_time = state if self.time_label is not None: self.time_label.setVisible(state) def create_time_label(self): """Create elapsed time label widget (if necessary) and return it""" if self.time_label is None: self.time_label = QLabel() return self.time_label def update_time_label_visibility(self): self.time_label.setVisible(self.show_elapsed_time) def is_running(self): if self.process is not None: return self.process.state() == QProcess.Running def get_toolbar_buttons(self): if self.run_button is None: self.run_button = create_toolbutton( self, text=_("Run"), icon=ima.icon("run"), tip=_("Run again this program"), triggered=self.start_shell ) if self.kill_button is None: self.kill_button = create_toolbutton( self, text=_("Kill"), icon=ima.icon("kill"), tip=_("Kills the current process, " "causing it to exit immediately"), ) buttons = [self.run_button] if self.options_button is None: options = self.get_options_menu() if options: self.options_button = create_toolbutton(self, text=_("Options"), icon=ima.icon("tooloptions")) self.options_button.setPopupMode(QToolButton.InstantPopup) menu = QMenu(self) add_actions(menu, options) self.options_button.setMenu(menu) if self.options_button is not None: buttons.append(self.options_button) buttons.append(self.kill_button) return buttons def set_icontext_visible(self, state): """Set icon text visibility""" for widget in self.get_toolbar_buttons(): if state: widget.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) else: widget.setToolButtonStyle(Qt.ToolButtonIconOnly) def get_options_menu(self): self.show_time_action = create_action(self, _("Show elapsed time"), toggled=self.set_elapsed_time_visible) self.show_time_action.setChecked(self.show_elapsed_time) actions = [self.show_time_action] if self.menu_actions is not None: actions += [None] + self.menu_actions return actions def get_shell_widget(self): return self.shell def get_icon(self): raise NotImplementedError def show_time(self, end=False): if self.time_label is None: return elapsed_time = time() - self.t0 if elapsed_time > 24 * 3600: # More than a day...! format = "%d %H:%M:%S" else: format = "%H:%M:%S" if end: color = "#AAAAAA" else: color = "#AA6655" text = "<span style='color: %s'><b>%s" "</b></span>" % (color, strftime(format, gmtime(elapsed_time))) self.time_label.setText(text) def closeEvent(self, event): if self.process is not None: self.is_closing = True self.process.kill() self.process.waitForFinished(100) try: self.timer.timeout.disconnect(self.show_time) except (RuntimeError, TypeError): pass def set_running_state(self, state=True): self.set_buttons_runnning_state(state) self.shell.setReadOnly(not state) if state: if self.state_label is not None: self.state_label.setText(_("<span style='color: #44AA44'><b>Running...</b></span>")) self.t0 = time() self.timer.timeout.connect(self.show_time) self.timer.start(1000) else: if self.state_label is not None: self.state_label.setText(_("Terminated.")) try: self.timer.timeout.disconnect(self.show_time) except (RuntimeError, TypeError): pass def set_buttons_runnning_state(self, state): self.run_button.setVisible(not state and not self.is_ipykernel) self.kill_button.setVisible(state) @Slot(bool) def start_shell(self, ask_for_arguments=False): """Start shell""" if ask_for_arguments and not self.get_arguments(): self.set_running_state(False) return try: self.terminate_button.clicked.disconnect(self.process.terminate) self.kill_button.clicked.disconnect(self.process.terminate) except (AttributeError, RuntimeError, TypeError): pass self.create_process() @Slot() def get_arguments(self): arguments, valid = QInputDialog.getText( self, _("Arguments"), _("Command line arguments:"), QLineEdit.Normal, self.arguments ) if valid: self.arguments = to_text_string(arguments) return valid def create_process(self): raise NotImplementedError def finished(self, exit_code, exit_status): self.shell.flush() self.sig_finished.emit() if self.is_closing: return self.set_running_state(False) self.show_time(end=True) # =============================================================================== # Input/Output # =============================================================================== def transcode(self, qba): try: return to_text_string(qba.data(), "utf8") except UnicodeDecodeError: return qba.data() def get_stdout(self): self.process.setReadChannel(QProcess.StandardOutput) qba = QByteArray() while self.process.bytesAvailable(): qba += self.process.readAllStandardOutput() return self.transcode(qba) def get_stderr(self): self.process.setReadChannel(QProcess.StandardError) qba = QByteArray() while self.process.bytesAvailable(): qba += self.process.readAllStandardError() return self.transcode(qba) def write_output(self): self.shell.write(self.get_stdout(), flush=True) # Commenting the line below improves crashes on long # output. See Issue 2251 # QApplication.processEvents() def send_to_process(self, qstr): raise NotImplementedError def send_ctrl_to_process(self, letter): char = chr("abcdefghijklmnopqrstuvwxyz".index(letter) + 1) byte_array = QByteArray() byte_array.append(char) self.process.write(byte_array) self.process.waitForBytesWritten(-1) self.shell.write(LOCALE_CODEC.toUnicode(byte_array), flush=True) def keyboard_interrupt(self): raise NotImplementedError
def __init__( self, parent=None, fname=None, wdir=None, history_filename=None, show_icontext=True, light_background=True, menu_actions=None, show_buttons_inside=True, show_elapsed_time=True, ): QWidget.__init__(self, parent) self.menu_actions = menu_actions self.run_button = None self.kill_button = None self.options_button = None self.icontext_action = None self.show_elapsed_time = show_elapsed_time self.fname = fname if wdir is None: wdir = osp.dirname(osp.abspath(fname)) self.wdir = wdir if osp.isdir(wdir) else None self.arguments = "" self.shell = self.SHELL_CLASS(parent, get_conf_path(history_filename)) self.shell.set_light_background(light_background) self.shell.execute.connect(self.send_to_process) self.shell.sig_keyboard_interrupt.connect(self.keyboard_interrupt) # Redirecting some SIGNALs: self.shell.redirect_stdio.connect(lambda state: self.redirect_stdio.emit(state)) self.state_label = None self.time_label = None vlayout = QVBoxLayout() toolbar_buttons = self.get_toolbar_buttons() if show_buttons_inside: self.state_label = QLabel() hlayout = QHBoxLayout() hlayout.addWidget(self.state_label) hlayout.addStretch(0) hlayout.addWidget(self.create_time_label()) hlayout.addStretch(0) for button in toolbar_buttons: hlayout.addWidget(button) vlayout.addLayout(hlayout) else: vlayout.setContentsMargins(0, 0, 0, 0) vlayout.addWidget(self.get_shell_widget()) self.setLayout(vlayout) self.resize(640, 480) if parent is None: self.setWindowIcon(self.get_icon()) self.setWindowTitle(_("Console")) self.t0 = None self.timer = QTimer(self) self.process = None self.is_closing = False if show_buttons_inside: self.update_time_label_visibility()
class AsyncClient(QObject): """ A class which handles a connection to a client through a QProcess. """ # Emitted when the client has initialized. initialized = Signal() # Emitted when the client errors. errored = Signal() # Emitted when a request response is received. received = Signal(object) def __init__(self, target, executable=None, name=None, extra_args=None, libs=None, cwd=None, env=None): super(AsyncClient, self).__init__() self.executable = executable or sys.executable self.extra_args = extra_args self.target = target self.name = name or self self.libs = libs self.cwd = cwd self.env = env self.is_initialized = False self.closing = False self.context = zmq.Context() QApplication.instance().aboutToQuit.connect(self.close) # Set up the heartbeat timer. self.timer = QTimer(self) self.timer.timeout.connect(self._heartbeat) self.timer.start(HEARTBEAT) def run(self): """Handle the connection with the server. """ # Set up the zmq port. self.socket = self.context.socket(zmq.PAIR) self.port = self.socket.bind_to_random_port('tcp://*') # Set up the process. self.process = QProcess(self) if self.cwd: self.process.setWorkingDirectory(self.cwd) p_args = ['-u', self.target, str(self.port)] if self.extra_args is not None: p_args += self.extra_args # Set up environment variables. processEnvironment = QProcessEnvironment() env = self.process.systemEnvironment() if (self.env and 'PYTHONPATH' not in self.env) or DEV: python_path = osp.dirname(get_module_path('spyderlib')) # Add the libs to the python path. for lib in self.libs: try: path = osp.dirname(imp.find_module(lib)[1]) python_path = osp.pathsep.join([python_path, path]) except ImportError: pass env.append("PYTHONPATH=%s" % python_path) if self.env: env.update(self.env) for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) self.process.setProcessEnvironment(processEnvironment) # Start the process and wait for started. self.process.start(self.executable, p_args) self.process.finished.connect(self._on_finished) running = self.process.waitForStarted() if not running: raise IOError('Could not start %s' % self) # Set up the socket notifer. fid = self.socket.getsockopt(zmq.FD) self.notifier = QSocketNotifier(fid, QSocketNotifier.Read, self) self.notifier.activated.connect(self._on_msg_received) def request(self, func_name, *args, **kwargs): """Send a request to the server. The response will be a dictionary the 'request_id' and the 'func_name' as well as a 'result' field with the object returned by the function call or or an 'error' field with a traceback. """ if not self.is_initialized: return request_id = uuid.uuid4().hex request = dict(func_name=func_name, args=args, kwargs=kwargs, request_id=request_id) try: self.socket.send_pyobj(request) except zmq.ZMQError: pass return request_id def close(self): """Cleanly close the connection to the server. """ self.closing = True self.timer.stop() self.request('server_quit') self.process.waitForFinished(1000) self.context.destroy(0) def _on_finished(self): """Handle a finished signal from the process. """ if self.closing: return if self.is_initialized: debug_print('Restarting %s' % self.name) debug_print(self.process.readAllStandardOutput()) debug_print(self.process.readAllStandardError()) self.is_initialized = False self.notifier.setEnabled(False) self.run() else: debug_print('Errored %s' % self.name) debug_print(self.process.readAllStandardOutput()) debug_print(self.process.readAllStandardError()) self.errored.emit() def _on_msg_received(self): """Handle a message trigger from the socket. """ self.notifier.setEnabled(False) while 1: try: resp = self.socket.recv_pyobj(flags=zmq.NOBLOCK) except zmq.ZMQError: self.notifier.setEnabled(True) return if not self.is_initialized: self.is_initialized = True debug_print('Initialized %s' % self.name) self.initialized.emit() continue resp['name'] = self.name self.received.emit(resp) def _heartbeat(self): """Send a heartbeat to keep the server alive. """ if not self.is_initialized: return self.socket.send_pyobj(dict(func_name='server_heartbeat'))
class FindReplace(QWidget): """Find widget""" STYLE = {False: "background-color:rgb(255, 175, 90);", True: ""} visibility_changed = Signal(bool) def __init__(self, parent, enable_replace=False): QWidget.__init__(self, parent) self.enable_replace = enable_replace self.editor = None self.is_code_editor = None glayout = QGridLayout() glayout.setContentsMargins(0, 0, 0, 0) self.setLayout(glayout) self.close_button = create_toolbutton(self, triggered=self.hide, icon=ima.icon('DialogCloseButton')) glayout.addWidget(self.close_button, 0, 0) # Find layout self.search_text = PatternComboBox(self, tip=_("Search string"), adjust_to_minimum=False) self.search_text.valid.connect( lambda state: self.find(changed=False, forward=True, rehighlight=False)) self.search_text.lineEdit().textEdited.connect( self.text_has_been_edited) self.previous_button = create_toolbutton(self, triggered=self.find_previous, icon=ima.icon('ArrowUp')) self.next_button = create_toolbutton(self, triggered=self.find_next, icon=ima.icon('ArrowDown')) self.next_button.clicked.connect(self.update_search_combo) self.previous_button.clicked.connect(self.update_search_combo) self.re_button = create_toolbutton(self, icon=ima.icon('advanced'), tip=_("Regular expression")) self.re_button.setCheckable(True) self.re_button.toggled.connect(lambda state: self.find()) self.case_button = create_toolbutton(self, icon=get_icon("upper_lower.png"), tip=_("Case Sensitive")) self.case_button.setCheckable(True) self.case_button.toggled.connect(lambda state: self.find()) self.words_button = create_toolbutton(self, icon=get_icon("whole_words.png"), tip=_("Whole words")) self.words_button.setCheckable(True) self.words_button.toggled.connect(lambda state: self.find()) self.highlight_button = create_toolbutton(self, icon=get_icon("highlight.png"), tip=_("Highlight matches")) self.highlight_button.setCheckable(True) self.highlight_button.toggled.connect(self.toggle_highlighting) hlayout = QHBoxLayout() self.widgets = [self.close_button, self.search_text, self.previous_button, self.next_button, self.re_button, self.case_button, self.words_button, self.highlight_button] for widget in self.widgets[1:]: hlayout.addWidget(widget) glayout.addLayout(hlayout, 0, 1) # Replace layout replace_with = QLabel(_("Replace with:")) self.replace_text = PatternComboBox(self, adjust_to_minimum=False, tip=_('Replace string')) self.replace_button = create_toolbutton(self, text=_('Replace/find'), icon=ima.icon('DialogApplyButton'), triggered=self.replace_find, text_beside_icon=True) self.replace_button.clicked.connect(self.update_replace_combo) self.replace_button.clicked.connect(self.update_search_combo) self.all_check = QCheckBox(_("Replace all")) self.replace_layout = QHBoxLayout() widgets = [replace_with, self.replace_text, self.replace_button, self.all_check] for widget in widgets: self.replace_layout.addWidget(widget) glayout.addLayout(self.replace_layout, 1, 1) self.widgets.extend(widgets) self.replace_widgets = widgets self.hide_replace() self.search_text.setTabOrder(self.search_text, self.replace_text) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.shortcuts = self.create_shortcuts(parent) self.highlight_timer = QTimer(self) self.highlight_timer.setSingleShot(True) self.highlight_timer.setInterval(1000) self.highlight_timer.timeout.connect(self.highlight_matches) def create_shortcuts(self, parent): """Create shortcuts for this widget""" # Configurable findnext = create_shortcut(self.find_next, context='Editor', name='Find next', parent=parent) findprev = create_shortcut(self.find_previous, context='Editor', name='Find previous', parent=parent) togglefind = create_shortcut(self.show, context='Editor', name='Find text', parent=parent) togglereplace = create_shortcut(self.toggle_replace_widgets, context='Editor', name='Replace text', parent=parent) # Fixed new_shortcut("Escape", self, self.hide) return [findnext, findprev, togglefind, togglereplace] def get_shortcut_data(self): """ Returns shortcut data, a list of tuples (shortcut, text, default) shortcut (QShortcut or QAction instance) text (string): action/shortcut description default (string): default key sequence """ return [sc.data for sc in self.shortcuts] def update_search_combo(self): self.search_text.lineEdit().returnPressed.emit() def update_replace_combo(self): self.replace_text.lineEdit().returnPressed.emit() def toggle_replace_widgets(self): if self.enable_replace: # Toggle replace widgets if self.replace_widgets[0].isVisible(): self.hide_replace() self.hide() else: self.show_replace() self.replace_text.setFocus() @Slot(bool) def toggle_highlighting(self, state): """Toggle the 'highlight all results' feature""" if self.editor is not None: if state: self.highlight_matches() else: self.clear_matches() def show(self): """Overrides Qt Method""" QWidget.show(self) self.visibility_changed.emit(True) if self.editor is not None: text = self.editor.get_selected_text() # If no text is highlighted for search, use whatever word is under the cursor if not text: cursor = self.editor.textCursor() cursor.select(QTextCursor.WordUnderCursor) text = to_text_string(cursor.selectedText()) # Now that text value is sorted out, use it for the search if text: self.search_text.setEditText(text) self.search_text.lineEdit().selectAll() self.refresh() else: self.search_text.lineEdit().selectAll() self.search_text.setFocus() @Slot() def hide(self): """Overrides Qt Method""" for widget in self.replace_widgets: widget.hide() QWidget.hide(self) self.visibility_changed.emit(False) if self.editor is not None: self.editor.setFocus() self.clear_matches() def show_replace(self): """Show replace widgets""" self.show() for widget in self.replace_widgets: widget.show() def hide_replace(self): """Hide replace widgets""" for widget in self.replace_widgets: widget.hide() def refresh(self): """Refresh widget""" if self.isHidden(): if self.editor is not None: self.clear_matches() return state = self.editor is not None for widget in self.widgets: widget.setEnabled(state) if state: self.find() def set_editor(self, editor, refresh=True): """ Set associated editor/web page: codeeditor.base.TextEditBaseWidget browser.WebView """ self.editor = editor from spyderlib.qt.QtWebKit import QWebView self.words_button.setVisible(not isinstance(editor, QWebView)) self.re_button.setVisible(not isinstance(editor, QWebView)) from spyderlib.widgets.sourcecode.codeeditor import CodeEditor self.is_code_editor = isinstance(editor, CodeEditor) self.highlight_button.setVisible(self.is_code_editor) if refresh: self.refresh() if self.isHidden() and editor is not None: self.clear_matches() @Slot() def find_next(self): """Find next occurrence""" state = self.find(changed=False, forward=True, rehighlight=False) self.editor.setFocus() self.search_text.add_current_text() return state @Slot() def find_previous(self): """Find previous occurrence""" state = self.find(changed=False, forward=False, rehighlight=False) self.editor.setFocus() return state def text_has_been_edited(self, text): """Find text has been edited (this slot won't be triggered when setting the search pattern combo box text programmatically""" self.find(changed=True, forward=True, start_highlight_timer=True) def highlight_matches(self): """Highlight found results""" if self.is_code_editor and self.highlight_button.isChecked(): text = self.search_text.currentText() words = self.words_button.isChecked() regexp = self.re_button.isChecked() self.editor.highlight_found_results(text, words=words, regexp=regexp) def clear_matches(self): """Clear all highlighted matches""" if self.is_code_editor: self.editor.clear_found_results() def find(self, changed=True, forward=True, rehighlight=True, start_highlight_timer=False): """Call the find function""" text = self.search_text.currentText() if len(text) == 0: self.search_text.lineEdit().setStyleSheet("") return None else: case = self.case_button.isChecked() words = self.words_button.isChecked() regexp = self.re_button.isChecked() found = self.editor.find_text(text, changed, forward, case=case, words=words, regexp=regexp) self.search_text.lineEdit().setStyleSheet(self.STYLE[found]) if self.is_code_editor and found: if rehighlight or not self.editor.found_results: self.highlight_timer.stop() if start_highlight_timer: self.highlight_timer.start() else: self.highlight_matches() else: self.clear_matches() return found @Slot() def replace_find(self): """Replace and find""" if (self.editor is not None): replace_text = to_text_string(self.replace_text.currentText()) search_text = to_text_string(self.search_text.currentText()) pattern = search_text if self.re_button.isChecked() else None case = self.case_button.isChecked() first = True cursor = None while True: if first: # First found seltxt = to_text_string(self.editor.get_selected_text()) cmptxt1 = search_text if case else search_text.lower() cmptxt2 = seltxt if case else seltxt.lower() if self.editor.has_selected_text() and cmptxt1 == cmptxt2: # Text was already found, do nothing pass else: if not self.find(changed=False, forward=True, rehighlight=False): break first = False wrapped = False position = self.editor.get_position('cursor') position0 = position cursor = self.editor.textCursor() cursor.beginEditBlock() else: position1 = self.editor.get_position('cursor') if is_position_inf(position1, position0 + len(replace_text) - len(search_text) + 1): # Identify wrapping even when the replace string # includes part of the search string wrapped = True if wrapped: if position1 == position or \ is_position_sup(position1, position): # Avoid infinite loop: replace string includes # part of the search string break if position1 == position0: # Avoid infinite loop: single found occurrence break position0 = position1 if pattern is None: cursor.removeSelectedText() cursor.insertText(replace_text) else: seltxt = to_text_string(cursor.selectedText()) cursor.removeSelectedText() cursor.insertText(re.sub(pattern, replace_text, seltxt)) if self.find_next(): found_cursor = self.editor.textCursor() cursor.setPosition(found_cursor.selectionStart(), QTextCursor.MoveAnchor) cursor.setPosition(found_cursor.selectionEnd(), QTextCursor.KeepAnchor) else: break if not self.all_check.isChecked(): break self.all_check.setCheckState(Qt.Unchecked) if cursor is not None: cursor.endEditBlock()
class FindReplace(QWidget): """ Find widget Signals: visibility_changed(bool) """ STYLE = {False: "background-color:rgb(255, 175, 90);", True: ""} def __init__(self, parent, enable_replace=False): QWidget.__init__(self, parent) self.enable_replace = enable_replace self.editor = None self.is_code_editor = None glayout = QGridLayout() glayout.setContentsMargins(0, 0, 0, 0) self.setLayout(glayout) self.close_button = create_toolbutton(self, triggered=self.hide, icon=get_std_icon("DialogCloseButton")) glayout.addWidget(self.close_button, 0, 0) # Find layout self.search_text = PatternComboBox(self, tip=_("Search string"), adjust_to_minimum=False) self.connect(self.search_text.lineEdit(), SIGNAL("textEdited(QString)"), self.text_has_been_edited) self.previous_button = create_toolbutton(self, triggered=self.find_previous, icon=get_std_icon("ArrowBack")) self.next_button = create_toolbutton(self, triggered=self.find_next, icon=get_std_icon("ArrowForward")) self.connect(self.next_button, SIGNAL("clicked()"), self.update_search_combo) self.connect(self.previous_button, SIGNAL("clicked()"), self.update_search_combo) self.re_button = create_toolbutton(self, icon=get_icon("advanced.png"), tip=_("Regular expression")) self.re_button.setCheckable(True) self.connect(self.re_button, SIGNAL("toggled(bool)"), lambda state: self.find()) self.case_button = create_toolbutton(self, icon=get_icon("upper_lower.png"), tip=_("Case Sensitive")) self.case_button.setCheckable(True) self.connect(self.case_button, SIGNAL("toggled(bool)"), lambda state: self.find()) self.words_button = create_toolbutton(self, icon=get_icon("whole_words.png"), tip=_("Whole words")) self.words_button.setCheckable(True) self.connect(self.words_button, SIGNAL("toggled(bool)"), lambda state: self.find()) self.highlight_button = create_toolbutton(self, icon=get_icon("highlight.png"), tip=_("Highlight matches")) self.highlight_button.setCheckable(True) self.connect(self.highlight_button, SIGNAL("toggled(bool)"), self.toggle_highlighting) hlayout = QHBoxLayout() self.widgets = [ self.close_button, self.search_text, self.previous_button, self.next_button, self.re_button, self.case_button, self.words_button, self.highlight_button, ] for widget in self.widgets[1:]: hlayout.addWidget(widget) glayout.addLayout(hlayout, 0, 1) # Replace layout replace_with = QLabel(_("Replace with:")) self.replace_text = PatternComboBox(self, adjust_to_minimum=False, tip=_("Replace string")) self.replace_button = create_toolbutton( self, text=_("Replace/find"), icon=get_std_icon("DialogApplyButton"), triggered=self.replace_find, text_beside_icon=True, ) self.connect(self.replace_button, SIGNAL("clicked()"), self.update_replace_combo) self.connect(self.replace_button, SIGNAL("clicked()"), self.update_search_combo) self.all_check = QCheckBox(_("Replace all")) self.replace_layout = QHBoxLayout() widgets = [replace_with, self.replace_text, self.replace_button, self.all_check] for widget in widgets: self.replace_layout.addWidget(widget) glayout.addLayout(self.replace_layout, 1, 1) self.widgets.extend(widgets) self.replace_widgets = widgets self.hide_replace() self.search_text.setTabOrder(self.search_text, self.replace_text) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.findnext_sc = QShortcut(QKeySequence("F3"), parent, self.find_next) self.findnext_sc.setContext(Qt.WidgetWithChildrenShortcut) self.findprev_sc = QShortcut(QKeySequence("Shift+F3"), parent, self.find_previous) self.findprev_sc.setContext(Qt.WidgetWithChildrenShortcut) self.togglefind_sc = QShortcut(QKeySequence("Ctrl+F"), parent, self.show) self.togglefind_sc.setContext(Qt.WidgetWithChildrenShortcut) self.togglereplace_sc = QShortcut(QKeySequence("Ctrl+H"), parent, self.toggle_replace_widgets) self.togglereplace_sc.setContext(Qt.WidgetWithChildrenShortcut) escape_sc = QShortcut(QKeySequence("Escape"), parent, self.hide) escape_sc.setContext(Qt.WidgetWithChildrenShortcut) self.highlight_timer = QTimer(self) self.highlight_timer.setSingleShot(True) self.highlight_timer.setInterval(1000) self.connect(self.highlight_timer, SIGNAL("timeout()"), self.highlight_matches) def get_shortcut_data(self): """ Returns shortcut data, a list of tuples (shortcut, text, default) shortcut (QShortcut or QAction instance) text (string): action/shortcut description default (string): default key sequence """ return [ (self.findnext_sc, "Find next", "F3"), (self.findprev_sc, "Find previous", "Shift+F3"), (self.togglefind_sc, "Find text", "Ctrl+F"), (self.togglereplace_sc, "Replace text", "Ctrl+H"), ] def update_search_combo(self): self.search_text.lineEdit().emit(SIGNAL("returnPressed()")) def update_replace_combo(self): self.replace_text.lineEdit().emit(SIGNAL("returnPressed()")) def toggle_replace_widgets(self): if self.enable_replace: # Toggle replace widgets if self.replace_widgets[0].isVisible(): self.hide_replace() self.hide() else: self.show_replace() self.replace_text.setFocus() def toggle_highlighting(self, state): """Toggle the 'highlight all results' feature""" if self.editor is not None: if state: self.highlight_matches() else: self.clear_matches() def show(self): """Overrides Qt Method""" QWidget.show(self) self.emit(SIGNAL("visibility_changed(bool)"), True) if self.editor is not None: text = self.editor.get_selected_text() if len(text) > 0: self.search_text.setEditText(text) self.search_text.lineEdit().selectAll() self.refresh() else: self.search_text.lineEdit().selectAll() self.search_text.setFocus() def hide(self): """Overrides Qt Method""" for widget in self.replace_widgets: widget.hide() QWidget.hide(self) self.emit(SIGNAL("visibility_changed(bool)"), False) if self.editor is not None: self.editor.setFocus() self.clear_matches() def show_replace(self): """Show replace widgets""" self.show() for widget in self.replace_widgets: widget.show() def hide_replace(self): """Hide replace widgets""" for widget in self.replace_widgets: widget.hide() def refresh(self): """Refresh widget""" if self.isHidden(): if self.editor is not None: self.clear_matches() return state = self.editor is not None for widget in self.widgets: widget.setEnabled(state) if state: self.find() def set_editor(self, editor, refresh=True): """ Set associated editor/web page: codeeditor.base.TextEditBaseWidget browser.WebView """ self.editor = editor from spyderlib.qt.QtWebKit import QWebView self.words_button.setVisible(not isinstance(editor, QWebView)) self.re_button.setVisible(not isinstance(editor, QWebView)) from spyderlib.widgets.sourcecode.codeeditor import CodeEditor self.is_code_editor = isinstance(editor, CodeEditor) self.highlight_button.setVisible(self.is_code_editor) if refresh: self.refresh() if self.isHidden() and editor is not None: self.clear_matches() def find_next(self): """Find next occurence""" state = self.find(changed=False, forward=True, rehighlight=False) self.editor.setFocus() self.search_text.add_current_text() return state def find_previous(self): """Find previous occurence""" state = self.find(changed=False, forward=False, rehighlight=False) self.editor.setFocus() return state def text_has_been_edited(self, text): """Find text has been edited (this slot won't be triggered when setting the search pattern combo box text programmatically""" self.find(changed=True, forward=True, start_highlight_timer=True) def highlight_matches(self): """Highlight found results""" if self.is_code_editor and self.highlight_button.isChecked(): text = self.search_text.currentText() words = self.words_button.isChecked() regexp = self.re_button.isChecked() self.editor.highlight_found_results(text, words=words, regexp=regexp) def clear_matches(self): """Clear all highlighted matches""" if self.is_code_editor: self.editor.clear_found_results() def find(self, changed=True, forward=True, rehighlight=True, start_highlight_timer=False): """Call the find function""" text = self.search_text.currentText() if len(text) == 0: self.search_text.lineEdit().setStyleSheet("") return None else: case = self.case_button.isChecked() words = self.words_button.isChecked() regexp = self.re_button.isChecked() found = self.editor.find_text(text, changed, forward, case=case, words=words, regexp=regexp) self.search_text.lineEdit().setStyleSheet(self.STYLE[found]) if found: if rehighlight or not self.editor.found_results: self.highlight_timer.stop() if start_highlight_timer: self.highlight_timer.start() else: self.highlight_matches() else: self.clear_matches() return found def replace_find(self): """Replace and find""" if self.editor is not None: replace_text = self.replace_text.currentText() search_text = self.search_text.currentText() pattern = search_text if self.re_button.isChecked() else None first = True while True: if first: # First found if self.editor.has_selected_text() and self.editor.get_selected_text() == unicode(search_text): # Text was already found, do nothing pass else: if not self.find(changed=False, forward=True, rehighlight=False): break first = False wrapped = False position = self.editor.get_position("cursor") position0 = position else: position1 = self.editor.get_position("cursor") if wrapped: if position1 == position or self.editor.is_position_sup(position1, position): # Avoid infinite loop: replace string includes # part of the search string break if position1 == position0: # Avoid infinite loop: single found occurence break if self.editor.is_position_inf(position1, position0): wrapped = True position0 = position1 self.editor.replace(replace_text, pattern=pattern) if not self.find_next(): break if not self.all_check.isChecked(): break self.all_check.setCheckState(Qt.Unchecked)
def focusOutEvent(self, event): """Handle focus out event""" # Calling asynchronously the 'add_current_text' to avoid crash # https://groups.google.com/group/spyderlib/browse_thread/thread/2257abf530e210bd QTimer.singleShot(50, self.add_current_text_if_valid) QComboBox.focusOutEvent(self, event)
class Restarter(QWidget): """Widget in charge of displaying the splash information screen and the error messages. """ def __init__(self): super(Restarter, self).__init__() self.ellipsis = ['', '.', '..', '...', '..', '.'] # Widgets self.timer_ellipsis = QTimer(self) self.splash = QSplashScreen(QPixmap(get_image_path('splash.svg'), 'svg')) # Widget setup self.setVisible(False) font = self.splash.font() font.setPixelSize(10) self.splash.setFont(font) self.splash.show() self.timer_ellipsis.timeout.connect(self.animate_ellipsis) def _show_message(self, text): """Show message on splash screen.""" self.splash.showMessage(text, Qt.AlignBottom | Qt.AlignCenter | Qt.AlignAbsolute, QColor(Qt.white)) def animate_ellipsis(self): """Animate dots at the end of the splash screen message.""" ellipsis = self.ellipsis.pop(0) text = ' '*len(ellipsis) + self.splash_text + ellipsis self.ellipsis.append(ellipsis) self._show_message(text) def set_splash_message(self, text): """Sets the text in the bottom of the Splash screen.""" self.splash_text = text self._show_message(text) self.timer_ellipsis.start(500) def launch_error_message(self, error_type, error=None): """Launch a message box with a predefined error message. Parameters ---------- error_type : int [CLOSE_ERROR, RESET_ERROR, RESTART_ERROR] Possible error codes when restarting/reseting spyder. error : Exception Actual Python exception error caught. """ messages = {CLOSE_ERROR: _("It was not possible to close the previous " "Spyder instance.\nRestart aborted."), RESET_ERROR: _("Spyder could not reset to factory " "defaults.\nRestart aborted."), RESTART_ERROR: _("It was not possible to restart Spyder.\n" "Operation aborted.")} titles = {CLOSE_ERROR: _("Spyder exit error"), RESET_ERROR: _("Spyder reset error"), RESTART_ERROR: _("Spyder restart error")} if error: e = error.__repr__() message = messages[error_type] + _("\n\n{0}").format(e) else: message = messages[error_type] title = titles[error_type] self.splash.hide() QMessageBox.warning(self, title, message, QMessageBox.Ok) raise RuntimeError(message)
class ShellBaseWidget(ConsoleBaseWidget, SaveHistoryMixin): """ Shell base widget """ redirect_stdio = Signal(bool) sig_keyboard_interrupt = Signal() execute = Signal(str) append_to_history = Signal(str, str) def __init__(self, parent, history_filename, profile=False): """ parent : specifies the parent widget """ ConsoleBaseWidget.__init__(self, parent) SaveHistoryMixin.__init__(self) # Prompt position: tuple (line, index) self.current_prompt_pos = None self.new_input_line = True # History self.histidx = None self.hist_wholeline = False assert is_text_string(history_filename) self.history_filename = history_filename self.history = self.load_history() # Session self.historylog_filename = CONF.get('main', 'historylog_filename', get_conf_path('history.log')) # Context menu self.menu = None self.setup_context_menu() # Simple profiling test self.profile = profile # Buffer to increase performance of write/flush operations self.__buffer = [] self.__timestamp = 0.0 self.__flushtimer = QTimer(self) self.__flushtimer.setSingleShot(True) self.__flushtimer.timeout.connect(self.flush) # Give focus to widget self.setFocus() # Completion completion_size = CONF.get('shell_appearance', 'completion/size') completion_font = get_font('console') self.completion_widget.setup_appearance(completion_size, completion_font) # Cursor width self.setCursorWidth(CONF.get('shell_appearance', 'cursor/width')) def toggle_wrap_mode(self, enable): """Enable/disable wrap mode""" self.set_wrap_mode('character' if enable else None) def set_font(self, font): """Set shell styles font""" self.setFont(font) self.set_pythonshell_font(font) cursor = self.textCursor() cursor.select(QTextCursor.Document) charformat = QTextCharFormat() charformat.setFontFamily(font.family()) charformat.setFontPointSize(font.pointSize()) cursor.mergeCharFormat(charformat) #------ Context menu def setup_context_menu(self): """Setup shell context menu""" self.menu = QMenu(self) self.cut_action = create_action(self, _("Cut"), shortcut=keybinding('Cut'), icon=ima.icon('editcut'), triggered=self.cut) self.copy_action = create_action(self, _("Copy"), shortcut=keybinding('Copy'), icon=ima.icon('editcopy'), triggered=self.copy) paste_action = create_action(self, _("Paste"), shortcut=keybinding('Paste'), icon=ima.icon('editpaste'), triggered=self.paste) save_action = create_action(self, _("Save history log..."), icon=ima.icon('filesave'), tip=_( "Save current history log (i.e. all " "inputs and outputs) in a text file"), triggered=self.save_historylog) self.delete_action = create_action(self, _("Delete"), shortcut=keybinding('Delete'), icon=ima.icon('editdelete'), triggered=self.delete) selectall_action = create_action(self, _("Select All"), shortcut=keybinding('SelectAll'), icon=ima.icon('selectall'), triggered=self.selectAll) add_actions( self.menu, (self.cut_action, self.copy_action, paste_action, self.delete_action, None, selectall_action, None, save_action)) def contextMenuEvent(self, event): """Reimplement Qt method""" state = self.has_selected_text() self.copy_action.setEnabled(state) self.cut_action.setEnabled(state) self.delete_action.setEnabled(state) self.menu.popup(event.globalPos()) event.accept() #------ Input buffer def get_current_line_from_cursor(self): return self.get_text('cursor', 'eof') def _select_input(self): """Select current line (without selecting console prompt)""" line, index = self.get_position('eof') if self.current_prompt_pos is None: pline, pindex = line, index else: pline, pindex = self.current_prompt_pos self.setSelection(pline, pindex, line, index) @Slot() def clear_line(self): """Clear current line (without clearing console prompt)""" if self.current_prompt_pos is not None: self.remove_text(self.current_prompt_pos, 'eof') @Slot() def clear_terminal(self): """ Clear terminal window Child classes reimplement this method to write prompt """ self.clear() # The buffer being edited def _set_input_buffer(self, text): """Set input buffer""" if self.current_prompt_pos is not None: self.replace_text(self.current_prompt_pos, 'eol', text) else: self.insert(text) self.set_cursor_position('eof') def _get_input_buffer(self): """Return input buffer""" input_buffer = '' if self.current_prompt_pos is not None: input_buffer = self.get_text(self.current_prompt_pos, 'eol') input_buffer = input_buffer.replace(os.linesep, '\n') return input_buffer input_buffer = Property("QString", _get_input_buffer, _set_input_buffer) #------ Prompt def new_prompt(self, prompt): """ Print a new prompt and save its (line, index) position """ if self.get_cursor_line_column()[1] != 0: self.write('\n') self.write(prompt, prompt=True) # now we update our cursor giving end of prompt self.current_prompt_pos = self.get_position('cursor') self.ensureCursorVisible() self.new_input_line = False def check_selection(self): """ Check if selected text is r/w, otherwise remove read-only parts of selection """ if self.current_prompt_pos is None: self.set_cursor_position('eof') else: self.truncate_selection(self.current_prompt_pos) #------ Copy / Keyboard interrupt @Slot() def copy(self): """Copy text to clipboard... or keyboard interrupt""" if self.has_selected_text(): ConsoleBaseWidget.copy(self) elif not sys.platform == 'darwin': self.interrupt() def interrupt(self): """Keyboard interrupt""" self.sig_keyboard_interrupt.emit() @Slot() def cut(self): """Cut text""" self.check_selection() if self.has_selected_text(): ConsoleBaseWidget.cut(self) @Slot() def delete(self): """Remove selected text""" self.check_selection() if self.has_selected_text(): ConsoleBaseWidget.remove_selected_text(self) @Slot() def save_historylog(self): """Save current history log (all text in console)""" title = _("Save history log") self.redirect_stdio.emit(False) filename, _selfilter = getsavefilename( self, title, self.historylog_filename, "%s (*.log)" % _("History logs")) self.redirect_stdio.emit(True) if filename: filename = osp.normpath(filename) try: encoding.write(to_text_string(self.get_text_with_eol()), filename) self.historylog_filename = filename CONF.set('main', 'historylog_filename', filename) except EnvironmentError as error: QMessageBox.critical( self, title, _("<b>Unable to save file '%s'</b>" "<br><br>Error message:<br>%s") % (osp.basename(filename), to_text_string(error))) #------ Basic keypress event handler def on_enter(self, command): """on_enter""" self.execute_command(command) def execute_command(self, command): self.execute.emit(command) self.add_to_history(command) self.new_input_line = True def on_new_line(self): """On new input line""" self.set_cursor_position('eof') self.current_prompt_pos = self.get_position('cursor') self.new_input_line = False @Slot() def paste(self): """Reimplemented slot to handle multiline paste action""" if self.new_input_line: self.on_new_line() ConsoleBaseWidget.paste(self) def keyPressEvent(self, event): """ Reimplement Qt Method Basic keypress event handler (reimplemented in InternalShell to add more sophisticated features) """ if self.preprocess_keyevent(event): # Event was accepted in self.preprocess_keyevent return self.postprocess_keyevent(event) def preprocess_keyevent(self, event): """Pre-process keypress event: return True if event is accepted, false otherwise""" # Copy must be done first to be able to copy read-only text parts # (otherwise, right below, we would remove selection # if not on current line) ctrl = event.modifiers() & Qt.ControlModifier meta = event.modifiers() & Qt.MetaModifier # meta=ctrl in OSX if event.key() == Qt.Key_C and \ ((Qt.MetaModifier | Qt.ControlModifier) & event.modifiers()): if meta and sys.platform == 'darwin': self.interrupt() elif ctrl: self.copy() event.accept() return True if self.new_input_line and ( len(event.text()) or event.key() in \ (Qt.Key_Up, Qt.Key_Down, Qt.Key_Left, Qt.Key_Right) ): self.on_new_line() return False def postprocess_keyevent(self, event): """Post-process keypress event: in InternalShell, this is method is called when shell is ready""" event, text, key, ctrl, shift = restore_keyevent(event) # Is cursor on the last line? and after prompt? if len(text): #XXX: Shouldn't it be: `if len(unicode(text).strip(os.linesep))` ? if self.has_selected_text(): self.check_selection() self.restrict_cursor_position(self.current_prompt_pos, 'eof') cursor_position = self.get_position('cursor') if key in (Qt.Key_Return, Qt.Key_Enter): if self.is_cursor_on_last_line(): self._key_enter() # add and run selection else: self.insert_text(self.get_selected_text(), at_end=True) elif key == Qt.Key_Insert and not shift and not ctrl: self.setOverwriteMode(not self.overwriteMode()) elif key == Qt.Key_Delete: if self.has_selected_text(): self.check_selection() self.remove_selected_text() elif self.is_cursor_on_last_line(): self.stdkey_clear() elif key == Qt.Key_Backspace: self._key_backspace(cursor_position) elif key == Qt.Key_Tab: self._key_tab() elif key == Qt.Key_Space and ctrl: self._key_ctrl_space() elif key == Qt.Key_Left: if self.current_prompt_pos == cursor_position: # Avoid moving cursor on prompt return method = self.extend_selection_to_next if shift \ else self.move_cursor_to_next method('word' if ctrl else 'character', direction='left') elif key == Qt.Key_Right: if self.is_cursor_at_end(): return method = self.extend_selection_to_next if shift \ else self.move_cursor_to_next method('word' if ctrl else 'character', direction='right') elif (key == Qt.Key_Home) or ((key == Qt.Key_Up) and ctrl): self._key_home(shift, ctrl) elif (key == Qt.Key_End) or ((key == Qt.Key_Down) and ctrl): self._key_end(shift, ctrl) elif key == Qt.Key_Up: if not self.is_cursor_on_last_line(): self.set_cursor_position('eof') y_cursor = self.get_coordinates(cursor_position)[1] y_prompt = self.get_coordinates(self.current_prompt_pos)[1] if y_cursor > y_prompt: self.stdkey_up(shift) else: self.browse_history(backward=True) elif key == Qt.Key_Down: if not self.is_cursor_on_last_line(): self.set_cursor_position('eof') y_cursor = self.get_coordinates(cursor_position)[1] y_end = self.get_coordinates('eol')[1] if y_cursor < y_end: self.stdkey_down(shift) else: self.browse_history(backward=False) elif key in (Qt.Key_PageUp, Qt.Key_PageDown): #XXX: Find a way to do this programmatically instead of calling # widget keyhandler (this won't work if the *event* is coming from # the event queue - i.e. if the busy buffer is ever implemented) ConsoleBaseWidget.keyPressEvent(self, event) elif key == Qt.Key_Escape and shift: self.clear_line() elif key == Qt.Key_Escape: self._key_escape() elif key == Qt.Key_L and ctrl: self.clear_terminal() elif key == Qt.Key_V and ctrl: self.paste() elif key == Qt.Key_X and ctrl: self.cut() elif key == Qt.Key_Z and ctrl: self.undo() elif key == Qt.Key_Y and ctrl: self.redo() elif key == Qt.Key_A and ctrl: self.selectAll() elif key == Qt.Key_Question and not self.has_selected_text(): self._key_question(text) elif key == Qt.Key_ParenLeft and not self.has_selected_text(): self._key_parenleft(text) elif key == Qt.Key_Period and not self.has_selected_text(): self._key_period(text) elif len(text) and not self.isReadOnly(): self.hist_wholeline = False self.insert_text(text) self._key_other(text) else: # Let the parent widget handle the key press event ConsoleBaseWidget.keyPressEvent(self, event) #------ Key handlers def _key_enter(self): command = self.input_buffer self.insert_text('\n', at_end=True) self.on_enter(command) self.flush() def _key_other(self, text): raise NotImplementedError def _key_backspace(self, cursor_position): raise NotImplementedError def _key_tab(self): raise NotImplementedError def _key_ctrl_space(self): raise NotImplementedError def _key_home(self, shift, ctrl): if self.is_cursor_on_last_line(): self.stdkey_home(shift, ctrl, self.current_prompt_pos) def _key_end(self, shift, ctrl): if self.is_cursor_on_last_line(): self.stdkey_end(shift, ctrl) def _key_pageup(self): raise NotImplementedError def _key_pagedown(self): raise NotImplementedError def _key_escape(self): raise NotImplementedError def _key_question(self, text): raise NotImplementedError def _key_parenleft(self, text): raise NotImplementedError def _key_period(self, text): raise NotImplementedError #------ History Management def load_history(self): """Load history from a .py file in user home directory""" if osp.isfile(self.history_filename): rawhistory, _ = encoding.readlines(self.history_filename) rawhistory = [line.replace('\n', '') for line in rawhistory] if rawhistory[1] != self.INITHISTORY[1]: rawhistory[1] = self.INITHISTORY[1] else: rawhistory = self.INITHISTORY history = [line for line in rawhistory \ if line and not line.startswith('#')] # Truncating history to X entries: while len(history) >= CONF.get('historylog', 'max_entries'): del history[0] while rawhistory[0].startswith('#'): del rawhistory[0] del rawhistory[0] # Saving truncated history: encoding.writelines(rawhistory, self.history_filename) return history def browse_history(self, backward): """Browse history""" if self.is_cursor_before('eol') and self.hist_wholeline: self.hist_wholeline = False tocursor = self.get_current_line_to_cursor() text, self.histidx = self.__find_in_history(tocursor, self.histidx, backward) if text is not None: if self.hist_wholeline: self.clear_line() self.insert_text(text) else: cursor_position = self.get_position('cursor') # Removing text from cursor to the end of the line self.remove_text('cursor', 'eol') # Inserting history text self.insert_text(text) self.set_cursor_position(cursor_position) def __find_in_history(self, tocursor, start_idx, backward): """Find text 'tocursor' in history, from index 'start_idx'""" if start_idx is None: start_idx = len(self.history) # Finding text in history step = -1 if backward else 1 idx = start_idx if len(tocursor) == 0 or self.hist_wholeline: idx += step if idx >= len(self.history) or len(self.history) == 0: return "", len(self.history) elif idx < 0: idx = 0 self.hist_wholeline = True return self.history[idx], idx else: for index in range(len(self.history)): idx = (start_idx + step * (index + 1)) % len(self.history) entry = self.history[idx] if entry.startswith(tocursor): return entry[len(tocursor):], idx else: return None, start_idx #------ Simulation standards input/output def write_error(self, text): """Simulate stderr""" self.flush() self.write(text, flush=True, error=True) if DEBUG: STDERR.write(text) def write(self, text, flush=False, error=False, prompt=False): """Simulate stdout and stderr""" if prompt: self.flush() if not is_string(text): # This test is useful to discriminate QStrings from decoded str text = to_text_string(text) self.__buffer.append(text) ts = time.time() if flush or prompt: self.flush(error=error, prompt=prompt) elif ts - self.__timestamp > 0.05: self.flush(error=error) self.__timestamp = ts # Timer to flush strings cached by last write() operation in series self.__flushtimer.start(50) def flush(self, error=False, prompt=False): """Flush buffer, write text to console""" # Fix for Issue 2452 if PY3: try: text = "".join(self.__buffer) except TypeError: text = b"".join(self.__buffer) try: text = text.decode(locale.getdefaultlocale()[1]) except: pass else: text = "".join(self.__buffer) self.__buffer = [] self.insert_text(text, at_end=True, error=error, prompt=prompt) QCoreApplication.processEvents() self.repaint() # Clear input buffer: self.new_input_line = True #------ Text Insertion def insert_text(self, text, at_end=False, error=False, prompt=False): """ Insert text at the current cursor position or at the end of the command line """ if at_end: # Insert text at the end of the command line self.append_text_to_shell(text, error, prompt) else: # Insert text at current cursor position ConsoleBaseWidget.insert_text(self, text) #------ Re-implemented Qt Methods def focusNextPrevChild(self, next): """ Reimplemented to stop Tab moving to the next window """ if next: return False return ConsoleBaseWidget.focusNextPrevChild(self, next) #------ Drag and drop def dragEnterEvent(self, event): """Drag and Drop - Enter event""" event.setAccepted(event.mimeData().hasFormat("text/plain")) def dragMoveEvent(self, event): """Drag and Drop - Move event""" if (event.mimeData().hasFormat("text/plain")): event.setDropAction(Qt.MoveAction) event.accept() else: event.ignore() def dropEvent(self, event): """Drag and Drop - Drop event""" if (event.mimeData().hasFormat("text/plain")): text = to_text_string(event.mimeData().text()) if self.new_input_line: self.on_new_line() self.insert_text(text, at_end=True) self.setFocus() event.setDropAction(Qt.MoveAction) event.accept() else: event.ignore() def drop_pathlist(self, pathlist): """Drop path list""" raise NotImplementedError
def __init__(self, parent, enable_replace=False): QWidget.__init__(self, parent) self.enable_replace = enable_replace self.editor = None self.is_code_editor = None glayout = QGridLayout() glayout.setContentsMargins(0, 0, 0, 0) self.setLayout(glayout) self.close_button = create_toolbutton(self, triggered=self.hide, icon=get_std_icon("DialogCloseButton")) glayout.addWidget(self.close_button, 0, 0) # Find layout self.search_text = PatternComboBox(self, tip=_("Search string"), adjust_to_minimum=False) self.connect(self.search_text.lineEdit(), SIGNAL("textEdited(QString)"), self.text_has_been_edited) self.previous_button = create_toolbutton(self, triggered=self.find_previous, icon=get_std_icon("ArrowBack")) self.next_button = create_toolbutton(self, triggered=self.find_next, icon=get_std_icon("ArrowForward")) self.connect(self.next_button, SIGNAL("clicked()"), self.update_search_combo) self.connect(self.previous_button, SIGNAL("clicked()"), self.update_search_combo) self.re_button = create_toolbutton(self, icon=get_icon("advanced.png"), tip=_("Regular expression")) self.re_button.setCheckable(True) self.connect(self.re_button, SIGNAL("toggled(bool)"), lambda state: self.find()) self.case_button = create_toolbutton(self, icon=get_icon("upper_lower.png"), tip=_("Case Sensitive")) self.case_button.setCheckable(True) self.connect(self.case_button, SIGNAL("toggled(bool)"), lambda state: self.find()) self.words_button = create_toolbutton(self, icon=get_icon("whole_words.png"), tip=_("Whole words")) self.words_button.setCheckable(True) self.connect(self.words_button, SIGNAL("toggled(bool)"), lambda state: self.find()) self.highlight_button = create_toolbutton(self, icon=get_icon("highlight.png"), tip=_("Highlight matches")) self.highlight_button.setCheckable(True) self.connect(self.highlight_button, SIGNAL("toggled(bool)"), self.toggle_highlighting) hlayout = QHBoxLayout() self.widgets = [ self.close_button, self.search_text, self.previous_button, self.next_button, self.re_button, self.case_button, self.words_button, self.highlight_button, ] for widget in self.widgets[1:]: hlayout.addWidget(widget) glayout.addLayout(hlayout, 0, 1) # Replace layout replace_with = QLabel(_("Replace with:")) self.replace_text = PatternComboBox(self, adjust_to_minimum=False, tip=_("Replace string")) self.replace_button = create_toolbutton( self, text=_("Replace/find"), icon=get_std_icon("DialogApplyButton"), triggered=self.replace_find, text_beside_icon=True, ) self.connect(self.replace_button, SIGNAL("clicked()"), self.update_replace_combo) self.connect(self.replace_button, SIGNAL("clicked()"), self.update_search_combo) self.all_check = QCheckBox(_("Replace all")) self.replace_layout = QHBoxLayout() widgets = [replace_with, self.replace_text, self.replace_button, self.all_check] for widget in widgets: self.replace_layout.addWidget(widget) glayout.addLayout(self.replace_layout, 1, 1) self.widgets.extend(widgets) self.replace_widgets = widgets self.hide_replace() self.search_text.setTabOrder(self.search_text, self.replace_text) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.findnext_sc = QShortcut(QKeySequence("F3"), parent, self.find_next) self.findnext_sc.setContext(Qt.WidgetWithChildrenShortcut) self.findprev_sc = QShortcut(QKeySequence("Shift+F3"), parent, self.find_previous) self.findprev_sc.setContext(Qt.WidgetWithChildrenShortcut) self.togglefind_sc = QShortcut(QKeySequence("Ctrl+F"), parent, self.show) self.togglefind_sc.setContext(Qt.WidgetWithChildrenShortcut) self.togglereplace_sc = QShortcut(QKeySequence("Ctrl+H"), parent, self.toggle_replace_widgets) self.togglereplace_sc.setContext(Qt.WidgetWithChildrenShortcut) escape_sc = QShortcut(QKeySequence("Escape"), parent, self.hide) escape_sc.setContext(Qt.WidgetWithChildrenShortcut) self.highlight_timer = QTimer(self) self.highlight_timer.setSingleShot(True) self.highlight_timer.setInterval(1000) self.connect(self.highlight_timer, SIGNAL("timeout()"), self.highlight_matches)
class ExternalShellBase(QWidget): """External Shell widget: execute Python script in a separate process""" SHELL_CLASS = None redirect_stdio = Signal(bool) sig_finished = Signal() def __init__(self, parent=None, fname=None, wdir=None, history_filename=None, show_icontext=True, light_background=True, menu_actions=None, show_buttons_inside=True, show_elapsed_time=True): QWidget.__init__(self, parent) self.menu_actions = menu_actions self.run_button = None self.kill_button = None self.options_button = None self.icontext_action = None self.show_elapsed_time = show_elapsed_time self.fname = fname if wdir is None: wdir = osp.dirname(osp.abspath(fname)) self.wdir = wdir if osp.isdir(wdir) else None self.arguments = "" self.shell = self.SHELL_CLASS(parent, get_conf_path(history_filename)) self.shell.set_light_background(light_background) self.shell.execute.connect(self.send_to_process) self.shell.sig_keyboard_interrupt.connect(self.keyboard_interrupt) # Redirecting some SIGNALs: self.shell.redirect_stdio.connect( lambda state: self.redirect_stdio.emit(state)) self.state_label = None self.time_label = None vlayout = QVBoxLayout() toolbar_buttons = self.get_toolbar_buttons() if show_buttons_inside: self.state_label = QLabel() hlayout = QHBoxLayout() hlayout.addWidget(self.state_label) hlayout.addStretch(0) hlayout.addWidget(self.create_time_label()) hlayout.addStretch(0) for button in toolbar_buttons: hlayout.addWidget(button) vlayout.addLayout(hlayout) else: vlayout.setContentsMargins(0, 0, 0, 0) vlayout.addWidget(self.get_shell_widget()) self.setLayout(vlayout) self.resize(640, 480) if parent is None: self.setWindowIcon(self.get_icon()) self.setWindowTitle(_("Console")) self.t0 = None self.timer = QTimer(self) self.process = None self.is_closing = False if show_buttons_inside: self.update_time_label_visibility() @Slot(bool) def set_elapsed_time_visible(self, state): self.show_elapsed_time = state if self.time_label is not None: self.time_label.setVisible(state) def create_time_label(self): """Create elapsed time label widget (if necessary) and return it""" if self.time_label is None: self.time_label = QLabel() return self.time_label def update_time_label_visibility(self): self.time_label.setVisible(self.show_elapsed_time) def is_running(self): if self.process is not None: return self.process.state() == QProcess.Running def get_toolbar_buttons(self): if self.run_button is None: self.run_button = create_toolbutton( self, text=_("Run"), icon=get_icon('run.png'), tip=_("Run again this program"), triggered=self.start_shell) if self.kill_button is None: self.kill_button = create_toolbutton( self, text=_("Kill"), icon=get_icon('kill.png'), tip=_("Kills the current process, " "causing it to exit immediately")) buttons = [self.run_button] if self.options_button is None: options = self.get_options_menu() if options: self.options_button = create_toolbutton( self, text=_("Options"), icon=get_icon('tooloptions.png')) self.options_button.setPopupMode(QToolButton.InstantPopup) menu = QMenu(self) add_actions(menu, options) self.options_button.setMenu(menu) if self.options_button is not None: buttons.append(self.options_button) buttons.append(self.kill_button) return buttons def set_icontext_visible(self, state): """Set icon text visibility""" for widget in self.get_toolbar_buttons(): if state: widget.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) else: widget.setToolButtonStyle(Qt.ToolButtonIconOnly) def get_options_menu(self): self.show_time_action = create_action( self, _("Show elapsed time"), toggled=self.set_elapsed_time_visible) self.show_time_action.setChecked(self.show_elapsed_time) actions = [self.show_time_action] if self.menu_actions is not None: actions += [None] + self.menu_actions return actions def get_shell_widget(self): return self.shell def get_icon(self): raise NotImplementedError def show_time(self, end=False): if self.time_label is None: return elapsed_time = time() - self.t0 if elapsed_time > 24 * 3600: # More than a day...! format = "%d %H:%M:%S" else: format = "%H:%M:%S" if end: color = "#AAAAAA" else: color = "#AA6655" text = "<span style=\'color: %s\'><b>%s" \ "</b></span>" % (color, strftime(format, gmtime(elapsed_time))) self.time_label.setText(text) def closeEvent(self, event): if self.process is not None: self.is_closing = True self.process.kill() self.process.waitForFinished(100) try: self.timer.timeout.disconnect(self.show_time) except RuntimeError: pass def set_running_state(self, state=True): self.set_buttons_runnning_state(state) self.shell.setReadOnly(not state) if state: if self.state_label is not None: self.state_label.setText( _("<span style=\'color: #44AA44\'><b>Running...</b></span>" )) self.t0 = time() self.timer.timeout.connect(self.show_time) self.timer.start(1000) else: if self.state_label is not None: self.state_label.setText(_('Terminated.')) try: self.timer.timeout.disconnect(self.show_time) except: pass def set_buttons_runnning_state(self, state): self.run_button.setVisible(not state and not self.is_ipykernel) self.kill_button.setVisible(state) @Slot(bool) def start_shell(self, ask_for_arguments=False): """Start shell""" if ask_for_arguments and not self.get_arguments(): self.set_running_state(False) return try: self.terminate_button.clicked.disconnect(self.process.terminate) self.kill_button.clicked.disconnect(self.process.terminate) except: pass self.create_process() @Slot() def get_arguments(self): arguments, valid = QInputDialog.getText(self, _('Arguments'), _('Command line arguments:'), QLineEdit.Normal, self.arguments) if valid: self.arguments = to_text_string(arguments) return valid def create_process(self): raise NotImplementedError def finished(self, exit_code, exit_status): self.shell.flush() self.sig_finished.emit() if self.is_closing: return self.set_running_state(False) self.show_time(end=True) #=============================================================================== # Input/Output #=============================================================================== def transcode(self, qba): try: return to_text_string(qba.data(), 'utf8') except UnicodeDecodeError: return qba.data() def get_stdout(self): self.process.setReadChannel(QProcess.StandardOutput) qba = QByteArray() while self.process.bytesAvailable(): qba += self.process.readAllStandardOutput() return self.transcode(qba) def get_stderr(self): self.process.setReadChannel(QProcess.StandardError) qba = QByteArray() while self.process.bytesAvailable(): qba += self.process.readAllStandardError() return self.transcode(qba) def write_output(self): self.shell.write(self.get_stdout(), flush=True) QApplication.processEvents() def send_to_process(self, qstr): raise NotImplementedError def send_ctrl_to_process(self, letter): char = chr("abcdefghijklmnopqrstuvwxyz".index(letter) + 1) byte_array = QByteArray() byte_array.append(char) self.process.write(byte_array) self.process.waitForBytesWritten(-1) self.shell.write(LOCALE_CODEC.toUnicode(byte_array), flush=True) def keyboard_interrupt(self): raise NotImplementedError
class PluginManager(QObject): introspection_complete = Signal(object) def __init__(self, executable): super(PluginManager, self).__init__() plugins = OrderedDict() for name in PLUGINS: try: plugin = PluginClient(name, executable) plugin.run() except Exception as e: debug_print('Introspection Plugin Failed: %s' % name) debug_print(str(e)) continue debug_print('Introspection Plugin Loaded: %s' % name) plugins[name] = plugin plugin.received.connect(self.handle_response) self.plugins = plugins self.timer = QTimer() self.desired = [] self.ids = dict() self.info = None self.request = None self.pending = None self.pending_request = None self.waiting = False def send_request(self, info): """Handle an incoming request from the user.""" if self.waiting: if info.serialize() != self.info.serialize(): self.pending_request = info else: debug_print('skipping duplicate request') return debug_print('%s request' % info.name) desired = None self.info = info editor = info.editor if (info.name == 'completion' and 'jedi' not in self.plugins and info.line.lstrip().startswith(('import ', 'from '))): desired = 'fallback' if ((not editor.is_python_like()) or sourcecode.is_keyword(info.obj) or (editor.in_comment_or_string() and info.name != 'info')): desired = 'fallback' plugins = self.plugins.values() if desired: plugins = [self.plugins[desired]] self.desired = [desired] elif (info.name == 'definition' and not info.editor.is_python() or info.name == 'info'): self.desired = list(self.plugins.keys()) else: # Use all but the fallback plugins = list(self.plugins.values())[:-1] self.desired = list(self.plugins.keys())[:-1] self._start_time = time.time() self.waiting = True method = 'get_%s' % info.name value = info.serialize() self.ids = dict() for plugin in plugins: request_id = plugin.request(method, value) self.ids[request_id] = plugin.name self.timer.stop() self.timer.singleShot(LEAD_TIME_SEC * 1000, self._handle_timeout) def validate(self): for plugin in self.plugins.values(): plugin.request('validate') def handle_response(self, response): name = self.ids.get(response['request_id'], None) if not name: return if response.get('error', None): debug_print('Response error:', response['error']) return if name == self.desired[0] or not self.waiting: if response.get('result', None): self._finalize(response) else: self.pending = response def close(self): [plugin.close() for plugin in self.plugins] def _finalize(self, response): self.waiting = False self.pending = None if self.info: delta = time.time() - self._start_time debug_print('%s request from %s finished: "%s" in %.1f sec' % (self.info.name, response['name'], str(response['result'])[:100], delta)) response['info'] = self.info self.introspection_complete.emit(response) self.info = None if self.pending_request: info = self.pending_request self.pending_request = None self.send_request(info) def _handle_timeout(self): self.waiting = False if self.pending: self._finalize(self.pending) else: debug_print('No valid responses acquired')
def __init__(self, parent=None, fname=None, wdir=None, history_filename=None, show_icontext=True, light_background=True, menu_actions=None, show_buttons_inside=True, show_elapsed_time=True): QWidget.__init__(self, parent) self.menu_actions = menu_actions self.run_button = None self.kill_button = None self.options_button = None self.icontext_action = None self.show_elapsed_time = show_elapsed_time self.fname = fname if wdir is None: wdir = osp.dirname(osp.abspath(fname)) self.wdir = wdir if osp.isdir(wdir) else None self.arguments = "" self.shell = self.SHELL_CLASS(parent, get_conf_path(history_filename)) self.shell.set_light_background(light_background) self.shell.execute.connect(self.send_to_process) self.shell.sig_keyboard_interrupt.connect(self.keyboard_interrupt) # Redirecting some SIGNALs: self.shell.redirect_stdio.connect( lambda state: self.redirect_stdio.emit(state)) self.state_label = None self.time_label = None vlayout = QVBoxLayout() toolbar_buttons = self.get_toolbar_buttons() if show_buttons_inside: self.state_label = QLabel() hlayout = QHBoxLayout() hlayout.addWidget(self.state_label) hlayout.addStretch(0) hlayout.addWidget(self.create_time_label()) hlayout.addStretch(0) for button in toolbar_buttons: hlayout.addWidget(button) vlayout.addLayout(hlayout) else: vlayout.setContentsMargins(0, 0, 0, 0) vlayout.addWidget(self.get_shell_widget()) self.setLayout(vlayout) self.resize(640, 480) if parent is None: self.setWindowIcon(self.get_icon()) self.setWindowTitle(_("Console")) self.t0 = None self.timer = QTimer(self) self.process = None self.is_closing = False if show_buttons_inside: self.update_time_label_visibility()
class RequestHandler(QObject): """Handle introspection request. """ introspection_complete = Signal() def __init__(self, code_info, plugins): super(RequestHandler, self).__init__() self.info = code_info self.timer = QTimer() self.timer.singleShot(LEAD_TIME_SEC * 1000, self._handle_timeout) self.waiting = True self.pending = {} self.result = None self.plugins = plugins self._start_time = time.time() self._threads = {} for plugin in plugins: self._make_async_call(plugin, code_info) def _handle_timeout(self): debug_print('got timeout: %s' % self.plugins) if self.pending: for plugin in self.plugins: if plugin.name in self.pending: self._finalize(plugin.name, self.pending[plugin.name]) return self.waiting = False def _handle_incoming(self, name): # coerce to a str in case it is a QString name = str(name) try: self._threads[name].wait() except AttributeError: return if self.result: return result = self._threads[name].result if name == self.plugins[0].name or not self.waiting: if result: self._finalize(name, result) else: debug_print('No valid responses acquired') self.introspection_complete.emit() else: self.pending[name] = result def _make_async_call(self, plugin, info): """Trigger an introspection job in a thread""" self._threads[str(plugin.name)] = thread = IntrospectionThread( plugin, info) thread.request_handled.connect(self._handle_incoming) thread.start() def _finalize(self, name, result): self.result = result self.waiting = False self.pending = None delta = time.time() - self._start_time debug_print('%s request from %s finished: "%s" in %.1f sec' % (self.info.name, name, str(result)[:100], delta)) self.introspection_complete.emit()
class Restarter(QWidget): """Widget in charge of displaying the splash information screen and the error messages. """ def __init__(self): super(Restarter, self).__init__() self.ellipsis = ['', '.', '..', '...', '..', '.'] # Widgets self.timer_ellipsis = QTimer(self) self.splash = QSplashScreen( QPixmap(get_image_path('splash.svg'), 'svg')) # Widget setup self.setVisible(False) font = self.splash.font() font.setPixelSize(10) self.splash.setFont(font) self.splash.show() self.timer_ellipsis.timeout.connect(self.animate_ellipsis) def _show_message(self, text): """Show message on splash screen.""" self.splash.showMessage( text, Qt.AlignBottom | Qt.AlignCenter | Qt.AlignAbsolute, QColor(Qt.white)) def animate_ellipsis(self): """Animate dots at the end of the splash screen message.""" ellipsis = self.ellipsis.pop(0) text = ' ' * len(ellipsis) + self.splash_text + ellipsis self.ellipsis.append(ellipsis) self._show_message(text) def set_splash_message(self, text): """Sets the text in the bottom of the Splash screen.""" self.splash_text = text self._show_message(text) self.timer_ellipsis.start(500) def launch_error_message(self, error_type, error=None): """Launch a message box with a predefined error message. Parameters ---------- error_type : int [CLOSE_ERROR, RESET_ERROR, RESTART_ERROR] Possible error codes when restarting/reseting spyder. error : Exception Actual Python exception error caught. """ messages = { CLOSE_ERROR: _("It was not possible to close the previous " "Spyder instance.\nRestart aborted."), RESET_ERROR: _("Spyder could not reset to factory " "defaults.\nRestart aborted."), RESTART_ERROR: _("It was not possible to restart Spyder.\n" "Operation aborted.") } titles = { CLOSE_ERROR: _("Spyder exit error"), RESET_ERROR: _("Spyder reset error"), RESTART_ERROR: _("Spyder restart error") } if error: e = error.__repr__() message = messages[error_type] + _("\n\n{0}").format(e) else: message = messages[error_type] title = titles[error_type] self.splash.hide() QMessageBox.warning(self, title, message, QMessageBox.Ok) raise RuntimeError(message)
class ShellBaseWidget(ConsoleBaseWidget, SaveHistoryMixin): """ Shell base widget """ redirect_stdio = Signal(bool) sig_keyboard_interrupt = Signal() execute = Signal(str) append_to_history = Signal(str, str) def __init__(self, parent, history_filename, profile=False): """ parent : specifies the parent widget """ ConsoleBaseWidget.__init__(self, parent) SaveHistoryMixin.__init__(self) # Prompt position: tuple (line, index) self.current_prompt_pos = None self.new_input_line = True # History self.histidx = None self.hist_wholeline = False assert is_text_string(history_filename) self.history_filename = history_filename self.history = self.load_history() # Session self.historylog_filename = CONF.get('main', 'historylog_filename', get_conf_path('history.log')) # Context menu self.menu = None self.setup_context_menu() # Simple profiling test self.profile = profile # Buffer to increase performance of write/flush operations self.__buffer = [] self.__timestamp = 0.0 self.__flushtimer = QTimer(self) self.__flushtimer.setSingleShot(True) self.__flushtimer.timeout.connect(self.flush) # Give focus to widget self.setFocus() # Completion completion_size = CONF.get('shell_appearance', 'completion/size') completion_font = get_font('console') self.completion_widget.setup_appearance(completion_size, completion_font) # Cursor width self.setCursorWidth( CONF.get('shell_appearance', 'cursor/width') ) def toggle_wrap_mode(self, enable): """Enable/disable wrap mode""" self.set_wrap_mode('character' if enable else None) def set_font(self, font): """Set shell styles font""" self.setFont(font) self.set_pythonshell_font(font) cursor = self.textCursor() cursor.select(QTextCursor.Document) charformat = QTextCharFormat() charformat.setFontFamily(font.family()) charformat.setFontPointSize(font.pointSize()) cursor.mergeCharFormat(charformat) #------ Context menu def setup_context_menu(self): """Setup shell context menu""" self.menu = QMenu(self) self.cut_action = create_action(self, _("Cut"), shortcut=keybinding('Cut'), icon=ima.icon('editcut'), triggered=self.cut) self.copy_action = create_action(self, _("Copy"), shortcut=keybinding('Copy'), icon=ima.icon('editcopy'), triggered=self.copy) paste_action = create_action(self, _("Paste"), shortcut=keybinding('Paste'), icon=ima.icon('editpaste'), triggered=self.paste) save_action = create_action(self, _("Save history log..."), icon=ima.icon('filesave'), tip=_("Save current history log (i.e. all " "inputs and outputs) in a text file"), triggered=self.save_historylog) self.delete_action = create_action(self, _("Delete"), shortcut=keybinding('Delete'), icon=ima.icon('editdelete'), triggered=self.delete) selectall_action = create_action(self, _("Select All"), shortcut=keybinding('SelectAll'), icon=ima.icon('selectall'), triggered=self.selectAll) add_actions(self.menu, (self.cut_action, self.copy_action, paste_action, self.delete_action, None, selectall_action, None, save_action) ) def contextMenuEvent(self, event): """Reimplement Qt method""" state = self.has_selected_text() self.copy_action.setEnabled(state) self.cut_action.setEnabled(state) self.delete_action.setEnabled(state) self.menu.popup(event.globalPos()) event.accept() #------ Input buffer def get_current_line_from_cursor(self): return self.get_text('cursor', 'eof') def _select_input(self): """Select current line (without selecting console prompt)""" line, index = self.get_position('eof') if self.current_prompt_pos is None: pline, pindex = line, index else: pline, pindex = self.current_prompt_pos self.setSelection(pline, pindex, line, index) @Slot() def clear_line(self): """Clear current line (without clearing console prompt)""" if self.current_prompt_pos is not None: self.remove_text(self.current_prompt_pos, 'eof') @Slot() def clear_terminal(self): """ Clear terminal window Child classes reimplement this method to write prompt """ self.clear() # The buffer being edited def _set_input_buffer(self, text): """Set input buffer""" if self.current_prompt_pos is not None: self.replace_text(self.current_prompt_pos, 'eol', text) else: self.insert(text) self.set_cursor_position('eof') def _get_input_buffer(self): """Return input buffer""" input_buffer = '' if self.current_prompt_pos is not None: input_buffer = self.get_text(self.current_prompt_pos, 'eol') input_buffer = input_buffer.replace(os.linesep, '\n') return input_buffer input_buffer = Property("QString", _get_input_buffer, _set_input_buffer) #------ Prompt def new_prompt(self, prompt): """ Print a new prompt and save its (line, index) position """ if self.get_cursor_line_column()[1] != 0: self.write('\n') self.write(prompt, prompt=True) # now we update our cursor giving end of prompt self.current_prompt_pos = self.get_position('cursor') self.ensureCursorVisible() self.new_input_line = False def check_selection(self): """ Check if selected text is r/w, otherwise remove read-only parts of selection """ if self.current_prompt_pos is None: self.set_cursor_position('eof') else: self.truncate_selection(self.current_prompt_pos) #------ Copy / Keyboard interrupt @Slot() def copy(self): """Copy text to clipboard... or keyboard interrupt""" if self.has_selected_text(): ConsoleBaseWidget.copy(self) elif not sys.platform == 'darwin': self.interrupt() def interrupt(self): """Keyboard interrupt""" self.sig_keyboard_interrupt.emit() @Slot() def cut(self): """Cut text""" self.check_selection() if self.has_selected_text(): ConsoleBaseWidget.cut(self) @Slot() def delete(self): """Remove selected text""" self.check_selection() if self.has_selected_text(): ConsoleBaseWidget.remove_selected_text(self) @Slot() def save_historylog(self): """Save current history log (all text in console)""" title = _("Save history log") self.redirect_stdio.emit(False) filename, _selfilter = getsavefilename(self, title, self.historylog_filename, "%s (*.log)" % _("History logs")) self.redirect_stdio.emit(True) if filename: filename = osp.normpath(filename) try: encoding.write(to_text_string(self.get_text_with_eol()), filename) self.historylog_filename = filename CONF.set('main', 'historylog_filename', filename) except EnvironmentError as error: QMessageBox.critical(self, title, _("<b>Unable to save file '%s'</b>" "<br><br>Error message:<br>%s" ) % (osp.basename(filename), to_text_string(error))) #------ Basic keypress event handler def on_enter(self, command): """on_enter""" self.execute_command(command) def execute_command(self, command): self.execute.emit(command) self.add_to_history(command) self.new_input_line = True def on_new_line(self): """On new input line""" self.set_cursor_position('eof') self.current_prompt_pos = self.get_position('cursor') self.new_input_line = False @Slot() def paste(self): """Reimplemented slot to handle multiline paste action""" if self.new_input_line: self.on_new_line() ConsoleBaseWidget.paste(self) def keyPressEvent(self, event): """ Reimplement Qt Method Basic keypress event handler (reimplemented in InternalShell to add more sophisticated features) """ if self.preprocess_keyevent(event): # Event was accepted in self.preprocess_keyevent return self.postprocess_keyevent(event) def preprocess_keyevent(self, event): """Pre-process keypress event: return True if event is accepted, false otherwise""" # Copy must be done first to be able to copy read-only text parts # (otherwise, right below, we would remove selection # if not on current line) ctrl = event.modifiers() & Qt.ControlModifier meta = event.modifiers() & Qt.MetaModifier # meta=ctrl in OSX if event.key() == Qt.Key_C and \ ((Qt.MetaModifier | Qt.ControlModifier) & event.modifiers()): if meta and sys.platform == 'darwin': self.interrupt() elif ctrl: self.copy() event.accept() return True if self.new_input_line and ( len(event.text()) or event.key() in \ (Qt.Key_Up, Qt.Key_Down, Qt.Key_Left, Qt.Key_Right) ): self.on_new_line() return False def postprocess_keyevent(self, event): """Post-process keypress event: in InternalShell, this is method is called when shell is ready""" event, text, key, ctrl, shift = restore_keyevent(event) # Is cursor on the last line? and after prompt? if len(text): #XXX: Shouldn't it be: `if len(unicode(text).strip(os.linesep))` ? if self.has_selected_text(): self.check_selection() self.restrict_cursor_position(self.current_prompt_pos, 'eof') cursor_position = self.get_position('cursor') if key in (Qt.Key_Return, Qt.Key_Enter): if self.is_cursor_on_last_line(): self._key_enter() # add and run selection else: self.insert_text(self.get_selected_text(), at_end=True) elif key == Qt.Key_Insert and not shift and not ctrl: self.setOverwriteMode(not self.overwriteMode()) elif key == Qt.Key_Delete: if self.has_selected_text(): self.check_selection() self.remove_selected_text() elif self.is_cursor_on_last_line(): self.stdkey_clear() elif key == Qt.Key_Backspace: self._key_backspace(cursor_position) elif key == Qt.Key_Tab: self._key_tab() elif key == Qt.Key_Space and ctrl: self._key_ctrl_space() elif key == Qt.Key_Left: if self.current_prompt_pos == cursor_position: # Avoid moving cursor on prompt return method = self.extend_selection_to_next if shift \ else self.move_cursor_to_next method('word' if ctrl else 'character', direction='left') elif key == Qt.Key_Right: if self.is_cursor_at_end(): return method = self.extend_selection_to_next if shift \ else self.move_cursor_to_next method('word' if ctrl else 'character', direction='right') elif (key == Qt.Key_Home) or ((key == Qt.Key_Up) and ctrl): self._key_home(shift, ctrl) elif (key == Qt.Key_End) or ((key == Qt.Key_Down) and ctrl): self._key_end(shift, ctrl) elif key == Qt.Key_Up: if not self.is_cursor_on_last_line(): self.set_cursor_position('eof') y_cursor = self.get_coordinates(cursor_position)[1] y_prompt = self.get_coordinates(self.current_prompt_pos)[1] if y_cursor > y_prompt: self.stdkey_up(shift) else: self.browse_history(backward=True) elif key == Qt.Key_Down: if not self.is_cursor_on_last_line(): self.set_cursor_position('eof') y_cursor = self.get_coordinates(cursor_position)[1] y_end = self.get_coordinates('eol')[1] if y_cursor < y_end: self.stdkey_down(shift) else: self.browse_history(backward=False) elif key in (Qt.Key_PageUp, Qt.Key_PageDown): #XXX: Find a way to do this programmatically instead of calling # widget keyhandler (this won't work if the *event* is coming from # the event queue - i.e. if the busy buffer is ever implemented) ConsoleBaseWidget.keyPressEvent(self, event) elif key == Qt.Key_Escape and shift: self.clear_line() elif key == Qt.Key_Escape: self._key_escape() elif key == Qt.Key_L and ctrl: self.clear_terminal() elif key == Qt.Key_V and ctrl: self.paste() elif key == Qt.Key_X and ctrl: self.cut() elif key == Qt.Key_Z and ctrl: self.undo() elif key == Qt.Key_Y and ctrl: self.redo() elif key == Qt.Key_A and ctrl: self.selectAll() elif key == Qt.Key_Question and not self.has_selected_text(): self._key_question(text) elif key == Qt.Key_ParenLeft and not self.has_selected_text(): self._key_parenleft(text) elif key == Qt.Key_Period and not self.has_selected_text(): self._key_period(text) elif len(text) and not self.isReadOnly(): self.hist_wholeline = False self.insert_text(text) self._key_other(text) else: # Let the parent widget handle the key press event ConsoleBaseWidget.keyPressEvent(self, event) #------ Key handlers def _key_enter(self): command = self.input_buffer self.insert_text('\n', at_end=True) self.on_enter(command) self.flush() def _key_other(self, text): raise NotImplementedError def _key_backspace(self, cursor_position): raise NotImplementedError def _key_tab(self): raise NotImplementedError def _key_ctrl_space(self): raise NotImplementedError def _key_home(self, shift, ctrl): if self.is_cursor_on_last_line(): self.stdkey_home(shift, ctrl, self.current_prompt_pos) def _key_end(self, shift, ctrl): if self.is_cursor_on_last_line(): self.stdkey_end(shift, ctrl) def _key_pageup(self): raise NotImplementedError def _key_pagedown(self): raise NotImplementedError def _key_escape(self): raise NotImplementedError def _key_question(self, text): raise NotImplementedError def _key_parenleft(self, text): raise NotImplementedError def _key_period(self, text): raise NotImplementedError #------ History Management def load_history(self): """Load history from a .py file in user home directory""" if osp.isfile(self.history_filename): rawhistory, _ = encoding.readlines(self.history_filename) rawhistory = [line.replace('\n', '') for line in rawhistory] if rawhistory[1] != self.INITHISTORY[1]: rawhistory[1] = self.INITHISTORY[1] else: rawhistory = self.INITHISTORY history = [line for line in rawhistory \ if line and not line.startswith('#')] # Truncating history to X entries: while len(history) >= CONF.get('historylog', 'max_entries'): del history[0] while rawhistory[0].startswith('#'): del rawhistory[0] del rawhistory[0] # Saving truncated history: encoding.writelines(rawhistory, self.history_filename) return history def browse_history(self, backward): """Browse history""" if self.is_cursor_before('eol') and self.hist_wholeline: self.hist_wholeline = False tocursor = self.get_current_line_to_cursor() text, self.histidx = self.__find_in_history(tocursor, self.histidx, backward) if text is not None: if self.hist_wholeline: self.clear_line() self.insert_text(text) else: cursor_position = self.get_position('cursor') # Removing text from cursor to the end of the line self.remove_text('cursor', 'eol') # Inserting history text self.insert_text(text) self.set_cursor_position(cursor_position) def __find_in_history(self, tocursor, start_idx, backward): """Find text 'tocursor' in history, from index 'start_idx'""" if start_idx is None: start_idx = len(self.history) # Finding text in history step = -1 if backward else 1 idx = start_idx if len(tocursor) == 0 or self.hist_wholeline: idx += step if idx >= len(self.history) or len(self.history) == 0: return "", len(self.history) elif idx < 0: idx = 0 self.hist_wholeline = True return self.history[idx], idx else: for index in range(len(self.history)): idx = (start_idx+step*(index+1)) % len(self.history) entry = self.history[idx] if entry.startswith(tocursor): return entry[len(tocursor):], idx else: return None, start_idx #------ Simulation standards input/output def write_error(self, text): """Simulate stderr""" self.flush() self.write(text, flush=True, error=True) if DEBUG: STDERR.write(text) def write(self, text, flush=False, error=False, prompt=False): """Simulate stdout and stderr""" if prompt: self.flush() if not is_string(text): # This test is useful to discriminate QStrings from decoded str text = to_text_string(text) self.__buffer.append(text) ts = time.time() if flush or prompt: self.flush(error=error, prompt=prompt) elif ts - self.__timestamp > 0.05: self.flush(error=error) self.__timestamp = ts # Timer to flush strings cached by last write() operation in series self.__flushtimer.start(50) def flush(self, error=False, prompt=False): """Flush buffer, write text to console""" # Fix for Issue 2452 if PY3: try: text = "".join(self.__buffer) except TypeError: text = b"".join(self.__buffer) try: text = text.decode( locale.getdefaultlocale()[1] ) except: pass else: text = "".join(self.__buffer) self.__buffer = [] self.insert_text(text, at_end=True, error=error, prompt=prompt) QCoreApplication.processEvents() self.repaint() # Clear input buffer: self.new_input_line = True #------ Text Insertion def insert_text(self, text, at_end=False, error=False, prompt=False): """ Insert text at the current cursor position or at the end of the command line """ if at_end: # Insert text at the end of the command line self.append_text_to_shell(text, error, prompt) else: # Insert text at current cursor position ConsoleBaseWidget.insert_text(self, text) #------ Re-implemented Qt Methods def focusNextPrevChild(self, next): """ Reimplemented to stop Tab moving to the next window """ if next: return False return ConsoleBaseWidget.focusNextPrevChild(self, next) #------ Drag and drop def dragEnterEvent(self, event): """Drag and Drop - Enter event""" event.setAccepted(event.mimeData().hasFormat("text/plain")) def dragMoveEvent(self, event): """Drag and Drop - Move event""" if (event.mimeData().hasFormat("text/plain")): event.setDropAction(Qt.MoveAction) event.accept() else: event.ignore() def dropEvent(self, event): """Drag and Drop - Drop event""" if (event.mimeData().hasFormat("text/plain")): text = to_text_string(event.mimeData().text()) if self.new_input_line: self.on_new_line() self.insert_text(text, at_end=True) self.setFocus() event.setDropAction(Qt.MoveAction) event.accept() else: event.ignore() def drop_pathlist(self, pathlist): """Drop path list""" raise NotImplementedError
class PluginManager(QObject): introspection_complete = Signal(object) def __init__(self, executable): super(PluginManager, self).__init__() plugins = OrderedDict() for name in PLUGINS: try: plugin = PluginClient(name, executable) except Exception as e: debug_print('Introspection Plugin Failed: %s' % name) debug_print(str(e)) continue debug_print('Introspection Plugin Loaded: %s' % name) plugins[name] = plugin plugin.request_handled.connect(self.handle_response) self.plugins = plugins self.timer = QTimer() self.desired = [] self.info = None self.request = None self.pending = None self.pending_request = None self.waiting = False def send_request(self, info): """Handle an incoming request from the user.""" if self.waiting: if info.serialize() != self.info.serialize(): self.pending_request = info else: debug_print('skipping duplicate request') return debug_print('%s request' % info.name) desired = None self.info = info editor = info.editor if (info.name == 'completion' and 'jedi' not in self.plugins and info.line.lstrip().startswith(('import ', 'from '))): desired = 'fallback' if ((not editor.is_python_like()) or sourcecode.is_keyword(info.obj) or (editor.in_comment_or_string() and info.name != 'info')): desired = 'fallback' plugins = self.plugins.values() if desired: plugins = [self.plugins[desired]] self.desired = [desired] elif (info.name == 'definition' and not info.editor.is_python() or info.name == 'info'): self.desired = list(self.plugins.keys()) else: # Use all but the fallback plugins = list(self.plugins.values())[:-1] self.desired = list(self.plugins.keys())[:-1] self._start_time = time.time() self.waiting = True request = dict(method='get_%s' % info.name, args=[info.serialize()], request_id=id(info)) for plugin in plugins: plugin.send(request) debug_print('sending %s' % (plugin.plugin_name)) self.timer.stop() self.timer.singleShot(LEAD_TIME_SEC * 1000, self._handle_timeout) self.request = request def validate(self): message = dict(method='validate') for plugin in self.plugins.values(): plugin.send(message) def handle_response(self, response): if (self.request is None): return if (response.get('request_id', None) != id(self.info)): return name = response['plugin_name'] response['info'] = self.info if response.get('error', None): debug_print('Response error:', response['error']) return if name == self.desired[0] or not self.waiting: if response.get('result', None): self._finalize(response) else: self.pending = response def close(self): [plugin.close() for plugin in self.plugins] def _finalize(self, response): self.waiting = False self.pending = None delta = time.time() - self._start_time debug_print(str(response.keys())) debug_print('%s request from %s finished: "%s" in %.1f sec' % (self.info.name, response['plugin_name'], str(response['result'])[:100], delta)) self.introspection_complete.emit(response) self.info = None if self.pending_request: info = self.pending_request self.pending_request = None self.send_request(info) def _handle_timeout(self): self.waiting = False if self.pending: self._finalize(self.pending) else: debug_print('No valid responses acquired', self.request['method'])
class ShellBaseWidget(ConsoleBaseWidget): """ Shell base widget """ INITHISTORY = None SEPARATOR = None def __init__(self, parent, history_filename, debug=False, profile=False): """ parent : specifies the parent widget """ ConsoleBaseWidget.__init__(self, parent) # Prompt position: tuple (line, index) self.current_prompt_pos = None self.new_input_line = True # History self.histidx = None self.hist_wholeline = False assert isinstance(history_filename, (str, unicode)) self.history_filename = history_filename self.history = self.load_history() # Session self.historylog_filename = CONF.get('main', 'historylog_filename', get_conf_path('history.log')) # Context menu self.menu = None self.setup_context_menu() # Debug mode self.debug = debug # Simple profiling test self.profile = profile # Buffer to increase performance of write/flush operations self.__buffer = [] self.__timestamp = 0.0 self.__flushtimer = QTimer(self) self.__flushtimer.setSingleShot(True) self.connect(self.__flushtimer, SIGNAL('timeout()'), self.flush) # Give focus to widget self.setFocus() # Calltips calltip_size = CONF.get('shell_appearance', 'calltips/size') calltip_font = get_font('shell_appearance', 'calltips') self.setup_calltips(calltip_size, calltip_font) # Completion completion_size = CONF.get('shell_appearance', 'completion/size') completion_font = get_font('shell_appearance', 'completion') self.completion_widget.setup_appearance(completion_size, completion_font) # Cursor width self.setCursorWidth( CONF.get('shell_appearance', 'cursor/width') ) def toggle_wrap_mode(self, enable): """Enable/disable wrap mode""" self.set_wrap_mode('character' if enable else None) def set_font(self, font): """Set shell styles font""" self.set_pythonshell_font(font) cursor = self.textCursor() cursor.select(QTextCursor.Document) charformat = QTextCharFormat() charformat.setFontFamily(font.family()) charformat.setFontPointSize(font.pointSize()) cursor.mergeCharFormat(charformat) #------ Context menu def setup_context_menu(self): """Setup shell context menu""" self.menu = QMenu(self) self.cut_action = create_action(self, _("Cut"), shortcut=keybinding('Cut'), icon=get_icon('editcut.png'), triggered=self.cut) self.copy_action = create_action(self, _("Copy"), shortcut=keybinding('Copy'), icon=get_icon('editcopy.png'), triggered=self.copy) paste_action = create_action(self, _("Paste"), shortcut=keybinding('Paste'), icon=get_icon('editpaste.png'), triggered=self.paste) save_action = create_action(self, _("Save history log..."), icon=get_icon('filesave.png'), tip=_("Save current history log (i.e. all " "inputs and outputs) in a text file"), triggered=self.save_historylog) self.delete_action = create_action(self, _("Delete"), shortcut=keybinding('Delete'), icon=get_icon('editdelete.png'), triggered=self.delete) selectall_action = create_action(self, _("Select All"), shortcut=keybinding('SelectAll'), icon=get_icon('selectall.png'), triggered=self.selectAll) add_actions(self.menu, (self.cut_action, self.copy_action, paste_action, self.delete_action, None, selectall_action, None, save_action) ) def contextMenuEvent(self, event): """Reimplement Qt method""" state = self.has_selected_text() self.copy_action.setEnabled(state) self.cut_action.setEnabled(state) self.delete_action.setEnabled(state) self.menu.popup(event.globalPos()) event.accept() #------ Input buffer def get_current_line_to_cursor(self): return self.get_text(self.current_prompt_pos, 'cursor') def get_current_line_from_cursor(self): return self.get_text('cursor', 'eof') def _select_input(self): """Select current line (without selecting console prompt)""" line, index = self.get_position('eof') if self.current_prompt_pos is None: pline, pindex = line, index else: pline, pindex = self.current_prompt_pos self.setSelection(pline, pindex, line, index) def clear_line(self): """Clear current line (without clearing console prompt)""" if self.current_prompt_pos is not None: self.remove_text(self.current_prompt_pos, 'eof') def clear_terminal(self): """ Clear terminal window Child classes reimplement this method to write prompt """ self.clear() # The buffer being edited def _set_input_buffer(self, text): """Set input buffer""" if self.current_prompt_pos is not None: self.replace_text(self.current_prompt_pos, 'eol', text) else: self.insert(text) self.set_cursor_position('eof') def _get_input_buffer(self): """Return input buffer""" input_buffer = '' if self.current_prompt_pos is not None: input_buffer = self.get_text(self.current_prompt_pos, 'eol') input_buffer = input_buffer.replace(os.linesep, '\n') return input_buffer input_buffer = Property("QString", _get_input_buffer, _set_input_buffer) #------ Prompt def new_prompt(self, prompt): """ Print a new prompt and save its (line, index) position """ self.write(prompt, prompt=True) # now we update our cursor giving end of prompt self.current_prompt_pos = self.get_position('cursor') self.ensureCursorVisible() self.new_input_line = False def check_selection(self): """ Check if selected text is r/w, otherwise remove read-only parts of selection """ if self.current_prompt_pos is None: self.set_cursor_position('eof') else: self.truncate_selection(self.current_prompt_pos) #------ Copy / Keyboard interrupt def copy(self): """Copy text to clipboard... or keyboard interrupt""" if self.has_selected_text(): ConsoleBaseWidget.copy(self) else: self.emit(SIGNAL("keyboard_interrupt()")) def cut(self): """Cut text""" self.check_selection() if self.has_selected_text(): ConsoleBaseWidget.cut(self) def delete(self): """Remove selected text""" self.check_selection() if self.has_selected_text(): ConsoleBaseWidget.remove_selected_text(self) def save_historylog(self): """Save current history log (all text in console)""" title = _("Save history log") self.emit(SIGNAL('redirect_stdio(bool)'), False) filename, _selfilter = getsavefilename(self, title, self.historylog_filename, "%s (*.log)" % _("History logs")) self.emit(SIGNAL('redirect_stdio(bool)'), True) if filename: filename = osp.normpath(filename) try: encoding.write(unicode(self.get_text_with_eol()), filename) self.historylog_filename = filename CONF.set('main', 'historylog_filename', filename) except EnvironmentError, error: QMessageBox.critical(self, title, _("<b>Unable to save file '%s'</b>" "<br><br>Error message:<br>%s" ) % (osp.basename(filename), unicode(error)))
def __init__(self, parent, enable_replace=False): QWidget.__init__(self, parent) self.enable_replace = enable_replace self.editor = None self.is_code_editor = None glayout = QGridLayout() glayout.setContentsMargins(0, 0, 0, 0) self.setLayout(glayout) self.close_button = create_toolbutton(self, triggered=self.hide, icon=ima.icon('DialogCloseButton')) glayout.addWidget(self.close_button, 0, 0) # Find layout self.search_text = PatternComboBox(self, tip=_("Search string"), adjust_to_minimum=False) self.search_text.valid.connect( lambda state: self.find(changed=False, forward=True, rehighlight=False)) self.search_text.lineEdit().textEdited.connect( self.text_has_been_edited) self.previous_button = create_toolbutton(self, triggered=self.find_previous, icon=ima.icon('ArrowUp')) self.next_button = create_toolbutton(self, triggered=self.find_next, icon=ima.icon('ArrowDown')) self.next_button.clicked.connect(self.update_search_combo) self.previous_button.clicked.connect(self.update_search_combo) self.re_button = create_toolbutton(self, icon=ima.icon('advanced'), tip=_("Regular expression")) self.re_button.setCheckable(True) self.re_button.toggled.connect(lambda state: self.find()) self.case_button = create_toolbutton(self, icon=get_icon("upper_lower.png"), tip=_("Case Sensitive")) self.case_button.setCheckable(True) self.case_button.toggled.connect(lambda state: self.find()) self.words_button = create_toolbutton(self, icon=get_icon("whole_words.png"), tip=_("Whole words")) self.words_button.setCheckable(True) self.words_button.toggled.connect(lambda state: self.find()) self.highlight_button = create_toolbutton(self, icon=get_icon("highlight.png"), tip=_("Highlight matches")) self.highlight_button.setCheckable(True) self.highlight_button.toggled.connect(self.toggle_highlighting) hlayout = QHBoxLayout() self.widgets = [self.close_button, self.search_text, self.previous_button, self.next_button, self.re_button, self.case_button, self.words_button, self.highlight_button] for widget in self.widgets[1:]: hlayout.addWidget(widget) glayout.addLayout(hlayout, 0, 1) # Replace layout replace_with = QLabel(_("Replace with:")) self.replace_text = PatternComboBox(self, adjust_to_minimum=False, tip=_('Replace string')) self.replace_button = create_toolbutton(self, text=_('Replace/find'), icon=ima.icon('DialogApplyButton'), triggered=self.replace_find, text_beside_icon=True) self.replace_button.clicked.connect(self.update_replace_combo) self.replace_button.clicked.connect(self.update_search_combo) self.all_check = QCheckBox(_("Replace all")) self.replace_layout = QHBoxLayout() widgets = [replace_with, self.replace_text, self.replace_button, self.all_check] for widget in widgets: self.replace_layout.addWidget(widget) glayout.addLayout(self.replace_layout, 1, 1) self.widgets.extend(widgets) self.replace_widgets = widgets self.hide_replace() self.search_text.setTabOrder(self.search_text, self.replace_text) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.shortcuts = self.create_shortcuts(parent) self.highlight_timer = QTimer(self) self.highlight_timer.setSingleShot(True) self.highlight_timer.setInterval(1000) self.highlight_timer.timeout.connect(self.highlight_matches)
def refresh_plugin(self): """Refresh widget""" if self._starting_up: self._starting_up = False QTimer.singleShot(5000, self.refresh_plugin) self.set_object_text(None, force_refresh=False)