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()
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)))
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)
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
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)
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)
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())
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)
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()
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)))
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 __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 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
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)
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
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)
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))
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
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)
def __init__(self, *args): QtGui.QToolButton.__init__(self, *args) # Init self.setIconSize(QtCore.QSize(*self.SIZE)) self.setStyleSheet("QToolButton{ border: none; }")
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
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()
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
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
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()
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')
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
def postEventWithCallback(self, callback, *args): self.queue.put((callback, args)) QtGui.qApp.postEvent(self, QtCore.QEvent(QtCore.QEvent.User))
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()
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)