Example #1
0
    def swapRows(self, row1, row2):
        if row1 == row2:
            return

        #Ensure row1<row2
        if row2 > row1:
            row1, row2 = row2, row1

        self.beginMoveRows(QtCore.QModelIndex(), row1, row1,
                           QtCore.QModelIndex(), row2)
        project1 = self.config.projects[row1]
        self.config.projects[row1] = self.config.projects[row2]
        self.config.projects[row2] = project1
        self.endMoveRows()
Example #2
0
def loadIcons():
    """ loadIcons()
    Load all icons in the icon dir.
    """
    # Get directory containing the icons
    iconDir = os.path.join(iep.iepDir, 'resources', 'icons')

    # Construct other icons
    dummyIcon = IconArtist().finish()
    iep.icons = ssdf.new()
    for fname in os.listdir(iconDir):
        if fname.startswith('iep'):
            continue
        if fname.endswith('.png'):
            try:
                # Short and full name
                name = fname.split('.')[0]
                ffname = os.path.join(iconDir, fname)
                # Create icon
                icon = QtGui.QIcon()
                icon.addFile(ffname, QtCore.QSize(16, 16))
                # Store
                iep.icons[name] = icon
            except Exception as err:
                iep.icons[name] = dummyIcon
                print('Could not load icon %s: %s' % (fname, str(err)))
Example #3
0
    def __init__(self, parent):
        QtGui.QWidget.__init__(self, parent)

        # Create tool button
        self._up = QtGui.QToolButton(self)
        style = QtGui.qApp.style()
        self._up.setIcon(style.standardIcon(style.SP_ArrowLeft))
        self._up.setIconSize(QtCore.QSize(16, 16))

        # Create "path" line edit
        self._line = QtGui.QLineEdit(self)
        self._line.setReadOnly(True)
        self._line.setStyleSheet("QLineEdit { background:#ddd; }")
        self._line.setFocusPolicy(QtCore.Qt.NoFocus)

        # Create tree
        self._tree = WorkspaceTree(self)

        # Set layout
        layout = QtGui.QHBoxLayout()
        layout.addWidget(self._up, 0)
        layout.addWidget(self._line, 1)
        #
        mainLayout = QtGui.QVBoxLayout(self)
        mainLayout.addLayout(layout, 0)
        mainLayout.addWidget(self._tree, 1)
        mainLayout.setSpacing(2)
        self.setLayout(mainLayout)

        # Bind up event
        self._up.pressed.connect(self._tree._proxy.goUp)
Example #4
0
    def __init__(self, parent):
        QtGui.QWidget.__init__(self, parent)

        # Create text field, checkbox, and button
        self._text = QtGui.QLineEdit(self)
        self._printBut = QtGui.QPushButton("Print", self)

        # Create options button
        self._options = QtGui.QToolButton(self)
        self._options.setIcon(iep.icons.wrench)
        self._options.setIconSize(QtCore.QSize(16, 16))
        self._options.setPopupMode(self._options.InstantPopup)
        self._options.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)

        # Create options menu
        self._options._menu = QtGui.QMenu()
        self._options.setMenu(self._options._menu)

        # Create browser
        self._browser = QtGui.QTextBrowser(self)
        self._browser_text = initText

        # Create two sizers
        self._sizer1 = QtGui.QVBoxLayout(self)
        self._sizer2 = QtGui.QHBoxLayout()

        # Put the elements together
        self._sizer2.addWidget(self._text, 4)
        self._sizer2.addWidget(self._printBut, 0)
        self._sizer2.addStretch(1)
        self._sizer2.addWidget(self._options, 2)
        #
        self._sizer1.addLayout(self._sizer2, 0)
        self._sizer1.addWidget(self._browser, 1)
        #
        self._sizer1.setSpacing(2)
        self.setLayout(self._sizer1)

        # Set config
        toolId = self.__class__.__name__.lower()
        self._config = config = iep.config.tools[toolId]
        #
        if not hasattr(config, 'smartNewlines'):
            config.smartNewlines = True
        if not hasattr(config, 'fontSize'):
            if sys.platform == 'darwin':
                config.fontSize = 12
            else:
                config.fontSize = 10

        # Create callbacks
        self._text.returnPressed.connect(self.queryDoc)
        self._printBut.clicked.connect(self.printDoc)
        #
        self._options.pressed.connect(self.onOptionsPress)
        self._options._menu.triggered.connect(self.onOptionMenuTiggered)

        # Start
        self.setText()  # Set default text
        self.onOptionsPress()  # Fill menu
Example #5
0
 def __init__(self, parent):
     QtGui.QWidget.__init__(self, parent)
     
     # create toolbar
     self._toolbar = QtGui.QToolBar(self)
     self._toolbar.setMaximumHeight(25)
     self._toolbar.setIconSize(QtCore.QSize(16,16))
     
     # create stack
     self._stack = QtGui.QStackedWidget(self)
     
     # Populate toolbar
     self._shellButton = ShellControl(self._toolbar, self._stack)
     self._dbc = DebugControl(self._toolbar)
     #
     self._toolbar.addWidget(self._shellButton)
     self._toolbar.addSeparator()
     # self._toolbar.addWidget(self._dbc) -> delayed, see addContextMenu()
     
     # widget layout
     layout = QtGui.QVBoxLayout()
     layout.setSpacing(0)
     layout.setContentsMargins(0, 0, 0, 0)
     layout.addWidget(self._toolbar)
     layout.addWidget(self._stack)
     self.setLayout(layout)
     
     # make callbacks
     self._stack.currentChanged.connect(self.onCurrentChanged)
Example #6
0
    def icon(self, type_or_info):
        class MyFileInfo(QtCore.QFileInfo):
            def __getattr__(self, attr):
                print(attr)
                return getattr(QtCore.QFileInfo, attr)

        if isinstance(type_or_info, QtCore.QFileInfo):
            if type_or_info.isDir():
                # Use folder icon
                icon = QtGui.QFileIconProvider.icon(self, self.Folder)
                # Add overlay?
                path = type_or_info.absoluteFilePath()
                if os.path.isdir(os.path.join(path, '.hg')):
                    icon = self._addOverlays(icon, iep.icons.overlay_hg)
                elif os.path.isdir(os.path.join(path, '.svn')):
                    icon = self._addOverlays(icon, iep.icons.overlay_svn)
                # Done
                return icon
            else:
                # Get extension
                root, ext = os.path.splitext(type_or_info.fileName())
                # Create dummy file in iep user dir
                dir = os.path.join(iep.appDataDir, 'dummyFiles')
                path = os.path.join(dir, 'dummy' + ext)
                if not os.path.exists(dir):
                    os.makedirs(dir)
                f = open(path, 'wb')
                f.close()
                # Use that file
                type_or_info = QtCore.QFileInfo(path)

        # Call base method
        return QtGui.QFileIconProvider.icon(self, type_or_info)
Example #7
0
    def __init__(self):
        QtGui.QToolButton.__init__(self)

        # Init
        self.setIconSize(QtCore.QSize(*self.SIZE))
        self.setStyleSheet(
            "QToolButton{ border:none; padding:0px; margin:0px; }")
        self.setIcon(self.getCrossIcon1())
Example #8
0
def getLocale(languageName):
    """ getLocale(languageName)
    Get the QtCore.QLocale object for the given language (as a string).
    """

    # Apply synonyms
    languageName = LANGUAGE_SYNONYMS.get(languageName, languageName)

    # Select language in qt terms
    qtLanguage = LANGUAGES.get(languageName, None)
    if qtLanguage is None:
        raise ValueError('Unknown language')

    # Return locale
    if isinstance(qtLanguage, tuple):
        return QtCore.QLocale(*qtLanguage)
    else:
        return QtCore.QLocale(qtLanguage)
Example #9
0
    def find(self, forward=True, wrapAround=True):
        """ The main find method.
        Returns True if a match was found. """
        
        # Reset timer
        self.autoHideTimerReset()
        
        # get editor
        editor = self.parent().getCurrentEditor()
        if not editor:
            return       
        
        # find flags
        flags = QtGui.QTextDocument.FindFlags()
        if self._caseCheck.isChecked():
            flags |= QtGui.QTextDocument.FindCaseSensitively

        if self._wholeWord.isChecked():
            flags |= QtGui.QTextDocument.FindWholeWords
        
        if not forward:
            flags |= QtGui.QTextDocument.FindBackward

        
        # focus
        self.selectFindText()
        
        # get text to find
        needle = self._findText.text()
        if self._regExp.isChecked():
            #Make needle a QRegExp; speciffy case-sensitivity here since the
            #FindCaseSensitively flag is ignored when finding using a QRegExp
            needle = QtCore.QRegExp(needle,
                QtCore.Qt.CaseSensitive if self._caseCheck.isChecked() else
                QtCore.Qt.CaseInsensitive)
        
        # estblish start position
        cursor = editor.textCursor()
        result = editor.document().find(needle, cursor, flags)
        
        if not result.isNull():
            editor.setTextCursor(result)
        elif wrapAround:
            self.notifyPassBeginEnd()
            #Move cursor to start or end of document
            if forward:
                cursor.movePosition(cursor.Start)
            else:
                cursor.movePosition(cursor.End)
            #Try again
            result = editor.document().find(needle, cursor, flags)
            if not result.isNull():
                editor.setTextCursor(result)
        
        # done
        editor.setFocus()
        return not result.isNull()
Example #10
0
    def __init__(self, parent, **kwds):
        super().__init__(parent, showLineNumbers=True, **kwds)

        # Init filename and name
        self._filename = ''
        self._name = '<TMP>'

        # View settings
        self.setShowWhitespace(iep.config.view.showWhitespace)
        #TODO: self.setViewWrapSymbols(view.showWrapSymbols)
        self.setShowLineEndings(iep.config.view.showLineEndings)
        self.setShowIndentationGuides(iep.config.view.showIndentationGuides)
        #
        self.setWrap(bool(iep.config.view.wrap))
        self.setHighlightCurrentLine(iep.config.view.highlightCurrentLine)
        self.setLongLineIndicatorPosition(iep.config.view.edgeColumn)
        #TODO: self.setFolding( int(view.codeFolding)*5 )
        # bracematch is set in baseTextCtrl, since it also applies to shells
        # dito for zoom and tabWidth

        # Set line endings to default
        self.lineEndings = iep.config.settings.defaultLineEndings

        # Set encoding to default
        self.encoding = 'UTF-8'

        # Modification time to test file change
        self._modifyTime = 0

        self.modificationChanged.connect(self._onModificationChanged)

        # To see whether the doc has changed to update the parser.
        self.textChanged.connect(self._onModified)

        # This timer is used to hide the marker that shows which code is executed
        self._showRunCursorTimer = QtCore.QTimer()

        # Add context menu (the offset is to prevent accidental auto-clicking)
        self._menu = EditorContextMenu(self)
        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(lambda p: self._menu.popup(
            self.mapToGlobal(p) + QtCore.QPoint(0, 3)))
Example #11
0
    def __init__(self, parent, info):
        BaseShell.__init__(self, parent)

        # Get standard info if not given.
        if info is None and iep.config.shellConfigs2:
            info = iep.config.shellConfigs2[0]
        if not info:
            info = KernelInfo(None)

        # Store info so we can reuse it on a restart
        self._info = info

        # For the editor to keep track of attempted imports
        self._importAttempts = []

        # To keep track of the response for introspection
        self._currentCTO = None
        self._currentACO = None

        # Write buffer to store messages in for writing
        self._write_buffer = None

        # Create timer to keep polling any results
        # todo: Maybe use yoton events to process messages as they arrive.
        # I tried this briefly, but it seemd to be less efficient because
        # messages are not so much bach-processed anymore. We should decide
        # on either method.
        self._timer = QtCore.QTimer(self)
        self._timer.setInterval(POLL_TIMER_INTERVAL)  # ms
        self._timer.setSingleShot(False)
        self._timer.timeout.connect(self.poll)
        self._timer.start()

        # Add context menu
        self._menu = ShellContextMenu(shell=self, parent=self)
        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(lambda p: self._menu.popup(
            self.mapToGlobal(p + QtCore.QPoint(0, 3))))

        # Start!
        self.resetVariables()
        self.connectToKernel(info)
Example #12
0
    def __init__(self, *args, padding=(4, 4, 6, 6), preventEqualTexts=True):
        QtGui.QTabBar.__init__(self, *args)

        # Put tab widget in document mode
        self.setDocumentMode(True)

        # Widget needs to draw its background (otherwise Mac has a dark bg)
        self.setDrawBase(False)
        if sys.platform == 'darwin':
            self.setAutoFillBackground(True)

        # Set whether we want to prevent eliding for names that start the same.
        self._preventEqualTexts = preventEqualTexts

        # Allow moving tabs around
        self.setMovable(True)

        # Get padding
        if isinstance(padding, (int, float)):
            padding = padding, padding, padding, padding
        elif isinstance(padding, (tuple, list)):
            pass
        else:
            raise ValueError('Invalid value for padding.')

        # Set style sheet
        stylesheet = STYLESHEET
        stylesheet = stylesheet.replace('PADDING_TOP', str(padding[0]))
        stylesheet = stylesheet.replace('PADDING_BOTTOM', str(padding[1]))
        stylesheet = stylesheet.replace('PADDING_LEFT', str(padding[2]))
        stylesheet = stylesheet.replace('PADDING_RIGHT', str(padding[3]))
        self.setStyleSheet(stylesheet)

        # We do our own eliding
        self.setElideMode(QtCore.Qt.ElideNone)

        # Make tabs wider if there's plenty space?
        self.setExpanding(False)

        # If there's not enough space, use scroll buttons
        self.setUsesScrollButtons(True)

        # When a tab is removed, select previous
        self.setSelectionBehaviorOnRemove(self.SelectPreviousTab)

        # Init alignment parameters
        self._alignWidth = MIN_NAME_WIDTH  # Width in characters
        self._alignWidthIsReducing = False  # Whether in process of reducing

        # Create timer for aligning
        self._alignTimer = QtCore.QTimer(self)
        self._alignTimer.setInterval(10)
        self._alignTimer.setSingleShot(True)
        self._alignTimer.timeout.connect(self._alignRecursive)
Example #13
0
def setLanguage(languageName):
    """ setLanguage(languageName)
    Set the language for the app. Loads qt and iep translations.
    Returns the QLocale instance to pass to the main widget.
    """

    # Get locale
    locale = getLocale(languageName)

    # Get paths were language files are
    qtTransPath = str(
        QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.TranslationsPath))
    iepTransPath = os.path.join(iep.iepDir, 'resources', 'translations')

    # Get possible names for language files
    # (because Qt's .tr files may not have the language component.)
    localeName1 = locale.name()
    localeName2 = localeName1.split('_')[0]

    # Uninstall translators
    if not hasattr(QtCore, '_translators'):
        QtCore._translators = []
    for trans in QtCore._translators:
        QtGui.QApplication.removeTranslator(trans)

    # The default language
    if localeName1 == 'C':
        return locale

    # Set Qt translations
    # Note that the translator instances must be stored
    # Note that the load() method is very forgiving with the file name
    for what, where in [('qt', qtTransPath), ('iep', iepTransPath)]:
        trans = QtCore.QTranslator()
        # Try loading both names
        for localeName in [localeName1, localeName2]:
            success = trans.load(what + '_' + localeName + '.tr', where)
            if success:
                QtGui.QApplication.installTranslator(trans)
                QtCore._translators.append(trans)
                print('loading %s %s: ok' % (what, languageName))
                break
        else:
            print('loading %s %s: failed' % (what, languageName))

    # Done
    return locale
Example #14
0
    def __init__(self, objectWithIcon):

        self._objectWithIcon = objectWithIcon

        # Motion properties
        self._index = 0
        self._level = 0
        self._count = 0  #  to count number of iters in level 1

        # Prepare blob pixmap
        self._blob = self._createBlobPixmap()
        self._legs = self._createLegsPixmap()

        # Create timer
        self._timer = QtCore.QTimer(None)
        self._timer.setInterval(150)
        self._timer.setSingleShot(False)
        self._timer.timeout.connect(self.onTimer)
Example #15
0
    def __init__(self):
        QtGui.QToolButton.__init__(self)

        # Init
        self.setIconSize(QtCore.QSize(*self.SIZE))
        self.setStyleSheet("QToolButton{ border: none; }")

        # Create arrow pixmaps
        self._menuarrow1 = self._createMenuArrowPixmap(0)
        self._menuarrow2 = self._createMenuArrowPixmap(70)
        self._menuarrow = self._menuarrow1

        # Variable to keep icon
        self._icon = None

        # Variable to keep track of when the mouse was pressed, so that
        # we can allow dragging as well as clicking the menu.
        self._menuPressed = False
Example #16
0
 def __init__(self, *args, **kwds):
     super().__init__(*args, **kwds)
     
     # Set font and zooming
     self.setFont(iep.config.view.fontname)
     self.setZoom(iep.config.view.zoom)
     
     # Create timer for autocompletion delay
     self._delayTimer = QtCore.QTimer(self)
     self._delayTimer.setSingleShot(True)
     self._delayTimer.timeout.connect(self._introspectNow)
     
     # For buffering autocompletion and calltip info
     self._callTipBuffer_name = ''
     self._callTipBuffer_time = 0
     self._callTipBuffer_result = ''
     self._autoCompBuffer_name = ''
     self._autoCompBuffer_time = 0
     self._autoCompBuffer_result = []
     
     # The string with names given to SCI_AUTOCSHOW
     self._autoCompNameString = ''
     
     # Set autocomp accept key to default if necessary.
     # We force it to be string (see issue 134)
     if not isinstance(iep.config.settings.autoComplete_acceptKeys, str):
         iep.config.settings.autoComplete_acceptKeys = 'Tab'
     
     # Set autocomp accept keys
     qtKeys = []
     for key in iep.config.settings.autoComplete_acceptKeys.split(' '):
         if len(key) > 1:
             key = 'Key_' + key[0].upper() + key[1:].lower()
             qtkey = getattr(QtCore.Qt, key, None)
         else:
             qtkey = ord(key)
         if qtkey:
             qtKeys.append(qtkey)
     self.setAutoCompletionAcceptKeys(*qtKeys)
     
     self.completer().highlighted.connect(self.updateHelp)
     self.setIndentUsingSpaces(iep.config.settings.defaultIndentUsingSpaces)
     self.setIndentWidth(iep.config.settings.defaultIndentWidth) 
     self.setAutocompletPopupSize(*iep.config.view.autoComplete_popupSize) 
Example #17
0
    def contextMenuEvent(self, event):
        """ contextMenuEvent(event)
        Show the context menu. 
        """

        QtGui.QTreeView.contextMenuEvent(self, event)

        # Get if an item is selected
        item = self.currentItem()
        if not item:
            return

        # Create menu
        self._menu.clear()
        for a in ['Show namespace', 'Show help', 'Delete']:
            action = self._menu.addAction(a)
            parts = splitName(self._proxy._name)
            parts.append(item.text(0))
            action._objectName = joinName(parts)
            action._item = item

        # Show
        self._menu.popup(QtGui.QCursor.pos() + QtCore.QPoint(3, 3))
Example #18
0
def loadAppIcons():
    """ loadAppIcons()
    Load the application iconsr.
    """
    # Get directory containing the icons
    appiconDir = os.path.join(iep.iepDir, 'resources', 'appicons')

    # Determine template for filename of the application icon-files.
    # Use the Pyzo logo if in pyzo_mode.
    if iep.pyzo_mode:
        fnameT = 'pyzologo{}.png'
    else:
        fnameT = 'ieplogo{}.png'

    # Construct application icon. Include a range of resolutions. Note that
    # Qt somehow does not use the highest possible res on Linux/Gnome(?), even
    # the logo of qt-designer when alt-tabbing looks a bit ugly.
    iep.icon = QtGui.QIcon()
    for sze in [16, 32, 48, 64, 128, 256]:
        fname = os.path.join(appiconDir, fnameT.format(sze))
        if os.path.isfile(fname):
            iep.icon.addFile(fname, QtCore.QSize(sze, sze))

    # Set as application icon. This one is used as the default for all
    # windows of the application.
    QtGui.qApp.setWindowIcon(iep.icon)

    # Construct another icon to show when the current shell is busy
    artist = IconArtist(iep.icon)  # extracts the 16x16 version
    artist.setPenColor('#0B0')
    for x in range(11, 16):
        d = x - 11  # runs from 0 to 4
        artist.addLine(x, 6 + d, x, 15 - d)
    pm = artist.finish().pixmap(16, 16)
    #
    iep.iconRunning = QtGui.QIcon(iep.icon)
    iep.iconRunning.addPixmap(pm)  # Change only 16x16 icon
Example #19
0
 def __init__(self, parent, shellStack):
     QtGui.QToolButton.__init__(self, parent)
     
     # Store reference of shell stack
     self._shellStack = shellStack
     
     # Keep reference of actions corresponding to shells
     self._shellActions = []
     
     # Set text and tooltip
     self.setText('Warming up ...')
     self.setToolTip("Click to select shell.")
     self.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
     self.setPopupMode(self.InstantPopup)
     
     # Set icon
     self._iconMaker = ShellIconMaker(self)
     self._iconMaker.updateIcon('busy') # Busy initializing
     
     # Create timer
     self._elapsedTimesTimer = QtCore.QTimer(self)
     self._elapsedTimesTimer.setInterval(200)
     self._elapsedTimesTimer.setSingleShot(False)
     self._elapsedTimesTimer.timeout.connect(self.onElapsedTimesTimer)
Example #20
0
    def __init__(self, *args):
        QtGui.QToolButton.__init__(self, *args)

        # Init
        self.setIconSize(QtCore.QSize(*self.SIZE))
        self.setStyleSheet("QToolButton{ border: none; }")
Example #21
0
    def _alignRecursive(self):
        """ _alignRecursive()
        
        Recursive alignment of the items. The alignment process
        should be initiated from alignTabs().
        
        """

        # Only if visible
        if not self.isVisible():
            return

        # Get tab bar and number of items
        N = self.count()

        # Get right edge of last tab and left edge of corner widget
        pos1 = self.tabRect(0).topLeft()
        pos2 = self.tabRect(N - 1).topRight()
        cornerWidget = self.parent().cornerWidget()
        if cornerWidget:
            pos3 = cornerWidget.pos()
        else:
            pos3 = QtCore.QPoint(self.width(), 0)
        x1 = pos1.x()
        x2 = pos2.x()
        x3 = pos3.x()
        alignMargin = x3 - (x2 - x1) - 3  # Must be positive (has margin)

        # Are the tabs too wide?
        if alignMargin < 0:
            # Tabs extend beyond corner widget

            # Reduce width then
            self._alignWidth -= 1
            self._alignWidth = max(self._alignWidth, MIN_NAME_WIDTH)

            # Apply
            self._setMaxWidthOfAllItems()
            self._alignWidthIsReducing = True

            # Try again if there's still room for reduction
            if self._alignWidth > MIN_NAME_WIDTH:
                self._alignTimer.start()

        elif alignMargin > 10 and not self._alignWidthIsReducing:
            # Gap between tabs and corner widget is a bit large

            # Increase width then
            self._alignWidth += 1
            self._alignWidth = min(self._alignWidth, MAX_NAME_WIDTH)

            # Apply
            itemsElided = self._setMaxWidthOfAllItems()

            # Try again if there's still room for increment
            if itemsElided and self._alignWidth < MAX_NAME_WIDTH:
                self._alignTimer.start()
                #self._alignTimer.timeout.emit()

        else:
            pass  # margin is good
Example #22
0
class PythonShell(BaseShell):
    """ The PythonShell class implements the python part of the shell
    by connecting to a remote process that runs a Python interpreter.
    """

    # Emits when the status string has changed or when receiving a new prompt
    stateChanged = QtCore.Signal(BaseShell)

    # Emits when the debug status is changed
    debugStateChanged = QtCore.Signal(BaseShell)

    def __init__(self, parent, info):
        BaseShell.__init__(self, parent)

        # Get standard info if not given.
        if info is None and iep.config.shellConfigs2:
            info = iep.config.shellConfigs2[0]
        if not info:
            info = KernelInfo(None)

        # Store info so we can reuse it on a restart
        self._info = info

        # For the editor to keep track of attempted imports
        self._importAttempts = []

        # To keep track of the response for introspection
        self._currentCTO = None
        self._currentACO = None

        # Write buffer to store messages in for writing
        self._write_buffer = None

        # Create timer to keep polling any results
        # todo: Maybe use yoton events to process messages as they arrive.
        # I tried this briefly, but it seemd to be less efficient because
        # messages are not so much bach-processed anymore. We should decide
        # on either method.
        self._timer = QtCore.QTimer(self)
        self._timer.setInterval(POLL_TIMER_INTERVAL)  # ms
        self._timer.setSingleShot(False)
        self._timer.timeout.connect(self.poll)
        self._timer.start()

        # Add context menu
        self._menu = ShellContextMenu(shell=self, parent=self)
        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(lambda p: self._menu.popup(
            self.mapToGlobal(p + QtCore.QPoint(0, 3))))

        # Start!
        self.resetVariables()
        self.connectToKernel(info)

    def resetVariables(self):
        """ Resets some variables. """

        # Reset read state
        self.setReadOnly(False)

        # Variables to store state, python version, builtins and keywords
        self._state = ''
        self._debugState = {}
        self._version = ""
        self._builtins = []
        self._keywords = []
        self._startup_info = {}
        self._start_time = 0

        # (re)set import attempts
        self._importAttempts[:] = []

        # Update
        self.stateChanged.emit(self)

    def connectToKernel(self, info):
        """ connectToKernel()
        
        Create kernel and connect to it.
        
        """

        # Create yoton context
        self._context = ct = yoton.Context()

        # Create stream channels
        self._strm_out = yoton.SubChannel(ct, 'strm-out')
        self._strm_err = yoton.SubChannel(ct, 'strm-err')
        self._strm_raw = yoton.SubChannel(ct, 'strm-raw')
        self._strm_echo = yoton.SubChannel(ct, 'strm-echo')
        self._strm_prompt = yoton.SubChannel(ct, 'strm-prompt')
        self._strm_broker = yoton.SubChannel(ct, 'strm-broker')
        self._strm_action = yoton.SubChannel(ct, 'strm-action', yoton.OBJECT)

        # Set channels to sync mode. This means that if the IEP cannot process
        # the messages fast enough, the sending side is blocked for a short
        # while. We don't want our users to miss any messages.
        for c in [self._strm_out, self._strm_err]:
            c.set_sync_mode(True)

        # Create control channels
        self._ctrl_command = yoton.PubChannel(ct, 'ctrl-command')
        self._ctrl_code = yoton.PubChannel(ct, 'ctrl-code', yoton.OBJECT)
        self._ctrl_broker = yoton.PubChannel(ct, 'ctrl-broker')

        # Create status channels
        self._stat_interpreter = yoton.StateChannel(ct, 'stat-interpreter')
        self._stat_debug = yoton.StateChannel(ct, 'stat-debug', yoton.OBJECT)
        self._stat_startup = yoton.StateChannel(ct, 'stat-startup',
                                                yoton.OBJECT)
        self._stat_startup.received.bind(self._onReceivedStartupInfo)

        # Create introspection request channel
        self._request = yoton.ReqChannel(ct, 'reqp-introspect')

        # Connect! The broker will only start the kernel AFTER
        # we connect, so we do not miss out on anything.
        slot = iep.localKernelManager.createKernel(finishKernelInfo(info))
        self._brokerConnection = ct.connect('localhost:%i' % slot)
        self._brokerConnection.closed.bind(self._onConnectionClose)

        # todo: see polling vs events
#         # Detect incoming messages
#         for c in [self._strm_out, self._strm_err, self._strm_raw,
#                 self._strm_echo, self._strm_prompt, self._strm_broker,
#                 self._strm_action,
#                 self._stat_interpreter, self._stat_debug]:
#             c.received.bind(self.poll)

    def _onReceivedStartupInfo(self, channel):
        startup_info = channel.recv()

        # Store the whole dict
        self._startup_info = startup_info

        # Store when we received this
        self._start_time = time.time()

        # Set version
        version = startup_info.get('version', None)
        if isinstance(version, tuple):
            version = [str(v) for v in version]
            self._version = '.'.join(version[:2])

        # Set keywords
        L = startup_info.get('keywords', None)
        if isinstance(L, list):
            self._keywords = L

        # Set builtins
        L = startup_info.get('builtins', None)
        if isinstance(L, list):
            self._builtins = L

        # Notify
        self.stateChanged.emit(self)

    ## Introspection processing methods

    def processCallTip(self, cto):
        """ Processes a calltip request using a CallTipObject instance. 
        """

        # Try using buffer first (not if we're not the requester)
        if self is cto.textCtrl:
            if cto.tryUsingBuffer():
                return

        # Clear buffer to prevent doing a second request
        # and store cto to see whether the response is still wanted.
        cto.setBuffer('')
        self._currentCTO = cto

        # Post request
        future = self._request.signature(cto.name)
        future.add_done_callback(self._processCallTip_response)
        future.cto = cto

    def _processCallTip_response(self, future):
        """ Process response of shell to show signature. 
        """

        # Process future
        if future.cancelled():
            #print('Introspect cancelled')  # No kernel
            return
        elif future.exception():
            print('Introspect-exception: ', future.exception())
            return
        else:
            response = future.result()
            cto = future.cto

        # First see if this is still the right editor (can also be a shell)
        editor1 = iep.editors.getCurrentEditor()
        editor2 = iep.shells.getCurrentShell()
        if cto.textCtrl not in [editor1, editor2]:
            # The editor or shell starting the autocomp is no longer active
            aco.textCtrl.autocompleteCancel()
            return

        # Invalid response
        if response is None:
            cto.textCtrl.autocompleteCancel()
            return

        # If still required, show tip, otherwise only store result
        if cto is self._currentCTO:
            cto.finish(response)
        else:
            cto.setBuffer(response)

    def processAutoComp(self, aco):
        """ Processes an autocomp request using an AutoCompObject instance. 
        """

        # Try using buffer first (not if we're not the requester)
        if self is aco.textCtrl:
            if aco.tryUsingBuffer():
                return

        # Include builtins and keywords?
        if not aco.name:
            aco.addNames(self._builtins)
            if iep.config.settings.autoComplete_keywords:
                aco.addNames(self._keywords)

        # Set buffer to prevent doing a second request
        # and store aco to see whether the response is still wanted.
        aco.setBuffer()
        self._currentACO = aco

        # Post request
        future = self._request.dir(aco.name)
        future.add_done_callback(self._processAutoComp_response)
        future.aco = aco

    def _processAutoComp_response(self, future):
        """ Process the response of the shell for the auto completion. 
        """

        # Process future
        if future.cancelled():
            #print('Introspect cancelled') # No living kernel
            return
        elif future.exception():
            print('Introspect-exception: ', future.exception())
            return
        else:
            response = future.result()
            aco = future.aco

        # First see if this is still the right editor (can also be a shell)
        editor1 = iep.editors.getCurrentEditor()
        editor2 = iep.shells.getCurrentShell()
        if aco.textCtrl not in [editor1, editor2]:
            # The editor or shell starting the autocomp is no longer active
            aco.textCtrl.autocompleteCancel()
            return

        # Add result to the list
        foundNames = []
        if response is not None:
            foundNames = response
        aco.addNames(foundNames)

        # Process list
        if aco.name and not foundNames:
            # No names found for the requested name. This means
            # it does not exist, let's try to import it
            importNames, importLines = iep.parser.getFictiveImports(editor1)
            baseName = aco.nameInImportNames(importNames)
            if baseName:
                line = importLines[baseName].strip()
                if line not in self._importAttempts:
                    # Do import
                    self.processLine(line + ' # auto-import')
                    self._importAttempts.append(line)
                    # Wait a barely noticable time to increase the chances
                    # That the import is complete when we repost the request.
                    time.sleep(0.2)
                    # To be sure, decrease the experiration date on the buffer
                    aco.setBuffer(timeout=1)
                    # Repost request
                    future = self._request.signature(aco.name)
                    future.add_done_callback(self._processAutoComp_response)
                    future.aco = aco
        else:
            # If still required, show list, otherwise only store result
            if self._currentACO is aco:
                aco.finish()
            else:
                aco.setBuffer()

    ## Methods for executing code

    def executeCommand(self, text):
        """ executeCommand(text)
        Execute one-line command in the remote Python session. 
        """
        self._ctrl_command.send(text)

    def executeCode(self, text, fname, lineno=0, cellName=None):
        """ executeCode(text, fname, lineno, cellName=None)
        Execute (run) a large piece of code in the remote shell.
        text: the source code to execute
        filename: the file from which the source comes
        lineno: the first lineno of the text in the file, where 0 would be
        the first line of the file...
        
        The text (source code) is first pre-processed:
        - convert all line-endings to \n
        - remove all empty lines at the end
        - remove commented lines at the end
        - convert tabs to spaces
        - dedent so minimal indentation is zero        
        """

        # Convert tabs to spaces
        text = text.replace("\t", " " * 4)

        # Make sure there is always *some* text
        if not text:
            text = ' '

        # Examine the text line by line...
        # - check for empty/commented lined at the end
        # - calculate minimal indentation
        lines = text.splitlines()
        lastLineOfCode = 0
        minIndent = 99
        for linenr in range(len(lines)):
            # Get line
            line = lines[linenr]
            # Check if empty (can be commented, but nothing more)
            tmp = line.split("#", 1)[0]  # get part before first #
            if tmp.count(" ") == len(tmp):
                continue  # empty line, proceed
            else:
                lastLineOfCode = linenr
            # Calculate indentation
            tmp = line.lstrip(" ")
            indent = len(line) - len(tmp)
            if indent < minIndent:
                minIndent = indent

        # Copy all proper lines to a new list,
        # remove minimal indentation, but only if we then would only remove
        # spaces (in the case of commented lines)
        lines2 = []
        for linenr in range(lastLineOfCode + 1):
            line = lines[linenr]
            # Remove indentation,
            if line[:minIndent].count(" ") == minIndent:
                line = line[minIndent:]
            else:
                line = line.lstrip(" ")
            lines2.append(line)

        # Send message
        text = "\n".join(lines2)
        msg = {
            'source': text,
            'fname': fname,
            'lineno': lineno,
            'cellName': cellName
        }
        self._ctrl_code.send(msg)

    ## The polling methods and terminating methods

    def poll(self, channel=None):
        """ poll()
        To keep the shell up-to-date.
        Call this periodically. 
        """

        if self._write_buffer:
            # There is still data in the buffer
            sub, M = self._write_buffer
        else:
            # Check what subchannel has the latest message pending
            sub = yoton.select_sub_channel(self._strm_out, self._strm_err,
                                           self._strm_echo, self._strm_raw,
                                           self._strm_broker,
                                           self._strm_prompt)
            # Read messages from it
            if sub:
                M = sub.recv_selected()
                #M = [sub.recv()] # Slow version (for testing)
                # Optimization: handle backspaces on stack of messages
                if sub is self._strm_out:
                    M = self._handleBackspacesOnList(M)
            # New prompt?
            if sub is self._strm_prompt:
                self.stateChanged.emit(self)

        # Write all pending messages that are later than any other message
        if sub:
            # Select messages to process
            N = 256
            M, buffer = M[:N], M[N:]
            # Buffer the rest
            if buffer:
                self._write_buffer = sub, buffer
            else:
                self._write_buffer = None
            # Get how to deal with prompt
            prompt = 0
            if sub is self._strm_echo:
                prompt = 1
            elif sub is self._strm_prompt:
                prompt = 2
            # Get color
            color = None
            if sub is self._strm_broker:
                color = '#000'
            elif sub is self._strm_raw:
                color = '#888888'  # Halfway
            elif sub is self._strm_err:
                color = '#F00'
            # Write
            self.write(''.join(M), prompt, color)

        # Do any actions?
        action = self._strm_action.recv(False)
        if action:
            if action.startswith('open '):
                fname = action.split(' ', 1)[1]
                iep.editors.loadFile(fname)
            else:
                print('Unkown action: %s' % action)

        # Update status
        state = self._stat_interpreter.recv()
        if state != self._state:
            self._state = state
            self.stateChanged.emit(self)

        # Update debug status
        state = self._stat_debug.recv()
        if state != self._debugState:
            self._debugState = state
            self.debugStateChanged.emit(self)

    def interrupt(self):
        """ interrupt()
        Send a Keyboard interrupt signal to the main thread of the 
        remote process. 
        """
        self._ctrl_broker.send('INT')

    def restart(self, scriptFile=None):
        """ restart(scriptFile=None)
        Terminate the shell, after which it is restarted. 
        Args can be a filename, to execute as a script as soon as the
        shell is back up.
        """

        # Get info
        info = finishKernelInfo(self._info, scriptFile)

        # Create message and send
        msg = 'RESTART\n' + ssdf.saves(info)
        self._ctrl_broker.send(msg)

        # Reset
        self.resetVariables()

    def terminate(self):
        """ terminate()
        Terminates the python process. It will first try gently, but 
        if that does not work, the process shall be killed.
        To be notified of the termination, connect to the "terminated"
        signal of the shell.
        """
        self._ctrl_broker.send('TERM')

    def closeShell(self):  # do not call it close(); that is a reserved method.
        """ closeShell()
        
        Very simple. This closes the shell. If possible, we will first
        tell the broker to terminate the kernel.
        
        The broker will be cleaned up if there are no clients connected
        and if there is no active kernel. In a multi-user environment,
        we should thus be able to close the shell without killing the
        kernel. But in a closed 1-to-1 environment we really want to 
        prevent loose brokers and kernels dangling around.
        
        In both cases however, it is the responsibility of the broker to
        terminate the kernel, and the shell will simply assume that this
        will work :) 
        
        """

        # If we can, try to tell the broker to terminate the kernel
        if self._context and self._context.connection_count:
            self.terminate()
            self._context.flush()  # Important, make sure the message is send!
            self._context.close()

        # Adios
        iep.shells.removeShell(self)

    def _onConnectionClose(self, c, why):
        """ To be called after disconnecting.
        In general, the broker will not close the connection, so it can
        be considered an error-state if this function is called.
        """

        # Stop context
        if self._context:
            self._context.close()

        # New (empty prompt)
        self._cursor1.movePosition(self._cursor1.End, A_MOVE)
        self._cursor2.movePosition(self._cursor2.End, A_MOVE)

        self.write('\n\n')
        self.write('Lost connection with broker:\n')
        self.write(why)
        self.write('\n\n')

        # Set style to indicate dead-ness
        self.setReadOnly(True)

        # Goto end such that the closing message is visible
        cursor = self.textCursor()
        cursor.movePosition(cursor.End, A_MOVE)
        self.setTextCursor(cursor)
        self.ensureCursorVisible()
Example #23
0
class CompactTabBar(QtGui.QTabBar):
    """ CompactTabBar(parent, *args, padding=(4,4,6,6), preventEqualTexts=True)
    
    Tab bar corresponcing to the CompactTabWidget.
    
    With the "padding" argument the padding of the tabs can be chosen.
    It should be an integer, or a 4 element tuple specifying the padding
    for top, bottom, left, right. When a tab has a button,
    the padding is the space between button and text.
    
    With preventEqualTexts to True, will reduce the amount of eliding if
    two tabs have (partly) the same name, so that they can always be
    distinguished.
    
    """

    # Add signal to be notified of double clicks on tabs
    tabDoubleClicked = QtCore.Signal(int)
    barDoubleClicked = QtCore.Signal()

    def __init__(self, *args, padding=(4, 4, 6, 6), preventEqualTexts=True):
        QtGui.QTabBar.__init__(self, *args)

        # Put tab widget in document mode
        self.setDocumentMode(True)

        # Widget needs to draw its background (otherwise Mac has a dark bg)
        self.setDrawBase(False)
        if sys.platform == 'darwin':
            self.setAutoFillBackground(True)

        # Set whether we want to prevent eliding for names that start the same.
        self._preventEqualTexts = preventEqualTexts

        # Allow moving tabs around
        self.setMovable(True)

        # Get padding
        if isinstance(padding, (int, float)):
            padding = padding, padding, padding, padding
        elif isinstance(padding, (tuple, list)):
            pass
        else:
            raise ValueError('Invalid value for padding.')

        # Set style sheet
        stylesheet = STYLESHEET
        stylesheet = stylesheet.replace('PADDING_TOP', str(padding[0]))
        stylesheet = stylesheet.replace('PADDING_BOTTOM', str(padding[1]))
        stylesheet = stylesheet.replace('PADDING_LEFT', str(padding[2]))
        stylesheet = stylesheet.replace('PADDING_RIGHT', str(padding[3]))
        self.setStyleSheet(stylesheet)

        # We do our own eliding
        self.setElideMode(QtCore.Qt.ElideNone)

        # Make tabs wider if there's plenty space?
        self.setExpanding(False)

        # If there's not enough space, use scroll buttons
        self.setUsesScrollButtons(True)

        # When a tab is removed, select previous
        self.setSelectionBehaviorOnRemove(self.SelectPreviousTab)

        # Init alignment parameters
        self._alignWidth = MIN_NAME_WIDTH  # Width in characters
        self._alignWidthIsReducing = False  # Whether in process of reducing

        # Create timer for aligning
        self._alignTimer = QtCore.QTimer(self)
        self._alignTimer.setInterval(10)
        self._alignTimer.setSingleShot(True)
        self._alignTimer.timeout.connect(self._alignRecursive)

    def _compactTabBarData(self, i):
        """ _compactTabBarData(i)
        
        Get the underlying tab data for tab i. Only for internal use.
        
        """

        # Get current TabData instance
        tabData = QtGui.QTabBar.tabData(self, i)
        if (tabData is not None) and hasattr(tabData, 'toPyObject'):
            tabData = tabData.toPyObject()  # Older version of Qt

        # If none, make it as good as we can
        if not tabData:
            name = str(QtGui.QTabBar.tabText(self, i))
            tabData = TabData(name)
            QtGui.QTabBar.setTabData(self, i, tabData)

        # Done
        return tabData

    ## Overload a few methods

    def mouseDoubleClickEvent(self, event):
        i = self.tabAt(event.pos())
        if i == -1:
            # There was no tab under the cursor
            self.barDoubleClicked.emit()
        else:
            # Tab was double clicked
            self.tabDoubleClicked.emit(i)

    def setTabData(self, i, data):
        """ setTabData(i, data)
        
        Set the given object at the tab with index 1.
        
        """
        # Get underlying python instance
        tabData = self._compactTabBarData(i)

        # Attach given data
        tabData.data = data

    def tabData(self, i):
        """ tabData(i)
        
        Get the tab data at item i. Always returns a Python object.
        
        """

        # Get underlying python instance
        tabData = self._compactTabBarData(i)

        # Return stored data
        return tabData.data

    def setTabText(self, i, text):
        """ setTabText(i, text)
        
        Set the text for tab i.
        
        """
        tabData = self._compactTabBarData(i)
        if text != tabData.name:
            tabData.name = text
            self.alignTabs()

    def tabText(self, i):
        """ tabText(i)
        
        Get the title of the tab at index i.
        
        """
        tabData = self._compactTabBarData(i)
        return tabData.name

    ## Overload events and protected functions

    def tabInserted(self, i):
        QtGui.QTabBar.tabInserted(self, i)

        # Is called when a tab is inserted

        # Get given name and store
        name = str(QtGui.QTabBar.tabText(self, i))
        tabData = TabData(name)
        QtGui.QTabBar.setTabData(self, i, tabData)

        # Update
        self.alignTabs()

    def tabRemoved(self, i):
        QtGui.QTabBar.tabRemoved(self, i)

        # Update
        self.alignTabs()

    def resizeEvent(self, event):
        QtGui.QTabBar.resizeEvent(self, event)
        self.alignTabs()

    def showEvent(self, event):
        QtGui.QTabBar.showEvent(self, event)
        self.alignTabs()

    ## For aligning

    def alignTabs(self):
        """ alignTabs()
        
        Align the tab items. Their names are ellided if required so that
        all tabs fit on the tab bar if possible. When there is too little
        space, the QTabBar will kick in and draw scroll arrows.
        
        """

        # Set name widths correct (in case new names were added)
        self._setMaxWidthOfAllItems()

        # Start alignment process
        self._alignWidthIsReducing = False
        self._alignTimer.start()

    def _alignRecursive(self):
        """ _alignRecursive()
        
        Recursive alignment of the items. The alignment process
        should be initiated from alignTabs().
        
        """

        # Only if visible
        if not self.isVisible():
            return

        # Get tab bar and number of items
        N = self.count()

        # Get right edge of last tab and left edge of corner widget
        pos1 = self.tabRect(0).topLeft()
        pos2 = self.tabRect(N - 1).topRight()
        cornerWidget = self.parent().cornerWidget()
        if cornerWidget:
            pos3 = cornerWidget.pos()
        else:
            pos3 = QtCore.QPoint(self.width(), 0)
        x1 = pos1.x()
        x2 = pos2.x()
        x3 = pos3.x()
        alignMargin = x3 - (x2 - x1) - 3  # Must be positive (has margin)

        # Are the tabs too wide?
        if alignMargin < 0:
            # Tabs extend beyond corner widget

            # Reduce width then
            self._alignWidth -= 1
            self._alignWidth = max(self._alignWidth, MIN_NAME_WIDTH)

            # Apply
            self._setMaxWidthOfAllItems()
            self._alignWidthIsReducing = True

            # Try again if there's still room for reduction
            if self._alignWidth > MIN_NAME_WIDTH:
                self._alignTimer.start()

        elif alignMargin > 10 and not self._alignWidthIsReducing:
            # Gap between tabs and corner widget is a bit large

            # Increase width then
            self._alignWidth += 1
            self._alignWidth = min(self._alignWidth, MAX_NAME_WIDTH)

            # Apply
            itemsElided = self._setMaxWidthOfAllItems()

            # Try again if there's still room for increment
            if itemsElided and self._alignWidth < MAX_NAME_WIDTH:
                self._alignTimer.start()
                #self._alignTimer.timeout.emit()

        else:
            pass  # margin is good

    def _getAllNames(self):
        """ _getAllNames()
        
        Get a list of all (full) tab names.
        
        """
        return [self._compactTabBarData(i).name for i in range(self.count())]

    def _setMaxWidthOfAllItems(self):
        """ _setMaxWidthOfAllItems()
        
        Sets the maximum width of all items now, by eliding the names.
        Returns whether any items were elided.
        
        """

        # Prepare for measuring font sizes
        font = self.font()
        metrics = QtGui.QFontMetrics(font)

        # Get whether an item was reduced in size
        itemReduced = False

        for i in range(self.count()):

            # Get width
            w = self._alignWidth

            # Get name
            name = name0 = self._compactTabBarData(i).name

            # Check if we can reduce the name size, correct w if necessary
            if ((w + 1) < len(name0)) and self._preventEqualTexts:

                # Increase w untill there are no names that start the same
                allNames = self._getAllNames()
                hasSimilarNames = True
                diff = 2
                w -= 1
                while hasSimilarNames and w < len(name0):
                    w += 1
                    w2 = w - (diff - 1)
                    shortName = name[:w2]
                    similarnames = [n for n in allNames if n[:w2] == shortName]
                    hasSimilarNames = len(similarnames) > 1

            # Check again, with corrected w
            if (w + 1) < len(name0):
                name = name[:w] + ELLIPSIS
                itemReduced = True

            # Set text now
            QtGui.QTabBar.setTabText(self, i, name)

        # Done
        return itemReduced
Example #24
0
class ToolManager(QtCore.QObject):
    """ Manages the tools. """

    # This signal indicates a change in the loaded tools
    toolInstanceChange = QtCore.Signal()

    def __init__(self, parent=None):
        QtCore.QObject.__init__(self, parent)

        # list of ToolDescription instances
        self._toolInfo = None
        self._activeTools = {}

    def loadToolInfo(self):
        """ (re)load the tool information. 
        """
        # Get paths to load files from
        toolDir1 = os.path.join(iep.iepDir, 'tools')
        toolDir2 = os.path.join(iep.appDataDir, 'tools')

        # Create list of tool files
        toolfiles = []
        for toolDir in [toolDir1, toolDir2]:
            tmp = [os.path.join(toolDir, f) for f in os.listdir(toolDir)]
            toolfiles.extend(tmp)

        # Note: we do not use the code below anymore, since even the frozen
        # app makes use of the .py files.
#         # Get list of files, also when we're in a zip file.
#         i = tooldir.find('.zip')
#         if i>0:
#             # Get list of files from zipfile
#             tooldir = tooldir[:i+4]
#             import zipfile
#             z = zipfile.ZipFile(tooldir)
#             toolfiles = [os.path.split(i)[1] for i in z.namelist()
#                         if i.startswith('visvis') and i.count('functions')]
#         else:
#             # Get list of files from file system
#             toolfiles = os.listdir(tooldir)

# Iterate over tool modules
        newlist = []
        for file in toolfiles:
            modulePath = file

            # Check
            if os.path.isdir(file):
                file = os.path.join(file, '__init__.py')  # A package perhaps
                if not os.path.isfile(file):
                    continue
            elif file.endswith('__.py') or not file.endswith('.py'):
                continue
            elif file.endswith('iepFileBrowser.py'):
                # Skip old file browser (the file can be there from a previous install)
                continue

            #
            toolName = ""
            toolSummary = ""
            # read file to find name or summary
            linecount = 0
            for line in open(file, encoding='utf-8'):
                linecount += 1
                if linecount > 50:
                    break
                if line.startswith("tool_name"):
                    i = line.find("=")
                    if i < 0: continue
                    line = line.rstrip("\n").rstrip("\r")
                    line = line[i + 1:].strip(" ")
                    toolName = line.strip("'").strip('"')
                elif line.startswith("tool_summary"):
                    i = line.find("=")
                    if i < 0: continue
                    line = line.rstrip("\n").rstrip("\r")
                    line = line[i + 1:].strip(" ")
                    toolSummary = line.strip("'").strip('"')
                else:
                    pass

            # Add stuff
            tmp = ToolDescription(modulePath, toolName, toolSummary)
            newlist.append(tmp)

        # Store and return
        self._toolInfo = sorted(newlist, key=lambda x: x.id)
        self.updateToolInstances()
        return self._toolInfo

    def updateToolInstances(self):
        """ Make tool instances up to date, so that it can be seen what
        tools are now active. """
        for toolDes in self.getToolInfo():
            if toolDes.id in self._activeTools:
                toolDes.instance = self._activeTools[toolDes.id]
            else:
                toolDes.instance = None

        # Emit update signal
        self.toolInstanceChange.emit()

    def getToolInfo(self):
        """ Like loadToolInfo(), but use buffered instance if available.
        """
        if self._toolInfo is None:
            self.loadToolInfo()
        return self._toolInfo

    def getToolClass(self, toolId):
        """ Get the class of the tool.
        It will import (and reload) the module and get the class.
        Some checks are performed, like whether the class inherits 
        from QWidget.
        Returns the class or None if failed...
        """

        # Make sure we have the info
        if self._toolInfo is None:
            self.loadToolInfo()

        # Get module name and path
        for toolDes in self._toolInfo:
            if toolDes.id == toolId:
                moduleName = toolDes.moduleName
                modulePath = toolDes.modulePath
                break
        else:
            print("WARNING: could not find module for tool", repr(toolId))
            return None

        # Remove from sys.modules, to force the module to reload
        for key in [key for key in sys.modules]:
            if key and key.startswith('ieptools.' + moduleName):
                del sys.modules[key]

        # Load module
        try:
            m_file, m_fname, m_des = imp.find_module(
                moduleName, [os.path.dirname(modulePath)])
            mod = imp.load_module('ieptools.' + moduleName, m_file, m_fname,
                                  m_des)
        except Exception as why:
            print("Invalid tool " + toolId + ":", why)
            return None

        # Is the expected class present?
        className = ""
        for member in dir(mod):
            if member.lower() == toolId:
                className = member
                break
        else:
            print("Invalid tool, Classname must match module name '%s'!" %
                  toolId)
            return None

        # Does it inherit from QWidget?
        plug = mod.__dict__[className]
        if not (isinstance(plug, type) and issubclass(plug, QtGui.QWidget)):
            print("Invalid tool, tool class must inherit from QWidget!")
            return None

        # Succes!
        return plug

    def loadTool(self, toolId):
        """ Load a tool by creating a dock widget containing the tool widget.
        """

        # A tool id should always be lower case
        toolId = toolId.lower()

        # Close old one
        if toolId in self._activeTools:
            old = self._activeTools[toolId].widget()
            self._activeTools[toolId].setWidget(QtGui.QWidget(iep.main))
            if old:
                old.close()
                old.deleteLater()

        # Get tool class (returns None on failure)
        toolClass = self.getToolClass(toolId)
        if toolClass is None:
            return

        # Already loaded? reload!
        if toolId in self._activeTools:
            self._activeTools[toolId].reload(toolClass)
            return

        # Obtain name from buffered list of names
        for toolDes in self._toolInfo:
            if toolDes.id == toolId:
                name = toolDes.name
                break
        else:
            name = toolId

        # Make sure there is a config entry for this tool
        if not hasattr(iep.config.tools, toolId):
            iep.config.tools[toolId] = ssdf.new()

        # Create dock widget and add in the main window
        dock = ToolDockWidget(iep.main, self)
        dock.setTool(toolId, name, toolClass)
        iep.main.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock)

        # Add to list
        self._activeTools[toolId] = dock
        self.updateToolInstances()

    def reloadTools(self):
        """ Reload all tools. """
        for id in self.getLoadedTools():
            self.loadTool(id)

    def closeTool(self, toolId):
        """ Close the tool with specified id.
        """
        if toolId in self._activeTools:
            dock = self._activeTools[toolId]
            dock.close()

    def getTool(self, toolId):
        """ Get the tool widget instance, or None
        if not available. """
        if toolId in self._activeTools:
            return self._activeTools[toolId].widget()
        else:
            return None

    def onToolClose(self, toolId):
        # Remove from dict
        self._activeTools.pop(toolId, None)
        # Set instance to None
        self.updateToolInstances()

    def getLoadedTools(self):
        """ Get a list with id's of loaded tools. """
        tmp = []
        for toolDes in self.getToolInfo():
            if toolDes.id in self._activeTools:
                tmp.append(toolDes.id)
        return tmp
Example #25
0
 def __init__(self, parent):
     QtGui.QWidget.__init__(self, parent)
     
     # Make sure there is a configuration entry for this tool
     # The IEP tool manager makes sure that there is an entry in
     # config.tools before the tool is instantiated.
     toolId = self.__class__.__name__.lower()        
     self._config = iep.config.tools[toolId]
     if not hasattr(self._config, 'showTypes'):
         self._config.showTypes = ['class', 'def', 'cell', 'todo']
     if not hasattr(self._config, 'level'):
         self._config.level = 2
     
     # Create icon for slider
     self._sliderIcon = QtGui.QToolButton(self)
     self._sliderIcon.setIcon(iep.icons.text_align_right)
     self._sliderIcon.setIconSize(QtCore.QSize(16,16))
     self._sliderIcon.setStyleSheet("QToolButton { border: none; padding: 0px; }")   
     
     # Create slider
     self._slider = QtGui.QSlider(QtCore.Qt.Horizontal, self)
     self._slider.setTickPosition(QtGui.QSlider.TicksBelow)
     self._slider.setSingleStep(1)
     self._slider.setPageStep(1)
     self._slider.setRange(1,9)
     self._slider.setValue(self._config.level)
     self._slider.valueChanged.connect(self.updateStructure)
     
     # Create options button
     #self._options = QtGui.QPushButton(self)
     #self._options.setText('Options'))        
     #self._options.setToolTip("What elements to show.")
     self._options = QtGui.QToolButton(self)
     self._options.setIcon(iep.icons.filter)
     self._options.setIconSize(QtCore.QSize(16,16))
     self._options.setPopupMode(self._options.InstantPopup)
     self._options.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
     
     # Create options menu
     self._options._menu = QtGui.QMenu()
     self._options.setMenu(self._options._menu)
     
     # Create tree widget        
     self._tree = QtGui.QTreeWidget(self)
     self._tree.setHeaderHidden(True)
     self._tree.itemCollapsed.connect(self.updateStructure) # keep expanded
     self._tree.itemClicked.connect(self.onItemClick)
     
     # Create two sizers
     self._sizer1 = QtGui.QVBoxLayout(self)
     self._sizer2 = QtGui.QHBoxLayout()
     # self._sizer1.setSpacing()
     
     # Set layout
     self._sizer1.addLayout(self._sizer2, 0)
     self._sizer1.addWidget(self._tree, 1)
     self._sizer2.addWidget(self._sliderIcon, 0)
     self._sizer2.addWidget(self._slider, 4)
     self._sizer2.addStretch(1)
     self._sizer2.addWidget(self._options, 2)
     #
     self._sizer1.setSpacing(2)
     self.setLayout(self._sizer1)
     
     # Init current-file name
     self._currentEditorId = 0
     
     # Bind to events
     iep.editors.currentChanged.connect(self.onEditorsCurrentChanged)
     iep.editors.parserDone.connect(self.updateStructure)
     
     self._options.pressed.connect(self.onOptionsPress)
     self._options._menu.triggered.connect(self.onOptionMenuTiggered)
     
     # Start
     # When the tool is loaded, the editorStack is already done loading
     # all previous files and selected the appropriate file.
     self.onOptionsPress() # Create menu now
     self.onEditorsCurrentChanged()
Example #26
0
    def __init__(self, parent):
        QtGui.QFrame.__init__(self, parent)

        # Init config
        toolId = self.__class__.__name__.lower()
        self._config = iep.config.tools[toolId]
        if not hasattr(self._config, 'zoomFactor'):
            self._config.zoomFactor = 1.0
        if not hasattr(self._config, 'bookMarks'):
            self._config.bookMarks = default_bookmarks

        # Get style object (for icons)
        style = QtGui.QApplication.style()

        # Create some buttons
        self._back = QtGui.QToolButton(self)
        self._back.setIcon(style.standardIcon(style.SP_ArrowBack))
        self._back.setIconSize(QtCore.QSize(16, 16))
        #
        self._forward = QtGui.QToolButton(self)
        self._forward.setIcon(style.standardIcon(style.SP_ArrowForward))
        self._forward.setIconSize(QtCore.QSize(16, 16))

        # Create address bar
        #self._address = QtGui.QLineEdit(self)
        self._address = QtGui.QComboBox(self)
        self._address.setEditable(True)
        self._address.setInsertPolicy(self._address.NoInsert)
        #
        for a in self._config.bookMarks:
            self._address.addItem(a)
        self._address.setEditText('')

        # Create web view
        self._view = WebView(self)
        #
        #         self._view.setZoomFactor(self._config.zoomFactor)
        #         settings = self._view.settings()
        #         settings.setAttribute(settings.JavascriptEnabled, True)
        #         settings.setAttribute(settings.PluginsEnabled, True)

        # Layout
        self._sizer1 = QtGui.QVBoxLayout(self)
        self._sizer2 = QtGui.QHBoxLayout()
        #
        self._sizer2.addWidget(self._back, 0)
        self._sizer2.addWidget(self._forward, 0)
        self._sizer2.addWidget(self._address, 1)
        #
        self._sizer1.addLayout(self._sizer2, 0)
        self._sizer1.addWidget(self._view, 1)
        #
        self._sizer1.setSpacing(2)
        self.setLayout(self._sizer1)

        # Bind signals
        self._back.clicked.connect(self.onBack)
        self._forward.clicked.connect(self.onForward)
        self._address.lineEdit().returnPressed.connect(self.go)
        self._address.activated.connect(self.go)
        self._view.loadFinished.connect(self.onLoadEnd)
        self._view.loadStarted.connect(self.onLoadStart)

        # Start
        self._view.show()
        self.go('http://docs.python.org')
Example #27
0
class WebView(QtGui.QTextBrowser):
    """ Inherit the webview class to implement zooming using
    the mouse wheel. 
    """

    loadStarted = QtCore.Signal()
    loadFinished = QtCore.Signal(bool)

    def __init__(self, parent):
        QtGui.QTextBrowser.__init__(self, parent)

        # Current url
        self._url = ''
        self._history = []
        self._history2 = []

        # Connect
        self.anchorClicked.connect(self.load)

    def wheelEvent(self, event):
        # Zooming does not work for this widget
        if QtCore.Qt.ControlModifier & QtGui.qApp.keyboardModifiers():
            self.parent().wheelEvent(event)
        else:
            QtGui.QTextBrowser.wheelEvent(self, event)

    def url(self):
        return self._url

    def _getUrlParts(self):
        r = urllib.parse.urlparse(self._url)
        base = r.scheme + '://' + r.netloc
        return base, r.path, r.fragment


#
#     def loadCss(self, urls=[]):
#         urls.append('http://docs.python.org/_static/default.css')
#         urls.append('http://docs.python.org/_static/pygments.css')
#         text = ''
#         for url in urls:
#             tmp = urllib.request.urlopen(url).read().decode('utf-8')
#             text += '\n' + tmp
#         self.document().setDefaultStyleSheet(text)

    def back(self):

        # Get url and update forward history
        url = self._history.pop()
        self._history2.append(self._url)

        # Go there
        url = self._load(url)

    def forward(self):

        if not self._history2:
            return

        # Get url and update forward history
        url = self._history2.pop()
        self._history.append(self._url)

        # Go there
        url = self._load(url)

    def load(self, url):

        # Clear forward history
        self._history2 = []

        # Store current url in history
        while self._url in self._history:
            self._history.remove(self._url)
        self._history.append(self._url)

        # Load
        url = self._load(url)

    def _load(self, url):
        """ _load(url)
        Convert url and load page, returns new url.
        """
        # Make url a string
        if isinstance(url, QtCore.QUrl):
            url = str(url.toString())

        # Compose relative url to absolute
        if url.startswith('#'):
            base, path, frag = self._getUrlParts()
            url = base + path + url
        elif not '//' in url:
            base, path, frag = self._getUrlParts()
            url = base + '/' + url.lstrip('/')

        # Try loading
        self.loadStarted.emit()
        self._url = url
        try:
            #print('URL:', url)
            text = urllib.request.urlopen(url).read().decode('utf-8')
            self.setHtml(text)
            self.loadFinished.emit(True)
        except Exception as err:
            self.setHtml(str(err))
            self.loadFinished.emit(False)

        # Set
        return url
Example #28
0
 def postEventWithCallback(self, callback, *args):
     self.queue.put((callback, args))
     QtGui.qApp.postEvent(self, QtCore.QEvent(QtCore.QEvent.User))
Example #29
0
class WorkspaceProxy(QtCore.QObject):
    """ WorkspaceProxy
    
    A proxy class to handle the asynchonous behaviour of getting information
    from the shell. The workspace tool asks for a certain name, and this
    class notifies when new data is available using a qt signal.
    
    """

    haveNewData = QtCore.Signal()

    def __init__(self):
        QtCore.QObject.__init__(self)

        # Variables
        self._variables = []

        # Element to get more info of
        self._name = ''

        # Bind to events
        iep.shells.currentShellChanged.connect(self.onCurrentShellChanged)
        iep.shells.currentShellStateChanged.connect(
            self.onCurrentShellStateChanged)

        # Initialize
        self.onCurrentShellStateChanged()

    def addNamePart(self, part):
        """ addNamePart(part)
        Add a part to the name.
        """
        parts = splitName(self._name)
        parts.append(part)
        self.setName(joinName(parts))

    def setName(self, name):
        """ setName(name)        
        Set the name that we want to know more of. 
        """
        self._name = name

        shell = iep.shells.getCurrentShell()
        if shell:
            future = shell._request.dir2(self._name)
            future.add_done_callback(self.processResponse)

    def goUp(self):
        """ goUp()
        Cut the last part off the name. 
        """
        parts = splitName(self._name)
        if parts:
            parts.pop()
        self.setName(joinName(parts))

    def onCurrentShellChanged(self):
        """ onCurrentShellChanged()
        When no shell is selected now, update this. In all other cases,
        the onCurrentShellStateChange will be fired too. 
        """
        shell = iep.shells.getCurrentShell()
        if not shell:
            self._variables = []
            self.haveNewData.emit()

    def onCurrentShellStateChanged(self):
        """ onCurrentShellStateChanged()
        Do a request for information! 
        """
        shell = iep.shells.getCurrentShell()
        if not shell:
            # Should never happen I think, but just to be sure
            self._variables = []
        elif shell._state.lower() != 'busy':
            future = shell._request.dir2(self._name)
            future.add_done_callback(self.processResponse)

    def processResponse(self, future):
        """ processResponse(response)
        We got a response, update our list and notify the tree.
        """

        response = []

        # Process future
        if future.cancelled():
            pass  #print('Introspect cancelled') # No living kernel
        elif future.exception():
            print('Introspect-queryDoc-exception: ', future.exception())
        else:
            response = future.result()

        self._variables = response
        self.haveNewData.emit()
Example #30
0
    def __init__(self, parent):
        QtGui.QWidget.__init__(self, parent)

        # Init config
        toolId = self.__class__.__name__.lower()
        self.config = iep.config.tools[toolId]
        if not hasattr(self.config, 'projects'):
            self.config.projects = []
        if not hasattr(self.config, 'activeproject'):
            self.config.activeproject = -1
        if not hasattr(self.config, 'filter'):
            self.config.filter = '!*.pyc'
        if not hasattr(self.config, 'listdisclosed'):
            self.config.listdisclosed = True

        # Create example?
        if not self.config.projects:
            exampleProject = Project('Example', os.path.expanduser('~'))
            self.config.projects.append(exampleProject)
            self.config.activeproject = 0

        #Init projects model
        self.projectsModel = ProjectsModel(self.config)

        #Init dir model and filtered dir model
        self.dirModel = QtGui.QFileSystemModel()
        #TODO: using the default IconProvider bugs on mac, restoring window state fails

        self.dirModel.setIconProvider(IconProviderWindows())

        #TODO: self.dirModel.setSorting(QtCore.QDir.DirsFirst)
        # todo: huh? QFileSystemModel.setSorting Does not exist
        self.filteredDirModel = DirSortAndFilter()
        self.filteredDirModel.setSourceModel(self.dirModel)

        #Init widgets and layout
        self.buttonLayout = QtGui.QVBoxLayout()
        self.configButton = QtGui.QPushButton(self)
        self.configButton.setIcon(iep.icons.wrench)
        self.configButton.setIconSize(QtCore.QSize(16, 16))

        self.buttonLayout.addWidget(self.configButton, 0)
        self.buttonLayout.addStretch(1)

        self.projectsCombo = QtGui.QComboBox()
        self.projectsCombo.setModel(self.projectsModel)

        self.hLayout = QtGui.QHBoxLayout()
        self.hLayout.addWidget(self.projectsCombo, 1)
        self.hLayout.addLayout(self.buttonLayout, 0)

        self.dirList = QtGui.QTreeView()
        self.dirList.setHeaderHidden(True)

        # The lessThan function in DirSortAndFilter ensures dirs are before files
        self.dirList.sortByColumn(0, QtCore.Qt.AscendingOrder)
        self.filterCombo = QtGui.QComboBox()

        self.layout = QtGui.QVBoxLayout()
        self.layout.addWidget(DeprLabel(self))
        self.layout.addLayout(self.hLayout)
        self.layout.addWidget(self.dirList, 10)
        self.layout.addWidget(self.filterCombo)

        self.setLayout(self.layout)

        #Load projects in the list
        self.projectsCombo.show()

        #Load default filters
        self.filterCombo.setEditable(True)
        self.filterCombo.setCompleter(None)
        self.filterCombo.setInsertPolicy(self.filterCombo.NoInsert)
        for pattern in [
                '*', '!*.pyc', '*.py *.pyw *.pyx *.pxd', '*.h *.c *.cpp'
        ]:
            self.filterCombo.addItem(pattern)
        self.filterCombo.editTextChanged.connect(self.filterChanged)

        # Set file pattern line edit (in combobox)
        self.filterPattern = self.filterCombo.lineEdit()
        self.filterPattern.setText(self.config.filter)
        self.filterPattern.setToolTip('File filter pattern')

        #Connect signals
        self.projectsCombo.currentIndexChanged.connect(self.comboChangedEvent)
        self.dirList.doubleClicked.connect(self.itemDoubleClicked)
        self.configButton.clicked.connect(self.showConfigDialog)

        #Apply previous selected project
        self.activeProject = None
        self.projectChanged(self.config.activeproject)

        #Attach the context menu
        self.dirList.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.dirList.customContextMenuRequested.connect(
            self.contextMenuTriggered)