Пример #1
0
class RichText(QWidget):
    """
    WebView widget with find dialog
    """
    def __init__(self, parent):
        QWidget.__init__(self, parent)

        self.webview = FrameWebView(self)
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.webview)
        self.find_widget.hide()

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.webview)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)

    def set_font(self, font, fixed_font=None):
        """Set font"""
        self.webview.set_font(font, fixed_font=fixed_font)

    def set_html(self, html_text, base_url):
        """Set html text"""
        self.webview.setHtml(html_text, base_url)

    def clear(self):
        self.set_html('', self.webview.url())
Пример #2
0
class PlainText(QWidget):
    """
    Read-only editor widget with find dialog
    """
    # Signals
    focus_changed = Signal()

    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self.editor = None

        # Read-only editor
        self.editor = codeeditor.CodeEditor(self)
        self.editor.setup_editor(linenumbers=False, language='py',
                                 scrollflagarea=False, edge_line=False)
        self.editor.focus_changed.connect(lambda: self.focus_changed.emit())
        self.editor.setReadOnly(True)

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.editor)
        self.find_widget.hide()

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.editor)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)

    def set_font(self, font, color_scheme=None):
        """Set font"""
        self.editor.set_font(font, color_scheme=color_scheme)

    def set_color_scheme(self, color_scheme):
        """Set color scheme"""
        self.editor.set_color_scheme(color_scheme)

    def set_text(self, text, is_code):
        self.editor.set_highlight_current_line(is_code)
        self.editor.set_occurrence_highlighting(is_code)
        if is_code:
            self.editor.set_language('py')
        else:
            self.editor.set_language(None)
        self.editor.set_text(text)
        self.editor.set_cursor_position('sof')

    def clear(self):
        self.editor.clear()
Пример #3
0
    def __init__(self, parent):
        self.tabwidget = None
        self.menu_actions = None
        self.dockviewer = None
        
        self.editors = []
        self.filenames = []
        self.icons = []
        
        SpyderPluginWidget.__init__(self, parent)
        
        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
                     self.refresh_plugin)
        self.connect(self.tabwidget, SIGNAL('move_data(int,int)'),
                     self.move_tab)
        layout.addWidget(self.tabwidget)

        # Menu as corner widget
        options_button = create_toolbutton(self, text=self.tr("Options"),
                                           icon=get_icon('tooloptions.png'))
        options_button.setPopupMode(QToolButton.InstantPopup)
        menu = QMenu(self)
        add_actions(menu, self.menu_actions)
        options_button.setMenu(menu)
        self.tabwidget.setCornerWidget(options_button)
        
        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.hide()
        layout.addWidget(self.find_widget)
        
        self.setLayout(layout)
Пример #4
0
    def __init__(self, parent):
        PluginWidget.__init__(self, parent)

        # Read-only editor
        self.editor = QsciEditor(self)
        self.editor.setup_editor(linenumbers=False, language='py',
                                 code_folding=True)
        self.connect(self.editor, SIGNAL("focus_changed()"),
                     lambda: self.emit(SIGNAL("focus_changed()")))
        self.editor.setReadOnly(True)
        self.editor.set_font( get_font(self.ID) )
        self.editor.toggle_wrap_mode( CONF.get(self.ID, 'wrap') )
        
        # Add entries to read-only editor context-menu
        font_action = create_action(self, translate("Editor", "&Font..."), None,
                                    'font.png',
                                    translate("Editor", "Set font style"),
                                    triggered=self.change_font)
        wrap_action = create_action(self, translate("Editor", "Wrap lines"),
                                    toggled=self.toggle_wrap_mode)
        wrap_action.setChecked( CONF.get(self.ID, 'wrap') )
        self.editor.readonly_menu.addSeparator()
        add_actions(self.editor.readonly_menu, (font_action, wrap_action))
        
        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.editor)
        self.find_widget.hide()
Пример #5
0
    def __init__(self, parent):
        self.tabwidget = None
        self.menu_actions = None
        self.dockviewer = None
        self.wrap_action = None

        self.editors = []
        self.filenames = []
        self.icons = []
        if PYQT5:
            SpyderPluginWidget.__init__(self, parent, main=parent)
        else:
            SpyderPluginWidget.__init__(self, parent)

        # Initialize plugin
        self.initialize_plugin()

        self.set_default_color_scheme()

        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        self.tabwidget.currentChanged.connect(self.refresh_plugin)
        self.tabwidget.move_data.connect(self.move_tab)
        layout.addWidget(self.tabwidget)

        # Menu as corner widget
        options_button = create_toolbutton(self,
                                           text=_("Options"),
                                           icon=get_icon('tooloptions.png'))
        options_button.setPopupMode(QToolButton.InstantPopup)
        menu = QMenu(self)
        add_actions(menu, self.menu_actions)
        options_button.setMenu(menu)
        self.tabwidget.setCornerWidget(options_button)

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.hide()
        self.register_widget_shortcuts("Editor", self.find_widget)

        layout.addWidget(self.find_widget)

        self.setLayout(layout)
Пример #6
0
    def __init__(self, parent):
        self.tabwidget = None
        self.menu_actions = None
        self.dockviewer = None
        self.wrap_action = None
        
        self.editors = []
        self.filenames = []
        self.icons = []
        if PYQT5:        
            SpyderPluginWidget.__init__(self, parent, main = parent)
        else:
            SpyderPluginWidget.__init__(self, parent)

        # Initialize plugin
        self.initialize_plugin()
        
        self.set_default_color_scheme()
        
        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        self.tabwidget.currentChanged.connect(self.refresh_plugin)
        self.tabwidget.move_data.connect(self.move_tab)

        if sys.platform == 'darwin':
            tab_container = QWidget()
            tab_container.setObjectName('tab-container')
            tab_layout = QHBoxLayout(tab_container)
            tab_layout.setContentsMargins(0, 0, 0, 0)
            tab_layout.addWidget(self.tabwidget)
            layout.addWidget(tab_container)
        else:
            layout.addWidget(self.tabwidget)

        self.tabwidget.setStyleSheet("QTabWidget::pane {border: 0;}")

        # Menu as corner widget
        options_button = create_toolbutton(self, text=_('Options'),
                                           icon=ima.icon('tooloptions'))
        options_button.setPopupMode(QToolButton.InstantPopup)
        menu = QMenu(self)
        add_actions(menu, self.menu_actions)
        options_button.setMenu(menu)
        self.tabwidget.setCornerWidget(options_button)
        
        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.hide()
        self.register_widget_shortcuts("Editor", self.find_widget)
        
        layout.addWidget(self.find_widget)
        
        self.setLayout(layout)
Пример #7
0
    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self.editor = None

        # Read-only editor
        self.editor = codeeditor.CodeEditor(self)
        self.editor.setup_editor(linenumbers=False, language='py',
                                 scrollflagarea=False, edge_line=False)
        self.editor.focus_changed.connect(lambda: self.focus_changed.emit())
        self.editor.setReadOnly(True)

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.editor)
        self.find_widget.hide()

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.editor)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)
Пример #8
0
    def __init__(
        self, parent=None, namespace=None, commands=[], message=None, exitfunc=None, profile=False, multithreaded=False
    ):
        if PYQT5:
            SpyderPluginWidget.__init__(self, parent, main=parent)
        else:
            SpyderPluginWidget.__init__(self, parent)

        debug_print("    ..internal console: initializing")
        self.dialog_manager = DialogManager()

        # Shell
        light_background = self.get_option("light_background")
        self.shell = InternalShell(
            parent,
            namespace,
            commands,
            message,
            self.get_option("max_line_count"),
            self.get_plugin_font(),
            exitfunc,
            profile,
            multithreaded,
            light_background=light_background,
        )
        self.shell.status.connect(lambda msg: self.show_message.emit(msg, 0))
        self.shell.go_to_error.connect(self.go_to_error)
        self.shell.focus_changed.connect(lambda: self.focus_changed.emit())

        # Redirecting some signals:
        self.shell.redirect_stdio.connect(lambda state: self.redirect_stdio.emit(state))

        # Initialize plugin
        self.initialize_plugin()

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.shell)
        self.find_widget.hide()
        self.register_widget_shortcuts("Editor", self.find_widget)

        # Main layout
        layout = QVBoxLayout()
        layout.addWidget(self.shell)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)

        # Parameters
        self.shell.toggle_wrap_mode(self.get_option("wrap"))

        # Accepting drops
        self.setAcceptDrops(True)
Пример #9
0
    def __init__(self, parent):
        QWidget.__init__(self, parent)

        self.webview = FrameWebView(self)
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.webview)
        self.find_widget.hide()

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.webview)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)
Пример #10
0
    def __init__(self, parent, commands=None):
        self.commands = commands
        self.tabwidget = None
        self.menu_actions = None
        self.docviewer = None
        self.historylog = None

        self.shells = []
        self.filenames = []
        self.icons = []

        PluginWidget.__init__(self, parent)

        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
                     self.refresh)
        self.connect(self.tabwidget, SIGNAL("close_tab(int)"),
                     self.tabwidget.removeTab)
        self.connect(self.tabwidget, SIGNAL('move_data(int,int)'),
                     self.move_tab)
        self.close_button = create_toolbutton(
            self.tabwidget,
            icon=get_icon("fileclose.png"),
            triggered=self.close_console,
            tip=self.tr("Close current console"))
        self.tabwidget.setCornerWidget(self.close_button)
        layout.addWidget(self.tabwidget)

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.hide()
        layout.addWidget(self.find_widget)

        self.setLayout(layout)

        # Accepting drops
        self.setAcceptDrops(True)
Пример #11
0
    def __init__(self, parent):
        self.tabwidget = None
        self.menu_actions = None
        self.dockviewer = None

        self.editors = []
        self.filenames = []
        self.icons = []

        PluginWidget.__init__(self, parent)

        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
                     self.refresh)
        self.connect(self.tabwidget, SIGNAL("close_tab(int)"),
                     self.tabwidget.removeTab)
        self.connect(self.tabwidget, SIGNAL('move_data(int,int)'),
                     self.move_tab)
        layout.addWidget(self.tabwidget)

        # Menu as corner widget
        options_button = create_toolbutton(self,
                                           text=self.tr("Options"),
                                           icon=get_icon('tooloptions.png'))
        options_button.setPopupMode(QToolButton.InstantPopup)
        menu = QMenu(self)
        add_actions(menu, self.menu_actions)
        options_button.setMenu(menu)
        self.tabwidget.setCornerWidget(options_button)

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.hide()
        layout.addWidget(self.find_widget)

        self.setLayout(layout)
Пример #12
0
    def __init__(self, parent=None, namespace=None, commands=[], message=None,
                 exitfunc=None, profile=False, multithreaded=False):
        SpyderPluginWidget.__init__(self, parent)
        
        debug_print("    ..internal console: initializing")
        self.dialog_manager = DialogManager()

        # Shell
        light_background = self.get_option('light_background')
        self.shell = InternalShell(parent, namespace, commands, message,
                                   self.get_option('max_line_count'),
                                   self.get_plugin_font(), exitfunc, profile,
                                   multithreaded,
                                   light_background=light_background)
        self.connect(self.shell, SIGNAL('status(QString)'),
                     lambda msg:
                     self.emit(SIGNAL('show_message(QString,int)'), msg, 0))
        self.connect(self.shell, SIGNAL("go_to_error(QString)"),
                     self.go_to_error)
        self.connect(self.shell, SIGNAL("focus_changed()"),
                     lambda: self.emit(SIGNAL("focus_changed()")))
        # Redirecting some SIGNALs:
        self.connect(self.shell, SIGNAL('redirect_stdio(bool)'),
                     lambda state: self.emit(SIGNAL('redirect_stdio(bool)'),
                                             state))
        
        # Initialize plugin
        self.initialize_plugin()
                
        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.shell)
        self.find_widget.hide()
        self.register_widget_shortcuts("Editor", self.find_widget)

        # Main layout
        layout = QVBoxLayout()
        layout.addWidget(self.shell)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)
        
        # Parameters
        self.shell.toggle_wrap_mode(self.get_option('wrap'))
            
        # Accepting drops
        self.setAcceptDrops(True)
Пример #13
0
    def __init__(self, parent, light_mode):
        self.light_mode = light_mode
        self.commands = []
        self.tabwidget = None
        self.menu_actions = None
        self.inspector = None
        self.historylog = None
        self.variableexplorer = None # variable explorer plugin
        
        self.ipython_count = 0
        self.python_count = 0
        self.terminal_count = 0
        
        if CONF.get(self.ID, 'ipython_options', None) is None:
            CONF.set(self.ID, 'ipython_options',
                     self.get_default_ipython_options())
        
        self.shells = []
        self.filenames = []
        self.icons = []
        
        SpyderPluginWidget.__init__(self, parent)
        
        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
                     self.refresh_plugin)
        self.connect(self.tabwidget, SIGNAL('move_data(int,int)'),
                     self.move_tab)
                     
        self.tabwidget.set_close_function(self.close_console)

        layout.addWidget(self.tabwidget)
        
        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.hide()
        layout.addWidget(self.find_widget)
        
        self.setLayout(layout)
            
        # Accepting drops
        self.setAcceptDrops(True)
    def setupUi(self):
        self.tabs = QTabWidget()
        self.tabs.setTabsClosable(True)
        self.tabs.tabCloseRequested.connect(self.close_file)
        self.tabs.currentChanged.connect(self._current_editor_changed)
        self.tabs.setMovable(True)
        self.tabs.setDocumentMode(True)

        self.find_widget = FindReplace(self, enable_replace=True)
        self.find_widget.hide()

        self.content_widget = QWidget()
        layout = QGridLayout(self.content_widget)
        layout.addWidget(self.tabs)
        layout.addWidget(self.find_widget)

        self.setWidget(self.content_widget)

        self.setAcceptDrops(True)
Пример #15
0
    def __init__(self, parent):
        SpyderPluginWidget.__init__(self, parent)

        self.tabwidget = None
        self.menu_actions = None

        self.extconsole = None         # External console plugin
        self.inspector = None          # Object inspector plugin
        self.historylog = None         # History log plugin
        self.variableexplorer = None   # Variable explorer plugin
        
        self.clients = []
        
        # Initialize plugin
        self.initialize_plugin()
        
        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        if hasattr(self.tabwidget, 'setDocumentMode')\
           and not sys.platform == 'darwin':
            # Don't set document mode to true on OSX because it generates
            # a crash when the console is detached from the main window
            # Fixes Issue 561
            self.tabwidget.setDocumentMode(True)
        self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
                     self.refresh_plugin)
        self.connect(self.tabwidget, SIGNAL('move_data(int,int)'),
                     self.move_tab)
                     
        self.tabwidget.set_close_function(self.close_client)

        layout.addWidget(self.tabwidget)

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.hide()
        self.register_widget_shortcuts("Editor", self.find_widget)
        layout.addWidget(self.find_widget)
        
        self.setLayout(layout)
            
        # Accepting drops
        self.setAcceptDrops(True)
Пример #16
0
    def __init__(self, parent):
        self.tabwidget = None
        self.menu_actions = None
        self.dockviewer = None
        self.wrap_action = None
        
        self.editors = []
        self.filenames = []
        self.icons = []
        if PYQT5:        
            SpyderPluginWidget.__init__(self, parent, main = parent)
        else:
            SpyderPluginWidget.__init__(self, parent)

        # Initialize plugin
        self.initialize_plugin()
        
        self.set_default_color_scheme()
        
        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        self.tabwidget.currentChanged.connect(self.refresh_plugin)
        self.tabwidget.move_data.connect(self.move_tab)
        layout.addWidget(self.tabwidget)

        # Menu as corner widget
        options_button = create_toolbutton(self, text=_('Options'),
                                           icon=ima.icon('tooloptions'))
        options_button.setPopupMode(QToolButton.InstantPopup)
        menu = QMenu(self)
        add_actions(menu, self.menu_actions)
        options_button.setMenu(menu)
        self.tabwidget.setCornerWidget(options_button)
        
        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.hide()
        self.register_widget_shortcuts("Editor", self.find_widget)
        
        layout.addWidget(self.find_widget)
        
        self.setLayout(layout)
Пример #17
0
    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self.editor = None

        # Read-only editor
        self.editor = codeeditor.CodeEditor(self)
        self.editor.setup_editor(linenumbers=False, language="py", scrollflagarea=False, edge_line=False)
        self.editor.focus_changed.connect(lambda: self.focus_changed.emit())
        self.editor.setReadOnly(True)

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.editor)
        self.find_widget.hide()

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.editor)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)
Пример #18
0
    def __init__(self, parent=None, namespace=None, commands=None, message="",
                 debug=False, exitfunc=None, profile=False):
        # Shell
        self.shell = InteractiveShell(parent, namespace, commands, message,
                                      get_font(self.ID),
                                      debug, exitfunc, profile)
        self.connect(self.shell, SIGNAL('status(QString)'),
                     lambda msg:
                     self.emit(SIGNAL('show_message(QString,int)'), msg, 0))
        self.connect(self.shell, SIGNAL("go_to_error(QString)"),
                     self.go_to_error)
        self.connect(self.shell, SIGNAL("focus_changed()"),
                     lambda: self.emit(SIGNAL("focus_changed()")))
        # Redirecting some SIGNALs:
        self.connect(self.shell, SIGNAL('redirect_stdio(bool)'),
                     lambda state: self.emit(SIGNAL('redirect_stdio(bool)'),
                                             state))
        
        PluginWidget.__init__(self, parent)
        
        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.shell)
        self.find_widget.hide()

        # Main layout
        layout = QVBoxLayout()
        layout.addWidget(self.shell)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)
        
        # Parameters
        self.shell.toggle_wrap_mode( CONF.get(self.ID, 'wrap') )
        
        self.connect(self, SIGNAL("executing_command(bool)"),
                     self.change_cursor)
            
        # Accepting drops
        self.setAcceptDrops(True)
Пример #19
0
 def __init__(self, parent, commands=None):
     self.commands = commands
     self.tabwidget = None
     self.menu_actions = None
     self.docviewer = None
     self.historylog = None
     
     self.shells = []
     self.filenames = []
     self.icons = []
     
     PluginWidget.__init__(self, parent)
     
     layout = QVBoxLayout()
     self.tabwidget = Tabs(self, self.menu_actions)
     self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
                  self.refresh)
     self.connect(self.tabwidget, SIGNAL("close_tab(int)"),
                  self.tabwidget.removeTab)
     self.connect(self.tabwidget, SIGNAL('move_data(int,int)'),
                  self.move_tab)
     self.close_button = create_toolbutton(self.tabwidget,
                                       icon=get_icon("fileclose.png"),
                                       triggered=self.close_console,
                                       tip=self.tr("Close current console"))
     self.tabwidget.setCornerWidget(self.close_button)
     layout.addWidget(self.tabwidget)
     
     # Find/replace widget
     self.find_widget = FindReplace(self)
     self.find_widget.hide()
     layout.addWidget(self.find_widget)
     
     self.setLayout(layout)
         
     # Accepting drops
     self.setAcceptDrops(True)
Пример #20
0
class IPythonConsole(SpyderPluginWidget):
    """
    IPython Console plugin

    This is a widget with tabs where each one is an IPythonClient
    """
    CONF_SECTION = 'ipython_console'
    CONFIGWIDGET_CLASS = IPythonConsoleConfigPage
    DISABLE_ACTIONS_WHEN_HIDDEN = False

    def __init__(self, parent):
        SpyderPluginWidget.__init__(self, parent)

        self.tabwidget = None
        self.menu_actions = None

        self.extconsole = None         # External console plugin
        self.inspector = None          # Object inspector plugin
        self.historylog = None         # History log plugin
        self.variableexplorer = None   # Variable explorer plugin
        
        self.clients = []
        
        # Initialize plugin
        self.initialize_plugin()
        
        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        if hasattr(self.tabwidget, 'setDocumentMode')\
           and not sys.platform == 'darwin':
            # Don't set document mode to true on OSX because it generates
            # a crash when the console is detached from the main window
            # Fixes Issue 561
            self.tabwidget.setDocumentMode(True)
        self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
                     self.refresh_plugin)
        self.connect(self.tabwidget, SIGNAL('move_data(int,int)'),
                     self.move_tab)
                     
        self.tabwidget.set_close_function(self.close_console)

        layout.addWidget(self.tabwidget)

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.hide()
        self.register_widget_shortcuts("Editor", self.find_widget)
        layout.addWidget(self.find_widget)
        
        self.setLayout(layout)
            
        # Accepting drops
        self.setAcceptDrops(True)
    
    #------ SpyderPluginWidget API --------------------------------------------
    def get_plugin_title(self):
        """Return widget title"""
        return _('IPython console')
    
    def get_plugin_icon(self):
        """Return widget icon"""
        return get_icon('ipython_console.png')
    
    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        client = self.tabwidget.currentWidget()
        if client is not None:
            return client.get_control()

    def get_current_client(self):
        """
        Return the currently selected client
        """
        client = self.tabwidget.currentWidget()
        if client is not None:
            return client

    def run_script_in_current_client(self, filename, wdir, args, debug):
        """Run script in current client, if any"""
        norm = lambda text: remove_backslashes(to_text_string(text))
        client = self.get_current_client()
        if client is not None:
            # Internal kernels, use runfile
            if client.kernel_widget_id is not None:
                line = "%s('%s'" % ('debugfile' if debug else 'runfile',
                                    norm(filename))
                if args:
                    line += ", args='%s'" % norm(args)
                if wdir:
                    line += ", wdir='%s'" % norm(wdir)
                line += ")"
            else: # External kernels, use %run
                line = "%run "
                if debug:
                    line += "-d "
                line += "\"%s\"" % to_text_string(filename)
                if args:
                    line += " %s" % norm(args)
            self.execute_python_code(line)
            self.visibility_changed(True)
            self.raise_()
        else:
            #XXX: not sure it can really happen
            QMessageBox.warning(self, _('Warning'),
                _("No IPython console is currently available to run <b>%s</b>."
                  "<br><br>Please open a new one and try again."
                  ) % osp.basename(filename), QMessageBox.Ok)

    def execute_python_code(self, lines):
        client = self.get_current_client()
        if client is not None:
            client.shellwidget.execute(to_text_string(lines))
            self.activateWindow()
            client.get_control().setFocus()

    def write_to_stdin(self, line):
        client = self.get_current_client()
        if client is not None:
            client.shellwidget.write_to_stdin(line)

    def create_new_client(self):
        """Create a new client"""
        client = IPythonClient(self, history_filename='history.py',
                               menu_actions=self.menu_actions)
        self.add_tab(client, name=client.get_name())
        self.main.extconsole.start_ipykernel(client)

    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        create_client_action = create_action(self,
                                _("Open an &IPython console"),
                                None, 'ipython_console.png',
                                triggered=self.create_new_client)

        connect_to_kernel_action = create_action(self,
               _("Connect to an existing kernel"), None, None,
               _("Open a new IPython console connected to an existing kernel"),
               triggered=self.create_client_for_kernel)
        
        # Add the action to the 'Consoles' menu on the main window
        main_consoles_menu = self.main.consoles_menu_actions
        main_consoles_menu.insert(0, create_client_action)
        main_consoles_menu += [None, connect_to_kernel_action]
        
        # Plugin actions
        self.menu_actions = [create_client_action, connect_to_kernel_action]
        
        return self.menu_actions

    def on_first_registration(self):
        """Action to be performed on first plugin registration"""
        self.main.tabify_plugins(self.main.extconsole, self)

    def register_plugin(self):
        """Register plugin in Spyder's main window"""
        self.main.add_dockwidget(self)

        self.extconsole = self.main.extconsole
        self.inspector = self.main.inspector
        self.historylog = self.main.historylog
        self.variableexplorer = self.main.variableexplorer

        self.connect(self, SIGNAL('focus_changed()'),
                     self.main.plugin_focus_changed)

        if self.main.editor is not None:
            self.connect(self, SIGNAL("edit_goto(QString,int,QString)"),
                         self.main.editor.load)
            self.connect(self.main.editor,
                         SIGNAL('run_in_current_ipyclient(QString,QString,QString,bool)'),
                         self.run_script_in_current_client)
        
    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        for client in self.clients:
            client.close()
        return True
    
    def refresh_plugin(self):
        """Refresh tabwidget"""
        client = None
        if self.tabwidget.count():
            # Give focus to the control widget of the selected tab
            client = self.tabwidget.currentWidget()
            control = client.get_control()
            control.setFocus()
            widgets = client.get_toolbar_buttons()+[5]
            
            # Change extconsole tab to the client's kernel widget
            idx = self.extconsole.get_shell_index_from_id(
                                                       client.kernel_widget_id)
            if idx is not None:
                self.extconsole.tabwidget.setCurrentIndex(idx)
        else:
            control = None
            widgets = []
        self.find_widget.set_editor(control)
        self.tabwidget.set_corner_widgets({Qt.TopRightCorner: widgets})
        self.main.last_console_plugin_focus_was_python = False
        self.emit(SIGNAL('update_plugin_title()'))
    
    def apply_plugin_settings(self, options):
        """Apply configuration file's plugin settings"""
        font_n = 'plugin_font'
        font_o = self.get_plugin_font()
        inspector_n = 'connect_to_oi'
        inspector_o = CONF.get('inspector', 'connect/ipython_console')
        for client in self.clients:
            control = client.get_control()
            if font_n in options:
                client.set_font(font_o)
            if inspector_n in options and control is not None:
                control.set_inspector_enabled(inspector_o)
    
    def kernel_and_frontend_match(self, connection_file):
        # Determine kernel version
        ci = get_connection_info(connection_file, unpack=True,
                                 profile='default')
        if u('control_port') in ci:
            kernel_ver = '>=1.0'
        else:
            kernel_ver = '<1.0'
        # is_module_installed checks if frontend version agrees with the
        # kernel one
        return programs.is_module_installed('IPython', version=kernel_ver)

    def create_kernel_manager_and_client(self, connection_file=None):
        """Create kernel manager and client"""
        cf = find_connection_file(connection_file, profile='default')
        kernel_manager = QtKernelManager(connection_file=cf, config=None)
        if programs.is_module_installed('IPython', '>=1.0'):
            kernel_client = kernel_manager.client()
            kernel_client.load_connection_file()
            kernel_client.start_channels()
            # To rely on kernel's heartbeat to know when a kernel has died
            kernel_client.hb_channel.unpause()
        else:
            kernel_client = None
            kernel_manager.load_connection_file()
            kernel_manager.start_channels()
        return kernel_manager, kernel_client

    def connect_client_to_kernel(self, client):
        """
        Connect a client to its kernel
        """
        connection_file = client.connection_file
        widget = client.shellwidget
        km, kc = self.create_kernel_manager_and_client(connection_file)
        widget.kernel_manager = km
        widget.kernel_client = kc
    
    #------ Private API -------------------------------------------------------
    def _show_rich_help(self, text):
        """Use our Object Inspector to show IPython help texts in rich mode"""
        from spyderlib.utils.inspector import sphinxify as spx
        
        context = spx.generate_context(name='', argspec='', note='',
                                       math=False)
        html_text = spx.sphinxify(text, context)
        inspector = self.inspector
        inspector.visibility_changed(True)
        inspector.raise_()
        inspector.switch_to_rich_text()
        inspector.set_rich_text_html(html_text,
                                     QUrl.fromLocalFile(spx.CSS_PATH))
    
    def _show_plain_help(self, text):
        """Use our Object Inspector to show IPython help texts in plain mode"""
        inspector = self.inspector
        inspector.visibility_changed(True)
        inspector.raise_()
        inspector.switch_to_plain_text()
        inspector.set_plain_text(text, is_code=False)
    
    #------ Public API --------------------------------------------------------
    def get_clients(self):
        """Return IPython client widgets list"""
        return [cl for cl in self.clients if isinstance(cl, IPythonClient)]
        
#    def get_kernels(self):
#        """Return IPython kernel widgets list"""
#        return [sw for sw in self.shellwidgets
#                if isinstance(sw, IPythonKernel)]
#        

    def get_focus_client(self):
        """Return current client with focus, if any"""
        widget = QApplication.focusWidget()
        for client in self.get_clients():
            if widget is client or widget is client.get_control():
                return client

    def create_client_for_kernel(self):
        """Create a client connected to an existing kernel"""
        example = _("(for example: kernel-3764.json, or simply 3764)")
        while True:
            cf, valid = QInputDialog.getText(self, _('IPython'),
                          _('Provide an IPython kernel connection file:')+\
                          '\n'+example,
                          QLineEdit.Normal)
            if valid:
                cf = str(cf)
                match = re.match('(kernel-|^)([a-fA-F0-9-]+)(.json|$)', cf)
                if match is not None:
                    kernel_num = match.groups()[1]
                    if kernel_num:
                        cf = 'kernel-%s.json' % kernel_num
                        break
            else:
                return

        # Generating the client name and setting kernel_widget_id
        match = re.match('^kernel-([a-fA-F0-9-]+).json', cf)
        count = 0
        kernel_widget_id = None
        while True:
            client_name = match.groups()[0]
            if '-' in client_name:  # Avoid long names
                client_name = client_name.split('-')[0]
            client_name = client_name + '/' + chr(65+count)
            for cl in self.get_clients():
                if cl.name == client_name:
                    kernel_widget_id = cl.kernel_widget_id
                    break
            else:
                break
            count += 1
        
        # Trying to get kernel_widget_id from the currently opened kernels if
        # the previous procedure fails. This could happen when the first
        # client connected to a kernel is closed but the kernel is left open
        # and you try to connect new clients to it
        if kernel_widget_id is None:
            for sw in self.extconsole.shellwidgets:
                if sw.connection_file == cf:
                    kernel_widget_id = id(sw)

        # Verifying if the kernel exists
        try:
            find_connection_file(cf, profile='default')
        except (IOError, UnboundLocalError):
            QMessageBox.critical(self, _('IPython'),
                                 _("Unable to connect to IPython <b>%s") % cf)
            return
        
        # Verifying if frontend and kernel have compatible versions
        if not self.kernel_and_frontend_match(cf):
            QMessageBox.critical(self,
                                 _("Mismatch between kernel and frontend"),
                                 _("Your IPython frontend and kernel versions "
                                   "are <b>incompatible!!</b>"
                                   "<br><br>"
                                   "We're sorry but we can't create an IPython "
                                   "console for you."
                                ), QMessageBox.Ok)
            return
        
        # Creating the client
        client = IPythonClient(self, history_filename='history.py',
                               connection_file=cf,
                               kernel_widget_id=kernel_widget_id,
                               menu_actions=self.menu_actions)
        self.add_tab(client, name=client.get_name())
        self.register_client(client, client_name)

    def ipywidget_config(self):
        """Generate a Config instance for IPython widgets using our config
        system
        
        This let us create each widget with its own config (as oppossed to
        IPythonQtConsoleApp, where all widgets have the same config)
        """
        # ---- IPython config ----
        try:
            profile_path = osp.join(get_ipython_dir(), 'profile_default')
            full_ip_cfg = load_pyconfig_files(['ipython_qtconsole_config.py'],
                                              profile_path)
            
            # From the full config we only select the IPythonWidget section
            # because the others have no effect here.
            ip_cfg = Config({'IPythonWidget': full_ip_cfg.IPythonWidget})
        except:
            ip_cfg = Config()
       
        # ---- Spyder config ----
        spy_cfg = Config()
        
        # Make the pager widget a rich one (i.e a QTextEdit)
        spy_cfg.IPythonWidget.kind = 'rich'
        
        # Gui completion widget
        gui_comp_o = self.get_option('use_gui_completion')
        completions = {True: 'droplist', False: 'ncurses'}
        spy_cfg.IPythonWidget.gui_completion = completions[gui_comp_o]

        # Pager
        pager_o = self.get_option('use_pager')
        if pager_o:
            spy_cfg.IPythonWidget.paging = 'inside'
        else:
            spy_cfg.IPythonWidget.paging = 'none'
        
        # Calltips
        calltips_o = self.get_option('show_calltips')
        spy_cfg.IPythonWidget.enable_calltips = calltips_o

        # Buffer size
        buffer_size_o = self.get_option('buffer_size')
        spy_cfg.IPythonWidget.buffer_size = buffer_size_o
        
        # Prompts
        in_prompt_o = self.get_option('in_prompt')
        out_prompt_o = self.get_option('out_prompt')
        if in_prompt_o:
            spy_cfg.IPythonWidget.in_prompt = in_prompt_o
        if out_prompt_o:
            spy_cfg.IPythonWidget.out_prompt = out_prompt_o
        
        # Merge IPython and Spyder configs. Spyder prefs will have prevalence
        # over IPython ones
        ip_cfg._merge(spy_cfg)
        return ip_cfg

    def register_client(self, client, name, restart=False):
        """Register new IPython client"""
        self.connect_client_to_kernel(client)
        client.show_shellwidget()
        client.name = name
        
        # If we are restarting the kernel we just need to rename the client tab
        if restart:
            self.rename_ipyclient_tab(client)
            return
        
        shellwidget = client.shellwidget
        control = shellwidget._control
        page_control = shellwidget._page_control
        
        # For tracebacks
        self.connect(control, SIGNAL("go_to_error(QString)"), self.go_to_error)

        # Handle kernel interrupts
        extconsoles = self.extconsole.shellwidgets
        kernel_widget = None
        if extconsoles:
            if extconsoles[-1].connection_file == client.connection_file:
                kernel_widget = extconsoles[-1]
                shellwidget.custom_interrupt_requested.connect(
                                              kernel_widget.keyboard_interrupt)
        if kernel_widget is None:
            shellwidget.custom_interrupt_requested.connect(
                                                      client.interrupt_message)
        
        # Handle kernel restarts asked by the user
        if kernel_widget is not None:
            shellwidget.custom_restart_requested.connect(
                                 lambda cl=client: self.restart_kernel(client))
        else:
            shellwidget.custom_restart_requested.connect(client.restart_message)
        
        # Print a message if kernel dies unexpectedly
        shellwidget.custom_restart_kernel_died.connect(
                                            lambda t: client.if_kernel_dies(t))
        
        # Connect text widget to our inspector
        if kernel_widget is not None and self.inspector is not None:
            control.set_inspector(self.inspector)
            control.set_inspector_enabled(CONF.get('inspector',
                                                   'connect/ipython_console'))

        # Connect to our variable explorer
        if kernel_widget is not None and self.variableexplorer is not None:
            nsb = self.variableexplorer.currentWidget()
            # When the autorefresh button is active, our kernels
            # start to consume more and more CPU during time
            # Fix Issue 1450
            # ----------------
            # When autorefresh is off by default we need the next
            # line so that kernels don't start to consume CPU
            # Fix Issue 1595
            nsb.auto_refresh_button.setChecked(True)
            nsb.auto_refresh_button.setChecked(False)
            nsb.auto_refresh_button.setEnabled(False)
            nsb.set_ipyclient(client)
            client.set_namespacebrowser(nsb)
        
        # Connect client to our history log
        if self.historylog is not None:
            self.historylog.add_history(client.history_filename)
            self.connect(client, SIGNAL('append_to_history(QString,QString)'),
                         self.historylog.append_to_history)
        
        # Apply settings to newly created client widget:
        client.set_font( self.get_plugin_font() )
        
        # Add tab and connect focus signal to client's control widget
        self.connect(control, SIGNAL('focus_changed()'),
                     lambda: self.emit(SIGNAL('focus_changed()')))
        
        # Update the find widget if focus changes between control and
        # page_control
        self.find_widget.set_editor(control)
        if page_control:
            self.connect(page_control, SIGNAL('focus_changed()'),
                         lambda: self.emit(SIGNAL('focus_changed()')))
            self.connect(control, SIGNAL('visibility_changed(bool)'),
                         self.refresh_plugin)
            self.connect(page_control, SIGNAL('visibility_changed(bool)'),
                         self.refresh_plugin)
            self.connect(page_control, SIGNAL('show_find_widget()'),
                         self.find_widget.show)

        # Update client name
        self.rename_ipyclient_tab(client)
    
    def open_client_at_startup(self):
        if self.get_option('open_ipython_at_startup', False):
            self.create_new_client()
    
    def close_related_ipyclients(self, client):
        """Close all IPython clients related to *client*, except itself"""
        for cl in self.clients[:]:
            if cl is not client and \
              cl.connection_file == client.connection_file:
                self.close_console(client=cl)
    
    def get_shellwidget_by_kernelwidget_id(self, kernel_id):
        """Return the IPython widget associated to a kernel widget id"""
        for cl in self.clients:
            if cl.kernel_widget_id == kernel_id:
                return cl.shellwidget
        else:
            raise ValueError("Unknown kernel widget ID %r" % kernel_id)
        
    def add_tab(self, widget, name):
        """Add tab"""
        self.clients.append(widget)
        index = self.tabwidget.addTab(widget, get_icon('ipython_console.png'),
                                      name)
        self.tabwidget.setCurrentIndex(index)
        if self.dockwidget and not self.ismaximized:
            self.dockwidget.setVisible(True)
            self.dockwidget.raise_()
        self.activateWindow()
        widget.get_control().setFocus()
        
    def move_tab(self, index_from, index_to):
        """
        Move tab (tabs themselves have already been moved by the tabwidget)
        """
        client = self.clients.pop(index_from)
        self.clients.insert(index_to, client)
        self.emit(SIGNAL('update_plugin_title()'))
        
    def close_console(self, index=None, client=None, force=False):
        """Close console tab from index or widget (or close current tab)"""
        if not self.tabwidget.count():
            return
        if client is not None:
            index = self.tabwidget.indexOf(client)
        if index is None and client is None:
            index = self.tabwidget.currentIndex()
        if index is not None:
            client = self.tabwidget.widget(index)

        # Check if related clients or kernels are opened
        # and eventually ask before closing them
        if not force and isinstance(client, IPythonClient):
            idx = self.extconsole.get_shell_index_from_id(
                                                       client.kernel_widget_id)
            if idx is not None:
                close_all = True
                if self.get_option('ask_before_closing'):
                    ans = QMessageBox.question(self, self.get_plugin_title(),
                           _("%s will be closed.\n"
                             "Do you want to kill the associated kernel "
                             "and all of its clients?") % client.get_name(),
                           QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel)
                    if ans == QMessageBox.Cancel:
                        return
                    close_all = ans == QMessageBox.Yes
                if close_all:
                    self.extconsole.close_console(index=idx,
                                                  from_ipyclient=True)
                    self.close_related_ipyclients(client)
        client.close()
        
        # Note: client index may have changed after closing related widgets
        self.tabwidget.removeTab(self.tabwidget.indexOf(client))
        self.clients.remove(client)

        self.emit(SIGNAL('update_plugin_title()'))
        
    def go_to_error(self, text):
        """Go to error if relevant"""
        match = get_error_match(to_text_string(text))
        if match:
            fname, lnb = match.groups()
            self.emit(SIGNAL("edit_goto(QString,int,QString)"),
                      osp.abspath(fname), int(lnb), '')
    
    def show_intro(self):
        """Show intro to IPython help"""
        from IPython.core.usage import interactive_usage
        self._show_rich_help(interactive_usage)
    
    def show_guiref(self):
        """Show qtconsole help"""
        from IPython.core.usage import gui_reference
        self._show_rich_help(gui_reference)
    
    def show_quickref(self):
        """Show IPython Cheat Sheet"""
        from IPython.core.usage import quick_reference
        self._show_plain_help(quick_reference)
    
    def get_client_index_from_id(self, client_id):
        """Return client index from id"""
        for index, client in enumerate(self.clients):
            if id(client) == client_id:
                return index
    
    def rename_ipyclient_tab(self, client):
        """Add the pid of the kernel process to an IPython client tab"""
        index = self.get_client_index_from_id(id(client))
        self.tabwidget.setTabText(index, client.get_name())
    
    def restart_kernel(self, client):
        """
        Create a new kernel and connect it to `client` if the user asks for it
        """
        # Took this bit of code (until if result == ) from the IPython project
        # (qt/frontend_widget.py - restart_kernel).
        # Licensed under the BSD license
        message = _('Are you sure you want to restart the kernel?')
        buttons = QMessageBox.Yes | QMessageBox.No
        result = QMessageBox.question(self, _('Restart kernel?'),
                                      message, buttons)
        if result == QMessageBox.Yes:
            client.show_restart_animation()
            
            # Close old kernel tab
            idx = self.extconsole.get_shell_index_from_id(client.kernel_widget_id)
            self.extconsole.close_console(index=idx, from_ipyclient=True)
            
            # Create a new one and connect it to the client
            self.main.extconsole.start_ipykernel(client)
Пример #21
0
class Console(PluginWidget):
    """
    Console widget
    """
    ID = 'shell'
    location = Qt.RightDockWidgetArea

    def __init__(self,
                 parent=None,
                 namespace=None,
                 commands=None,
                 message="",
                 debug=False,
                 exitfunc=None,
                 profile=False):
        # Shell
        self.shell = InteractiveShell(parent, namespace, commands, message,
                                      get_font(self.ID), debug, exitfunc,
                                      profile)
        self.connect(
            self.shell, SIGNAL('status(QString)'),
            lambda msg: self.emit(SIGNAL('show_message(QString,int)'), msg, 0))
        self.connect(self.shell, SIGNAL("go_to_error(QString)"),
                     self.go_to_error)
        self.connect(self.shell, SIGNAL("focus_changed()"),
                     lambda: self.emit(SIGNAL("focus_changed()")))
        # Redirecting some SIGNALs:
        self.connect(
            self.shell, SIGNAL('redirect_stdio(bool)'),
            lambda state: self.emit(SIGNAL('redirect_stdio(bool)'), state))

        PluginWidget.__init__(self, parent)

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.shell)
        self.find_widget.hide()

        # Main layout
        layout = QVBoxLayout()
        layout.addWidget(self.shell)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)

        # Parameters
        self.shell.toggle_wrap_mode(CONF.get(self.ID, 'wrap'))

        self.connect(self, SIGNAL("executing_command(bool)"),
                     self.change_cursor)

        # Accepting drops
        self.setAcceptDrops(True)

    def set_historylog(self, historylog):
        """Bind historylog instance to this console"""
        historylog.add_history(self.shell.history_filename)
        self.connect(self.shell, SIGNAL('append_to_history(QString,QString)'),
                     historylog.append_to_history)

    def set_docviewer(self, docviewer):
        """Bind docviewer instance to this console"""
        self.shell.docviewer = docviewer

    def change_cursor(self, state):
        """Change widget cursor"""
        if state:
            QApplication.setOverrideCursor(QCursor(Qt.BusyCursor))
        else:
            QApplication.restoreOverrideCursor()

    def get_widget_title(self):
        """Return widget title"""
        return self.tr('Interactive console')

    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.shell

    def closing(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        return True

    def quit(self):
        """Quit mainwindow"""
        self.main.close()

    def refresh(self):
        pass

    def set_actions(self):
        """Setup actions"""
        quit_action = create_action(self,
                                    self.tr("&Quit"),
                                    self.tr("Ctrl+Q"),
                                    'exit.png',
                                    self.tr("Quit"),
                                    triggered=self.quit)
        run_action = create_action(self,
                                   self.tr("&Run..."),
                                   None,
                                   'run_small.png',
                                   self.tr("Run a Python script"),
                                   triggered=self.run_script)
        environ_action = create_action(
            self,
            self.tr("Environment variables..."),
            icon='environ.png',
            tip=self.tr("Show and edit environment variables"
                        " (for current session)"),
            triggered=self.show_env)
        syspath_action = create_action(
            self,
            self.tr("Show sys.path contents..."),
            icon='syspath.png',
            tip=self.tr("Show (read-only) sys.path"),
            triggered=show_syspath)
        font_action = create_action(self,
                                    self.tr("&Font..."),
                                    None,
                                    'font.png',
                                    self.tr("Set shell font style"),
                                    triggered=self.change_font)
        exteditor_action = create_action(
            self,
            self.tr("External editor path..."),
            None,
            None,
            self.tr("Set external editor executable path"),
            triggered=self.change_exteditor)
        wrap_action = create_action(self,
                                    self.tr("Wrap lines"),
                                    toggled=self.toggle_wrap_mode)
        wrap_action.setChecked(CONF.get(self.ID, 'wrap'))
        calltips_action = create_action(self,
                                        self.tr("Balloon tips"),
                                        toggled=self.toggle_calltips)
        calltips_action.setChecked(CONF.get(self.ID, 'calltips'))
        codecompletion_action = create_action(
            self,
            self.tr("Code completion"),
            toggled=self.toggle_codecompletion)
        codecompletion_action.setChecked(
            CONF.get(self.ID, 'autocompletion/enabled'))
        codecompenter_action = create_action(
            self,
            self.tr("Enter key selects completion"),
            toggled=self.toggle_codecompletion_enter)
        codecompenter_action.setChecked(
            CONF.get(self.ID, 'autocompletion/enter-key'))
        rollbackimporter_action = create_action(
            self,
            self.tr("Force modules to be completely reloaded"),
            tip=self.tr("Force Python to reload modules imported when "
                        "executing a script in the interactive console"),
            toggled=self.toggle_rollbackimporter)
        rollbackimporter_action.setChecked(
            CONF.get(self.ID, 'rollback_importer'))
        try:
            imp.find_module('matplotlib')
            dockablefigures_action = create_action(
                self,
                self.tr("Dockable figures"),
                tip=self.tr("If enabled, matplotlib figures may "
                            "be docked to Spyder's main window "
                            "(will apply only for new figures)"),
                toggled=self.toggle_dockablefigures_mode)
            dockablefigures_action.setChecked(CONF.get('figure', 'dockable'))
        except ImportError:
            dockablefigures_action = None

        option_menu = QMenu(self.tr("Interactive console settings"), self)
        option_menu.setIcon(get_icon('tooloptions.png'))
        add_actions(
            option_menu,
            (font_action, wrap_action, calltips_action, codecompletion_action,
             codecompenter_action, rollbackimporter_action, exteditor_action,
             dockablefigures_action))

        menu_actions = [
            None, run_action, environ_action, syspath_action, option_menu,
            None, quit_action
        ]
        toolbar_actions = []

        # Add actions to context menu
        add_actions(self.shell.menu, menu_actions)

        return menu_actions, toolbar_actions

    def show_env(self):
        """Show environment variables"""
        dlg = EnvDialog()
        dlg.exec_()

    def run_script(self, filename=None, silent=False, set_focus=False):
        """Run a Python script"""
        if filename is None:
            self.shell.restore_stds()
            filename = QFileDialog.getOpenFileName(
                self, self.tr("Run Python script"), os.getcwdu(),
                self.tr("Python scripts") + " (*.py ; *.pyw)")
            self.shell.redirect_stds()
            if filename:
                filename = unicode(filename)
                os.chdir(os.path.dirname(filename))
                filename = os.path.basename(filename)
                self.emit(SIGNAL("refresh()"))
            else:
                return
        command = "execfile(%s)" % repr(osp.abspath(filename))
        if set_focus:
            self.shell.setFocus()
        if self.dockwidget and not self.ismaximized:
            self.dockwidget.setVisible(True)
            self.dockwidget.raise_()
        if silent:
            self.shell.write(command + '\n')
            self.shell.run_command(command)
        else:
            self.shell.write(command)

    def go_to_error(self, text):
        """Go to error if relevant"""
        match = get_error_match(unicode(text))
        if match:
            fname, lnb = match.groups()
            self.edit_script(fname, int(lnb))

    def edit_script(self, filename=None, goto=-1):
        """Edit script"""
        # Called from InteractiveShell
        if not hasattr(self, 'main') \
           or not hasattr(self.main, 'editor'):
            self.shell.external_editor(filename, goto)
            return
        if filename is not None:
            self.emit(SIGNAL("edit_goto(QString,int)"), osp.abspath(filename),
                      goto)

    def change_font(self):
        """Change console font"""
        font, valid = QFontDialog.getFont(get_font(self.ID), self,
                                          self.tr("Select a new font"))
        if valid:
            self.shell.set_font(font)
            set_font(font, self.ID)

    def change_exteditor(self):
        """Change external editor path"""
        path, valid = QInputDialog.getText(
            self, self.tr('External editor'),
            self.tr('External editor executable path:'), QLineEdit.Normal,
            CONF.get(self.ID, 'external_editor/path'))
        if valid:
            CONF.set(self.ID, 'external_editor/path', unicode(path))

    def toggle_dockablefigures_mode(self, checked):
        """Toggle dockable figures mode"""
        CONF.set('figure', 'dockable', checked)

    def toggle_wrap_mode(self, checked):
        """Toggle wrap mode"""
        self.shell.toggle_wrap_mode(checked)
        CONF.set(self.ID, 'wrap', checked)

    def toggle_calltips(self, checked):
        """Toggle calltips"""
        self.shell.set_calltips(checked)
        CONF.set(self.ID, 'calltips', checked)

    def toggle_codecompletion(self, checked):
        """Toggle code completion"""
        self.shell.set_codecompletion(checked)
        CONF.set(self.ID, 'autocompletion/enabled', checked)

    def toggle_codecompletion_enter(self, checked):
        """Toggle Enter key for code completion"""
        self.shell.set_codecompletion_enter(checked)
        CONF.set(self.ID, 'autocompletion/enter-key', checked)

    def toggle_rollbackimporter(self, checked):
        """Toggle rollback importer"""
        CONF.set(self.ID, 'rollback_importer', checked)
        if checked and self.isVisible():
            QMessageBox.warning(
                self, self.get_widget_title(),
                self.tr("The rollback importer requires a restart "
                        "of Spyder to be fully functionnal "
                        "(otherwise only newly imported modules "
                        "will be reloaded when executing scripts)."),
                QMessageBox.Ok)

    #----Drag and drop
    def dragEnterEvent(self, event):
        """Reimplement Qt method
        Inform Qt about the types of data that the widget accepts"""
        source = event.mimeData()
        if source.hasUrls() or source.hasText():
            event.acceptProposedAction()

    def dropEvent(self, event):
        """Reimplement Qt method
        Unpack dropped data and handle it"""
        source = event.mimeData()
        if source.hasUrls():
            files = mimedata2url(source)
            if files:
                files = ["r'%s'" % path for path in files]
                if len(files) == 1:
                    text = files[0]
                else:
                    text = "[" + ", ".join(files) + "]"
                self.shell.insert_text(text)
        elif source.hasText():
            lines = unicode(source.text())
            self.shell.set_cursor_position('eof')
            self.shell.execute_lines(lines)
        event.acceptProposedAction()
Пример #22
0
class IPythonConsole(SpyderPluginWidget):
    """
    IPython Console plugin

    This is a widget with tabs where each one is an IPythonClient
    """
    CONF_SECTION = 'ipython_console'
    CONFIGWIDGET_CLASS = IPythonConsoleConfigPage
    DISABLE_ACTIONS_WHEN_HIDDEN = False

    def __init__(self, parent):
        SpyderPluginWidget.__init__(self, parent)

        self.tabwidget = None
        self.menu_actions = None

        self.extconsole = None         # External console plugin
        self.inspector = None          # Object inspector plugin
        self.historylog = None         # History log plugin
        self.variableexplorer = None   # Variable explorer plugin
        
        self.clients = []
        
        # Initialize plugin
        self.initialize_plugin()
        
        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        if hasattr(self.tabwidget, 'setDocumentMode')\
           and not sys.platform == 'darwin':
            # Don't set document mode to true on OSX because it generates
            # a crash when the console is detached from the main window
            # Fixes Issue 561
            self.tabwidget.setDocumentMode(True)
        self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
                     self.refresh_plugin)
        self.connect(self.tabwidget, SIGNAL('move_data(int,int)'),
                     self.move_tab)
                     
        self.tabwidget.set_close_function(self.close_client)

        layout.addWidget(self.tabwidget)

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.hide()
        self.register_widget_shortcuts("Editor", self.find_widget)
        layout.addWidget(self.find_widget)
        
        self.setLayout(layout)
            
        # Accepting drops
        self.setAcceptDrops(True)
    
    #------ SpyderPluginMixin API ---------------------------------------------
    def on_first_registration(self):
        """Action to be performed on first plugin registration"""
        self.main.tabify_plugins(self.main.extconsole, self)

    def apply_plugin_settings(self, options):
        """Apply configuration file's plugin settings"""
        font_n = 'plugin_font'
        font_o = self.get_plugin_font()
        inspector_n = 'connect_to_oi'
        inspector_o = CONF.get('inspector', 'connect/ipython_console')
        for client in self.clients:
            control = client.get_control()
            if font_n in options:
                client.set_font(font_o)
            if inspector_n in options and control is not None:
                control.set_inspector_enabled(inspector_o)

    def toggle_view(self, checked):
        """Toggle view"""
        if checked:
            self.dockwidget.show()
            self.dockwidget.raise_()
            # Start a client in case there are none shown
            if not self.clients:
                if self.main.is_setting_up:
                    self.create_new_client(give_focus=False)
                else:
                    self.create_new_client(give_focus=True)
        else:
            self.dockwidget.hide()
    
    #------ SpyderPluginWidget API --------------------------------------------
    def get_plugin_title(self):
        """Return widget title"""
        return _('IPython console')
    
    def get_plugin_icon(self):
        """Return widget icon"""
        return get_icon('ipython_console.png')
    
    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        client = self.tabwidget.currentWidget()
        if client is not None:
            return client.get_control()

    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        for client in self.clients:
            client.close()
        return True
    
    def refresh_plugin(self):
        """Refresh tabwidget"""
        client = None
        if self.tabwidget.count():
            # Give focus to the control widget of the selected tab
            client = self.tabwidget.currentWidget()
            control = client.get_control()
            control.setFocus()
            widgets = client.get_toolbar_buttons()+[5]
            
            # Change extconsole tab to the client's kernel widget
            idx = self.extconsole.get_shell_index_from_id(
                                                       client.kernel_widget_id)
            if idx is not None:
                self.extconsole.tabwidget.setCurrentIndex(idx)
        else:
            control = None
            widgets = []
        self.find_widget.set_editor(control)
        self.tabwidget.set_corner_widgets({Qt.TopRightCorner: widgets})
        self.main.last_console_plugin_focus_was_python = False
        self.emit(SIGNAL('update_plugin_title()'))

    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        create_client_action = create_action(self,
                                _("Open an &IPython console"),
                                None, 'ipython_console.png',
                                triggered=self.create_new_client)

        connect_to_kernel_action = create_action(self,
               _("Connect to an existing kernel"), None, None,
               _("Open a new IPython console connected to an existing kernel"),
               triggered=self.create_client_for_kernel)
        
        # Add the action to the 'Consoles' menu on the main window
        main_consoles_menu = self.main.consoles_menu_actions
        main_consoles_menu.insert(0, create_client_action)
        main_consoles_menu += [None, connect_to_kernel_action]
        
        # Plugin actions
        self.menu_actions = [create_client_action, connect_to_kernel_action]
        
        return self.menu_actions

    def register_plugin(self):
        """Register plugin in Spyder's main window"""
        self.main.add_dockwidget(self)

        self.extconsole = self.main.extconsole
        self.inspector = self.main.inspector
        self.historylog = self.main.historylog
        self.variableexplorer = self.main.variableexplorer

        self.connect(self, SIGNAL('focus_changed()'),
                     self.main.plugin_focus_changed)

        if self.main.editor is not None:
            self.connect(self, SIGNAL("edit_goto(QString,int,QString)"),
                         self.main.editor.load)
            self.connect(self.main.editor,
                         SIGNAL('run_in_current_ipyclient(QString,QString,QString,bool)'),
                         self.run_script_in_current_client)

    #------ Public API (for clients) ------------------------------------------
    def get_clients(self):
        """Return clients list"""
        return [cl for cl in self.clients if isinstance(cl, IPythonClient)]
        
#    def get_kernels(self):
#        """Return IPython kernel widgets list"""
#        return [sw for sw in self.shellwidgets
#                if isinstance(sw, IPythonKernel)]
#        

    def get_focus_client(self):
        """Return current client with focus, if any"""
        widget = QApplication.focusWidget()
        for client in self.get_clients():
            if widget is client or widget is client.get_control():
                return client
    
    def get_current_client(self):
        """Return the currently selected client"""
        client = self.tabwidget.currentWidget()
        if client is not None:
            return client

    def run_script_in_current_client(self, filename, wdir, args, debug):
        """Run script in current client, if any"""
        norm = lambda text: remove_backslashes(to_text_string(text))
        client = self.get_current_client()
        if client is not None:
            # Internal kernels, use runfile
            if client.kernel_widget_id is not None:
                line = "%s('%s'" % ('debugfile' if debug else 'runfile',
                                    norm(filename))
                if args:
                    line += ", args='%s'" % norm(args)
                if wdir:
                    line += ", wdir='%s'" % norm(wdir)
                line += ")"
            else: # External kernels, use %run
                line = "%run "
                if debug:
                    line += "-d "
                line += "\"%s\"" % to_text_string(filename)
                if args:
                    line += " %s" % norm(args)
            self.execute_python_code(line)
            self.visibility_changed(True)
            self.raise_()
        else:
            #XXX: not sure it can really happen
            QMessageBox.warning(self, _('Warning'),
                _("No IPython console is currently available to run <b>%s</b>."
                  "<br><br>Please open a new one and try again."
                  ) % osp.basename(filename), QMessageBox.Ok)

    def execute_python_code(self, lines):
        client = self.get_current_client()
        if client is not None:
            client.shellwidget.execute(to_text_string(lines))
            self.activateWindow()
            client.get_control().setFocus()

    def write_to_stdin(self, line):
        client = self.get_current_client()
        if client is not None:
            client.shellwidget.write_to_stdin(line)

    def create_new_client(self, give_focus=True):
        """Create a new client"""
        client = IPythonClient(self, history_filename='history.py',
                               menu_actions=self.menu_actions)
        self.add_tab(client, name=client.get_name())
        self.main.extconsole.start_ipykernel(client, give_focus=give_focus)

    def register_client(self, client, name, restart=False, give_focus=True):
        """Register new client"""
        self.connect_client_to_kernel(client)
        client.show_shellwidget(give_focus=give_focus)
        client.name = name
        
        # Local vars
        shellwidget = client.shellwidget
        control = shellwidget._control
        page_control = shellwidget._page_control

        # Handle kernel interrupts
        extconsoles = self.extconsole.shellwidgets
        kernel_widget = None
        if extconsoles:
            if extconsoles[-1].connection_file == client.connection_file:
                kernel_widget = extconsoles[-1]
                if restart:
                    shellwidget.custom_interrupt_requested.disconnect()
                shellwidget.custom_interrupt_requested.connect(
                                              kernel_widget.keyboard_interrupt)
        if kernel_widget is None:
            shellwidget.custom_interrupt_requested.connect(
                                                      client.interrupt_message)
        
        # If we are restarting the kernel we need to rename
        # the client tab and do no more from here on
        if restart:
            self.rename_client_tab(client)
            return
        
        # For tracebacks
        self.connect(control, SIGNAL("go_to_error(QString)"), self.go_to_error)
        
        # Handle kernel restarts asked by the user
        if kernel_widget is not None:
            shellwidget.custom_restart_requested.connect(
                                 lambda cl=client: self.restart_kernel(client))
        else:
            shellwidget.custom_restart_requested.connect(client.restart_message)
        
        # Print a message if kernel dies unexpectedly
        shellwidget.custom_restart_kernel_died.connect(
                                            lambda t: client.if_kernel_dies(t))
        
        # Connect text widget to our inspector
        if kernel_widget is not None and self.inspector is not None:
            control.set_inspector(self.inspector)
            control.set_inspector_enabled(CONF.get('inspector',
                                                   'connect/ipython_console'))

        # Connect to our variable explorer
        if kernel_widget is not None and self.variableexplorer is not None:
            nsb = self.variableexplorer.currentWidget()
            # When the autorefresh button is active, our kernels
            # start to consume more and more CPU during time
            # Fix Issue 1450
            # ----------------
            # When autorefresh is off by default we need the next
            # line so that kernels don't start to consume CPU
            # Fix Issue 1595
            nsb.auto_refresh_button.setChecked(True)
            nsb.auto_refresh_button.setChecked(False)
            nsb.auto_refresh_button.setEnabled(False)
            nsb.set_ipyclient(client)
            client.set_namespacebrowser(nsb)
        
        # Connect client to our history log
        if self.historylog is not None:
            self.historylog.add_history(client.history_filename)
            self.connect(client, SIGNAL('append_to_history(QString,QString)'),
                         self.historylog.append_to_history)
        
        # Set font for client
        client.set_font( self.get_plugin_font() )
        
        # Connect focus signal to client's control widget
        self.connect(control, SIGNAL('focus_changed()'),
                     lambda: self.emit(SIGNAL('focus_changed()')))
        
        # Update the find widget if focus changes between control and
        # page_control
        self.find_widget.set_editor(control)
        if page_control:
            self.connect(page_control, SIGNAL('focus_changed()'),
                         lambda: self.emit(SIGNAL('focus_changed()')))
            self.connect(control, SIGNAL('visibility_changed(bool)'),
                         self.refresh_plugin)
            self.connect(page_control, SIGNAL('visibility_changed(bool)'),
                         self.refresh_plugin)
            self.connect(page_control, SIGNAL('show_find_widget()'),
                         self.find_widget.show)

        # Update client name
        self.rename_client_tab(client)

    def close_client(self, index=None, client=None, force=False):
        """Close client tab from index or widget (or close current tab)"""
        if not self.tabwidget.count():
            return
        if client is not None:
            index = self.tabwidget.indexOf(client)
        if index is None and client is None:
            index = self.tabwidget.currentIndex()
        if index is not None:
            client = self.tabwidget.widget(index)

        # Check if related clients or kernels are opened
        # and eventually ask before closing them
        if not force and isinstance(client, IPythonClient):
            idx = self.extconsole.get_shell_index_from_id(
                                                       client.kernel_widget_id)
            if idx is not None:
                close_all = True
                if self.get_option('ask_before_closing'):
                    ans = QMessageBox.question(self, self.get_plugin_title(),
                           _("%s will be closed.\n"
                             "Do you want to kill the associated kernel "
                             "and all of its clients?") % client.get_name(),
                           QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel)
                    if ans == QMessageBox.Cancel:
                        return
                    close_all = ans == QMessageBox.Yes
                if close_all:
                    self.extconsole.close_console(index=idx,
                                                  from_ipyclient=True)
                    self.close_related_clients(client)
        client.close()
        
        # Note: client index may have changed after closing related widgets
        self.tabwidget.removeTab(self.tabwidget.indexOf(client))
        self.clients.remove(client)
        self.emit(SIGNAL('update_plugin_title()'))

    def get_client_index_from_id(self, client_id):
        """Return client index from id"""
        for index, client in enumerate(self.clients):
            if id(client) == client_id:
                return index
    
    def rename_client_tab(self, client):
        """Add the pid of the kernel process to client tab"""
        index = self.get_client_index_from_id(id(client))
        self.tabwidget.setTabText(index, client.get_name())

    def close_related_clients(self, client):
        """Close all clients related to *client*, except itself"""
        for cl in self.clients[:]:
            if cl is not client and \
              cl.connection_file == client.connection_file:
                self.close_client(client=cl)

    #------ Public API (for kernels) ------------------------------------------
    def kernel_and_frontend_match(self, connection_file):
        # Determine kernel version
        ci = get_connection_info(connection_file, unpack=True,
                                 profile='default')
        if u('control_port') in ci:
            kernel_ver = '>=1.0'
        else:
            kernel_ver = '<1.0'
        # is_module_installed checks if frontend version agrees with the
        # kernel one
        return programs.is_module_installed('IPython', version=kernel_ver)

    def create_kernel_manager_and_client(self, connection_file=None,
                                         hostname=None, sshkey=None,
                                         password=None):
        """Create kernel manager and client"""
        cf = find_connection_file(connection_file, profile='default')
        kernel_manager = QtKernelManager(connection_file=cf, config=None)
        if programs.is_module_installed('IPython', '>=1.0'):
            kernel_client = kernel_manager.client()
            kernel_client.load_connection_file()
            if hostname is not None:
                try:
                    newports = tunnel_to_kernel(dict(ip=kernel_client.ip,
                                          shell_port=kernel_client.shell_port,
                                          iopub_port=kernel_client.iopub_port,
                                          stdin_port=kernel_client.stdin_port,
                                          hb_port=kernel_client.hb_port),
                                          hostname, sshkey, password)
                    (kernel_client.shell_port, kernel_client.iopub_port,
                     kernel_client.stdin_port, kernel_client.hb_port) = newports
                except Exception as e:
                    QMessageBox.critical(self, _('Connection error'), 
                                     _('Could not open ssh tunnel\n') + str(e))
                    return None, None
            kernel_client.start_channels()
            # To rely on kernel's heartbeat to know when a kernel has died
            kernel_client.hb_channel.unpause()
            return kernel_manager, kernel_client
        else:
            kernel_manager.load_connection_file()
            if hostname is not None:
                try:
                    newports = tunnel_to_kernel(dict(ip=kernel_manager.ip,
                                          shell_port=kernel_manager.shell_port,
                                          iopub_port=kernel_manager.iopub_port,
                                          stdin_port=kernel_manager.stdin_port,
                                          hb_port=kernel_manager.hb_port),
                                          hostname, sshkey, password)
                    (kernel_manager.shell_port, kernel_manager.iopub_port,
                     kernel_manager.stdin_port, kernel_manager.hb_port) = newports
                except Exception as e:
                    QMessageBox.critical(self, _('Connection error'), 
                                     _('Could not open ssh tunnel\n') + str(e))
                    return None, None
            kernel_manager.start_channels()
            return kernel_manager, None

    def connect_client_to_kernel(self, client):
        """
        Connect a client to its kernel
        """
        km, kc = self.create_kernel_manager_and_client(client.connection_file, 
                                                       client.hostname,
                                                       client.sshkey,
                                                       client.password)
        if km is not None:
            widget = client.shellwidget
            widget.kernel_manager = km
            widget.kernel_client = kc

    def create_client_for_kernel(self):
        """Create a client connected to an existing kernel"""
        (cf, hostname,
         kf, pw, ok) = KernelConnectionDialog.get_connection_parameters(self)
        if not ok:
            return
        else:
            self._create_client_for_kernel(cf, hostname, kf, pw)

    def _create_client_for_kernel(self, cf, hostname, kf, pw):
        # Verifying if the connection file exists - in the case of an empty
        # file name, the last used connection file is returned. 
        try:
            cf = find_connection_file(cf, profile='default')
        except (IOError, UnboundLocalError):
            QMessageBox.critical(self, _('IPython'),
                                 _("Unable to connect to IPython <b>%s") % cf)
            return
        
        # Base client name: 
        # remove path and extension, and use the last part when split by '-'
        base_client_name = osp.splitext(cf.split('/')[-1])[0].split('-')[-1]
        
        # Generating the client name by appending /A, /B... until it is unique
        count = 0
        while True:
            client_name = base_client_name + '/' + chr(65 + count)
            for cl in self.get_clients():
                if cl.name == client_name: 
                    break
            else:
                break
            count += 1
        
        # Getting kernel_widget_id from the currently open kernels.
        kernel_widget_id = None
        for sw in self.extconsole.shellwidgets:
            if sw.connection_file == cf.split('/')[-1]:  
                kernel_widget_id = id(sw)                 
        
        # Verifying if frontend and kernel have compatible versions
        if not self.kernel_and_frontend_match(cf):
            QMessageBox.critical(self,
                                 _("Mismatch between kernel and frontend"),
                                 _("Your IPython frontend and kernel versions "
                                   "are <b>incompatible!!</b>"
                                   "<br><br>"
                                   "We're sorry but we can't create an IPython "
                                   "console for you."
                                ), QMessageBox.Ok)
            return
        
        # Creating the client
        client = IPythonClient(self, history_filename='history.py',
                               connection_file=cf,
                               kernel_widget_id=kernel_widget_id,
                               menu_actions=self.menu_actions,
                               hostname=hostname, sshkey=kf, password=pw)
        
        # Adding the tab
        self.add_tab(client, name=client.get_name())
        
        # Connecting kernel and client
        self.register_client(client, client_name)

    def restart_kernel(self, client):
        """
        Create a new kernel and connect it to `client` if the user asks for it
        """
        # Took this bit of code (until if result == ) from the IPython project
        # (qt/frontend_widget.py - restart_kernel).
        # Licensed under the BSD license
        message = _('Are you sure you want to restart the kernel?')
        buttons = QMessageBox.Yes | QMessageBox.No
        result = QMessageBox.question(self, _('Restart kernel?'),
                                      message, buttons)
        if result == QMessageBox.Yes:
            client.show_restart_animation()
            # Close old kernel tab
            idx = self.extconsole.get_shell_index_from_id(client.kernel_widget_id)
            self.extconsole.close_console(index=idx, from_ipyclient=True)
            
            # Create a new one and connect it to the client
            self.extconsole.start_ipykernel(client)
    
    def get_shellwidget_by_kernelwidget_id(self, kernel_id):
        """Return the IPython widget associated to a kernel widget id"""
        for cl in self.clients:
            if cl.kernel_widget_id == kernel_id:
                return cl.shellwidget
        else:
            raise ValueError("Unknown kernel widget ID %r" % kernel_id)

    #------ Public API (for tabs) ---------------------------------------------
    def add_tab(self, widget, name):
        """Add tab"""
        self.clients.append(widget)
        index = self.tabwidget.addTab(widget, get_icon('ipython_console.png'),
                                      name)
        self.tabwidget.setCurrentIndex(index)
        if self.dockwidget and not self.ismaximized:
            self.dockwidget.setVisible(True)
            self.dockwidget.raise_()
        self.activateWindow()
        widget.get_control().setFocus()
        
    def move_tab(self, index_from, index_to):
        """
        Move tab (tabs themselves have already been moved by the tabwidget)
        """
        client = self.clients.pop(index_from)
        self.clients.insert(index_to, client)
        self.emit(SIGNAL('update_plugin_title()'))

    #------ Public API (for help) ---------------------------------------------
    def go_to_error(self, text):
        """Go to error if relevant"""
        match = get_error_match(to_text_string(text))
        if match:
            fname, lnb = match.groups()
            self.emit(SIGNAL("edit_goto(QString,int,QString)"),
                      osp.abspath(fname), int(lnb), '')
    
    def show_intro(self):
        """Show intro to IPython help"""
        from IPython.core.usage import interactive_usage
        self.inspector.show_rich_text(interactive_usage)
    
    def show_guiref(self):
        """Show qtconsole help"""
        from IPython.core.usage import gui_reference
        self.inspector.show_rich_text(gui_reference, collapse=True)
    
    def show_quickref(self):
        """Show IPython Cheat Sheet"""
        from IPython.core.usage import quick_reference
        self.inspector.show_plain_text(quick_reference)
Пример #23
0
class Console(PluginWidget):
    """
    Console widget
    """
    ID = 'shell'
    location = Qt.RightDockWidgetArea
    def __init__(self, parent=None, namespace=None, commands=None, message="",
                 debug=False, exitfunc=None, profile=False):
        # Shell
        self.shell = InteractiveShell(parent, namespace, commands, message,
                                      get_font(self.ID),
                                      debug, exitfunc, profile)
        self.connect(self.shell, SIGNAL('status(QString)'),
                     lambda msg:
                     self.emit(SIGNAL('show_message(QString,int)'), msg, 0))
        self.connect(self.shell, SIGNAL("go_to_error(QString)"),
                     self.go_to_error)
        self.connect(self.shell, SIGNAL("focus_changed()"),
                     lambda: self.emit(SIGNAL("focus_changed()")))
        # Redirecting some SIGNALs:
        self.connect(self.shell, SIGNAL('redirect_stdio(bool)'),
                     lambda state: self.emit(SIGNAL('redirect_stdio(bool)'),
                                             state))
        
        PluginWidget.__init__(self, parent)
        
        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.shell)
        self.find_widget.hide()

        # Main layout
        layout = QVBoxLayout()
        layout.addWidget(self.shell)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)
        
        # Parameters
        self.shell.toggle_wrap_mode( CONF.get(self.ID, 'wrap') )
        
        self.connect(self, SIGNAL("executing_command(bool)"),
                     self.change_cursor)
            
        # Accepting drops
        self.setAcceptDrops(True)
        
    def set_historylog(self, historylog):
        """Bind historylog instance to this console"""
        historylog.add_history(self.shell.history_filename)
        self.connect(self.shell, SIGNAL('append_to_history(QString,QString)'),
                     historylog.append_to_history)
        
    def set_docviewer(self, docviewer):
        """Bind docviewer instance to this console"""
        self.shell.docviewer = docviewer
        
    def change_cursor(self, state):
        """Change widget cursor"""
        if state:
            QApplication.setOverrideCursor(QCursor(Qt.BusyCursor))
        else:
            QApplication.restoreOverrideCursor()
            
    def get_widget_title(self):
        """Return widget title"""
        return self.tr('Interactive console')
    
    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.shell
        
    def closing(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        return True
    
    def quit(self):
        """Quit mainwindow"""
        self.main.close()
        
    def refresh(self):
        pass
    
    def set_actions(self):
        """Setup actions"""
        quit_action = create_action(self, self.tr("&Quit"), self.tr("Ctrl+Q"),
                            'exit.png', self.tr("Quit"),
                            triggered=self.quit)
        run_action = create_action(self, self.tr("&Run..."), None,
                            'run_small.png', self.tr("Run a Python script"),
                            triggered=self.run_script)
        environ_action = create_action(self,
                            self.tr("Environment variables..."),
                            icon = 'environ.png',
                            tip=self.tr("Show and edit environment variables"
                                        " (for current session)"),
                            triggered=self.show_env)
        syspath_action = create_action(self,
                            self.tr("Show sys.path contents..."),
                            icon = 'syspath.png',
                            tip=self.tr("Show (read-only) sys.path"),
                            triggered=show_syspath)
        font_action = create_action(self,
                            self.tr("&Font..."), None,
                            'font.png', self.tr("Set shell font style"),
                            triggered=self.change_font)
        exteditor_action = create_action(self,
                            self.tr("External editor path..."), None, None,
                            self.tr("Set external editor executable path"),
                            triggered=self.change_exteditor)
        wrap_action = create_action(self,
                            self.tr("Wrap lines"),
                            toggled=self.toggle_wrap_mode)
        wrap_action.setChecked( CONF.get(self.ID, 'wrap') )
        calltips_action = create_action(self, self.tr("Balloon tips"),
            toggled=self.toggle_calltips)
        calltips_action.setChecked( CONF.get(self.ID, 'calltips') )
        codecompletion_action = create_action(self, self.tr("Code completion"),
            toggled=self.toggle_codecompletion)
        codecompletion_action.setChecked( CONF.get(self.ID,
                                                   'autocompletion/enabled') )
        codecompenter_action = create_action(self,
                                    self.tr("Enter key selects completion"),
                                    toggled=self.toggle_codecompletion_enter)
        codecompenter_action.setChecked( CONF.get(self.ID,
                                                   'autocompletion/enter-key') )
        rollbackimporter_action = create_action(self,
                self.tr("Force modules to be completely reloaded"),
                tip=self.tr("Force Python to reload modules imported when "
                            "executing a script in the interactive console"),
                toggled=self.toggle_rollbackimporter)
        rollbackimporter_action.setChecked( CONF.get(self.ID,
                                                     'rollback_importer') )
        try:
            imp.find_module('matplotlib')
            dockablefigures_action = create_action(self,
                            self.tr("Dockable figures"),
                            tip=self.tr("If enabled, matplotlib figures may "
                                        "be docked to Spyder's main window "
                                        "(will apply only for new figures)"),
                            toggled=self.toggle_dockablefigures_mode)
            dockablefigures_action.setChecked( CONF.get('figure', 'dockable') )
        except ImportError:
            dockablefigures_action = None
            
        option_menu = QMenu(self.tr("Interactive console settings"), self)
        option_menu.setIcon(get_icon('tooloptions.png'))
        add_actions(option_menu, (font_action, wrap_action, calltips_action,
                                  codecompletion_action, codecompenter_action,
                                  rollbackimporter_action, exteditor_action,
                                  dockablefigures_action))
        
        menu_actions = [None, run_action, environ_action, syspath_action,
                        option_menu, None, quit_action]
        toolbar_actions = []
        
        # Add actions to context menu
        add_actions(self.shell.menu, menu_actions)
        
        return menu_actions, toolbar_actions
    
    def show_env(self):
        """Show environment variables"""
        dlg = EnvDialog()
        dlg.exec_()
        
    def run_script(self, filename=None, silent=False, set_focus=False):
        """Run a Python script"""
        if filename is None:
            self.shell.restore_stds()
            filename = QFileDialog.getOpenFileName(self,
                          self.tr("Run Python script"), os.getcwdu(),
                          self.tr("Python scripts")+" (*.py ; *.pyw)")
            self.shell.redirect_stds()
            if filename:
                filename = unicode(filename)
                os.chdir( os.path.dirname(filename) )
                filename = os.path.basename(filename)
                self.emit(SIGNAL("refresh()"))
            else:
                return
        command = "execfile(%s)" % repr(osp.abspath(filename))
        if set_focus:
            self.shell.setFocus()
        if self.dockwidget and not self.ismaximized:
            self.dockwidget.setVisible(True)
            self.dockwidget.raise_()
        if silent:
            self.shell.write(command+'\n')
            self.shell.run_command(command)
        else:
            self.shell.write(command)
            
    def go_to_error(self, text):
        """Go to error if relevant"""
        match = get_error_match(unicode(text))
        if match:
            fname, lnb = match.groups()
            self.edit_script(fname, int(lnb))
            
    def edit_script(self, filename=None, goto=-1):
        """Edit script"""
        # Called from InteractiveShell
        if not hasattr(self, 'main') \
           or not hasattr(self.main, 'editor'):
            self.shell.external_editor(filename, goto)
            return
        if filename is not None:
            self.emit(SIGNAL("edit_goto(QString,int)"),
                      osp.abspath(filename), goto)
        
    def change_font(self):
        """Change console font"""
        font, valid = QFontDialog.getFont(get_font(self.ID),
                       self, self.tr("Select a new font"))
        if valid:
            self.shell.set_font(font)
            set_font(font, self.ID)

    def change_exteditor(self):
        """Change external editor path"""
        path, valid = QInputDialog.getText(self, self.tr('External editor'),
                          self.tr('External editor executable path:'),
                          QLineEdit.Normal,
                          CONF.get(self.ID, 'external_editor/path'))
        if valid:
            CONF.set(self.ID, 'external_editor/path', unicode(path))
            
    def toggle_dockablefigures_mode(self, checked):
        """Toggle dockable figures mode"""
        CONF.set('figure', 'dockable', checked)
            
    def toggle_wrap_mode(self, checked):
        """Toggle wrap mode"""
        self.shell.toggle_wrap_mode(checked)
        CONF.set(self.ID, 'wrap', checked)
            
    def toggle_calltips(self, checked):
        """Toggle calltips"""
        self.shell.set_calltips(checked)
        CONF.set(self.ID, 'calltips', checked)
            
    def toggle_codecompletion(self, checked):
        """Toggle code completion"""
        self.shell.set_codecompletion(checked)
        CONF.set(self.ID, 'autocompletion/enabled', checked)
            
    def toggle_codecompletion_enter(self, checked):
        """Toggle Enter key for code completion"""
        self.shell.set_codecompletion_enter(checked)
        CONF.set(self.ID, 'autocompletion/enter-key', checked)
        
    def toggle_rollbackimporter(self, checked):
        """Toggle rollback importer"""
        CONF.set(self.ID, 'rollback_importer', checked)
        if checked and self.isVisible():
            QMessageBox.warning(self, self.get_widget_title(),
                        self.tr("The rollback importer requires a restart "
                                "of Spyder to be fully functionnal "
                                "(otherwise only newly imported modules "
                                "will be reloaded when executing scripts)."),
                        QMessageBox.Ok)
                
    #----Drag and drop                    
    def dragEnterEvent(self, event):
        """Reimplement Qt method
        Inform Qt about the types of data that the widget accepts"""
        source = event.mimeData()
        if source.hasUrls() or source.hasText():
            event.acceptProposedAction()
            
    def dropEvent(self, event):
        """Reimplement Qt method
        Unpack dropped data and handle it"""
        source = event.mimeData()
        if source.hasUrls():
            files = mimedata2url(source)
            if files:
                files = ["r'%s'" % path for path in files]
                if len(files) == 1:
                    text = files[0]
                else:
                    text = "[" + ", ".join(files) + "]"
                self.shell.insert_text(text)
        elif source.hasText():
            lines = unicode(source.text())
            self.shell.set_cursor_position('eof')
            self.shell.execute_lines(lines)
        event.acceptProposedAction()
Пример #24
0
class IPythonConsole(SpyderPluginWidget):
    """
    IPython Console plugin

    This is a widget with tabs where each one is an IPythonClient
    """
    CONF_SECTION = 'ipython_console'
    CONFIGWIDGET_CLASS = IPythonConsoleConfigPage
    DISABLE_ACTIONS_WHEN_HIDDEN = False

    def __init__(self, parent):
        SpyderPluginWidget.__init__(self, parent)

        self.tabwidget = None
        self.menu_actions = None

        self.extconsole = None  # External console plugin
        self.inspector = None  # Object inspector plugin
        self.historylog = None  # History log plugin
        self.variableexplorer = None  # Variable explorer plugin

        self.clients = []

        # Initialize plugin
        self.initialize_plugin()

        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        if hasattr(self.tabwidget, 'setDocumentMode')\
           and not sys.platform == 'darwin':
            # Don't set document mode to true on OSX because it generates
            # a crash when the console is detached from the main window
            # Fixes Issue 561
            self.tabwidget.setDocumentMode(True)
        self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
                     self.refresh_plugin)
        self.connect(self.tabwidget, SIGNAL('move_data(int,int)'),
                     self.move_tab)

        self.tabwidget.set_close_function(self.close_client)

        layout.addWidget(self.tabwidget)

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.hide()
        self.register_widget_shortcuts("Editor", self.find_widget)
        layout.addWidget(self.find_widget)

        self.setLayout(layout)

        # Accepting drops
        self.setAcceptDrops(True)

    #------ SpyderPluginMixin API ---------------------------------------------
    def on_first_registration(self):
        """Action to be performed on first plugin registration"""
        self.main.tabify_plugins(self.main.extconsole, self)

    def apply_plugin_settings(self, options):
        """Apply configuration file's plugin settings"""
        font_n = 'plugin_font'
        font_o = self.get_plugin_font()
        inspector_n = 'connect_to_oi'
        inspector_o = CONF.get('inspector', 'connect/ipython_console')
        for client in self.clients:
            control = client.get_control()
            if font_n in options:
                client.set_font(font_o)
            if inspector_n in options and control is not None:
                control.set_inspector_enabled(inspector_o)

    def toggle_view(self, checked):
        """Toggle view"""
        if checked:
            self.dockwidget.show()
            self.dockwidget.raise_()
            # Start a client in case there are none shown
            if not self.clients:
                if self.main.is_setting_up:
                    self.create_new_client(give_focus=False)
                else:
                    self.create_new_client(give_focus=True)
        else:
            self.dockwidget.hide()

    #------ SpyderPluginWidget API --------------------------------------------
    def get_plugin_title(self):
        """Return widget title"""
        return _('IPython console')

    def get_plugin_icon(self):
        """Return widget icon"""
        return get_icon('ipython_console.png')

    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        client = self.tabwidget.currentWidget()
        if client is not None:
            return client.get_control()

    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        for client in self.clients:
            client.close()
        return True

    def refresh_plugin(self):
        """Refresh tabwidget"""
        client = None
        if self.tabwidget.count():
            # Give focus to the control widget of the selected tab
            client = self.tabwidget.currentWidget()
            control = client.get_control()
            control.setFocus()
            widgets = client.get_toolbar_buttons() + [5]

            # Change extconsole tab to the client's kernel widget
            idx = self.extconsole.get_shell_index_from_id(
                client.kernel_widget_id)
            if idx is not None:
                self.extconsole.tabwidget.setCurrentIndex(idx)
        else:
            control = None
            widgets = []
        self.find_widget.set_editor(control)
        self.tabwidget.set_corner_widgets({Qt.TopRightCorner: widgets})
        self.main.last_console_plugin_focus_was_python = False
        self.emit(SIGNAL('update_plugin_title()'))

    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        create_client_action = create_action(self,
                                             _("Open an &IPython console"),
                                             None,
                                             'ipython_console.png',
                                             triggered=self.create_new_client)

        connect_to_kernel_action = create_action(
            self,
            _("Connect to an existing kernel"),
            None,
            None,
            _("Open a new IPython console connected to an existing kernel"),
            triggered=self.create_client_for_kernel)

        # Add the action to the 'Consoles' menu on the main window
        main_consoles_menu = self.main.consoles_menu_actions
        main_consoles_menu.insert(0, create_client_action)
        main_consoles_menu += [None, connect_to_kernel_action]

        # Plugin actions
        self.menu_actions = [create_client_action, connect_to_kernel_action]

        return self.menu_actions

    def register_plugin(self):
        """Register plugin in Spyder's main window"""
        self.main.add_dockwidget(self)

        self.extconsole = self.main.extconsole
        self.inspector = self.main.inspector
        self.historylog = self.main.historylog
        self.variableexplorer = self.main.variableexplorer

        self.connect(self, SIGNAL('focus_changed()'),
                     self.main.plugin_focus_changed)

        if self.main.editor is not None:
            self.connect(self, SIGNAL("edit_goto(QString,int,QString)"),
                         self.main.editor.load)
            self.connect(
                self.main.editor,
                SIGNAL(
                    'run_in_current_ipyclient(QString,QString,QString,bool)'),
                self.run_script_in_current_client)

    #------ Public API (for clients) ------------------------------------------
    def get_clients(self):
        """Return clients list"""
        return [cl for cl in self.clients if isinstance(cl, IPythonClient)]

#    def get_kernels(self):
#        """Return IPython kernel widgets list"""
#        return [sw for sw in self.shellwidgets
#                if isinstance(sw, IPythonKernel)]
#

    def get_focus_client(self):
        """Return current client with focus, if any"""
        widget = QApplication.focusWidget()
        for client in self.get_clients():
            if widget is client or widget is client.get_control():
                return client

    def get_current_client(self):
        """Return the currently selected client"""
        client = self.tabwidget.currentWidget()
        if client is not None:
            return client

    def run_script_in_current_client(self, filename, wdir, args, debug):
        """Run script in current client, if any"""
        norm = lambda text: remove_backslashes(to_text_string(text))
        client = self.get_current_client()
        if client is not None:
            # Internal kernels, use runfile
            if client.kernel_widget_id is not None:
                line = "%s('%s'" % ('debugfile' if debug else 'runfile',
                                    norm(filename))
                if args:
                    line += ", args='%s'" % norm(args)
                if wdir:
                    line += ", wdir='%s'" % norm(wdir)
                line += ")"
            else:  # External kernels, use %run
                line = "%run "
                if debug:
                    line += "-d "
                line += "\"%s\"" % to_text_string(filename)
                if args:
                    line += " %s" % norm(args)
            self.execute_python_code(line)
            self.visibility_changed(True)
            self.raise_()
        else:
            #XXX: not sure it can really happen
            QMessageBox.warning(
                self, _('Warning'),
                _("No IPython console is currently available to run <b>%s</b>."
                  "<br><br>Please open a new one and try again.") %
                osp.basename(filename), QMessageBox.Ok)

    def execute_python_code(self, lines):
        client = self.get_current_client()
        if client is not None:
            client.shellwidget.execute(to_text_string(lines))
            self.activateWindow()
            client.get_control().setFocus()

    def write_to_stdin(self, line):
        client = self.get_current_client()
        if client is not None:
            client.shellwidget.write_to_stdin(line)

    def create_new_client(self, give_focus=True):
        """Create a new client"""
        client = IPythonClient(self,
                               history_filename='history.py',
                               menu_actions=self.menu_actions)
        self.add_tab(client, name=client.get_name())
        self.main.extconsole.start_ipykernel(client, give_focus=give_focus)

    def register_client(self, client, name, restart=False, give_focus=True):
        """Register new client"""
        self.connect_client_to_kernel(client)
        client.show_shellwidget(give_focus=give_focus)
        client.name = name

        # Local vars
        shellwidget = client.shellwidget
        control = shellwidget._control
        page_control = shellwidget._page_control

        # Handle kernel interrupts
        extconsoles = self.extconsole.shellwidgets
        kernel_widget = None
        if extconsoles:
            if extconsoles[-1].connection_file == client.connection_file:
                kernel_widget = extconsoles[-1]
                if restart:
                    shellwidget.custom_interrupt_requested.disconnect()
                shellwidget.custom_interrupt_requested.connect(
                    kernel_widget.keyboard_interrupt)
        if kernel_widget is None:
            shellwidget.custom_interrupt_requested.connect(
                client.interrupt_message)

        # If we are restarting the kernel we need to rename
        # the client tab and do no more from here on
        if restart:
            self.rename_client_tab(client)
            return

        # For tracebacks
        self.connect(control, SIGNAL("go_to_error(QString)"), self.go_to_error)

        # Handle kernel restarts asked by the user
        if kernel_widget is not None:
            shellwidget.custom_restart_requested.connect(
                lambda cl=client: self.restart_kernel(client))
        else:
            shellwidget.custom_restart_requested.connect(
                client.restart_message)

        # Print a message if kernel dies unexpectedly
        shellwidget.custom_restart_kernel_died.connect(
            lambda t: client.if_kernel_dies(t))

        # Connect text widget to our inspector
        if kernel_widget is not None and self.inspector is not None:
            control.set_inspector(self.inspector)
            control.set_inspector_enabled(
                CONF.get('inspector', 'connect/ipython_console'))

        # Connect to our variable explorer
        if kernel_widget is not None and self.variableexplorer is not None:
            nsb = self.variableexplorer.currentWidget()
            # When the autorefresh button is active, our kernels
            # start to consume more and more CPU during time
            # Fix Issue 1450
            # ----------------
            # When autorefresh is off by default we need the next
            # line so that kernels don't start to consume CPU
            # Fix Issue 1595
            nsb.auto_refresh_button.setChecked(True)
            nsb.auto_refresh_button.setChecked(False)
            nsb.auto_refresh_button.setEnabled(False)
            nsb.set_ipyclient(client)
            client.set_namespacebrowser(nsb)

        # Connect client to our history log
        if self.historylog is not None:
            self.historylog.add_history(client.history_filename)
            self.connect(client, SIGNAL('append_to_history(QString,QString)'),
                         self.historylog.append_to_history)

        # Set font for client
        client.set_font(self.get_plugin_font())

        # Connect focus signal to client's control widget
        self.connect(control, SIGNAL('focus_changed()'),
                     lambda: self.emit(SIGNAL('focus_changed()')))

        # Update the find widget if focus changes between control and
        # page_control
        self.find_widget.set_editor(control)
        if page_control:
            self.connect(page_control, SIGNAL('focus_changed()'),
                         lambda: self.emit(SIGNAL('focus_changed()')))
            self.connect(control, SIGNAL('visibility_changed(bool)'),
                         self.refresh_plugin)
            self.connect(page_control, SIGNAL('visibility_changed(bool)'),
                         self.refresh_plugin)
            self.connect(page_control, SIGNAL('show_find_widget()'),
                         self.find_widget.show)

        # Update client name
        self.rename_client_tab(client)

    def close_client(self, index=None, client=None, force=False):
        """Close client tab from index or widget (or close current tab)"""
        if not self.tabwidget.count():
            return
        if client is not None:
            index = self.tabwidget.indexOf(client)
        if index is None and client is None:
            index = self.tabwidget.currentIndex()
        if index is not None:
            client = self.tabwidget.widget(index)

        # Check if related clients or kernels are opened
        # and eventually ask before closing them
        if not force and isinstance(client, IPythonClient):
            idx = self.extconsole.get_shell_index_from_id(
                client.kernel_widget_id)
            if idx is not None:
                close_all = True
                if self.get_option('ask_before_closing'):
                    ans = QMessageBox.question(
                        self, self.get_plugin_title(),
                        _("%s will be closed.\n"
                          "Do you want to kill the associated kernel "
                          "and all of its clients?") % client.get_name(),
                        QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
                    if ans == QMessageBox.Cancel:
                        return
                    close_all = ans == QMessageBox.Yes
                if close_all:
                    self.extconsole.close_console(index=idx,
                                                  from_ipyclient=True)
                    self.close_related_clients(client)
        client.close()

        # Note: client index may have changed after closing related widgets
        self.tabwidget.removeTab(self.tabwidget.indexOf(client))
        self.clients.remove(client)
        self.emit(SIGNAL('update_plugin_title()'))

    def get_client_index_from_id(self, client_id):
        """Return client index from id"""
        for index, client in enumerate(self.clients):
            if id(client) == client_id:
                return index

    def rename_client_tab(self, client):
        """Add the pid of the kernel process to client tab"""
        index = self.get_client_index_from_id(id(client))
        self.tabwidget.setTabText(index, client.get_name())

    def close_related_clients(self, client):
        """Close all clients related to *client*, except itself"""
        for cl in self.clients[:]:
            if cl is not client and \
              cl.connection_file == client.connection_file:
                self.close_client(client=cl)

    #------ Public API (for kernels) ------------------------------------------
    def kernel_and_frontend_match(self, connection_file):
        # Determine kernel version
        ci = get_connection_info(connection_file,
                                 unpack=True,
                                 profile='default')
        if u('control_port') in ci:
            kernel_ver = '>=1.0'
        else:
            kernel_ver = '<1.0'
        # is_module_installed checks if frontend version agrees with the
        # kernel one
        return programs.is_module_installed('IPython', version=kernel_ver)

    def create_kernel_manager_and_client(self,
                                         connection_file=None,
                                         hostname=None,
                                         sshkey=None,
                                         password=None):
        """Create kernel manager and client"""
        cf = find_connection_file(connection_file, profile='default')
        kernel_manager = QtKernelManager(connection_file=cf, config=None)
        if programs.is_module_installed('IPython', '>=1.0'):
            kernel_client = kernel_manager.client()
            kernel_client.load_connection_file()
            if hostname is not None:
                try:
                    newports = tunnel_to_kernel(
                        dict(ip=kernel_client.ip,
                             shell_port=kernel_client.shell_port,
                             iopub_port=kernel_client.iopub_port,
                             stdin_port=kernel_client.stdin_port,
                             hb_port=kernel_client.hb_port), hostname, sshkey,
                        password)
                    (kernel_client.shell_port, kernel_client.iopub_port,
                     kernel_client.stdin_port,
                     kernel_client.hb_port) = newports
                except Exception as e:
                    QMessageBox.critical(
                        self, _('Connection error'),
                        _('Could not open ssh tunnel\n') + str(e))
                    return None, None
            kernel_client.start_channels()
            # To rely on kernel's heartbeat to know when a kernel has died
            kernel_client.hb_channel.unpause()
            return kernel_manager, kernel_client
        else:
            kernel_manager.load_connection_file()
            if hostname is not None:
                try:
                    newports = tunnel_to_kernel(
                        dict(ip=kernel_manager.ip,
                             shell_port=kernel_manager.shell_port,
                             iopub_port=kernel_manager.iopub_port,
                             stdin_port=kernel_manager.stdin_port,
                             hb_port=kernel_manager.hb_port), hostname, sshkey,
                        password)
                    (kernel_manager.shell_port, kernel_manager.iopub_port,
                     kernel_manager.stdin_port,
                     kernel_manager.hb_port) = newports
                except Exception as e:
                    QMessageBox.critical(
                        self, _('Connection error'),
                        _('Could not open ssh tunnel\n') + str(e))
                    return None, None
            kernel_manager.start_channels()
            return kernel_manager, None

    def connect_client_to_kernel(self, client):
        """
        Connect a client to its kernel
        """
        km, kc = self.create_kernel_manager_and_client(client.connection_file,
                                                       client.hostname,
                                                       client.sshkey,
                                                       client.password)
        if km is not None:
            widget = client.shellwidget
            widget.kernel_manager = km
            widget.kernel_client = kc

    def create_client_for_kernel(self):
        """Create a client connected to an existing kernel"""
        (cf, hostname, kf, pw,
         ok) = KernelConnectionDialog.get_connection_parameters(self)
        if not ok:
            return
        else:
            self._create_client_for_kernel(cf, hostname, kf, pw)

    def _create_client_for_kernel(self, cf, hostname, kf, pw):
        # Verifying if the connection file exists - in the case of an empty
        # file name, the last used connection file is returned.
        try:
            cf = find_connection_file(cf, profile='default')
        except (IOError, UnboundLocalError):
            QMessageBox.critical(self, _('IPython'),
                                 _("Unable to connect to IPython <b>%s") % cf)
            return

        # Base client name:
        # remove path and extension, and use the last part when split by '-'
        base_client_name = osp.splitext(cf.split('/')[-1])[0].split('-')[-1]

        # Generating the client name by appending /A, /B... until it is unique
        count = 0
        while True:
            client_name = base_client_name + '/' + chr(65 + count)
            for cl in self.get_clients():
                if cl.name == client_name:
                    break
            else:
                break
            count += 1

        # Getting kernel_widget_id from the currently open kernels.
        kernel_widget_id = None
        for sw in self.extconsole.shellwidgets:
            if sw.connection_file == cf.split('/')[-1]:
                kernel_widget_id = id(sw)

        # Verifying if frontend and kernel have compatible versions
        if not self.kernel_and_frontend_match(cf):
            QMessageBox.critical(
                self, _("Mismatch between kernel and frontend"),
                _("Your IPython frontend and kernel versions "
                  "are <b>incompatible!!</b>"
                  "<br><br>"
                  "We're sorry but we can't create an IPython "
                  "console for you."), QMessageBox.Ok)
            return

        # Creating the client
        client = IPythonClient(self,
                               history_filename='history.py',
                               connection_file=cf,
                               kernel_widget_id=kernel_widget_id,
                               menu_actions=self.menu_actions,
                               hostname=hostname,
                               sshkey=kf,
                               password=pw)

        # Adding the tab
        self.add_tab(client, name=client.get_name())

        # Connecting kernel and client
        self.register_client(client, client_name)

    def restart_kernel(self, client):
        """
        Create a new kernel and connect it to `client` if the user asks for it
        """
        # Took this bit of code (until if result == ) from the IPython project
        # (qt/frontend_widget.py - restart_kernel).
        # Licensed under the BSD license
        message = _('Are you sure you want to restart the kernel?')
        buttons = QMessageBox.Yes | QMessageBox.No
        result = QMessageBox.question(self, _('Restart kernel?'), message,
                                      buttons)
        if result == QMessageBox.Yes:
            client.show_restart_animation()
            # Close old kernel tab
            idx = self.extconsole.get_shell_index_from_id(
                client.kernel_widget_id)
            self.extconsole.close_console(index=idx, from_ipyclient=True)

            # Create a new one and connect it to the client
            self.extconsole.start_ipykernel(client)

    def get_shellwidget_by_kernelwidget_id(self, kernel_id):
        """Return the IPython widget associated to a kernel widget id"""
        for cl in self.clients:
            if cl.kernel_widget_id == kernel_id:
                return cl.shellwidget
        else:
            raise ValueError("Unknown kernel widget ID %r" % kernel_id)

    #------ Public API (for tabs) ---------------------------------------------
    def add_tab(self, widget, name):
        """Add tab"""
        self.clients.append(widget)
        index = self.tabwidget.addTab(widget, get_icon('ipython_console.png'),
                                      name)
        self.tabwidget.setCurrentIndex(index)
        if self.dockwidget and not self.ismaximized:
            self.dockwidget.setVisible(True)
            self.dockwidget.raise_()
        self.activateWindow()
        widget.get_control().setFocus()

    def move_tab(self, index_from, index_to):
        """
        Move tab (tabs themselves have already been moved by the tabwidget)
        """
        client = self.clients.pop(index_from)
        self.clients.insert(index_to, client)
        self.emit(SIGNAL('update_plugin_title()'))

    #------ Public API (for help) ---------------------------------------------
    def go_to_error(self, text):
        """Go to error if relevant"""
        match = get_error_match(to_text_string(text))
        if match:
            fname, lnb = match.groups()
            self.emit(SIGNAL("edit_goto(QString,int,QString)"),
                      osp.abspath(fname), int(lnb), '')

    def show_intro(self):
        """Show intro to IPython help"""
        from IPython.core.usage import interactive_usage
        self.inspector.show_rich_text(interactive_usage)

    def show_guiref(self):
        """Show qtconsole help"""
        from IPython.core.usage import gui_reference
        self.inspector.show_rich_text(gui_reference, collapse=True)

    def show_quickref(self):
        """Show IPython Cheat Sheet"""
        from IPython.core.usage import quick_reference
        self.inspector.show_plain_text(quick_reference)
Пример #25
0
class Console(SpyderPluginWidget):
    """
    Console widget
    """
    CONF_SECTION = 'internal_console'
    focus_changed = Signal()
    redirect_stdio = Signal(bool)
    edit_goto = Signal(str, int, str)
    
    def __init__(self, parent=None, namespace=None, commands=[], message=None,
                 exitfunc=None, profile=False, multithreaded=False):
        if PYQT5:
            SpyderPluginWidget.__init__(self, parent, main = parent)
        else:
            SpyderPluginWidget.__init__(self, parent)
        
        debug_print("    ..internal console: initializing")
        self.dialog_manager = DialogManager()

        # Shell
        light_background = self.get_option('light_background')
        self.shell = InternalShell(parent, namespace, commands, message,
                                   self.get_option('max_line_count'),
                                   self.get_plugin_font(), exitfunc, profile,
                                   multithreaded,
                                   light_background=light_background)
        self.shell.status.connect(lambda msg: self.show_message.emit(msg, 0))
        self.shell.go_to_error.connect(self.go_to_error)
        self.shell.focus_changed.connect(lambda: self.focus_changed.emit())

        # Redirecting some signals:
        self.shell.redirect_stdio.connect(lambda state:
                                          self.redirect_stdio.emit(state))
        
        # Initialize plugin
        self.initialize_plugin()

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.shell)
        self.find_widget.hide()
        self.register_widget_shortcuts("Editor", self.find_widget)

        # Main layout
        layout = QVBoxLayout()
        layout.addWidget(self.shell)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)
        
        # Parameters
        self.shell.toggle_wrap_mode(self.get_option('wrap'))
            
        # Accepting drops
        self.setAcceptDrops(True)
        
    #------ Private API --------------------------------------------------------
    def set_historylog(self, historylog):
        """Bind historylog instance to this console
        Not used anymore since v2.0"""
        historylog.add_history(self.shell.history_filename)
        self.shell.append_to_history.connect(historylog.append_to_history)

    def set_help(self, help_plugin):
        """Bind help instance to this console"""
        self.shell.help = help_plugin

    #------ SpyderPluginWidget API ---------------------------------------------
    def get_plugin_title(self):
        """Return widget title"""
        return _('Internal console')
    
    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.shell
        
    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        self.dialog_manager.close_all()
        self.shell.exit_interpreter()
        return True
        
    def refresh_plugin(self):
        pass
    
    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        quit_action = create_action(self, _("&Quit"),
                                    icon=ima.icon('exit'), 
                                    tip=_("Quit"),
                                    triggered=self.quit)
        self.register_shortcut(quit_action, "_", "Quit", "Ctrl+Q")
        run_action = create_action(self, _("&Run..."), None,
                            ima.icon('run_small'),
                            _("Run a Python script"),
                            triggered=self.run_script)
        environ_action = create_action(self,
                            _("Environment variables..."),
                            icon=ima.icon('environ'),
                            tip=_("Show and edit environment variables"
                                        " (for current session)"),
                            triggered=self.show_env)
        syspath_action = create_action(self,
                            _("Show sys.path contents..."),
                            icon=ima.icon('syspath'),
                            tip=_("Show (read-only) sys.path"),
                            triggered=self.show_syspath)
        buffer_action = create_action(self,
                            _("Buffer..."), None,
                            tip=_("Set maximum line count"),
                            triggered=self.change_max_line_count)
        font_action = create_action(self,
                            _("&Font..."), None,
                            ima.icon('font'), _("Set shell font style"),
                            triggered=self.change_font)
        exteditor_action = create_action(self,
                            _("External editor path..."), None, None,
                            _("Set external editor executable path"),
                            triggered=self.change_exteditor)
        wrap_action = create_action(self,
                            _("Wrap lines"),
                            toggled=self.toggle_wrap_mode)
        wrap_action.setChecked(self.get_option('wrap'))
        calltips_action = create_action(self, _("Display balloon tips"),
            toggled=self.toggle_calltips)
        calltips_action.setChecked(self.get_option('calltips'))
        codecompletion_action = create_action(self,
                                          _("Automatic code completion"),
                                          toggled=self.toggle_codecompletion)
        codecompletion_action.setChecked(self.get_option('codecompletion/auto'))
        codecompenter_action = create_action(self,
                                    _("Enter key selects completion"),
                                    toggled=self.toggle_codecompletion_enter)
        codecompenter_action.setChecked(self.get_option(
                                                    'codecompletion/enter_key'))
        
        option_menu = QMenu(_('Internal console settings'), self)
        option_menu.setIcon(ima.icon('tooloptions'))
        add_actions(option_menu, (buffer_action, font_action, wrap_action,
                                  calltips_action, codecompletion_action,
                                  codecompenter_action, exteditor_action))
                    
        plugin_actions = [None, run_action, environ_action, syspath_action,
                          option_menu, None, quit_action]
        
        # Add actions to context menu
        add_actions(self.shell.menu, plugin_actions)
        
        return plugin_actions
    
    def register_plugin(self):
        """Register plugin in Spyder's main window"""
        self.focus_changed.connect(self.main.plugin_focus_changed)
        self.main.add_dockwidget(self)
        # Connecting the following signal once the dockwidget has been created:
        self.shell.traceback_available.connect(self.traceback_available)
    
    def traceback_available(self):
        """Traceback is available in the internal console: showing the 
        internal console automatically to warn the user"""
        if CONF.get('main', 'show_internal_console_if_traceback', False):
            self.dockwidget.show()
            self.dockwidget.raise_()
        
    #------ Public API ---------------------------------------------------------
    @Slot()
    def quit(self):
        """Quit mainwindow"""
        self.main.close()
    
    @Slot()
    def show_env(self):
        """Show environment variables"""
        self.dialog_manager.show(EnvDialog())
    
    @Slot()
    def show_syspath(self):
        """Show sys.path"""
        editor = CollectionsEditor()
        editor.setup(sys.path, title="sys.path", readonly=True,
                     width=600, icon=ima.icon('syspath'))
        self.dialog_manager.show(editor)
    
    @Slot()
    def run_script(self, filename=None, silent=False, set_focus=False,
                   args=None):
        """Run a Python script"""
        if filename is None:
            self.shell.interpreter.restore_stds()
            filename, _selfilter = getopenfilename(self, _("Run Python script"),
                   getcwd(), _("Python scripts")+" (*.py ; *.pyw ; *.ipy)")
            self.shell.interpreter.redirect_stds()
            if filename:
                os.chdir( osp.dirname(filename) )
                filename = osp.basename(filename)
            else:
                return
        debug_print(args)
        filename = osp.abspath(filename)
        rbs = remove_backslashes
        command = "runfile('%s', args='%s')" % (rbs(filename), rbs(args))
        if set_focus:
            self.shell.setFocus()
        if self.dockwidget and not self.ismaximized:
            self.dockwidget.setVisible(True)
            self.dockwidget.raise_()
        self.shell.write(command+'\n')
        self.shell.run_command(command)

            
    def go_to_error(self, text):
        """Go to error if relevant"""
        match = get_error_match(to_text_string(text))
        if match:
            fname, lnb = match.groups()
            self.edit_script(fname, int(lnb))
            
    def edit_script(self, filename=None, goto=-1):
        """Edit script"""
        # Called from InternalShell
        if not hasattr(self, 'main') \
           or not hasattr(self.main, 'editor'):
            self.shell.external_editor(filename, goto)
            return
        if filename is not None:
            self.edit_goto.emit(osp.abspath(filename), goto, '')
        
    def execute_lines(self, lines):
        """Execute lines and give focus to shell"""
        self.shell.execute_lines(to_text_string(lines))
        self.shell.setFocus()
    
    @Slot()
    def change_font(self):
        """Change console font"""
        font, valid = QFontDialog.getFont(self.get_plugin_font(),
                       self, _("Select a new font"))
        if valid:
            self.shell.set_font(font)
            self.set_plugin_font(font)
    
    @Slot()
    def change_max_line_count(self):
        "Change maximum line count"""
        mlc, valid = QInputDialog.getInteger(self, _('Buffer'),
                                           _('Maximum line count'),
                                           self.get_option('max_line_count'),
                                           0, 1000000)
        if valid:
            self.shell.setMaximumBlockCount(mlc)
            self.set_option('max_line_count', mlc)

    @Slot()
    def change_exteditor(self):
        """Change external editor path"""
        path, valid = QInputDialog.getText(self, _('External editor'),
                          _('External editor executable path:'),
                          QLineEdit.Normal,
                          self.get_option('external_editor/path'))
        if valid:
            self.set_option('external_editor/path', to_text_string(path))
    
    @Slot(bool)
    def toggle_wrap_mode(self, checked):
        """Toggle wrap mode"""
        self.shell.toggle_wrap_mode(checked)
        self.set_option('wrap', checked)
    
    @Slot(bool)
    def toggle_calltips(self, checked):
        """Toggle calltips"""
        self.shell.set_calltips(checked)
        self.set_option('calltips', checked)
    
    @Slot(bool)
    def toggle_codecompletion(self, checked):
        """Toggle automatic code completion"""
        self.shell.set_codecompletion_auto(checked)
        self.set_option('codecompletion/auto', checked)
    
    @Slot(bool)
    def toggle_codecompletion_enter(self, checked):
        """Toggle Enter key for code completion"""
        self.shell.set_codecompletion_enter(checked)
        self.set_option('codecompletion/enter_key', checked)
                
    #----Drag and drop                    
    def dragEnterEvent(self, event):
        """Reimplement Qt method
        Inform Qt about the types of data that the widget accepts"""
        source = event.mimeData()
        if source.hasUrls():
            if mimedata2url(source):
                event.acceptProposedAction()
            else:
                event.ignore()
        elif source.hasText():
            event.acceptProposedAction()
            
    def dropEvent(self, event):
        """Reimplement Qt method
        Unpack dropped data and handle it"""
        source = event.mimeData()
        if source.hasUrls():
            pathlist = mimedata2url(source)
            self.shell.drop_pathlist(pathlist)
        elif source.hasText():
            lines = to_text_string(source.text())
            self.shell.set_cursor_position('eof')
            self.shell.execute_lines(lines)
        event.acceptProposedAction()
Пример #26
0
class ExternalConsole(SpyderPluginWidget):
    """
    Console widget
    """
    ID = 'external_shell'
    def __init__(self, parent, light_mode):
        self.light_mode = light_mode
        self.commands = []
        self.tabwidget = None
        self.menu_actions = None
        self.inspector = None
        self.historylog = None
        self.variableexplorer = None # variable explorer plugin
        
        self.ipython_count = 0
        self.python_count = 0
        self.terminal_count = 0
        
        if CONF.get(self.ID, 'ipython_options', None) is None:
            CONF.set(self.ID, 'ipython_options',
                     self.get_default_ipython_options())
        
        self.shells = []
        self.filenames = []
        self.icons = []
        
        SpyderPluginWidget.__init__(self, parent)
        
        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
                     self.refresh_plugin)
        self.connect(self.tabwidget, SIGNAL('move_data(int,int)'),
                     self.move_tab)
                     
        self.tabwidget.set_close_function(self.close_console)

        layout.addWidget(self.tabwidget)
        
        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.hide()
        layout.addWidget(self.find_widget)
        
        self.setLayout(layout)
            
        # Accepting drops
        self.setAcceptDrops(True)
        
    def move_tab(self, index_from, index_to):
        """
        Move tab (tabs themselves have already been moved by the tabwidget)
        """
        filename = self.filenames.pop(index_from)
        shell = self.shells.pop(index_from)
        icons = self.icons.pop(index_from)
        
        self.filenames.insert(index_to, filename)
        self.shells.insert(index_to, shell)
        self.icons.insert(index_to, icons)

    def close_console(self, index=None):
        if not self.tabwidget.count():
            return
        if index is None:
            index = self.tabwidget.currentIndex()
        self.tabwidget.widget(index).close()
        self.tabwidget.removeTab(index)
        self.filenames.pop(index)
        self.shells.pop(index)
        self.icons.pop(index)
        
    def set_historylog(self, historylog):
        """Bind historylog instance to this console"""
        self.historylog = historylog
        
    def set_inspector(self, inspector):
        """Bind inspector instance to this console"""
        self.inspector = inspector
        inspector.set_external_console(self)
        
    def set_variableexplorer(self, variableexplorer):
        """Set variable explorer plugin"""
        self.variableexplorer = variableexplorer
        
    def __find_python_shell(self):
        current_index = self.tabwidget.currentIndex()
        if current_index == -1:
            return
        from spyderlib.widgets.externalshell import pythonshell
        for index in [current_index]+range(self.tabwidget.count()):
            shellwidget = self.tabwidget.widget(index)
            if isinstance(shellwidget, pythonshell.ExternalPythonShell):
                self.tabwidget.setCurrentIndex(index)
                return shellwidget
                
    def get_running_python_shell(self):
        """
        Called by object inspector to retrieve a running Python shell instance
        """
        current_index = self.tabwidget.currentIndex()
        if current_index == -1:
            return
        from spyderlib.widgets.externalshell import pythonshell
        shellwidgets = [self.tabwidget.widget(index)
                        for index in range(self.tabwidget.count())]
        shellwidgets = [_w for _w in shellwidgets
                        if isinstance(_w, pythonshell.ExternalPythonShell) \
                        and _w.is_running()]
        if shellwidgets:
            # First, iterate on interpreters only:
            for shellwidget in shellwidgets:
                if shellwidget.is_interpreter():
                    return shellwidget.shell
            else:
                return shellwidgets[0].shell
        
    def run_script_in_current_shell(self, filename):
        """Run script in current shell, if any"""
        shellwidget = self.__find_python_shell()
        if shellwidget is not None and shellwidget.is_running():
            line = "runfile(r'%s')" % unicode(filename)
            shellwidget.shell.execute_lines(line)
            shellwidget.shell.setFocus()
            
    def set_current_shell_working_directory(self, directory):
        """Set current shell working directory"""
        shellwidget = self.__find_python_shell()
        if shellwidget is not None and shellwidget.is_running():
            shellwidget.shell.set_cwd(unicode(directory))
        
    def execute_python_code(self, lines):
        """Execute Python code in an already opened Python interpreter"""
        shellwidget = self.__find_python_shell()
        if shellwidget is not None:
            shellwidget.shell.execute_lines(unicode(lines))
            shellwidget.shell.setFocus()
        
    def start(self, fname, wdir=None, ask_for_arguments=False,
              interact=False, debug=False, python=True,
              ipython=False, arguments=None, current=False):
        """Start new console"""
        # Note: fname is None <=> Python interpreter
        fname = unicode(fname) if isinstance(fname, QString) else fname
        wdir = unicode(wdir) if isinstance(wdir, QString) else wdir

        if fname is not None and fname in self.filenames:
            index = self.filenames.index(fname)
            if CONF.get(self.ID, 'single_tab'):
                old_shell = self.shells[index]
                if old_shell.is_running():
                    answer = QMessageBox.question(self, self.get_plugin_title(),
                        self.tr("%1 is already running in a separate process.\n"
                                "Do you want to kill the process before starting "
                                "a new one?").arg(osp.basename(fname)),
                        QMessageBox.Yes | QMessageBox.Cancel)
                    if answer == QMessageBox.Yes:
                        old_shell.process.kill()
                        old_shell.process.waitForFinished()
                    else:
                        return
                self.close_console(index)
        else:
            index = 0

        # Creating a new external shell
        pythonpath = self.main.get_spyder_pythonpath()
        if python:
            umd_enabled = CONF.get(self.ID, 'umd/enabled')
            umd_namelist = CONF.get(self.ID, 'umd/namelist')
            umd_verbose = CONF.get(self.ID, 'umd/verbose')
            shellwidget = ExternalPythonShell(self, fname, wdir, self.commands,
                           interact, debug, path=pythonpath, ipython=ipython,
                           arguments=arguments, stand_alone=self.light_mode,
                           umd_enabled=umd_enabled, umd_namelist=umd_namelist,
                           umd_verbose=umd_verbose)
            if self.variableexplorer is not None:
                self.variableexplorer.add_shellwidget(shellwidget)
        else:
            shellwidget = ExternalSystemShell(self, wdir, path=pythonpath)
        
        # Code completion / calltips
        case_sensitive = CONF.get(self.ID, 'codecompletion/case-sensitivity')
        show_single = CONF.get(self.ID, 'codecompletion/select-single')
        from_document = CONF.get(self.ID, 'codecompletion/from-document')
        shellwidget.shell.setup_code_completion(case_sensitive, show_single,
                                                 from_document)
        
        shellwidget.shell.setMaximumBlockCount( CONF.get(self.ID,
                                                          'max_line_count') )
        shellwidget.shell.set_font( get_font(self.ID) )
        shellwidget.shell.toggle_wrap_mode( CONF.get(self.ID, 'wrap') )
        shellwidget.shell.set_calltips( CONF.get(self.ID, 'calltips') )
        shellwidget.shell.set_codecompletion_auto( CONF.get(self.ID,
                                                 'codecompletion/auto') )
        shellwidget.shell.set_codecompletion_enter(CONF.get(self.ID,
                                                 'codecompletion/enter-key'))
        if python and self.inspector is not None:
            shellwidget.shell.set_inspector(self.inspector)
        if self.historylog is not None:
            self.historylog.add_history(shellwidget.shell.history_filename)
            self.connect(shellwidget.shell,
                         SIGNAL('append_to_history(QString,QString)'),
                         self.historylog.append_to_history)
        self.connect(shellwidget.shell, SIGNAL("go_to_error(QString)"),
                     self.go_to_error)
        self.connect(shellwidget.shell, SIGNAL("focus_changed()"),
                     lambda: self.emit(SIGNAL("focus_changed()")))
        if python:
            if fname is None:
                if ipython:
                    self.ipython_count += 1
                    tab_name = "IPython %d" % self.ipython_count
                    tab_icon1 = get_icon('ipython.png')
                    tab_icon2 = get_icon('ipython_t.png')
                else:
                    self.python_count += 1
                    tab_name = "Python %d" % self.python_count
                    tab_icon1 = get_icon('python.png')
                    tab_icon2 = get_icon('python_t.png')
            else:
                tab_name = osp.basename(fname)
                tab_icon1 = get_icon('run.png')
                tab_icon2 = get_icon('terminated.png')
        else:
            fname = id(shellwidget)
            if os.name == 'nt':
                tab_name = self.tr("Command Window")
            else:
                tab_name = self.tr("Terminal")
            self.terminal_count += 1
            tab_name += (" %d" % self.terminal_count)
            tab_icon1 = get_icon('cmdprompt.png')
            tab_icon2 = get_icon('cmdprompt_t.png')
        self.shells.insert(index, shellwidget)
        self.filenames.insert(index, fname)
        self.icons.insert(index, (tab_icon1, tab_icon2))
        if index is None:
            index = self.tabwidget.addTab(shellwidget, tab_name)
        else:
            self.tabwidget.insertTab(index, shellwidget, tab_name)
        
        self.connect(shellwidget, SIGNAL("started()"),
                     lambda sid=id(shellwidget): self.process_started(sid))
        self.connect(shellwidget, SIGNAL("finished()"),
                     lambda sid=id(shellwidget): self.process_finished(sid))
        self.find_widget.set_editor(shellwidget.shell)
        self.tabwidget.setTabToolTip(index, fname if wdir is None else wdir)
        self.tabwidget.setCurrentIndex(index)
        if self.dockwidget and not self.ismaximized:
            self.dockwidget.setVisible(True)
            self.dockwidget.raise_()
        
        self.toggle_icontext(CONF.get(self.ID, 'show_icontext'))
        
        # Start process and give focus to console
        shellwidget.start(ask_for_arguments)
        shellwidget.shell.setFocus()
        
    #------ Private API --------------------------------------------------------
    def process_started(self, shell_id):
        for index, shell in enumerate(self.shells):
            if id(shell) == shell_id:
                icon, _icon = self.icons[index]
                self.tabwidget.setTabIcon(index, icon)
                if self.inspector is not None:
                    self.inspector.set_shell(shell.shell)
                if self.variableexplorer is not None:
                    self.variableexplorer.add_shellwidget(shell)
        
    def process_finished(self, shell_id):
        for index, shell in enumerate(self.shells):
            if id(shell) == shell_id:
                _icon, icon = self.icons[index]
                self.tabwidget.setTabIcon(index, icon)
                if self.inspector is not None:
                    self.inspector.shell_terminated(shell.shell)
        if self.variableexplorer is not None:
            self.variableexplorer.remove_shellwidget(shell_id)
        
    #------ SpyderPluginWidget API ---------------------------------------------    
    def get_plugin_title(self):
        """Return widget title"""
        return self.tr('Console')
    
    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.tabwidget.currentWidget()
        
    def get_plugin_actions(self):
        """Setup actions"""
        interpreter_action = create_action(self,
                            self.tr("Open &interpreter"), None,
                            'python.png', self.tr("Open a Python interpreter"),
                            triggered=self.open_interpreter)
        if os.name == 'nt':
            text = self.tr("Open &command prompt")
            tip = self.tr("Open a Windows command prompt")
        else:
            text = self.tr("Open &terminal")
            tip = self.tr("Open a terminal window inside Spyder")
        console_action = create_action(self, text, None, 'cmdprompt.png', tip,
                            triggered=self.open_terminal)
        run_action = create_action(self,
                            self.tr("&Run..."), None,
                            'run_small.png', self.tr("Run a Python script"),
                            triggered=self.run_script)
        umd_action = create_action(self,
                self.tr("Force modules to be completely reloaded"),
                tip=self.tr("Force Python to reload modules imported when "
                            "executing a script in the external console "
                            "with the 'runfile' function (UMD: User Module "
                            "Deleter)"),
                toggled=self.toggle_umd)
        umd_action.setChecked( CONF.get(self.ID, 'umd/enabled') )
        
        python_startup = CONF.get(self.ID, 'open_python_at_startup', None)
        ipython_startup = CONF.get(self.ID, 'open_ipython_at_startup', None)
        if ipython_startup is None:
            ipython_startup = programs.is_module_installed("IPython")
            CONF.set(self.ID, 'open_ipython_at_startup', ipython_startup)
        if python_startup is None:
            python_startup = not ipython_startup
            CONF.set(self.ID, 'open_python_at_startup', python_startup)
        python_startup_action = create_action(self,
                self.tr("Open a Python interpreter at startup"),
                toggled=lambda checked:
                CONF.set(self.ID, 'open_python_at_startup', checked))
        python_startup_action.setChecked(python_startup)
        ipython_startup_action = create_action(self,
                self.tr("Open a IPython interpreter at startup"),
                toggled=lambda checked:
                CONF.set(self.ID, 'open_ipython_at_startup', checked))
        ipython_startup_action.setChecked(ipython_startup)
        
        buffer_action = create_action(self,
                            self.tr("Buffer..."), None,
                            tip=self.tr("Set maximum line count"),
                            triggered=self.change_max_line_count)
        font_action = create_action(self,
                            self.tr("&Font..."), None,
                            'font.png', self.tr("Set shell font style"),
                            triggered=self.change_font)
        wrap_action = create_action(self,
                            self.tr("Wrap lines"),
                            toggled=self.toggle_wrap_mode)
        wrap_action.setChecked( CONF.get(self.ID, 'wrap') )
        calltips_action = create_action(self, self.tr("Balloon tips"),
                            toggled=self.toggle_calltips)
        calltips_action.setChecked( CONF.get(self.ID, 'calltips') )
        codecompletion_action = create_action(self,
                                          self.tr("Automatic code completion"),
                                          toggled=self.toggle_codecompletion)
        codecompletion_action.setChecked( CONF.get(self.ID,
                                                   'codecompletion/auto') )
        codecompenter_action = create_action(self,
                                    self.tr("Enter key selects completion"),
                                    toggled=self.toggle_codecompletion_enter)
        codecompenter_action.setChecked( CONF.get(self.ID,
                                                  'codecompletion/enter-key') )
        singletab_action = create_action(self,
                            self.tr("One tab per script"),
                            toggled=self.toggle_singletab)
        singletab_action.setChecked( CONF.get(self.ID, 'single_tab') )
        icontext_action = create_action(self, self.tr("Show icons and text"),
                                        toggled=self.toggle_icontext)
        icontext_action.setChecked( CONF.get(self.ID, 'show_icontext') )
        
        self.menu_actions = [interpreter_action, console_action, run_action,
                             umd_action, python_startup_action,
                             ipython_startup_action, None,
                             buffer_action, font_action, wrap_action,
                             calltips_action, codecompletion_action,
                             codecompenter_action, singletab_action,
                             icontext_action]
        
        ipython_action = create_action(self,
                            self.tr("Open IPython interpreter"), None,
                            'ipython.png',
                            self.tr("Open an IPython interpreter"),
                            triggered=self.open_ipython)
        ipython_options_action = create_action(self,
                            self.tr("IPython interpreter options..."), None,
                            tip=self.tr("Set IPython interpreter "
                                        "command line arguments"),
                            triggered=self.set_ipython_options)
        if programs.is_module_installed("IPython"):
            self.menu_actions.insert(3, ipython_options_action)
            self.menu_actions.insert(1, ipython_action)
        
        return (self.menu_actions, None)
    
    def open_interpreter_at_startup(self):
        """Open an interpreter at startup, IPython if module is available"""
        if CONF.get(self.ID, 'open_ipython_at_startup') \
           and programs.is_module_installed("IPython"):
            self.open_ipython()
        if CONF.get(self.ID, 'open_python_at_startup'):
            self.open_interpreter()
        
    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        for shell in self.shells:
            shell.process.kill()
        return True
    
    def refresh_plugin(self):
        """Refresh tabwidget"""
        if self.tabwidget.count():
            editor = self.tabwidget.currentWidget().shell
            editor.setFocus()
        else:
            editor = None
        self.find_widget.set_editor(editor)
    
    #------ Public API ---------------------------------------------------------
    def open_interpreter(self, wdir=None):
        """Open interpreter"""
        if wdir is None:
            wdir = os.getcwdu()
        self.start(fname=None, wdir=unicode(wdir), ask_for_arguments=False,
                   interact=True, debug=False, python=True)
        
    def get_default_ipython_options(self):
        """Return default ipython command line arguments"""
        default_options = []
        if programs.is_module_installed('matplotlib'):
            default_options.append("-pylab")
        else:
            default_options.append("-q4thread")
        default_options.append("-colors LightBG")
        default_options.append("-xmode Plain")
        for editor_name in ("scite", "gedit"):
            real_name = programs.get_nt_program_name(editor_name)
            if programs.is_program_installed(real_name):
                default_options.append("-editor "+real_name)
                break
        return " ".join(default_options)
        
    def open_ipython(self, wdir=None):
        """Open IPython"""
        if wdir is None:
            wdir = os.getcwdu()
        self.start(fname=None, wdir=unicode(wdir), ask_for_arguments=False,
                   interact=True, debug=False, python=True, ipython=True,
                   arguments=CONF.get(self.ID, 'ipython_options', ""))
        
    def open_terminal(self, wdir=None):
        """Open terminal"""
        if wdir is None:
            wdir = os.getcwdu()
        self.start(fname=None, wdir=unicode(wdir), ask_for_arguments=False,
                   interact=True, debug=False, python=False)
        
    def run_script(self):
        """Run a Python script"""
        self.emit(SIGNAL('redirect_stdio(bool)'), False)
        filename = QFileDialog.getOpenFileName(self,
                      self.tr("Run Python script"), os.getcwdu(),
                      self.tr("Python scripts")+" (*.py ; *.pyw)")
        self.emit(SIGNAL('redirect_stdio(bool)'), True)
        if filename:
            self.start(fname=unicode(filename), wdir=None,
                       ask_for_arguments=False, interact=False, debug=False)
        
    def change_font(self):
        """Change console font"""
        font, valid = QFontDialog.getFont(get_font(self.ID),
                       self, self.tr("Select a new font"))
        if valid:
            for index in range(self.tabwidget.count()):
                self.tabwidget.widget(index).shell.set_font(font)
            set_font(font, self.ID)
        
    def change_max_line_count(self):
        "Change maximum line count"""
        mlc, valid = QInputDialog.getInteger(self, self.tr('Buffer'),
                                           self.tr('Maximum line count'),
                                           CONF.get(self.ID, 'max_line_count'),
                                           10, 1000000)
        if valid:
            for index in range(self.tabwidget.count()):
                self.tabwidget.widget(index).shell.setMaximumBlockCount(mlc)
            CONF.set(self.ID, 'max_line_count', mlc)
            
    def set_ipython_options(self):
        """Set IPython interpreter arguments"""
        arguments, valid = QInputDialog.getText(self,
                      self.tr('IPython'),
                      self.tr('IPython command line options:\n'
                              '(Qt4 support: -q4thread)\n'
                              '(Qt4 and matplotlib support: -q4thread -pylab)'),
                      QLineEdit.Normal, CONF.get(self.ID, 'ipython_options'))
        if valid:
            CONF.set(self.ID, 'ipython_options', unicode(arguments))
        
    def toggle_umd(self, checked):
        """Toggle UMD"""
        CONF.set(self.ID, 'umd/enabled', checked)
        if checked and self.isVisible():
            QMessageBox.warning(self, self.get_plugin_title(),
                self.tr("This option will enable the User Module Deleter (UMD) "
                        "in Python/IPython interpreters. UMD forces Python to "
                        "reload deeply modules during import when running a "
                        "Python script using the Spyder's builtin function "
                        "<b>runfile</b>."
                        "<br><br><b>1.</b> UMD may require to restart the "
                        "Python interpreter in which it will be called "
                        "(otherwise only newly imported modules will be "
                        "reloaded when executing scripts)."
                        "<br><br><b>2.</b> If errors occur when re-running a "
                        "PyQt-based program, please check that the Qt objects "
                        "are properly destroyed (e.g. you may have to use the "
                        "attribute <b>Qt.WA_DeleteOnClose</b> on your main "
                        "window, using the <b>setAttribute</b> method)"),
                QMessageBox.Ok)
            
    def toggle_wrap_mode(self, checked):
        """Toggle wrap mode"""
        if self.tabwidget is None:
            return
        for shell in self.shells:
            shell.shell.toggle_wrap_mode(checked)
        CONF.set(self.ID, 'wrap', checked)
            
    def toggle_calltips(self, checked):
        """Toggle calltips"""
        if self.tabwidget is None:
            return
        for shell in self.shells:
            shell.shell.set_calltips(checked)
        CONF.set(self.ID, 'calltips', checked)
            
    def toggle_codecompletion(self, checked):
        """Toggle automatic code completion"""
        if self.tabwidget is None:
            return
        for shell in self.shells:
            shell.shell.set_codecompletion_auto(checked)
        CONF.set(self.ID, 'codecompletion/auto', checked)
            
    def toggle_codecompletion_enter(self, checked):
        """Toggle Enter key for code completion"""
        if self.tabwidget is None:
            return
        for shell in self.shells:
            shell.shell.set_codecompletion_enter(checked)
        CONF.set(self.ID, 'codecompletion/enter-key', checked)
        
    def toggle_singletab(self, checked):
        """Toggle single tab mode"""
        CONF.set(self.ID, 'single_tab', checked)

    def toggle_icontext(self, checked):
        """Toggle icon text"""
        CONF.set(self.ID, 'show_icontext', checked)
        if self.tabwidget is None:
            return
        for index in range(self.tabwidget.count()):
            for widget in self.tabwidget.widget(index).get_toolbar_buttons():
                if checked:
                    widget.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
                else:
                    widget.setToolButtonStyle(Qt.ToolButtonIconOnly)
                
    def go_to_error(self, text):
        """Go to error if relevant"""
        match = get_error_match(unicode(text))
        if match:
            fname, lnb = match.groups()
            self.emit(SIGNAL("edit_goto(QString,int,QString)"),
                      osp.abspath(fname), int(lnb), '')
            
    #----Drag and drop
    def __is_python_script(self, qstr):
        """Is it a valid Python script?"""
        fname = unicode(qstr)
        return osp.isfile(fname) and \
               ( fname.endswith('.py') or fname.endswith('.pyw') )
        
    def dragEnterEvent(self, event):
        """Reimplement Qt method
        Inform Qt about the types of data that the widget accepts"""
        source = event.mimeData()
        if source.hasUrls():
            if mimedata2url(source):
                pathlist = mimedata2url(source)
                shellwidget = self.tabwidget.currentWidget()
                if all([self.__is_python_script(qstr) for qstr in pathlist]):
                    event.acceptProposedAction()
                elif shellwidget is None or not shellwidget.is_running():
                    event.ignore()
                else:
                    event.acceptProposedAction()
            else:
                event.ignore()
        elif source.hasText():
            event.acceptProposedAction()            
            
    def dropEvent(self, event):
        """Reimplement Qt method
        Unpack dropped data and handle it"""
        source = event.mimeData()
        shellwidget = self.tabwidget.currentWidget()
        if source.hasText():
            qstr = source.text()
            if self.__is_python_script(qstr):
                self.start(qstr, ask_for_arguments=True)
            elif shellwidget:
                shellwidget.shell.insert_text(qstr)
        elif source.hasUrls():
            pathlist = mimedata2url(source)
            if all([self.__is_python_script(qstr) for qstr in pathlist]):
                for fname in pathlist:
                    self.start(fname, ask_for_arguments=True)
            elif shellwidget:
                shellwidget.shell.drop_pathlist(pathlist)
        event.acceptProposedAction()
Пример #27
0
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)

        self.home_url = None

        self.webview = WebView(self)
        self.webview.loadFinished.connect(self.load_finished)
        self.webview.titleChanged.connect(self.setWindowTitle)
        self.webview.urlChanged.connect(self.url_changed)

        home_button = create_toolbutton(self,
                                        icon=ima.icon('home'),
                                        tip=_("Home"),
                                        triggered=self.go_home)

        zoom_out_button = action2button(self.webview.zoom_out_action)
        zoom_in_button = action2button(self.webview.zoom_in_action)

        pageact2btn = lambda prop: action2button(self.webview.pageAction(prop),
                                                 parent=self.webview)
        refresh_button = pageact2btn(QWebPage.Reload)
        stop_button = pageact2btn(QWebPage.Stop)
        previous_button = pageact2btn(QWebPage.Back)
        next_button = pageact2btn(QWebPage.Forward)

        stop_button.setEnabled(False)
        self.webview.loadStarted.connect(lambda: stop_button.setEnabled(True))
        self.webview.loadFinished.connect(
            lambda: stop_button.setEnabled(False))

        progressbar = QProgressBar(self)
        progressbar.setTextVisible(False)
        progressbar.hide()
        self.webview.loadStarted.connect(progressbar.show)
        self.webview.loadProgress.connect(progressbar.setValue)
        self.webview.loadFinished.connect(lambda _state: progressbar.hide())

        label = QLabel(self.get_label())

        self.url_combo = UrlComboBox(self)
        self.url_combo.valid.connect(self.url_combo_activated)
        self.webview.iconChanged.connect(self.icon_changed)

        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.webview)
        self.find_widget.hide()

        find_button = create_toolbutton(self,
                                        icon=ima.icon('find'),
                                        tip=_("Find text"),
                                        toggled=self.toggle_find_widget)
        self.find_widget.visibility_changed.connect(find_button.setChecked)

        hlayout = QHBoxLayout()
        for widget in (previous_button, next_button, home_button, find_button,
                       label, self.url_combo, zoom_out_button, zoom_in_button,
                       refresh_button, progressbar, stop_button):
            hlayout.addWidget(widget)

        layout = QVBoxLayout()
        layout.addLayout(hlayout)
        layout.addWidget(self.webview)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)
Пример #28
0
class Console(SpyderPluginWidget):
    """
    Console widget
    """
    ID = 'shell'
    def __init__(self, parent=None, namespace=None, commands=[], message="",
                 debug=False, exitfunc=None, profile=False, multithreaded=True):
        # Shell
        self.shell = InternalShell(parent, namespace, commands, message,
                                   CONF.get(self.ID, 'max_line_count'),
                                   get_font(self.ID),
                                   debug, exitfunc, profile, multithreaded)
        self.connect(self.shell, SIGNAL('status(QString)'),
                     lambda msg:
                     self.emit(SIGNAL('show_message(QString,int)'), msg, 0))
        self.connect(self.shell, SIGNAL("go_to_error(QString)"),
                     self.go_to_error)
        self.connect(self.shell, SIGNAL("focus_changed()"),
                     lambda: self.emit(SIGNAL("focus_changed()")))
        # Redirecting some SIGNALs:
        self.connect(self.shell, SIGNAL('redirect_stdio(bool)'),
                     lambda state: self.emit(SIGNAL('redirect_stdio(bool)'),
                                             state))
        
        SpyderPluginWidget.__init__(self, parent)
        
        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.shell)
        self.find_widget.hide()

        # Main layout
        layout = QVBoxLayout()
        layout.addWidget(self.shell)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)
        
        # Parameters
        self.shell.toggle_wrap_mode( CONF.get(self.ID, 'wrap') )
            
        # Accepting drops
        self.setAcceptDrops(True)
        
    #------ Private API --------------------------------------------------------
    def set_historylog(self, historylog):
        """Bind historylog instance to this console
        Not used anymore since v2.0"""
        historylog.add_history(self.shell.history_filename)
        self.connect(self.shell, SIGNAL('append_to_history(QString,QString)'),
                     historylog.append_to_history)
        
    def set_inspector(self, inspector):
        """Bind inspector instance to this console"""
        self.shell.inspector = inspector

    #------ SpyderPluginWidget API ---------------------------------------------
    def get_plugin_title(self):
        """Return widget title"""
        return self.tr('Internal console')
    
    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.shell
        
    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        self.shell.exit_interpreter()
        return True
        
    def refresh_plugin(self):
        pass
    
    def get_plugin_actions(self):
        """Setup actions"""
        quit_action = create_action(self, self.tr("&Quit"), self.tr("Ctrl+Q"),
                            'exit.png', self.tr("Quit"),
                            triggered=self.quit)
        run_action = create_action(self, self.tr("&Run..."), None,
                            'run_small.png', self.tr("Run a Python script"),
                            triggered=self.run_script)
        environ_action = create_action(self,
                            self.tr("Environment variables..."),
                            icon = 'environ.png',
                            tip=self.tr("Show and edit environment variables"
                                        " (for current session)"),
                            triggered=self.show_env)
        syspath_action = create_action(self,
                            self.tr("Show sys.path contents..."),
                            icon = 'syspath.png',
                            tip=self.tr("Show (read-only) sys.path"),
                            triggered=show_syspath)
        buffer_action = create_action(self,
                            self.tr("Buffer..."), None,
                            tip=self.tr("Set maximum line count"),
                            triggered=self.change_max_line_count)
        font_action = create_action(self,
                            self.tr("&Font..."), None,
                            'font.png', self.tr("Set shell font style"),
                            triggered=self.change_font)
        exteditor_action = create_action(self,
                            self.tr("External editor path..."), None, None,
                            self.tr("Set external editor executable path"),
                            triggered=self.change_exteditor)
        wrap_action = create_action(self,
                            self.tr("Wrap lines"),
                            toggled=self.toggle_wrap_mode)
        wrap_action.setChecked( CONF.get(self.ID, 'wrap') )
        calltips_action = create_action(self, self.tr("Balloon tips"),
            toggled=self.toggle_calltips)
        calltips_action.setChecked( CONF.get(self.ID, 'calltips') )
        codecompletion_action = create_action(self,
                                          self.tr("Automatic code completion"),
                                          toggled=self.toggle_codecompletion)
        codecompletion_action.setChecked( CONF.get(self.ID,
                                                   'codecompletion/auto') )
        codecompenter_action = create_action(self,
                                    self.tr("Enter key selects completion"),
                                    toggled=self.toggle_codecompletion_enter)
        codecompenter_action.setChecked( CONF.get(self.ID,
                                                   'codecompletion/enter-key') )
        
        option_menu = QMenu(self.tr("Internal console settings"), self)
        option_menu.setIcon(get_icon('tooloptions.png'))
        add_actions(option_menu, (buffer_action, font_action, wrap_action,
                                  calltips_action, codecompletion_action,
                                  codecompenter_action, exteditor_action))
                    
        menu_actions = [None, run_action, environ_action, syspath_action,
                        option_menu, None, quit_action]
        toolbar_actions = []
        
        # Add actions to context menu
        add_actions(self.shell.menu, menu_actions)
        
        return menu_actions, toolbar_actions
        
    #------ Public API ---------------------------------------------------------
    def quit(self):
        """Quit mainwindow"""
        self.main.close()
        
    def show_env(self):
        """Show environment variables"""
        dlg = EnvDialog()
        dlg.exec_()
        
    def run_script(self, filename=None, silent=False, set_focus=False,
                   args=None):
        """Run a Python script"""
        if filename is None:
            self.shell.interpreter.restore_stds()
            filename = QFileDialog.getOpenFileName(self,
                          self.tr("Run Python script"), os.getcwdu(),
                          self.tr("Python scripts")+" (*.py ; *.pyw)")
            self.shell.interpreter.redirect_stds()
            if filename:
                filename = unicode(filename)
                os.chdir( os.path.dirname(filename) )
                filename = os.path.basename(filename)
                self.emit(SIGNAL("refresh()"))
            else:
                return
        command = "runfile(%s, args=%s)" % (repr(osp.abspath(filename)),
                                            repr(args))
        if set_focus:
            self.shell.setFocus()
        if self.dockwidget and not self.ismaximized:
            self.dockwidget.setVisible(True)
            self.dockwidget.raise_()
        self.shell.write(command+'\n')
        self.shell.run_command(command)

            
    def go_to_error(self, text):
        """Go to error if relevant"""
        match = get_error_match(unicode(text))
        if match:
            fname, lnb = match.groups()
            self.edit_script(fname, int(lnb))
            
    def edit_script(self, filename=None, goto=-1):
        """Edit script"""
        # Called from InternalShell
        if not hasattr(self, 'main') \
           or not hasattr(self.main, 'editor'):
            self.shell.external_editor(filename, goto)
            return
        if filename is not None:
            self.emit(SIGNAL("edit_goto(QString,int,QString)"),
                      osp.abspath(filename), goto, '')
        
    def execute_lines(self, lines):
        """Execute lines and give focus to shell"""
        self.shell.execute_lines(unicode(lines))
        self.shell.setFocus()
        
    def change_font(self):
        """Change console font"""
        font, valid = QFontDialog.getFont(get_font(self.ID),
                       self, self.tr("Select a new font"))
        if valid:
            self.shell.set_font(font)
            set_font(font, self.ID)
        
    def change_max_line_count(self):
        "Change maximum line count"""
        mlc, valid = QInputDialog.getInteger(self, self.tr('Buffer'),
                                           self.tr('Maximum line count'),
                                           CONF.get(self.ID, 'max_line_count'),
                                           10, 1000000)
        if valid:
            self.shell.setMaximumBlockCount(mlc)
            CONF.set(self.ID, 'max_line_count', mlc)

    def change_exteditor(self):
        """Change external editor path"""
        path, valid = QInputDialog.getText(self, self.tr('External editor'),
                          self.tr('External editor executable path:'),
                          QLineEdit.Normal,
                          CONF.get(self.ID, 'external_editor/path'))
        if valid:
            CONF.set(self.ID, 'external_editor/path', unicode(path))
            
    def toggle_wrap_mode(self, checked):
        """Toggle wrap mode"""
        self.shell.toggle_wrap_mode(checked)
        CONF.set(self.ID, 'wrap', checked)
            
    def toggle_calltips(self, checked):
        """Toggle calltips"""
        self.shell.set_calltips(checked)
        CONF.set(self.ID, 'calltips', checked)
            
    def toggle_codecompletion(self, checked):
        """Toggle automatic code completion"""
        self.shell.set_codecompletion_auto(checked)
        CONF.set(self.ID, 'codecompletion/auto', checked)
            
    def toggle_codecompletion_enter(self, checked):
        """Toggle Enter key for code completion"""
        self.shell.set_codecompletion_enter(checked)
        CONF.set(self.ID, 'codecompletion/enter-key', checked)
                
    #----Drag and drop                    
    def dragEnterEvent(self, event):
        """Reimplement Qt method
        Inform Qt about the types of data that the widget accepts"""
        source = event.mimeData()
        if source.hasUrls():
            if mimedata2url(source):
                event.acceptProposedAction()
            else:
                event.ignore()
        elif source.hasText():
            event.acceptProposedAction()
            
    def dropEvent(self, event):
        """Reimplement Qt method
        Unpack dropped data and handle it"""
        source = event.mimeData()
        if source.hasUrls():
            pathlist = mimedata2url(source)
            self.shell.drop_pathlist(pathlist)
        elif source.hasText():
            lines = unicode(source.text())
            self.shell.set_cursor_position('eof')
            self.shell.execute_lines(lines)
        event.acceptProposedAction()
Пример #29
0
class WebBrowser(QWidget):
    """
    Web browser widget
    """
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)

        self.home_url = None

        self.webview = WebView(self)
        self.webview.loadFinished.connect(self.load_finished)
        self.webview.titleChanged.connect(self.setWindowTitle)
        self.webview.urlChanged.connect(self.url_changed)

        home_button = create_toolbutton(self,
                                        icon=ima.icon('home'),
                                        tip=_("Home"),
                                        triggered=self.go_home)

        zoom_out_button = action2button(self.webview.zoom_out_action)
        zoom_in_button = action2button(self.webview.zoom_in_action)

        pageact2btn = lambda prop: action2button(self.webview.pageAction(prop),
                                                 parent=self.webview)
        refresh_button = pageact2btn(QWebPage.Reload)
        stop_button = pageact2btn(QWebPage.Stop)
        previous_button = pageact2btn(QWebPage.Back)
        next_button = pageact2btn(QWebPage.Forward)

        stop_button.setEnabled(False)
        self.webview.loadStarted.connect(lambda: stop_button.setEnabled(True))
        self.webview.loadFinished.connect(
            lambda: stop_button.setEnabled(False))

        progressbar = QProgressBar(self)
        progressbar.setTextVisible(False)
        progressbar.hide()
        self.webview.loadStarted.connect(progressbar.show)
        self.webview.loadProgress.connect(progressbar.setValue)
        self.webview.loadFinished.connect(lambda _state: progressbar.hide())

        label = QLabel(self.get_label())

        self.url_combo = UrlComboBox(self)
        self.url_combo.valid.connect(self.url_combo_activated)
        self.webview.iconChanged.connect(self.icon_changed)

        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.webview)
        self.find_widget.hide()

        find_button = create_toolbutton(self,
                                        icon=ima.icon('find'),
                                        tip=_("Find text"),
                                        toggled=self.toggle_find_widget)
        self.find_widget.visibility_changed.connect(find_button.setChecked)

        hlayout = QHBoxLayout()
        for widget in (previous_button, next_button, home_button, find_button,
                       label, self.url_combo, zoom_out_button, zoom_in_button,
                       refresh_button, progressbar, stop_button):
            hlayout.addWidget(widget)

        layout = QVBoxLayout()
        layout.addLayout(hlayout)
        layout.addWidget(self.webview)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)

    def get_label(self):
        """Return address label text"""
        return _("Address:")

    def set_home_url(self, text):
        """Set home URL"""
        self.home_url = QUrl(text)

    def set_url(self, url):
        """Set current URL"""
        self.url_changed(url)
        self.go_to(url)

    def go_to(self, url_or_text):
        """Go to page *address*"""
        if is_text_string(url_or_text):
            url = QUrl(url_or_text)
        else:
            url = url_or_text
        self.webview.load(url)

    @Slot()
    def go_home(self):
        """Go to home page"""
        if self.home_url is not None:
            self.set_url(self.home_url)

    def text_to_url(self, text):
        """Convert text address into QUrl object"""
        return QUrl(text)

    def url_combo_activated(self, valid):
        """Load URL from combo box first item"""
        text = to_text_string(self.url_combo.currentText())
        self.go_to(self.text_to_url(text))

    def load_finished(self, ok):
        if not ok:
            self.webview.setHtml(_("Unable to load page"))

    def url_to_text(self, url):
        """Convert QUrl object to displayed text in combo box"""
        return url.toString()

    def url_changed(self, url):
        """Displayed URL has changed -> updating URL combo box"""
        self.url_combo.add_text(self.url_to_text(url))

    def icon_changed(self):
        self.url_combo.setItemIcon(self.url_combo.currentIndex(),
                                   self.webview.icon())
        self.setWindowIcon(self.webview.icon())

    @Slot(bool)
    def toggle_find_widget(self, state):
        if state:
            self.find_widget.show()
        else:
            self.find_widget.hide()
Пример #30
0
class HistoryLog(PluginWidget):
    """
    History log widget
    """
    ID = 'historylog'
    location = Qt.RightDockWidgetArea

    def __init__(self, parent):
        self.tabwidget = None
        self.menu_actions = None
        self.dockviewer = None

        self.editors = []
        self.filenames = []
        self.icons = []

        PluginWidget.__init__(self, parent)

        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
                     self.refresh)
        self.connect(self.tabwidget, SIGNAL("close_tab(int)"),
                     self.tabwidget.removeTab)
        self.connect(self.tabwidget, SIGNAL('move_data(int,int)'),
                     self.move_tab)
        layout.addWidget(self.tabwidget)

        # Menu as corner widget
        options_button = create_toolbutton(self,
                                           text=self.tr("Options"),
                                           icon=get_icon('tooloptions.png'))
        options_button.setPopupMode(QToolButton.InstantPopup)
        menu = QMenu(self)
        add_actions(menu, self.menu_actions)
        options_button.setMenu(menu)
        self.tabwidget.setCornerWidget(options_button)

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.hide()
        layout.addWidget(self.find_widget)

        self.setLayout(layout)

    def move_tab(self, index_from, index_to):
        """
        Move tab (tabs themselves have already been moved by the tabwidget)
        """
        filename = self.filenames.pop(index_from)
        editor = self.editors.pop(index_from)
        icon = self.icons.pop(index_from)

        self.filenames.insert(index_to, filename)
        self.editors.insert(index_to, editor)
        self.icons.insert(index_to, icon)

    def add_history(self, filename):
        """
        Add new history tab
        Slot for SIGNAL('add_history(QString)') emitted by shell instance
        """
        filename = encoding.to_unicode(filename)
        if filename in self.filenames:
            return
        editor = QsciEditor(self)
        if osp.splitext(filename)[1] == '.py':
            language = 'py'
            icon = get_icon('python.png')
        else:
            language = 'bat'
            icon = get_icon('cmdprompt.png')
        editor.setup_editor(linenumbers=False,
                            language=language,
                            code_folding=True)
        self.connect(editor, SIGNAL("focus_changed()"),
                     lambda: self.emit(SIGNAL("focus_changed()")))
        editor.setReadOnly(True)
        editor.set_font(get_font(self.ID))
        editor.toggle_wrap_mode(CONF.get(self.ID, 'wrap'))

        text, _ = encoding.read(filename)
        editor.set_text(text)
        editor.set_cursor_position('eof')

        self.editors.append(editor)
        self.filenames.append(filename)
        self.icons.append(icon)
        index = self.tabwidget.addTab(editor, osp.basename(filename))
        self.find_widget.set_editor(editor)
        self.tabwidget.setTabToolTip(index, filename)
        self.tabwidget.setTabIcon(index, icon)
        self.tabwidget.setCurrentIndex(index)

    def append_to_history(self, filename, command):
        """
        Append an entry to history filename
        Slot for SIGNAL('append_to_history(QString,QString)')
        emitted by shell instance
        """
        filename, command = encoding.to_unicode(filename), unicode(command)
        index = self.filenames.index(filename)
        self.editors[index].append(command)
        self.editors[index].set_cursor_position('eof')
        self.tabwidget.setCurrentIndex(index)

    def get_widget_title(self):
        """Return widget title"""
        return self.tr('History log')

    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.tabwidget.currentWidget()

    def set_actions(self):
        """Setup actions"""
        history_action = create_action(self,
                                       self.tr("History..."),
                                       None,
                                       'history.png',
                                       self.tr("Set history maximum entries"),
                                       triggered=self.change_history_depth)
        font_action = create_action(self,
                                    self.tr("&Font..."),
                                    None,
                                    'font.png',
                                    self.tr("Set shell font style"),
                                    triggered=self.change_font)
        wrap_action = create_action(self,
                                    self.tr("Wrap lines"),
                                    toggled=self.toggle_wrap_mode)
        wrap_action.setChecked(CONF.get(self.ID, 'wrap'))
        self.menu_actions = [history_action, font_action, wrap_action]
        return (self.menu_actions, None)

    def change_history_depth(self):
        "Change history max entries" ""
        depth, valid = QInputDialog.getInteger(
            self, self.tr('History'), self.tr('Maximum entries'),
            CONF.get(self.ID, 'max_entries'), 10, 10000)
        if valid:
            CONF.set(self.ID, 'max_entries', depth)

    def change_font(self):
        """Change console font"""
        font, valid = QFontDialog.getFont(get_font(self.ID), self,
                                          self.tr("Select a new font"))
        if valid:
            for editor in self.editors:
                editor.set_font(font)
            set_font(font, self.ID)

    def toggle_wrap_mode(self, checked):
        """Toggle wrap mode"""
        if self.tabwidget is None:
            return
        for editor in self.editors:
            editor.toggle_wrap_mode(checked)
        CONF.set(self.ID, 'wrap', checked)

    def closing(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        return True

    def refresh(self):
        """Refresh tabwidget"""
        if self.tabwidget.count():
            editor = self.tabwidget.currentWidget()
        else:
            editor = None
        self.find_widget.set_editor(editor)
Пример #31
0
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        
        self.home_url = None
        
        self.webview = WebView(self)
        self.webview.loadFinished.connect(self.load_finished)
        self.webview.titleChanged.connect(self.setWindowTitle)
        self.webview.urlChanged.connect(self.url_changed)
                
        home_button = create_toolbutton(self, icon=ima.icon('home'),
                                        tip=_("Home"),
                                        triggered=self.go_home)
        
        zoom_out_button = action2button(self.webview.zoom_out_action)
        zoom_in_button = action2button(self.webview.zoom_in_action)
        
        pageact2btn = lambda prop: action2button(self.webview.pageAction(prop),
                                                 parent=self.webview)
        refresh_button = pageact2btn(QWebPage.Reload)
        stop_button = pageact2btn(QWebPage.Stop)
        previous_button = pageact2btn(QWebPage.Back)
        next_button = pageact2btn(QWebPage.Forward)
        
        stop_button.setEnabled(False)
        self.webview.loadStarted.connect(lambda: stop_button.setEnabled(True))
        self.webview.loadFinished.connect(lambda: stop_button.setEnabled(False))
        
        progressbar = QProgressBar(self)
        progressbar.setTextVisible(False)
        progressbar.hide()
        self.webview.loadStarted.connect(progressbar.show)
        self.webview.loadProgress.connect(progressbar.setValue)
        self.webview.loadFinished.connect(lambda _state: progressbar.hide())
        
        label = QLabel(self.get_label())
        
        self.url_combo = UrlComboBox(self)
        self.url_combo.valid.connect(self.url_combo_activated)
        self.webview.iconChanged.connect(self.icon_changed)
        
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.webview)
        self.find_widget.hide()

        find_button = create_toolbutton(self, icon=ima.icon('find'),
                                        tip=_("Find text"),
                                        toggled=self.toggle_find_widget)
        self.find_widget.visibility_changed.connect(find_button.setChecked)

        hlayout = QHBoxLayout()
        for widget in (previous_button, next_button, home_button, find_button,
                       label, self.url_combo, zoom_out_button, zoom_in_button,
                       refresh_button, progressbar, stop_button):
            hlayout.addWidget(widget)
        
        layout = QVBoxLayout()
        layout.addLayout(hlayout)
        layout.addWidget(self.webview)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)
Пример #32
0
class HistoryLog(SpyderPluginWidget):
    """
    History log widget
    """
    CONF_SECTION = 'historylog'
    CONFIGWIDGET_CLASS = HistoryConfigPage
    def __init__(self, parent):
        self.tabwidget = None
        self.menu_actions = None
        self.dockviewer = None
        self.wrap_action = None
        
        self.editors = []
        self.filenames = []
        self.icons = []
        
        SpyderPluginWidget.__init__(self, parent)

        # Initialize plugin
        self.initialize_plugin()
        
        self.set_default_color_scheme()
        
        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
                     self.refresh_plugin)
        self.connect(self.tabwidget, SIGNAL('move_data(int,int)'),
                     self.move_tab)
        layout.addWidget(self.tabwidget)

        # Menu as corner widget
        options_button = create_toolbutton(self, text=_("Options"),
                                           icon=get_icon('tooloptions.png'))
        options_button.setPopupMode(QToolButton.InstantPopup)
        menu = QMenu(self)
        add_actions(menu, self.menu_actions)
        options_button.setMenu(menu)
        self.tabwidget.setCornerWidget(options_button)
        
        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.hide()
        self.register_widget_shortcuts("Editor", self.find_widget)
        
        layout.addWidget(self.find_widget)
        
        self.setLayout(layout)
            
    #------ SpyderPluginWidget API ---------------------------------------------    
    def get_plugin_title(self):
        """Return widget title"""
        return _('History log')
    
    def get_plugin_icon(self):
        """Return widget icon"""
        return get_icon('history.png')
    
    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.tabwidget.currentWidget()
        
    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        return True
    
    def refresh_plugin(self):
        """Refresh tabwidget"""
        if self.tabwidget.count():
            editor = self.tabwidget.currentWidget()
        else:
            editor = None
        self.find_widget.set_editor(editor)
        
    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        history_action = create_action(self, _("History..."),
                                       None, 'history.png',
                                       _("Set history maximum entries"),
                                       triggered=self.change_history_depth)
        font_action = create_action(self, _("&Font..."), None,
                                    'font.png', _("Set shell font style"),
                                    triggered=self.change_font)
        self.wrap_action = create_action(self, _("Wrap lines"),
                                    toggled=self.toggle_wrap_mode)
        self.wrap_action.setChecked( self.get_option('wrap') )
        self.menu_actions = [history_action, font_action, self.wrap_action]
        return self.menu_actions

    def on_first_registration(self):
        """Action to be performed on first plugin registration"""
        self.main.tabify_plugins(self.main.extconsole, self)
    
    def register_plugin(self):
        """Register plugin in Spyder's main window"""
        self.connect(self, SIGNAL('focus_changed()'),
                     self.main.plugin_focus_changed)
        self.main.add_dockwidget(self)
#        self.main.console.set_historylog(self)
        self.connect(self.main.console.shell, SIGNAL("refresh()"),
                     self.refresh_plugin)

    def apply_plugin_settings(self, options):
        """Apply configuration file's plugin settings"""
        color_scheme_n = 'color_scheme_name'
        color_scheme_o = get_color_scheme(self.get_option(color_scheme_n))
        font_n = 'plugin_font'
        font_o = self.get_plugin_font()
        wrap_n = 'wrap'
        wrap_o = self.get_option(wrap_n)
        self.wrap_action.setChecked(wrap_o)
        for editor in self.editors:
            if font_n in options:
                scs = color_scheme_o if color_scheme_n in options else None
                editor.set_font(font_o, scs)
            elif color_scheme_n in options:
                editor.set_color_scheme(color_scheme_o)
            if wrap_n in options:
                editor.toggle_wrap_mode(wrap_o)
        
    #------ Private API --------------------------------------------------------
    def move_tab(self, index_from, index_to):
        """
        Move tab (tabs themselves have already been moved by the tabwidget)
        """
        filename = self.filenames.pop(index_from)
        editor = self.editors.pop(index_from)
        icon = self.icons.pop(index_from)
        
        self.filenames.insert(index_to, filename)
        self.editors.insert(index_to, editor)
        self.icons.insert(index_to, icon)
        
    #------ Public API ---------------------------------------------------------
    def add_history(self, filename):
        """
        Add new history tab
        Slot for SIGNAL('add_history(QString)') emitted by shell instance
        """
        filename = encoding.to_unicode_from_fs(filename)
        if filename in self.filenames:
            return
        editor = codeeditor.CodeEditor(self)
        if osp.splitext(filename)[1] == '.py':
            language = 'py'
            icon = get_icon('python.png')
        else:
            language = 'bat'
            icon = get_icon('cmdprompt.png')
        editor.setup_editor(linenumbers=False, language=language,
                            scrollflagarea=False)
        self.connect(editor, SIGNAL("focus_changed()"),
                     lambda: self.emit(SIGNAL("focus_changed()")))
        editor.setReadOnly(True)
        color_scheme = get_color_scheme(self.get_option('color_scheme_name'))
        editor.set_font( self.get_plugin_font(), color_scheme )
        editor.toggle_wrap_mode( self.get_option('wrap') )

        text, _ = encoding.read(filename)
        editor.set_text(text)
        editor.set_cursor_position('eof')
        
        self.editors.append(editor)
        self.filenames.append(filename)
        self.icons.append(icon)
        index = self.tabwidget.addTab(editor, osp.basename(filename))
        self.find_widget.set_editor(editor)
        self.tabwidget.setTabToolTip(index, filename)
        self.tabwidget.setTabIcon(index, icon)
        self.tabwidget.setCurrentIndex(index)
        
    def append_to_history(self, filename, command):
        """
        Append an entry to history filename
        Slot for SIGNAL('append_to_history(QString,QString)')
        emitted by shell instance
        """
        if not is_text_string(filename): # filename is a QString
            filename = to_text_string(filename.toUtf8(), 'utf-8')
        command = to_text_string(command)
        index = self.filenames.index(filename)
        self.editors[index].append(command)
        if self.get_option('go_to_eof'):
            self.editors[index].set_cursor_position('eof')
        self.tabwidget.setCurrentIndex(index)
        
    def change_history_depth(self):
        "Change history max entries"""
        depth, valid = QInputDialog.getInteger(self, _('History'),
                                       _('Maximum entries'),
                                       self.get_option('max_entries'),
                                       10, 10000)
        if valid:
            self.set_option('max_entries', depth)
        
    def change_font(self):
        """Change console font"""
        font, valid = QFontDialog.getFont(self.get_plugin_font(),
                       self, _("Select a new font"))
        if valid:
            for editor in self.editors:
                editor.set_font(font)
            self.set_plugin_font(font)
            
    def toggle_wrap_mode(self, checked):
        """Toggle wrap mode"""
        if self.tabwidget is None:
            return
        for editor in self.editors:
            editor.toggle_wrap_mode(checked)
        self.set_option('wrap', checked)
Пример #33
0
class ReadOnlyEditor(PluginWidget):
    """
    Read-only editor plugin widget
    (see example of child class in docviewer.py)
    """
    def __init__(self, parent):
        PluginWidget.__init__(self, parent)

        # Read-only editor
        self.editor = QsciEditor(self)
        self.editor.setup_editor(linenumbers=False, language='py',
                                 code_folding=True)
        self.connect(self.editor, SIGNAL("focus_changed()"),
                     lambda: self.emit(SIGNAL("focus_changed()")))
        self.editor.setReadOnly(True)
        self.editor.set_font( get_font(self.ID) )
        self.editor.toggle_wrap_mode( CONF.get(self.ID, 'wrap') )
        
        # Add entries to read-only editor context-menu
        font_action = create_action(self, translate("Editor", "&Font..."), None,
                                    'font.png',
                                    translate("Editor", "Set font style"),
                                    triggered=self.change_font)
        wrap_action = create_action(self, translate("Editor", "Wrap lines"),
                                    toggled=self.toggle_wrap_mode)
        wrap_action.setChecked( CONF.get(self.ID, 'wrap') )
        self.editor.readonly_menu.addSeparator()
        add_actions(self.editor.readonly_menu, (font_action, wrap_action))
        
        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.editor)
        self.find_widget.hide()
        
        # <!> Layout will have to be implemented in child class!
    
    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.editor
            
    def set_actions(self):
        """Setup actions"""
        return (None, None)
        
    def closing(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        return True
        
    def change_font(self):
        """Change console font"""
        font, valid = QFontDialog.getFont(get_font(self.ID), self,
                                      translate("Editor", "Select a new font"))
        if valid:
            self.editor.set_font(font)
            set_font(font, self.ID)
            
    def toggle_wrap_mode(self, checked):
        """Toggle wrap mode"""
        self.editor.toggle_wrap_mode(checked)
        CONF.set(self.ID, 'wrap', checked)
class PluginEditorDock(QDockWidget):
    """ A dock for editing plugins.
    """

    template_code = \
        """from spykeutils.plugin import analysis_plugin, gui_data

class SamplePlugin(analysis_plugin.AnalysisPlugin):
    def get_name(self):
        return 'New plugin'

    def start(self, current, selections):
        print 'Plugin started.'
"""

    plugin_saved = pyqtSignal(str)
    file_available = pyqtSignal(bool)

    def __init__(self, title='Plugin Editor', default_path=None, parent=None):
        QDockWidget.__init__(self, title, parent)
        self.setupUi()

        self.thread_manager = ThreadManager(self)
        try:
            self.rope_project = codeeditor.get_rope_project()
        except IOError:  # Might happen when frozen
            self.rope_project = None

        data_path = QDesktopServices.storageLocation(
            QDesktopServices.DataLocation)
        self.default_path = default_path or os.getcwd()
        self.rope_temp_path = os.path.join(data_path, '.temp')
        self.tabs.currentChanged.connect(self._tab_changed)
        self.enter_completion = True

    def _tab_changed(self, tab):
        self.file_available.emit(tab != -1)

    def set_default_path(self, path):
        self.default_path = path

    def populate_groups(self):
        self.filterGroupComboBox.clear()
        self.filterGroupComboBox.addItem('')
        for g in sorted(self.groups[self.filterTypeComboBox.currentText()]):
            self.filterGroupComboBox.addItem(g)

    def setupUi(self):
        self.tabs = QTabWidget()
        self.tabs.setTabsClosable(True)
        self.tabs.tabCloseRequested.connect(self.close_file)
        self.tabs.currentChanged.connect(self._current_editor_changed)
        self.tabs.setMovable(True)
        self.tabs.setDocumentMode(True)

        self.find_widget = FindReplace(self, enable_replace=True)
        self.find_widget.hide()

        self.content_widget = QWidget()
        layout = QGridLayout(self.content_widget)
        layout.addWidget(self.tabs)
        layout.addWidget(self.find_widget)

        self.setWidget(self.content_widget)

        self.setAcceptDrops(True)

    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            for url in event.mimeData().urls():
                p = url.toString()
                if p.startswith('file://') and p.endswith('.py'):
                    event.acceptProposedAction()
                    return

    def dropEvent(self, event):
        for url in event.mimeData().urls():
            p = url.toString()
            if p.startswith('file://') and p.endswith('.py'):
                self.add_file(p[7:])
        event.acceptProposedAction()

    def _setup_editor(self):
        font = QFont('Some font that does not exist')
        font.setStyleHint(font.TypeWriter, font.PreferDefault)
        editor = codeeditor.CodeEditor(self)
        editor.setup_editor(
            linenumbers=True, language='py',
            scrollflagarea=False, codecompletion_enter=self.enter_completion,
            tab_mode=False, edge_line=False, font=font,
            codecompletion_auto=True, go_to_definition=True,
            codecompletion_single=True, calltips=True)
        editor.setCursor(Qt.IBeamCursor)
        editor.horizontalScrollBar().setCursor(Qt.ArrowCursor)
        editor.verticalScrollBar().setCursor(Qt.ArrowCursor)
        return editor

    def _trigger_code_completion(self, automatic):
        editor = self.tabs.currentWidget()
        source_code = unicode(editor.toPlainText())
        offset = editor.get_position('cursor')
        text = editor.get_text('sol', 'cursor')

        if text.startswith('import '):
            comp_list = module_completion(text)
            words = text.split(' ')
            if ',' in words[-1]:
                words = words[-1].split(',')
            if comp_list:
                editor.show_completion_list(comp_list,
                                            completion_text=words[-1],
                                            automatic=automatic)
            return
        elif text.startswith('from '):
            comp_list = module_completion(text)
            words = text.split(' ')
            if '(' in words[-1]:
                words = words[:-2] + words[-1].split('(')
            if ',' in words[-1]:
                words = words[:-2] + words[-1].split(',')
            editor.show_completion_list(comp_list,
                                        completion_text=words[-1],
                                        automatic=automatic)
            return
        elif self.rope_project:
            textlist = self.rope_project.get_completion_list(
                source_code, offset, editor.file_name or self.rope_temp_path)
            if textlist:
                completion_text = re.split(r"[^a-zA-Z0-9_]", text)[-1]
                if text.lstrip().startswith('#') and text.endswith('.'):
                    return
                else:
                    editor.show_completion_list(textlist, completion_text,
                                                automatic)
                return

    def _trigger_calltip(self, position, auto=True):
        if not self.rope_project:
            return

        editor = self.tabs.currentWidget()
        source_code = unicode(editor.toPlainText())
        offset = position

        textlist = self.rope_project.get_calltip_text(
            source_code, offset, editor.file_name or self.rope_temp_path)
        if not textlist:
            return
        obj_fullname = ''
        signatures = []
        cts, doc_text = textlist
        cts = cts.replace('.__init__', '')
        parpos = cts.find('(')
        if parpos:
            obj_fullname = cts[:parpos]
            obj_name = obj_fullname.split('.')[-1]
            cts = cts.replace(obj_fullname, obj_name)
            signatures = [cts]
            if '()' in cts:
                # Either inspected object has no argument, or it's
                # a builtin or an extension -- in this last case
                # the following attempt may succeed:
                signatures = getsignaturesfromtext(doc_text, obj_name)
        if not obj_fullname:
            obj_fullname = codeeditor.get_primary_at(source_code, offset)
        if obj_fullname and not obj_fullname.startswith('self.') and doc_text:
            if signatures:
                signature = signatures[0]
                module = obj_fullname.split('.')[0]
                note = '\n    Function of %s module\n\n' % module
                text = signature + note + doc_text
            else:
                text = doc_text
            editor.show_calltip(obj_fullname, text, at_position=position)

    def _go_to_definition(self, position):
        if not self.rope_project:
            return

        editor = self.tabs.currentWidget()
        source_code = unicode(editor.toPlainText())
        offset = position
        fname, lineno = self.rope_project.get_definition_location(
            source_code, offset, editor.file_name or self.rope_temp_path)
        self.show_position(fname, lineno)

    def show_position(self, file_name, line):
        if not file_name or file_name == '<console>':
            return
        if not self.add_file(file_name):
            return
        if line is None:
            return

        editor = self.tabs.currentWidget()
        cursor = editor.textCursor()
        cursor.setPosition(0, QTextCursor.MoveAnchor)
        cursor.movePosition(QTextCursor.Down, QTextCursor.MoveAnchor,
                            line - 1)
        editor.setTextCursor(cursor)
        editor.raise_()
        editor.setFocus()
        self.raise_()

    def _finalize_new_editor(self, editor, tab_name):
        editor.file_was_changed = False
        editor.textChanged.connect(lambda: self._file_changed(editor))
        self.connect(editor, SIGNAL('trigger_code_completion(bool)'),
                     self._trigger_code_completion)
        self.connect(editor, SIGNAL('trigger_calltip(int)'),
                     self._trigger_calltip)
        self.connect(editor, SIGNAL("go_to_definition(int)"),
                     self._go_to_definition)

        self.tabs.addTab(editor, tab_name)
        self.tabs.setCurrentWidget(editor)

        self.setVisible(True)
        self.raise_()

    def new_file(self):
        editor = self._setup_editor()
        editor.file_name = None
        editor.set_text(self.template_code)
        self._finalize_new_editor(editor, '*New Plugin')

    def add_file(self, file_name):
        if file_name and not file_name.endswith('py'):
            QMessageBox.warning(self, 'Cannot load file',
                                'Only Python files are supported for editing')
            return False

        for i in xrange(self.tabs.count()):
            if file_name == self.tabs.widget(i).file_name:
                self.tabs.setCurrentIndex(i)
                self.raise_()
                return True

        editor = self._setup_editor()
        editor.file_name = file_name
        editor.set_text_from_file(file_name, 'python')

        # Remove extra newline
        text = editor.toPlainText()
        if text.endswith('\n'):
            editor.setPlainText(text[:-1])

        tab_name = os.path.split(file_name.decode('utf-8'))[1]
        self._finalize_new_editor(editor, tab_name)
        return True

    def close_file(self, tab_index):
        if self.tabs.widget(tab_index).file_was_changed and \
                self.tabs.widget(tab_index).file_name:
            fname = os.path.split(self.tabs.widget(tab_index).file_name)[1]
            if not fname:
                fname = 'New Plugin'
            ans = QMessageBox.question(
                self, 'File was changed',
                'Do you want to save "%s" before closing?' % fname,
                QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
            if ans == QMessageBox.Yes:
                return self.save_file(self.tabs.widget(tab_index), False)
            elif ans == QMessageBox.Cancel:
                return False
        self.tabs.removeTab(tab_index)
        return True

    def closeEvent(self, event):
        if not self.close_all():
            event.ignore()
        else:
            event.accept()

    def close_all(self):
        while self.tabs.count():
            if not self.close_file(0):
                return False
        return True

    def get_editor(self, file_name):
        """ Get editor object for a file name. Returns None if no editor
        for the given file name exists.
        """
        for i in xrange(self.tabs.count()):
            if file_name == self.tabs.widget(i).file_name:
                return self.tabs.widget(i)
        return None

    def file_was_changed(self, editor):
        """ Returns if the file for an editor object has been changed.
        """
        if not editor:
            return False
        return editor.file_was_changed

    def _file_changed(self, editor):
        editor.file_was_changed = True

        if not editor.file_name:
            fname = 'New Plugin'
        else:
            fname = os.path.split(editor.file_name)[1]

        text = '*' + fname

        self.tabs.setTabText(self.tabs.indexOf(editor), text)

    def code(self, editor=None):
        """ Returns code for an editor object as a list of strings (one
        for each line).
        """
        if editor is None:
            editor = self.tabs.currentWidget()
        return [editor.get_text_line(l)
                for l in xrange(editor.get_line_count())]

    def code_has_errors(self, editor=None):
        """ Returns if the code from an editor object can be compiled.
        """
        code = '\n'.join(self.code(editor)).encode('UTF-8')
        try:
            compile(code, '<filter>', 'exec')
        except SyntaxError as e:
            return e.msg + ' (Line %d)' % (e.lineno)
        return None

    def save_file(self, editor, force_dialog=False):
        """ Save the file from an editor object.

        :param editor: The editor for which the while should be saved.
        :param bool force_dialog: If True, a "Save as..." dialog will be
            shown even if a file name is associated with the editor.
        """
        if not editor:
            return

        if force_dialog or not editor.file_name:
            d = QFileDialog(
                self, 'Choose where to save file',
                self.tabs.currentWidget().file_name or self.default_path)
            d.setAcceptMode(QFileDialog.AcceptSave)
            d.setNameFilter("Python files (*.py)")
            d.setDefaultSuffix('py')
            if d.exec_():
                file_name = unicode(d.selectedFiles()[0])
            else:
                return False
        else:
            file_name = editor.file_name

        err = self.code_has_errors(editor)
        if err:
            if QMessageBox.warning(
                    self, 'Error saving "%s"' % editor.file_name,
                    'Compile error:\n' + err + '\n\nIf this file contains '
                    'a plugin, it will disappear from the plugin list.\n'
                    'Save anyway?',
                    QMessageBox.Yes | QMessageBox.No) == QMessageBox.No:
                return False

        try:
            f = open(file_name, 'w')
            f.write('\n'.join(self.code(editor)).encode('UTF-8'))
            f.close()
        except IOError, e:
            QMessageBox.critical(
                self, 'Error saving "%s"' % editor.file_name, str(e))
            return False

        editor.file_name = file_name
        editor.file_was_changed = False
        fname = os.path.split(editor.file_name)[1]
        self.tabs.setTabText(self.tabs.indexOf(editor), fname)
        self.plugin_saved.emit(editor.file_name)
        return True
Пример #35
0
class HistoryLog(SpyderPluginWidget):
    """
    History log widget
    """
    ID = 'historylog'
    def __init__(self, parent):
        self.tabwidget = None
        self.menu_actions = None
        self.dockviewer = None
        
        self.editors = []
        self.filenames = []
        self.icons = []
        
        SpyderPluginWidget.__init__(self, parent)
        
        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
                     self.refresh_plugin)
        self.connect(self.tabwidget, SIGNAL('move_data(int,int)'),
                     self.move_tab)
        layout.addWidget(self.tabwidget)

        # Menu as corner widget
        options_button = create_toolbutton(self, text=self.tr("Options"),
                                           icon=get_icon('tooloptions.png'))
        options_button.setPopupMode(QToolButton.InstantPopup)
        menu = QMenu(self)
        add_actions(menu, self.menu_actions)
        options_button.setMenu(menu)
        self.tabwidget.setCornerWidget(options_button)
        
        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.hide()
        layout.addWidget(self.find_widget)
        
        self.setLayout(layout)
            
    #------ SpyderPluginWidget API ---------------------------------------------    
    def get_plugin_title(self):
        """Return widget title"""
        return self.tr('History log')
    
    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.tabwidget.currentWidget()
        
    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        return True
    
    def refresh_plugin(self):
        """Refresh tabwidget"""
        if self.tabwidget.count():
            editor = self.tabwidget.currentWidget()
        else:
            editor = None
        self.find_widget.set_editor(editor)
        
    def get_plugin_actions(self):
        """Setup actions"""
        history_action = create_action(self, self.tr("History..."),
                                       None, 'history.png',
                                       self.tr("Set history maximum entries"),
                                       triggered=self.change_history_depth)
        font_action = create_action(self, self.tr("&Font..."), None,
                                    'font.png', self.tr("Set shell font style"),
                                    triggered=self.change_font)
        wrap_action = create_action(self, self.tr("Wrap lines"),
                                    toggled=self.toggle_wrap_mode)
        wrap_action.setChecked( CONF.get(self.ID, 'wrap') )
        self.menu_actions = [history_action, font_action, wrap_action]
        return (self.menu_actions, None)
        
    #------ Private API --------------------------------------------------------
    def move_tab(self, index_from, index_to):
        """
        Move tab (tabs themselves have already been moved by the tabwidget)
        """
        filename = self.filenames.pop(index_from)
        editor = self.editors.pop(index_from)
        icon = self.icons.pop(index_from)
        
        self.filenames.insert(index_to, filename)
        self.editors.insert(index_to, editor)
        self.icons.insert(index_to, icon)
        
    #------ Public API ---------------------------------------------------------
    def add_history(self, filename):
        """
        Add new history tab
        Slot for SIGNAL('add_history(QString)') emitted by shell instance
        """
        filename = encoding.to_unicode(filename)
        if filename in self.filenames:
            return
        editor = CodeEditor(self)
        if osp.splitext(filename)[1] == '.py':
            language = 'py'
            icon = get_icon('python.png')
        else:
            language = 'bat'
            icon = get_icon('cmdprompt.png')
        editor.setup_editor(linenumbers=False, language=language,
                            code_folding=True, scrollflagarea=False)
        self.connect(editor, SIGNAL("focus_changed()"),
                     lambda: self.emit(SIGNAL("focus_changed()")))
        editor.setReadOnly(True)
        editor.set_font( get_font(self.ID) )
        editor.toggle_wrap_mode( CONF.get(self.ID, 'wrap') )

        text, _ = encoding.read(filename)
        editor.set_text(text)
        editor.set_cursor_position('eof')
        
        self.editors.append(editor)
        self.filenames.append(filename)
        self.icons.append(icon)
        index = self.tabwidget.addTab(editor, osp.basename(filename))
        self.find_widget.set_editor(editor)
        self.tabwidget.setTabToolTip(index, filename)
        self.tabwidget.setTabIcon(index, icon)
        self.tabwidget.setCurrentIndex(index)
        
    def append_to_history(self, filename, command):
        """
        Append an entry to history filename
        Slot for SIGNAL('append_to_history(QString,QString)')
        emitted by shell instance
        """
        filename, command = encoding.to_unicode(filename), unicode(command)
        index = self.filenames.index(filename)
        self.editors[index].append(command)
        self.editors[index].set_cursor_position('eof')
        self.tabwidget.setCurrentIndex(index)
        
    def change_history_depth(self):
        "Change history max entries"""
        depth, valid = QInputDialog.getInteger(self, self.tr('History'),
                                       self.tr('Maximum entries'),
                                       CONF.get(self.ID, 'max_entries'),
                                       10, 10000)
        if valid:
            CONF.set(self.ID, 'max_entries', depth)
        
    def change_font(self):
        """Change console font"""
        font, valid = QFontDialog.getFont(get_font(self.ID),
                       self, self.tr("Select a new font"))
        if valid:
            for editor in self.editors:
                editor.set_font(font)
            set_font(font, self.ID)
            
    def toggle_wrap_mode(self, checked):
        """Toggle wrap mode"""
        if self.tabwidget is None:
            return
        for editor in self.editors:
            editor.toggle_wrap_mode(checked)
        CONF.set(self.ID, 'wrap', checked)
Пример #36
0
class ExternalConsole(PluginWidget):
    """
    Console widget
    """
    ID = 'external_shell'
    location = Qt.RightDockWidgetArea
    def __init__(self, parent, commands=None):
        self.commands = commands
        self.tabwidget = None
        self.menu_actions = None
        self.docviewer = None
        self.historylog = None
        
        self.shells = []
        self.filenames = []
        self.icons = []
        
        PluginWidget.__init__(self, parent)
        
        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
                     self.refresh)
        self.connect(self.tabwidget, SIGNAL("close_tab(int)"),
                     self.tabwidget.removeTab)
        self.connect(self.tabwidget, SIGNAL('move_data(int,int)'),
                     self.move_tab)
        self.close_button = create_toolbutton(self.tabwidget,
                                          icon=get_icon("fileclose.png"),
                                          triggered=self.close_console,
                                          tip=self.tr("Close current console"))
        self.tabwidget.setCornerWidget(self.close_button)
        layout.addWidget(self.tabwidget)
        
        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.hide()
        layout.addWidget(self.find_widget)
        
        self.setLayout(layout)
            
        # Accepting drops
        self.setAcceptDrops(True)
        
    def move_tab(self, index_from, index_to):
        """
        Move tab (tabs themselves have already been moved by the tabwidget)
        """
        filename = self.filenames.pop(index_from)
        shell = self.shells.pop(index_from)
        icon = self.icons.pop(index_from)
        
        self.filenames.insert(index_to, filename)
        self.shells.insert(index_to, shell)
        self.icons.insert(index_to, icon)

    def close_console(self, index=None):
        if not self.tabwidget.count():
            return
        if index is None:
            index = self.tabwidget.currentIndex()
        self.tabwidget.widget(index).close()
        self.tabwidget.removeTab(index)
        self.filenames.pop(index)
        self.shells.pop(index)
        self.icons.pop(index)
        
    def set_historylog(self, historylog):
        """Bind historylog instance to this console"""
        self.historylog = historylog
        
    def set_docviewer(self, docviewer):
        """Bind docviewer instance to this console"""
        self.docviewer = docviewer
        
    def execute_python_code(self, lines):
        """Execute Python code in an already opened Python interpreter"""
        from spyderlib.widgets.externalshell.pythonshell import ExtPyQsciShell
        def execute(index):
            shell = self.tabwidget.widget(index).shell
            if isinstance(shell, ExtPyQsciShell):
                self.tabwidget.setCurrentIndex(index)
                shell.execute_lines(unicode(lines))
                shell.setFocus()
                return True
        # Find the Python shell, starting with current widget:
        current_index = self.tabwidget.currentIndex()
        if current_index == -1:
            # No shell!
            return
        if not execute(current_index):
            for index in self.tabwidget.count():
                execute(index)
        
    def start(self, fname, wdir=None, ask_for_arguments=False,
              interact=False, debug=False, python=True):
        """Start new console"""
        # Note: fname is None <=> Python interpreter
        fname = unicode(fname) if isinstance(fname, QString) else fname
        wdir = unicode(wdir) if isinstance(wdir, QString) else wdir

        if fname is not None and fname in self.filenames:
            index = self.filenames.index(fname)
            if CONF.get(self.ID, 'single_tab'):
                old_shell = self.shells[index]
                if old_shell.is_running():
                    answer = QMessageBox.question(self, self.get_widget_title(),
                        self.tr("%1 is already running in a separate process.\n"
                                "Do you want to kill the process before starting "
                                "a new one?").arg(osp.basename(fname)),
                        QMessageBox.Yes | QMessageBox.Cancel)
                    if answer == QMessageBox.Yes:
                        old_shell.process.kill()
                        old_shell.process.waitForFinished()
                    else:
                        return
                self.close_console(index)
        else:
            index = 0

        # Creating a new external shell
        if python:
            shell = ExternalPythonShell(self, fname, wdir, self.commands,
                                        interact, debug, path=self.main.path)
        else:
            shell = ExternalSystemShell(self, wdir)
        shell.shell.set_font( get_font(self.ID) )
        shell.shell.toggle_wrap_mode( CONF.get(self.ID, 'wrap') )
        shell.shell.set_calltips( CONF.get(self.ID, 'calltips') )
        shell.shell.set_codecompletion( CONF.get(self.ID,
                                                 'autocompletion/enabled') )
        shell.shell.set_codecompletion_enter(CONF.get(self.ID,
                                                 'autocompletion/enter-key'))
        if python:
            shell.shell.set_docviewer(self.docviewer)
        self.historylog.add_history(shell.shell.history_filename)
        self.connect(shell.shell, SIGNAL('append_to_history(QString,QString)'),
                     self.historylog.append_to_history)
        self.connect(shell.shell, SIGNAL("go_to_error(QString)"),
                     self.go_to_error)
        self.connect(shell.shell, SIGNAL("focus_changed()"),
                     lambda: self.emit(SIGNAL("focus_changed()")))
        if python:
            if fname is None:
                name = "Python"
                icon = get_icon('python.png')
            else:
                name = osp.basename(fname)
                icon = get_icon('run.png')
        else:
            name = "Command Window"
            icon = get_icon('cmdprompt.png')
        self.shells.insert(index, shell)
        self.filenames.insert(index, fname)
        self.icons.insert(index, icon)
        if index is None:
            index = self.tabwidget.addTab(shell, name)
        else:
            self.tabwidget.insertTab(index, shell, name)
        
        self.connect(shell, SIGNAL("started()"),
                     lambda sid=id(shell): self.process_started(sid))
        self.connect(shell, SIGNAL("finished()"),
                     lambda sid=id(shell): self.process_finished(sid))
        self.find_widget.set_editor(shell.shell)
        self.tabwidget.setTabToolTip(index, fname if wdir is None else wdir)
        self.tabwidget.setCurrentIndex(index)
        if self.dockwidget and not self.ismaximized:
            self.dockwidget.setVisible(True)
            self.dockwidget.raise_()
        
        # Start process and give focus to console
        shell.start(ask_for_arguments)
        shell.shell.setFocus()
        
    def process_started(self, shell_id):
        for index, shell in enumerate(self.shells):
            if id(shell) == shell_id:
                self.tabwidget.setTabIcon(index, self.icons[index])
        
    def process_finished(self, shell_id):
        for index, shell in enumerate(self.shells):
            if id(shell) == shell_id:
                self.tabwidget.setTabIcon(index, get_icon('terminated.png'))
        
    def get_widget_title(self):
        """Return widget title"""
        return self.tr('External console')
    
    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.tabwidget.currentWidget()
        
    def set_actions(self):
        """Setup actions"""
        interpreter_action = create_action(self,
                            self.tr("Open &interpreter"), None,
                            'python.png', self.tr("Open a Python interpreter"),
                            triggered=self.open_interpreter)
        if os.name == 'nt':
            text = self.tr("Open &command prompt")
            tip = self.tr("Open a Windows command prompt")
        else:
            text = self.tr("Open &command shell")
            tip = self.tr("Open a shell window inside Spyder")
        console_action = create_action(self, text, None, 'cmdprompt.png', tip,
                            triggered=self.open_console)
        run_action = create_action(self,
                            self.tr("&Run..."), None,
                            'run_small.png', self.tr("Run a Python script"),
                            triggered=self.run_script)
        font_action = create_action(self,
                            self.tr("&Font..."), None,
                            'font.png', self.tr("Set shell font style"),
                            triggered=self.change_font)
        wrap_action = create_action(self,
                            self.tr("Wrap lines"),
                            toggled=self.toggle_wrap_mode)
        wrap_action.setChecked( CONF.get(self.ID, 'wrap') )
        calltips_action = create_action(self, self.tr("Balloon tips"),
                            toggled=self.toggle_calltips)
        calltips_action.setChecked( CONF.get(self.ID, 'calltips') )
        codecompletion_action = create_action(self, self.tr("Code completion"),
                            toggled=self.toggle_codecompletion)
        codecompletion_action.setChecked( CONF.get(self.ID,
                                                   'autocompletion/enabled') )
        codecompenter_action = create_action(self,
                                    self.tr("Enter key selects completion"),
                                    toggled=self.toggle_codecompletion_enter)
        codecompenter_action.setChecked( CONF.get(self.ID,
                                                  'autocompletion/enter-key') )
        singletab_action = create_action(self,
                            self.tr("One tab per script"),
                            toggled=self.toggle_singletab)
        singletab_action.setChecked( CONF.get(self.ID, 'single_tab') )
        self.menu_actions = [interpreter_action, run_action, None,
                             font_action, wrap_action, calltips_action,
                             codecompletion_action, codecompenter_action,
                             singletab_action]
        if console_action:
            self.menu_actions.insert(1, console_action)
        return (self.menu_actions, None)
        
    def open_interpreter(self):
        """Open interpreter"""
        self.start(None, os.getcwdu(), False, True, False)
        
    def open_console(self):
        """Open interpreter"""
        self.start(None, os.getcwdu(), False, True, False, python=False)
        
    def run_script(self):
        """Run a Python script"""
        self.emit(SIGNAL('redirect_stdio(bool)'), False)
        filename = QFileDialog.getOpenFileName(self,
                      self.tr("Run Python script"), os.getcwdu(),
                      self.tr("Python scripts")+" (*.py ; *.pyw)")
        self.emit(SIGNAL('redirect_stdio(bool)'), True)
        if filename:
            self.start(unicode(filename), None, False, False, False)
        
    def change_font(self):
        """Change console font"""
        font, valid = QFontDialog.getFont(get_font(self.ID),
                       self, self.tr("Select a new font"))
        if valid:
            for index in range(self.tabwidget.count()):
                self.tabwidget.widget(index).shell.set_font(font)
            set_font(font, self.ID)
            
    def toggle_wrap_mode(self, checked):
        """Toggle wrap mode"""
        if self.tabwidget is None:
            return
        for shell in self.shells:
            shell.shell.toggle_wrap_mode(checked)
        CONF.set(self.ID, 'wrap', checked)
            
    def toggle_calltips(self, checked):
        """Toggle calltips"""
        if self.tabwidget is None:
            return
        for shell in self.shells:
            shell.shell.set_calltips(checked)
        CONF.set(self.ID, 'calltips', checked)
            
    def toggle_codecompletion(self, checked):
        """Toggle code completion"""
        if self.tabwidget is None:
            return
        for shell in self.shells:
            shell.shell.set_codecompletion(checked)
        CONF.set(self.ID, 'autocompletion/enabled', checked)
            
    def toggle_codecompletion_enter(self, checked):
        """Toggle Enter key for code completion"""
        if self.tabwidget is None:
            return
        for shell in self.shells:
            shell.shell.set_codecompletion_enter(checked)
        CONF.set(self.ID, 'autocompletion/enter-key', checked)
        
    def toggle_singletab(self, checked):
        """Toggle single tab mode"""
        CONF.set(self.ID, 'single_tab', checked)
        
    def closing(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        return True
    
    def refresh(self):
        """Refresh tabwidget"""
        if self.tabwidget.count():
            editor = self.tabwidget.currentWidget().shell
            editor.setFocus()
        else:
            editor = None
        self.find_widget.set_editor(editor)
    
    def go_to_error(self, text):
        """Go to error if relevant"""
        match = get_error_match(unicode(text))
        if match:
            fname, lnb = match.groups()
            self.emit(SIGNAL("edit_goto(QString,int)"),
                      osp.abspath(fname), int(lnb))
            
            
    #----Drag and drop
    def __is_python_script(self, qstr):
        """Is it a valid Python script?"""
        fname = unicode(qstr)
        return osp.isfile(fname) and \
               ( fname.endswith('.py') or fname.endswith('.pyw') )
        
    def dragEnterEvent(self, event):
        """Reimplement Qt method
        Inform Qt about the types of data that the widget accepts"""
        source = event.mimeData()
        if source.hasUrls() or \
           ( source.hasText() and self.__is_python_script(source.text()) ):
            event.acceptProposedAction()            
            
    def dropEvent(self, event):
        """Reimplement Qt method
        Unpack dropped data and handle it"""
        source = event.mimeData()
        if source.hasText():
            self.start(source.text())
        elif source.hasUrls():
            files = mimedata2url(source)
            for fname in files:
                if self.__is_python_script(fname):
                    self.start(fname)
        event.acceptProposedAction()
Пример #37
0
class WebBrowser(QWidget):
    """
    Web browser widget
    """

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)

        self.home_url = None
        self.zoom_factor = 1.0

        self.webview = WebView(self)
        self.connect(self.webview, SIGNAL("loadFinished(bool)"), self.load_finished)
        self.connect(self.webview, SIGNAL("titleChanged(QString)"), self.setWindowTitle)
        self.connect(self.webview, SIGNAL("urlChanged(QUrl)"), self.url_changed)

        previous_button = create_toolbutton(
            self, get_icon("previous.png"), tip=self.tr("Previous"), triggered=self.webview.back
        )
        next_button = create_toolbutton(self, get_icon("next.png"), tip=self.tr("Next"), triggered=self.webview.forward)
        home_button = create_toolbutton(self, get_icon("home.png"), tip=self.tr("Home"), triggered=self.go_home)
        zoom_out_button = create_toolbutton(
            self, get_icon("zoom_out.png"), tip=self.tr("Zoom out"), triggered=self.zoom_out
        )
        zoom_in_button = create_toolbutton(
            self, get_icon("zoom_in.png"), tip=self.tr("Zoom in"), triggered=self.zoom_in
        )
        refresh_button = create_toolbutton(self, get_icon("reload.png"), tip=self.tr("Reload"), triggered=self.reload)
        stop_button = create_toolbutton(self, get_icon("stop.png"), tip=self.tr("Stop"), triggered=self.webview.stop)
        stop_button.setEnabled(False)
        self.connect(self.webview, SIGNAL("loadStarted()"), lambda: stop_button.setEnabled(True))
        self.connect(self.webview, SIGNAL("loadFinished(bool)"), lambda: stop_button.setEnabled(False))

        progressbar = QProgressBar(self)
        progressbar.setTextVisible(False)
        progressbar.hide()
        self.connect(self.webview, SIGNAL("loadStarted()"), progressbar.show)
        self.connect(self.webview, SIGNAL("loadProgress(int)"), progressbar.setValue)
        self.connect(self.webview, SIGNAL("loadFinished(bool)"), progressbar.hide)

        label = QLabel(self.get_label())

        self.url_combo = UrlComboBox(self)
        self.connect(self.url_combo, SIGNAL("valid(bool)"), self.url_combo_activated)
        self.connect(self.webview, SIGNAL("iconChanged()"), self.icon_changed)

        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.webview)
        self.find_widget.hide()

        find_button = create_toolbutton(
            self, icon="find.png", tip=translate("FindReplace", "Find text"), toggled=self.toggle_find_widget
        )
        self.connect(self.find_widget, SIGNAL("visibility_changed(bool)"), find_button.setChecked)

        hlayout = QHBoxLayout()
        for widget in (
            previous_button,
            next_button,
            home_button,
            find_button,
            label,
            self.url_combo,
            zoom_out_button,
            zoom_in_button,
            refresh_button,
            progressbar,
            stop_button,
        ):
            hlayout.addWidget(widget)

        layout = QVBoxLayout()
        layout.addLayout(hlayout)
        layout.addWidget(self.webview)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)

    def get_label(self):
        """Return address label text"""
        return self.tr("Address:")

    def apply_zoom_factor(self):
        """Apply zoom factor"""
        if hasattr(self.webview, "setZoomFactor"):
            # Assuming Qt >=v4.5
            self.webview.setZoomFactor(self.zoom_factor)
        else:
            # Qt v4.4
            self.webview.setTextSizeMultiplier(self.zoom_factor)

    def set_zoom_factor(self, zoom_factor):
        """Set zoom factor"""
        self.zoom_factor = zoom_factor
        self.apply_zoom_factor()

    def get_zoom_factor(self):
        """Return zoom factor"""
        return self.zoom_factor

    def zoom_out(self):
        """Zoom out"""
        self.zoom_factor = max(0.1, self.zoom_factor - 0.1)
        self.apply_zoom_factor()

    def zoom_in(self):
        """Zoom in"""
        self.zoom_factor += 0.1
        self.apply_zoom_factor()

    def set_home_url(self, text):
        """Set home URL"""
        self.home_url = QUrl(text)

    def set_url(self, url):
        """Set current URL"""
        self.url_changed(url)
        self.go_to(url)

    def go_to(self, url_or_text):
        """Go to page *address*"""
        if isinstance(url_or_text, basestring):
            url = QUrl(url_or_text)
        else:
            url = url_or_text
        self.webview.load(url)

    def go_home(self):
        """Go to home page"""
        if self.home_url is not None:
            self.set_url(self.home_url)

    def reload(self):
        """Reload page"""
        self.webview.reload()

    def text_to_url(self, text):
        """Convert text address into QUrl object"""
        return QUrl(text)

    def url_combo_activated(self, valid):
        """Load URL from combo box first item"""
        self.go_to(self.text_to_url(self.url_combo.currentText()))

    def load_finished(self, ok):
        if not ok:
            self.webview.setHtml(self.tr("Unable to load page"))

    def url_to_text(self, url):
        """Convert QUrl object to displayed text in combo box"""
        return url.toString()

    def url_changed(self, url):
        """Displayed URL has changed -> updating URL combo box"""
        self.url_combo.add_text(self.url_to_text(url))

    def icon_changed(self):
        self.url_combo.setItemIcon(self.url_combo.currentIndex(), self.webview.icon())
        self.setWindowIcon(self.webview.icon())

    def toggle_find_widget(self, state):
        if state:
            self.find_widget.show()
        else:
            self.find_widget.hide()
Пример #38
0
class HistoryLog(SpyderPluginWidget):
    """
    History log widget
    """
    CONF_SECTION = 'historylog'
    CONFIGWIDGET_CLASS = HistoryConfigPage

    def __init__(self, parent):
        self.tabwidget = None
        self.menu_actions = None
        self.dockviewer = None
        self.wrap_action = None

        self.editors = []
        self.filenames = []
        self.icons = []

        SpyderPluginWidget.__init__(self, parent)

        # Initialize plugin
        self.initialize_plugin()

        self.set_default_color_scheme()

        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
                     self.refresh_plugin)
        self.connect(self.tabwidget, SIGNAL('move_data(int,int)'),
                     self.move_tab)
        layout.addWidget(self.tabwidget)

        # Menu as corner widget
        options_button = create_toolbutton(self,
                                           text=_("Options"),
                                           icon=get_icon('tooloptions.png'))
        options_button.setPopupMode(QToolButton.InstantPopup)
        menu = QMenu(self)
        add_actions(menu, self.menu_actions)
        options_button.setMenu(menu)
        self.tabwidget.setCornerWidget(options_button)

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.hide()
        self.register_widget_shortcuts("Editor", self.find_widget)

        layout.addWidget(self.find_widget)

        self.setLayout(layout)

    #------ SpyderPluginWidget API ---------------------------------------------
    def get_plugin_title(self):
        """Return widget title"""
        return _('History log')

    def get_plugin_icon(self):
        """Return widget icon"""
        return get_icon('history.png')

    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.tabwidget.currentWidget()

    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        return True

    def refresh_plugin(self):
        """Refresh tabwidget"""
        if self.tabwidget.count():
            editor = self.tabwidget.currentWidget()
        else:
            editor = None
        self.find_widget.set_editor(editor)

    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        history_action = create_action(self,
                                       _("History..."),
                                       None,
                                       'history.png',
                                       _("Set history maximum entries"),
                                       triggered=self.change_history_depth)
        font_action = create_action(self,
                                    _("&Font..."),
                                    None,
                                    'font.png',
                                    _("Set shell font style"),
                                    triggered=self.change_font)
        self.wrap_action = create_action(self,
                                         _("Wrap lines"),
                                         toggled=self.toggle_wrap_mode)
        self.wrap_action.setChecked(self.get_option('wrap'))
        self.menu_actions = [history_action, font_action, self.wrap_action]
        return self.menu_actions

    def on_first_registration(self):
        """Action to be performed on first plugin registration"""
        self.main.tabify_plugins(self.main.extconsole, self)

    def register_plugin(self):
        """Register plugin in Spyder's main window"""
        self.connect(self, SIGNAL('focus_changed()'),
                     self.main.plugin_focus_changed)
        self.main.add_dockwidget(self)
        #        self.main.console.set_historylog(self)
        self.connect(self.main.console.shell, SIGNAL("refresh()"),
                     self.refresh_plugin)

    def apply_plugin_settings(self, options):
        """Apply configuration file's plugin settings"""
        color_scheme_n = 'color_scheme_name'
        color_scheme_o = get_color_scheme(self.get_option(color_scheme_n))
        font_n = 'plugin_font'
        font_o = self.get_plugin_font()
        wrap_n = 'wrap'
        wrap_o = self.get_option(wrap_n)
        self.wrap_action.setChecked(wrap_o)
        for editor in self.editors:
            if font_n in options:
                scs = color_scheme_o if color_scheme_n in options else None
                editor.set_font(font_o, scs)
            elif color_scheme_n in options:
                editor.set_color_scheme(color_scheme_o)
            if wrap_n in options:
                editor.toggle_wrap_mode(wrap_o)

    #------ Private API --------------------------------------------------------
    def move_tab(self, index_from, index_to):
        """
        Move tab (tabs themselves have already been moved by the tabwidget)
        """
        filename = self.filenames.pop(index_from)
        editor = self.editors.pop(index_from)
        icon = self.icons.pop(index_from)

        self.filenames.insert(index_to, filename)
        self.editors.insert(index_to, editor)
        self.icons.insert(index_to, icon)

    #------ Public API ---------------------------------------------------------
    def add_history(self, filename):
        """
        Add new history tab
        Slot for SIGNAL('add_history(QString)') emitted by shell instance
        """
        filename = encoding.to_unicode_from_fs(filename)
        if filename in self.filenames:
            return
        editor = codeeditor.CodeEditor(self)
        if osp.splitext(filename)[1] == '.py':
            language = 'py'
            icon = get_icon('python.png')
        else:
            language = 'bat'
            icon = get_icon('cmdprompt.png')
        editor.setup_editor(linenumbers=False,
                            language=language,
                            scrollflagarea=False)
        self.connect(editor, SIGNAL("focus_changed()"),
                     lambda: self.emit(SIGNAL("focus_changed()")))
        editor.setReadOnly(True)
        color_scheme = get_color_scheme(self.get_option('color_scheme_name'))
        editor.set_font(self.get_plugin_font(), color_scheme)
        editor.toggle_wrap_mode(self.get_option('wrap'))

        text, _ = encoding.read(filename)
        editor.set_text(text)
        editor.set_cursor_position('eof')

        self.editors.append(editor)
        self.filenames.append(filename)
        self.icons.append(icon)
        index = self.tabwidget.addTab(editor, osp.basename(filename))
        self.find_widget.set_editor(editor)
        self.tabwidget.setTabToolTip(index, filename)
        self.tabwidget.setTabIcon(index, icon)
        self.tabwidget.setCurrentIndex(index)

    def append_to_history(self, filename, command):
        """
        Append an entry to history filename
        Slot for SIGNAL('append_to_history(QString,QString)')
        emitted by shell instance
        """
        if not is_text_string(filename):  # filename is a QString
            filename = to_text_string(filename.toUtf8(), 'utf-8')
        command = to_text_string(command)
        index = self.filenames.index(filename)
        self.editors[index].append(command)
        if self.get_option('go_to_eof'):
            self.editors[index].set_cursor_position('eof')
        self.tabwidget.setCurrentIndex(index)

    def change_history_depth(self):
        "Change history max entries" ""
        depth, valid = QInputDialog.getInteger(self, _('History'),
                                               _('Maximum entries'),
                                               self.get_option('max_entries'),
                                               10, 10000)
        if valid:
            self.set_option('max_entries', depth)

    def change_font(self):
        """Change console font"""
        font, valid = QFontDialog.getFont(self.get_plugin_font(), self,
                                          _("Select a new font"))
        if valid:
            for editor in self.editors:
                editor.set_font(font)
            self.set_plugin_font(font)

    def toggle_wrap_mode(self, checked):
        """Toggle wrap mode"""
        if self.tabwidget is None:
            return
        for editor in self.editors:
            editor.toggle_wrap_mode(checked)
        self.set_option('wrap', checked)
Пример #39
0
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)

        self.home_url = None
        self.zoom_factor = 1.0

        self.webview = WebView(self)
        self.connect(self.webview, SIGNAL("loadFinished(bool)"), self.load_finished)
        self.connect(self.webview, SIGNAL("titleChanged(QString)"), self.setWindowTitle)
        self.connect(self.webview, SIGNAL("urlChanged(QUrl)"), self.url_changed)

        previous_button = create_toolbutton(
            self, get_icon("previous.png"), tip=self.tr("Previous"), triggered=self.webview.back
        )
        next_button = create_toolbutton(self, get_icon("next.png"), tip=self.tr("Next"), triggered=self.webview.forward)
        home_button = create_toolbutton(self, get_icon("home.png"), tip=self.tr("Home"), triggered=self.go_home)
        zoom_out_button = create_toolbutton(
            self, get_icon("zoom_out.png"), tip=self.tr("Zoom out"), triggered=self.zoom_out
        )
        zoom_in_button = create_toolbutton(
            self, get_icon("zoom_in.png"), tip=self.tr("Zoom in"), triggered=self.zoom_in
        )
        refresh_button = create_toolbutton(self, get_icon("reload.png"), tip=self.tr("Reload"), triggered=self.reload)
        stop_button = create_toolbutton(self, get_icon("stop.png"), tip=self.tr("Stop"), triggered=self.webview.stop)
        stop_button.setEnabled(False)
        self.connect(self.webview, SIGNAL("loadStarted()"), lambda: stop_button.setEnabled(True))
        self.connect(self.webview, SIGNAL("loadFinished(bool)"), lambda: stop_button.setEnabled(False))

        progressbar = QProgressBar(self)
        progressbar.setTextVisible(False)
        progressbar.hide()
        self.connect(self.webview, SIGNAL("loadStarted()"), progressbar.show)
        self.connect(self.webview, SIGNAL("loadProgress(int)"), progressbar.setValue)
        self.connect(self.webview, SIGNAL("loadFinished(bool)"), progressbar.hide)

        label = QLabel(self.get_label())

        self.url_combo = UrlComboBox(self)
        self.connect(self.url_combo, SIGNAL("valid(bool)"), self.url_combo_activated)
        self.connect(self.webview, SIGNAL("iconChanged()"), self.icon_changed)

        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.webview)
        self.find_widget.hide()

        find_button = create_toolbutton(
            self, icon="find.png", tip=translate("FindReplace", "Find text"), toggled=self.toggle_find_widget
        )
        self.connect(self.find_widget, SIGNAL("visibility_changed(bool)"), find_button.setChecked)

        hlayout = QHBoxLayout()
        for widget in (
            previous_button,
            next_button,
            home_button,
            find_button,
            label,
            self.url_combo,
            zoom_out_button,
            zoom_in_button,
            refresh_button,
            progressbar,
            stop_button,
        ):
            hlayout.addWidget(widget)

        layout = QVBoxLayout()
        layout.addLayout(hlayout)
        layout.addWidget(self.webview)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)
Пример #40
0
class WebBrowser(QWidget):
    """
    Web browser widget
    """
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        
        self.home_url = None
        
        self.webview = WebView(self)
        self.webview.loadFinished.connect(self.load_finished)
        self.webview.titleChanged.connect(self.setWindowTitle)
        self.webview.urlChanged.connect(self.url_changed)
                
        home_button = create_toolbutton(self, icon=ima.icon('home'),
                                        tip=_("Home"),
                                        triggered=self.go_home)
        
        zoom_out_button = action2button(self.webview.zoom_out_action)
        zoom_in_button = action2button(self.webview.zoom_in_action)
        
        pageact2btn = lambda prop: action2button(self.webview.pageAction(prop),
                                                 parent=self.webview)
        refresh_button = pageact2btn(QWebPage.Reload)
        stop_button = pageact2btn(QWebPage.Stop)
        previous_button = pageact2btn(QWebPage.Back)
        next_button = pageact2btn(QWebPage.Forward)
        
        stop_button.setEnabled(False)
        self.webview.loadStarted.connect(lambda: stop_button.setEnabled(True))
        self.webview.loadFinished.connect(lambda: stop_button.setEnabled(False))
        
        progressbar = QProgressBar(self)
        progressbar.setTextVisible(False)
        progressbar.hide()
        self.webview.loadStarted.connect(progressbar.show)
        self.webview.loadProgress.connect(progressbar.setValue)
        self.webview.loadFinished.connect(lambda _state: progressbar.hide())
        
        label = QLabel(self.get_label())
        
        self.url_combo = UrlComboBox(self)
        self.url_combo.valid.connect(self.url_combo_activated)
        self.webview.iconChanged.connect(self.icon_changed)
        
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.webview)
        self.find_widget.hide()

        find_button = create_toolbutton(self, icon=ima.icon('find'),
                                        tip=_("Find text"),
                                        toggled=self.toggle_find_widget)
        self.find_widget.visibility_changed.connect(find_button.setChecked)

        hlayout = QHBoxLayout()
        for widget in (previous_button, next_button, home_button, find_button,
                       label, self.url_combo, zoom_out_button, zoom_in_button,
                       refresh_button, progressbar, stop_button):
            hlayout.addWidget(widget)
        
        layout = QVBoxLayout()
        layout.addLayout(hlayout)
        layout.addWidget(self.webview)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)
                
    def get_label(self):
        """Return address label text"""
        return _("Address:")
            
    def set_home_url(self, text):
        """Set home URL"""
        self.home_url = QUrl(text)
        
    def set_url(self, url):
        """Set current URL"""
        self.url_changed(url)
        self.go_to(url)
        
    def go_to(self, url_or_text):
        """Go to page *address*"""
        if is_text_string(url_or_text):
            url = QUrl(url_or_text)
        else:
            url = url_or_text
        self.webview.load(url)

    @Slot()
    def go_home(self):
        """Go to home page"""
        if self.home_url is not None:
            self.set_url(self.home_url)
        
    def text_to_url(self, text):
        """Convert text address into QUrl object"""
        return QUrl(text)
        
    def url_combo_activated(self, valid):
        """Load URL from combo box first item"""
        text = to_text_string(self.url_combo.currentText())
        self.go_to(self.text_to_url(text))
        
    def load_finished(self, ok):
        if not ok:
            self.webview.setHtml(_("Unable to load page"))
            
    def url_to_text(self, url):
        """Convert QUrl object to displayed text in combo box"""
        return url.toString()
            
    def url_changed(self, url):
        """Displayed URL has changed -> updating URL combo box"""
        self.url_combo.add_text(self.url_to_text(url))
            
    def icon_changed(self):
        self.url_combo.setItemIcon(self.url_combo.currentIndex(),
                                   self.webview.icon())
        self.setWindowIcon(self.webview.icon())

    @Slot(bool)
    def toggle_find_widget(self, state):
        if state:
            self.find_widget.show()
        else:
            self.find_widget.hide()
Пример #41
0
class PluginEditorDock(QDockWidget):
    """ A dock for editing plugins.
    """

    template_code = \
        """from spykeutils.plugin import analysis_plugin, gui_data

class SamplePlugin(analysis_plugin.AnalysisPlugin):
    def get_name(self):
        return 'New plugin'

    def start(self, current, selections):
        print 'Plugin started.'
"""

    plugin_saved = pyqtSignal(str)
    file_available = pyqtSignal(bool)

    def __init__(self, title='Plugin Editor', default_path=None, parent=None):
        QDockWidget.__init__(self, title, parent)
        self.setupUi()

        self.thread_manager = ThreadManager(self)
        try:
            self.rope_project = codeeditor.get_rope_project()
        except (IOError, AttributeError):  # Might happen when frozen
            self.rope_project = None

        data_path = QDesktopServices.storageLocation(
            QDesktopServices.DataLocation)
        self.default_path = default_path or os.getcwd()
        self.rope_temp_path = os.path.join(data_path, '.temp')
        self.tabs.currentChanged.connect(self._tab_changed)
        self.enter_completion = True

    def _tab_changed(self, tab):
        self.file_available.emit(tab != -1)

    def set_default_path(self, path):
        self.default_path = path

    def populate_groups(self):
        self.filterGroupComboBox.clear()
        self.filterGroupComboBox.addItem('')
        for g in sorted(self.groups[self.filterTypeComboBox.currentText()]):
            self.filterGroupComboBox.addItem(g)

    def setupUi(self):
        self.tabs = QTabWidget()
        self.tabs.setTabsClosable(True)
        self.tabs.tabCloseRequested.connect(self.close_file)
        self.tabs.currentChanged.connect(self._current_editor_changed)
        self.tabs.setMovable(True)
        self.tabs.setDocumentMode(True)

        self.find_widget = FindReplace(self, enable_replace=True)
        self.find_widget.hide()

        self.content_widget = QWidget()
        layout = QGridLayout(self.content_widget)
        layout.addWidget(self.tabs)
        layout.addWidget(self.find_widget)

        self.setWidget(self.content_widget)

        self.setAcceptDrops(True)

    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            for url in event.mimeData().urls():
                p = url.toString()
                if p.startswith('file://') and p.endswith('.py'):
                    event.acceptProposedAction()
                    return

    def dropEvent(self, event):
        for url in event.mimeData().urls():
            p = url.toString()
            if p.startswith('file://') and p.endswith('.py'):
                self.add_file(p[7:])
        event.acceptProposedAction()

    def _setup_editor(self):
        font = QFont('Some font that does not exist')
        font.setStyleHint(font.TypeWriter, font.PreferDefault)
        editor = codeeditor.CodeEditor(self)
        try:
            editor.setup_editor(linenumbers=True,
                                language='py',
                                scrollflagarea=False,
                                codecompletion_enter=self.enter_completion,
                                tab_mode=False,
                                edge_line=False,
                                font=font,
                                codecompletion_auto=True,
                                go_to_definition=True,
                                codecompletion_single=True,
                                calltips=True)
        except TypeError:  # codecompletion_single is gone in 2.3.0
            editor.setup_editor(linenumbers=True,
                                language='py',
                                scrollflagarea=False,
                                codecompletion_enter=self.enter_completion,
                                tab_mode=False,
                                edge_line=False,
                                font=font,
                                codecompletion_auto=True,
                                go_to_definition=True,
                                calltips=True)
        editor.setCursor(Qt.IBeamCursor)
        editor.horizontalScrollBar().setCursor(Qt.ArrowCursor)
        editor.verticalScrollBar().setCursor(Qt.ArrowCursor)
        return editor

    def _trigger_code_completion(self, automatic):
        editor = self.tabs.currentWidget()
        source_code = unicode(editor.toPlainText())
        offset = editor.get_position('cursor')
        text = editor.get_text('sol', 'cursor')

        if text.startswith('import '):
            comp_list = module_completion(text)
            words = text.split(' ')
            if ',' in words[-1]:
                words = words[-1].split(',')
            if comp_list:
                editor.show_completion_list(comp_list,
                                            completion_text=words[-1],
                                            automatic=automatic)
            return
        elif text.startswith('from '):
            comp_list = module_completion(text)
            words = text.split(' ')
            if '(' in words[-1]:
                words = words[:-2] + words[-1].split('(')
            if ',' in words[-1]:
                words = words[:-2] + words[-1].split(',')
            editor.show_completion_list(comp_list,
                                        completion_text=words[-1],
                                        automatic=automatic)
            return
        elif self.rope_project:
            textlist = self.rope_project.get_completion_list(
                source_code, offset, editor.file_name or self.rope_temp_path)
            if textlist:
                completion_text = re.split(r"[^a-zA-Z0-9_]", text)[-1]
                if text.lstrip().startswith('#') and text.endswith('.'):
                    return
                else:
                    editor.show_completion_list(textlist, completion_text,
                                                automatic)
                return

    def _trigger_calltip(self, position, auto=True):
        if not self.rope_project:
            return

        editor = self.tabs.currentWidget()
        source_code = unicode(editor.toPlainText())
        offset = position

        textlist = self.rope_project.get_calltip_text(
            source_code, offset, editor.file_name or self.rope_temp_path)
        if not textlist:
            return
        obj_fullname = ''
        signatures = []
        cts, doc_text = textlist
        cts = cts.replace('.__init__', '')
        parpos = cts.find('(')
        if parpos:
            obj_fullname = cts[:parpos]
            obj_name = obj_fullname.split('.')[-1]
            cts = cts.replace(obj_fullname, obj_name)
            signatures = [cts]
            if '()' in cts:
                # Either inspected object has no argument, or it's
                # a builtin or an extension -- in this last case
                # the following attempt may succeed:
                signatures = getsignaturesfromtext(doc_text, obj_name)
        if not obj_fullname:
            obj_fullname = codeeditor.get_primary_at(source_code, offset)
        if obj_fullname and not obj_fullname.startswith('self.') and doc_text:
            if signatures:
                signature = signatures[0]
                module = obj_fullname.split('.')[0]
                note = '\n    Function of %s module\n\n' % module
                text = signature + note + doc_text
            else:
                text = doc_text
            editor.show_calltip(obj_fullname, text, at_position=position)

    def _go_to_definition(self, position):
        if not self.rope_project:
            return

        editor = self.tabs.currentWidget()
        source_code = unicode(editor.toPlainText())
        offset = position
        fname, lineno = self.rope_project.get_definition_location(
            source_code, offset, editor.file_name or self.rope_temp_path)
        self.show_position(fname, lineno)

    def show_position(self, file_name, line):
        if not file_name or file_name == '<console>':
            return
        if not self.add_file(file_name):
            return
        if line is None:
            return

        editor = self.tabs.currentWidget()
        cursor = editor.textCursor()
        cursor.setPosition(0, QTextCursor.MoveAnchor)
        cursor.movePosition(QTextCursor.Down, QTextCursor.MoveAnchor, line - 1)
        editor.setTextCursor(cursor)
        editor.raise_()
        editor.setFocus()
        self.raise_()

    def _finalize_new_editor(self, editor, tab_name):
        editor.file_was_changed = False
        editor.textChanged.connect(lambda: self._file_changed(editor))
        self.connect(editor, SIGNAL('trigger_code_completion(bool)'),
                     self._trigger_code_completion)
        self.connect(editor, SIGNAL('trigger_calltip(int)'),
                     self._trigger_calltip)
        self.connect(editor, SIGNAL("go_to_definition(int)"),
                     self._go_to_definition)

        self.tabs.addTab(editor, tab_name)
        self.tabs.setCurrentWidget(editor)

        self.setVisible(True)
        self.raise_()

    def new_file(self):
        editor = self._setup_editor()
        editor.file_name = None
        editor.set_text(self.template_code)
        self._finalize_new_editor(editor, '*New Plugin')

    def add_file(self, file_name):
        if file_name and not file_name.endswith('py'):
            QMessageBox.warning(self, 'Cannot load file',
                                'Only Python files are supported for editing')
            return False

        for i in xrange(self.tabs.count()):
            if file_name == self.tabs.widget(i).file_name:
                self.tabs.setCurrentIndex(i)
                self.raise_()
                return True

        editor = self._setup_editor()
        editor.file_name = file_name
        editor.set_text_from_file(file_name, 'python')

        # Remove extra newline
        text = editor.toPlainText()
        if text.endswith('\n'):
            editor.setPlainText(text[:-1])

        tab_name = os.path.split(file_name.decode('utf-8'))[1]
        self._finalize_new_editor(editor, tab_name)
        return True

    def close_file(self, tab_index):
        if self.tabs.widget(tab_index).file_was_changed and \
                self.tabs.widget(tab_index).file_name:
            fname = os.path.split(self.tabs.widget(tab_index).file_name)[1]
            if not fname:
                fname = 'New Plugin'
            ans = QMessageBox.question(
                self, 'File was changed',
                'Do you want to save "%s" before closing?' % fname,
                QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
            if ans == QMessageBox.Yes:
                return self.save_file(self.tabs.widget(tab_index), False)
            elif ans == QMessageBox.Cancel:
                return False
        self.tabs.removeTab(tab_index)
        return True

    def closeEvent(self, event):
        if not self.close_all():
            event.ignore()
        else:
            event.accept()

    def close_all(self):
        while self.tabs.count():
            if not self.close_file(0):
                return False
        return True

    def get_editor(self, file_name):
        """ Get editor object for a file name. Returns None if no editor
        for the given file name exists.
        """
        for i in xrange(self.tabs.count()):
            if file_name == self.tabs.widget(i).file_name:
                return self.tabs.widget(i)
        return None

    def file_was_changed(self, editor):
        """ Returns if the file for an editor object has been changed.
        """
        if not editor:
            return False
        return editor.file_was_changed

    def _file_changed(self, editor):
        editor.file_was_changed = True

        if not editor.file_name:
            fname = 'New Plugin'
        else:
            fname = os.path.split(editor.file_name)[1]

        text = '*' + fname

        self.tabs.setTabText(self.tabs.indexOf(editor), text)

    def code(self, editor=None):
        """ Returns code for an editor object as a list of strings (one
        for each line).
        """
        if editor is None:
            editor = self.tabs.currentWidget()
        return [
            editor.get_text_line(l) for l in xrange(editor.get_line_count())
        ]

    def code_has_errors(self, editor=None):
        """ Returns if the code from an editor object can be compiled.
        """
        code = '\n'.join(self.code(editor)).encode('UTF-8')
        try:
            compile(code, '<filter>', 'exec')
        except SyntaxError as e:
            return e.msg + ' (Line %d)' % (e.lineno)
        return None

    def save_file(self, editor, force_dialog=False):
        """ Save the file from an editor object.

        :param editor: The editor for which the while should be saved.
        :param bool force_dialog: If True, a "Save as..." dialog will be
            shown even if a file name is associated with the editor.
        """
        if not editor:
            return

        if force_dialog or not editor.file_name:
            d = QFileDialog(
                self, 'Choose where to save file',
                self.tabs.currentWidget().file_name or self.default_path)
            d.setAcceptMode(QFileDialog.AcceptSave)
            d.setNameFilter("Python files (*.py)")
            d.setDefaultSuffix('py')
            if d.exec_():
                file_name = unicode(d.selectedFiles()[0])
            else:
                return False
        else:
            file_name = editor.file_name

        err = self.code_has_errors(editor)
        if err:
            if QMessageBox.warning(
                    self, 'Error saving "%s"' % editor.file_name,
                    'Compile error:\n' + err + '\n\nIf this file contains '
                    'a plugin, it will disappear from the plugin list.\n'
                    'Save anyway?',
                    QMessageBox.Yes | QMessageBox.No) == QMessageBox.No:
                return False

        try:
            f = open(file_name, 'w')
            f.write('\n'.join(self.code(editor)).encode('UTF-8'))
            f.close()
        except IOError, e:
            QMessageBox.critical(self, 'Error saving "%s"' % editor.file_name,
                                 str(e))
            return False

        editor.file_name = file_name
        editor.file_was_changed = False
        fname = os.path.split(editor.file_name)[1]
        self.tabs.setTabText(self.tabs.indexOf(editor), fname)
        self.plugin_saved.emit(editor.file_name)
        return True
Пример #42
0
class Console(SpyderPluginWidget):
    """
    Console widget
    """
    CONF_SECTION = 'internal_console'
    focus_changed = Signal()
    redirect_stdio = Signal(bool)
    edit_goto = Signal(str, int, str)
    
    def __init__(self, parent=None, namespace=None, commands=[], message=None,
                 exitfunc=None, profile=False, multithreaded=False):
        if PYQT5:
            SpyderPluginWidget.__init__(self, parent, main = parent)
        else:
            SpyderPluginWidget.__init__(self, parent)
        
        debug_print("    ..internal console: initializing")
        self.dialog_manager = DialogManager()

        # Shell
        light_background = self.get_option('light_background')
        self.shell = InternalShell(parent, namespace, commands, message,
                                   self.get_option('max_line_count'),
                                   self.get_plugin_font(), exitfunc, profile,
                                   multithreaded,
                                   light_background=light_background)
        self.shell.status.connect(lambda msg: self.show_message.emit(msg, 0))
        self.shell.go_to_error.connect(self.go_to_error)
        self.shell.focus_changed.connect(lambda: self.focus_changed.emit())

        # Redirecting some signals:
        self.shell.redirect_stdio.connect(lambda state:
                                          self.redirect_stdio.emit(state))
        
        # Initialize plugin
        self.initialize_plugin()

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.shell)
        self.find_widget.hide()
        self.register_widget_shortcuts("Editor", self.find_widget)

        # Main layout
        layout = QVBoxLayout()
        layout.addWidget(self.shell)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)
        
        # Parameters
        self.shell.toggle_wrap_mode(self.get_option('wrap'))
            
        # Accepting drops
        self.setAcceptDrops(True)
        
    #------ Private API --------------------------------------------------------
    def set_historylog(self, historylog):
        """Bind historylog instance to this console
        Not used anymore since v2.0"""
        historylog.add_history(self.shell.history_filename)
        self.shell.append_to_history.connect(historylog.append_to_history)
        
    def set_inspector(self, inspector):
        """Bind inspector instance to this console"""
        self.shell.inspector = inspector

    #------ SpyderPluginWidget API ---------------------------------------------
    def get_plugin_title(self):
        """Return widget title"""
        return _('Internal console')
    
    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.shell
        
    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        self.dialog_manager.close_all()
        self.shell.exit_interpreter()
        return True
        
    def refresh_plugin(self):
        pass
    
    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        quit_action = create_action(self, _("&Quit"),
                                    icon='exit.png', tip=_("Quit"),
                                    triggered=self.quit)
        self.register_shortcut(quit_action, "_", "Quit", "Ctrl+Q")
        run_action = create_action(self, _("&Run..."), None,
                            'run_small.png', _("Run a Python script"),
                            triggered=self.run_script)
        environ_action = create_action(self,
                            _("Environment variables..."),
                            icon = 'environ.png',
                            tip=_("Show and edit environment variables"
                                        " (for current session)"),
                            triggered=self.show_env)
        syspath_action = create_action(self,
                            _("Show sys.path contents..."),
                            icon = 'syspath.png',
                            tip=_("Show (read-only) sys.path"),
                            triggered=self.show_syspath)
        buffer_action = create_action(self,
                            _("Buffer..."), None,
                            tip=_("Set maximum line count"),
                            triggered=self.change_max_line_count)
        font_action = create_action(self,
                            _("&Font..."), None,
                            'font.png', _("Set shell font style"),
                            triggered=self.change_font)
        exteditor_action = create_action(self,
                            _("External editor path..."), None, None,
                            _("Set external editor executable path"),
                            triggered=self.change_exteditor)
        wrap_action = create_action(self,
                            _("Wrap lines"),
                            toggled=self.toggle_wrap_mode)
        wrap_action.setChecked(self.get_option('wrap'))
        calltips_action = create_action(self, _("Display balloon tips"),
            toggled=self.toggle_calltips)
        calltips_action.setChecked(self.get_option('calltips'))
        codecompletion_action = create_action(self,
                                          _("Automatic code completion"),
                                          toggled=self.toggle_codecompletion)
        codecompletion_action.setChecked(self.get_option('codecompletion/auto'))
        codecompenter_action = create_action(self,
                                    _("Enter key selects completion"),
                                    toggled=self.toggle_codecompletion_enter)
        codecompenter_action.setChecked(self.get_option(
                                                    'codecompletion/enter_key'))
        
        option_menu = QMenu(_("Internal console settings"), self)
        option_menu.setIcon(get_icon('tooloptions.png'))
        add_actions(option_menu, (buffer_action, font_action, wrap_action,
                                  calltips_action, codecompletion_action,
                                  codecompenter_action, exteditor_action))
                    
        plugin_actions = [None, run_action, environ_action, syspath_action,
                          option_menu, None, quit_action]
        
        # Add actions to context menu
        add_actions(self.shell.menu, plugin_actions)
        
        return plugin_actions
    
    def register_plugin(self):
        """Register plugin in Spyder's main window"""
        self.focus_changed.connect(self.main.plugin_focus_changed)
        self.main.add_dockwidget(self)
        # Connecting the following signal once the dockwidget has been created:
        self.shell.traceback_available.connect(self.traceback_available)
    
    def traceback_available(self):
        """Traceback is available in the internal console: showing the 
        internal console automatically to warn the user"""
        if CONF.get('main', 'show_internal_console_if_traceback', False):
            self.dockwidget.show()
            self.dockwidget.raise_()
        
    #------ Public API ---------------------------------------------------------
    @Slot()
    def quit(self):
        """Quit mainwindow"""
        self.main.close()
    
    @Slot()
    def show_env(self):
        """Show environment variables"""
        self.dialog_manager.show(EnvDialog())
    
    @Slot()
    def show_syspath(self):
        """Show sys.path"""
        editor = DictEditor()
        editor.setup(sys.path, title="sys.path", readonly=True,
                     width=600, icon='syspath.png')
        self.dialog_manager.show(editor)
    
    @Slot()
    def run_script(self, filename=None, silent=False, set_focus=False,
                   args=None):
        """Run a Python script"""
        if filename is None:
            self.shell.interpreter.restore_stds()
            filename, _selfilter = getopenfilename(self, _("Run Python script"),
                   getcwd(), _("Python scripts")+" (*.py ; *.pyw ; *.ipy)")
            self.shell.interpreter.redirect_stds()
            if filename:
                os.chdir( osp.dirname(filename) )
                filename = osp.basename(filename)
            else:
                return
        debug_print(args)
        filename = osp.abspath(filename)
        rbs = remove_backslashes
        command = "runfile('%s', args='%s')" % (rbs(filename), rbs(args))
        if set_focus:
            self.shell.setFocus()
        if self.dockwidget and not self.ismaximized:
            self.dockwidget.setVisible(True)
            self.dockwidget.raise_()
        self.shell.write(command+'\n')
        self.shell.run_command(command)

            
    def go_to_error(self, text):
        """Go to error if relevant"""
        match = get_error_match(to_text_string(text))
        if match:
            fname, lnb = match.groups()
            self.edit_script(fname, int(lnb))
            
    def edit_script(self, filename=None, goto=-1):
        """Edit script"""
        # Called from InternalShell
        if not hasattr(self, 'main') \
           or not hasattr(self.main, 'editor'):
            self.shell.external_editor(filename, goto)
            return
        if filename is not None:
            self.edit_goto.emit(osp.abspath(filename), goto, '')
        
    def execute_lines(self, lines):
        """Execute lines and give focus to shell"""
        self.shell.execute_lines(to_text_string(lines))
        self.shell.setFocus()
    
    @Slot()
    def change_font(self):
        """Change console font"""
        font, valid = QFontDialog.getFont(self.get_plugin_font(),
                       self, _("Select a new font"))
        if valid:
            self.shell.set_font(font)
            self.set_plugin_font(font)
    
    @Slot()
    def change_max_line_count(self):
        "Change maximum line count"""
        mlc, valid = QInputDialog.getInteger(self, _('Buffer'),
                                           _('Maximum line count'),
                                           self.get_option('max_line_count'),
                                           0, 1000000)
        if valid:
            self.shell.setMaximumBlockCount(mlc)
            self.set_option('max_line_count', mlc)

    @Slot()
    def change_exteditor(self):
        """Change external editor path"""
        path, valid = QInputDialog.getText(self, _('External editor'),
                          _('External editor executable path:'),
                          QLineEdit.Normal,
                          self.get_option('external_editor/path'))
        if valid:
            self.set_option('external_editor/path', to_text_string(path))
    
    @Slot(bool)
    def toggle_wrap_mode(self, checked):
        """Toggle wrap mode"""
        self.shell.toggle_wrap_mode(checked)
        self.set_option('wrap', checked)
    
    @Slot(bool)
    def toggle_calltips(self, checked):
        """Toggle calltips"""
        self.shell.set_calltips(checked)
        self.set_option('calltips', checked)
    
    @Slot(bool)
    def toggle_codecompletion(self, checked):
        """Toggle automatic code completion"""
        self.shell.set_codecompletion_auto(checked)
        self.set_option('codecompletion/auto', checked)
    
    @Slot(bool)
    def toggle_codecompletion_enter(self, checked):
        """Toggle Enter key for code completion"""
        self.shell.set_codecompletion_enter(checked)
        self.set_option('codecompletion/enter_key', checked)
                
    #----Drag and drop                    
    def dragEnterEvent(self, event):
        """Reimplement Qt method
        Inform Qt about the types of data that the widget accepts"""
        source = event.mimeData()
        if source.hasUrls():
            if mimedata2url(source):
                event.acceptProposedAction()
            else:
                event.ignore()
        elif source.hasText():
            event.acceptProposedAction()
            
    def dropEvent(self, event):
        """Reimplement Qt method
        Unpack dropped data and handle it"""
        source = event.mimeData()
        if source.hasUrls():
            pathlist = mimedata2url(source)
            self.shell.drop_pathlist(pathlist)
        elif source.hasText():
            lines = to_text_string(source.text())
            self.shell.set_cursor_position('eof')
            self.shell.execute_lines(lines)
        event.acceptProposedAction()
Пример #43
0
class ExternalConsole(PluginWidget):
    """
    Console widget
    """
    ID = 'external_shell'
    location = Qt.RightDockWidgetArea

    def __init__(self, parent, commands=None):
        self.commands = commands
        self.tabwidget = None
        self.menu_actions = None
        self.docviewer = None
        self.historylog = None

        self.shells = []
        self.filenames = []
        self.icons = []

        PluginWidget.__init__(self, parent)

        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
                     self.refresh)
        self.connect(self.tabwidget, SIGNAL("close_tab(int)"),
                     self.tabwidget.removeTab)
        self.connect(self.tabwidget, SIGNAL('move_data(int,int)'),
                     self.move_tab)
        self.close_button = create_toolbutton(
            self.tabwidget,
            icon=get_icon("fileclose.png"),
            triggered=self.close_console,
            tip=self.tr("Close current console"))
        self.tabwidget.setCornerWidget(self.close_button)
        layout.addWidget(self.tabwidget)

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.hide()
        layout.addWidget(self.find_widget)

        self.setLayout(layout)

        # Accepting drops
        self.setAcceptDrops(True)

    def move_tab(self, index_from, index_to):
        """
        Move tab (tabs themselves have already been moved by the tabwidget)
        """
        filename = self.filenames.pop(index_from)
        shell = self.shells.pop(index_from)
        icon = self.icons.pop(index_from)

        self.filenames.insert(index_to, filename)
        self.shells.insert(index_to, shell)
        self.icons.insert(index_to, icon)

    def close_console(self, index=None):
        if not self.tabwidget.count():
            return
        if index is None:
            index = self.tabwidget.currentIndex()
        self.tabwidget.widget(index).close()
        self.tabwidget.removeTab(index)
        self.filenames.pop(index)
        self.shells.pop(index)
        self.icons.pop(index)

    def set_historylog(self, historylog):
        """Bind historylog instance to this console"""
        self.historylog = historylog

    def set_docviewer(self, docviewer):
        """Bind docviewer instance to this console"""
        self.docviewer = docviewer

    def execute_python_code(self, lines):
        """Execute Python code in an already opened Python interpreter"""
        from spyderlib.widgets.externalshell.pythonshell import ExtPyQsciShell

        def execute(index):
            shell = self.tabwidget.widget(index).shell
            if isinstance(shell, ExtPyQsciShell):
                self.tabwidget.setCurrentIndex(index)
                shell.execute_lines(unicode(lines))
                shell.setFocus()
                return True

        # Find the Python shell, starting with current widget:
        current_index = self.tabwidget.currentIndex()
        if current_index == -1:
            # No shell!
            return
        if not execute(current_index):
            for index in self.tabwidget.count():
                execute(index)

    def start(self,
              fname,
              wdir=None,
              ask_for_arguments=False,
              interact=False,
              debug=False,
              python=True):
        """Start new console"""
        # Note: fname is None <=> Python interpreter
        fname = unicode(fname) if isinstance(fname, QString) else fname
        wdir = unicode(wdir) if isinstance(wdir, QString) else wdir

        if fname is not None and fname in self.filenames:
            index = self.filenames.index(fname)
            if CONF.get(self.ID, 'single_tab'):
                old_shell = self.shells[index]
                if old_shell.is_running():
                    answer = QMessageBox.question(
                        self, self.get_widget_title(),
                        self.tr(
                            "%1 is already running in a separate process.\n"
                            "Do you want to kill the process before starting "
                            "a new one?").arg(osp.basename(fname)),
                        QMessageBox.Yes | QMessageBox.Cancel)
                    if answer == QMessageBox.Yes:
                        old_shell.process.kill()
                        old_shell.process.waitForFinished()
                    else:
                        return
                self.close_console(index)
        else:
            index = 0

        # Creating a new external shell
        if python:
            shell = ExternalPythonShell(self,
                                        fname,
                                        wdir,
                                        self.commands,
                                        interact,
                                        debug,
                                        path=self.main.path)
        else:
            shell = ExternalSystemShell(self, wdir)
        shell.shell.set_font(get_font(self.ID))
        shell.shell.toggle_wrap_mode(CONF.get(self.ID, 'wrap'))
        shell.shell.set_calltips(CONF.get(self.ID, 'calltips'))
        shell.shell.set_codecompletion(
            CONF.get(self.ID, 'autocompletion/enabled'))
        shell.shell.set_codecompletion_enter(
            CONF.get(self.ID, 'autocompletion/enter-key'))
        if python:
            shell.shell.set_docviewer(self.docviewer)
        self.historylog.add_history(shell.shell.history_filename)
        self.connect(shell.shell, SIGNAL('append_to_history(QString,QString)'),
                     self.historylog.append_to_history)
        self.connect(shell.shell, SIGNAL("go_to_error(QString)"),
                     self.go_to_error)
        self.connect(shell.shell, SIGNAL("focus_changed()"),
                     lambda: self.emit(SIGNAL("focus_changed()")))
        if python:
            if fname is None:
                name = "Python"
                icon = get_icon('python.png')
            else:
                name = osp.basename(fname)
                icon = get_icon('run.png')
        else:
            name = "Command Window"
            icon = get_icon('cmdprompt.png')
        self.shells.insert(index, shell)
        self.filenames.insert(index, fname)
        self.icons.insert(index, icon)
        if index is None:
            index = self.tabwidget.addTab(shell, name)
        else:
            self.tabwidget.insertTab(index, shell, name)

        self.connect(shell,
                     SIGNAL("started()"),
                     lambda sid=id(shell): self.process_started(sid))
        self.connect(shell,
                     SIGNAL("finished()"),
                     lambda sid=id(shell): self.process_finished(sid))
        self.find_widget.set_editor(shell.shell)
        self.tabwidget.setTabToolTip(index, fname if wdir is None else wdir)
        self.tabwidget.setCurrentIndex(index)
        if self.dockwidget and not self.ismaximized:
            self.dockwidget.setVisible(True)
            self.dockwidget.raise_()

        # Start process and give focus to console
        shell.start(ask_for_arguments)
        shell.shell.setFocus()

    def process_started(self, shell_id):
        for index, shell in enumerate(self.shells):
            if id(shell) == shell_id:
                self.tabwidget.setTabIcon(index, self.icons[index])

    def process_finished(self, shell_id):
        for index, shell in enumerate(self.shells):
            if id(shell) == shell_id:
                self.tabwidget.setTabIcon(index, get_icon('terminated.png'))

    def get_widget_title(self):
        """Return widget title"""
        return self.tr('External console')

    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.tabwidget.currentWidget()

    def set_actions(self):
        """Setup actions"""
        interpreter_action = create_action(
            self,
            self.tr("Open &interpreter"),
            None,
            'python.png',
            self.tr("Open a Python interpreter"),
            triggered=self.open_interpreter)
        if os.name == 'nt':
            text = self.tr("Open &command prompt")
            tip = self.tr("Open a Windows command prompt")
        else:
            text = self.tr("Open &command shell")
            tip = self.tr("Open a shell window inside Spyder")
        console_action = create_action(self,
                                       text,
                                       None,
                                       'cmdprompt.png',
                                       tip,
                                       triggered=self.open_console)
        run_action = create_action(self,
                                   self.tr("&Run..."),
                                   None,
                                   'run_small.png',
                                   self.tr("Run a Python script"),
                                   triggered=self.run_script)
        font_action = create_action(self,
                                    self.tr("&Font..."),
                                    None,
                                    'font.png',
                                    self.tr("Set shell font style"),
                                    triggered=self.change_font)
        wrap_action = create_action(self,
                                    self.tr("Wrap lines"),
                                    toggled=self.toggle_wrap_mode)
        wrap_action.setChecked(CONF.get(self.ID, 'wrap'))
        calltips_action = create_action(self,
                                        self.tr("Balloon tips"),
                                        toggled=self.toggle_calltips)
        calltips_action.setChecked(CONF.get(self.ID, 'calltips'))
        codecompletion_action = create_action(
            self,
            self.tr("Code completion"),
            toggled=self.toggle_codecompletion)
        codecompletion_action.setChecked(
            CONF.get(self.ID, 'autocompletion/enabled'))
        codecompenter_action = create_action(
            self,
            self.tr("Enter key selects completion"),
            toggled=self.toggle_codecompletion_enter)
        codecompenter_action.setChecked(
            CONF.get(self.ID, 'autocompletion/enter-key'))
        singletab_action = create_action(self,
                                         self.tr("One tab per script"),
                                         toggled=self.toggle_singletab)
        singletab_action.setChecked(CONF.get(self.ID, 'single_tab'))
        self.menu_actions = [
            interpreter_action, run_action, None, font_action, wrap_action,
            calltips_action, codecompletion_action, codecompenter_action,
            singletab_action
        ]
        if console_action:
            self.menu_actions.insert(1, console_action)
        return (self.menu_actions, None)

    def open_interpreter(self):
        """Open interpreter"""
        self.start(None, os.getcwdu(), False, True, False)

    def open_console(self):
        """Open interpreter"""
        self.start(None, os.getcwdu(), False, True, False, python=False)

    def run_script(self):
        """Run a Python script"""
        self.emit(SIGNAL('redirect_stdio(bool)'), False)
        filename = QFileDialog.getOpenFileName(
            self, self.tr("Run Python script"), os.getcwdu(),
            self.tr("Python scripts") + " (*.py ; *.pyw)")
        self.emit(SIGNAL('redirect_stdio(bool)'), True)
        if filename:
            self.start(unicode(filename), None, False, False, False)

    def change_font(self):
        """Change console font"""
        font, valid = QFontDialog.getFont(get_font(self.ID), self,
                                          self.tr("Select a new font"))
        if valid:
            for index in range(self.tabwidget.count()):
                self.tabwidget.widget(index).shell.set_font(font)
            set_font(font, self.ID)

    def toggle_wrap_mode(self, checked):
        """Toggle wrap mode"""
        if self.tabwidget is None:
            return
        for shell in self.shells:
            shell.shell.toggle_wrap_mode(checked)
        CONF.set(self.ID, 'wrap', checked)

    def toggle_calltips(self, checked):
        """Toggle calltips"""
        if self.tabwidget is None:
            return
        for shell in self.shells:
            shell.shell.set_calltips(checked)
        CONF.set(self.ID, 'calltips', checked)

    def toggle_codecompletion(self, checked):
        """Toggle code completion"""
        if self.tabwidget is None:
            return
        for shell in self.shells:
            shell.shell.set_codecompletion(checked)
        CONF.set(self.ID, 'autocompletion/enabled', checked)

    def toggle_codecompletion_enter(self, checked):
        """Toggle Enter key for code completion"""
        if self.tabwidget is None:
            return
        for shell in self.shells:
            shell.shell.set_codecompletion_enter(checked)
        CONF.set(self.ID, 'autocompletion/enter-key', checked)

    def toggle_singletab(self, checked):
        """Toggle single tab mode"""
        CONF.set(self.ID, 'single_tab', checked)

    def closing(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        return True

    def refresh(self):
        """Refresh tabwidget"""
        if self.tabwidget.count():
            editor = self.tabwidget.currentWidget().shell
            editor.setFocus()
        else:
            editor = None
        self.find_widget.set_editor(editor)

    def go_to_error(self, text):
        """Go to error if relevant"""
        match = get_error_match(unicode(text))
        if match:
            fname, lnb = match.groups()
            self.emit(SIGNAL("edit_goto(QString,int)"), osp.abspath(fname),
                      int(lnb))

    #----Drag and drop
    def __is_python_script(self, qstr):
        """Is it a valid Python script?"""
        fname = unicode(qstr)
        return osp.isfile(fname) and \
               ( fname.endswith('.py') or fname.endswith('.pyw') )

    def dragEnterEvent(self, event):
        """Reimplement Qt method
        Inform Qt about the types of data that the widget accepts"""
        source = event.mimeData()
        if source.hasUrls() or \
           ( source.hasText() and self.__is_python_script(source.text()) ):
            event.acceptProposedAction()

    def dropEvent(self, event):
        """Reimplement Qt method
        Unpack dropped data and handle it"""
        source = event.mimeData()
        if source.hasText():
            self.start(source.text())
        elif source.hasUrls():
            files = mimedata2url(source)
            for fname in files:
                if self.__is_python_script(fname):
                    self.start(fname)
        event.acceptProposedAction()