def __init__(self): QtWidgets.QToolButton.__init__(self) # Init self.setIconSize(QtCore.QSize(*self.SIZE)) self.setStyleSheet( "QToolButton{ border:none; padding:0px; margin:0px; }") self.setIcon(self.getCrossIcon1())
def onSearchFinish(self, hits): if hits == 0: return hits = self._searchEngine.hits(0, hits) if not hits: return url = self.find_best_page(hits) self._helpBrowser.setSource(QtCore.QUrl(url))
def _insertSnippet(self, body): if isinstance(body, list): txt = "" for i, t in enumerate(body): txt = body[i] + "\n" body = txt body = body.replace("\t", " ") body = body.replace("$0", "") # Find patern ${num : text} # TODO: nested pattern ${2: ${3:Exception} as ${4:e}} d = {} pattern = "\$\{\d.*?}" match = re.findall(pattern, body) for fnd in match: fnd_split = fnd.split(":") # key key = fnd_split[0] key = key.replace("{", "") body = body.replace(fnd, key) # value value = fnd_split[1] value = value.replace("}", "") # dict d[key] = value # In snippet replace keys with values for i in reversed(range(10)): try: i = "$" + str(i) body = body.replace(i, d[i]) except: pass # Write in the editor editor = pyzo.editors.getCurrentEditor() # cursor cursor = editor.textCursor() # current position pos = cursor.position() # insert the snippet cursor.insertText(body) # Find target text in the editor text = editor.toPlainText() pattern = "$1" if pattern in d: # find text regex = QtCore.QRegExp(d[pattern]) index = regex.indexIn(text, pos) # select text cursor.setPosition(index) cursor.movePosition(QtGui.QTextCursor.EndOfWord, 1) editor.setTextCursor(cursor)
def __init__(self, parent, **kwds): super().__init__(parent, showLineNumbers=True, **kwds) # Init filename and name self._filename = '' self._name = '<TMP>' # View settings self.setShowWhitespace(pyzo.config.view.showWhitespace) #TODO: self.setViewWrapSymbols(view.showWrapSymbols) self.setShowLineEndings(pyzo.config.view.showLineEndings) self.setShowIndentationGuides(pyzo.config.view.showIndentationGuides) # self.setWrap(bool(pyzo.config.view.wrap)) self.setHighlightCurrentLine(pyzo.config.view.highlightCurrentLine) self.setLongLineIndicatorPosition(pyzo.config.view.edgeColumn) self.setHighlightMatchingBracket( pyzo.config.view.highlightMatchingBracket) #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 = pyzo.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, *args, padding=(4, 4, 6, 6), preventEqualTexts=True): QtWidgets.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 __init__(self, *args, **kwds): super().__init__(*args, **kwds) # Set style/theme try: theme = pyzo.themes[pyzo.config.settings.theme.lower()]["data"] self.setStyle(theme) # autocomplete popup theme if pyzo.config.view.get("autoComplete_withTheme", False): editor_text_theme = theme["editor.text"].split(",") popup_background = editor_text_theme[1].split(":")[1] popup_text = editor_text_theme[0].split(":")[1] autoComplete_theme = "color: {}; background-color:{};".format( popup_text, popup_background) self.completer().popup().setStyleSheet(autoComplete_theme) except Exception as err: print("Could not load theme: " + str(err)) # Set font and zooming self.setFont(pyzo.config.view.fontname) self.setZoom(pyzo.config.view.zoom) self.setShowWhitespace(pyzo.config.view.showWhitespace) self.setHighlightMatchingBracket( pyzo.config.view.highlightMatchingBracket) # 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 = [] self.setAutoCompletionAcceptKeysFromStr( pyzo.config.settings.autoComplete_acceptKeys) self.completer().highlighted.connect(self.updateHelp) self.setIndentUsingSpaces( pyzo.config.settings.defaultIndentUsingSpaces) self.setIndentWidth(pyzo.config.settings.defaultIndentWidth) self.setAutocompletPopupSize(*pyzo.config.view.autoComplete_popupSize) self.setAutocompleteMinChars( pyzo.config.settings.autoComplete_minChars) self.setCancelCallback(self.restoreHelp)
def setLanguage(languageName): """setLanguage(languageName) Set the language for the app. Loads qt and pyzo 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) ) pyzoTransPath = os.path.join(pyzo.pyzoDir, "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: QtWidgets.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), ("pyzo", pyzoTransPath)]: trans = QtCore.QTranslator() # Try loading both names for localeName in [localeName1, localeName2]: success = trans.load(what + "_" + localeName + ".tr", where) if success: QtWidgets.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, *args, **kwds): super().__init__(*args, **kwds) # Set font and zooming self.setFont(pyzo.config.view.fontname) self.setZoom(pyzo.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(pyzo.config.settings.autoComplete_acceptKeys, str): pyzo.config.settings.autoComplete_acceptKeys = 'Tab' # Set autocomp accept keys qtKeys = [] for key in pyzo.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( pyzo.config.settings.defaultIndentUsingSpaces) self.setIndentWidth(pyzo.config.settings.defaultIndentWidth) self.setAutocompletPopupSize(*pyzo.config.view.autoComplete_popupSize)
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): QtWidgets.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, engine): super().__init__() self._engine = engine layout = QtWidgets.QVBoxLayout(self) add_button = QtWidgets.QPushButton("Add") del_button = QtWidgets.QPushButton("Delete") self._view = QtWidgets.QListView() layout.addWidget(self._view) layout2 = QtWidgets.QHBoxLayout() layout2.addWidget(add_button) layout2.addWidget(del_button) layout.addLayout(layout2) self._model = QtCore.QStringListModel() self._view.setModel(self._model) self._model.setStringList(self._engine.registeredDocumentations()) add_button.clicked.connect(self.add_doc) del_button.clicked.connect(self.del_doc)
def helpOnText(self, pos): hw = pyzo.toolManager.getTool("pyzointeractivehelp") if not hw: return name = self.textCursor().selectedText().strip() if name == "": cursor = self.cursorForPosition( pos - self.mapToGlobal(QtCore.QPoint(0, 0))) line = cursor.block().text() limit = cursor.positionInBlock() while limit < len(line) and (line[limit].isalnum() or line[limit] in (".", "_")): limit += 1 cursor.movePosition(cursor.Right) _, tokens = self.getTokensUpToCursor(cursor) nameBefore, name = parseLine_autocomplete(tokens) if nameBefore: name = "%s.%s" % (nameBefore, name) if name != "": hw.setObjectName(name, True)
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._debugmode = 0 self._dbs = DebugStack(self._toolbar) # self._toolbar.addWidget(self._shellButton) self._toolbar.addSeparator() # self._toolbar.addWidget(self._dbc) -> delayed, see addContextMenu() self._condahelp = CondaHelper(self) # widget layout layout = QtGui.QVBoxLayout() layout.setSpacing(0) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self._toolbar) layout.addWidget(self._stack, 0) layout.addWidget(self._condahelp, 0) self.setLayout(layout) # make callbacks self._stack.currentChanged.connect(self.onCurrentChanged) # Make shared history (shared among shells) if PythonHistory is None: self.sharedHistory = None else: self.sharedHistory = PythonHistory('shellhistory.py') self.showCondaHelper()
def __init__(self, *args, **kwds): super().__init__(*args, **kwds) # Set style/theme try: self.setStyle( pyzo.themes[pyzo.config.settings.theme.lower()]['data']) except Exception as err: print("Could not load theme: " + str(err)) # Set font and zooming self.setFont(pyzo.config.view.fontname) self.setZoom(pyzo.config.view.zoom) self.setShowWhitespace(pyzo.config.view.showWhitespace) self.setHighlightMatchingBracket( pyzo.config.view.highlightMatchingBracket) # 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 = [] self.setAutoCompletionAcceptKeysFromStr( pyzo.config.settings.autoComplete_acceptKeys) self.completer().highlighted.connect(self.updateHelp) self.setIndentUsingSpaces( pyzo.config.settings.defaultIndentUsingSpaces) self.setIndentWidth(pyzo.config.settings.defaultIndentWidth) self.setAutocompletPopupSize(*pyzo.config.view.autoComplete_popupSize) self.setCancelCallback(self.restoreHelp)
def __init__(self, parent): QtWidgets.QWidget.__init__(self, parent) # create toolbar self._toolbar = QtWidgets.QToolBar(self) self._toolbar.setMaximumHeight(26) self._toolbar.setIconSize(QtCore.QSize(16, 16)) # create stack self._stack = QtWidgets.QStackedWidget(self) # Populate toolbar self._shellButton = ShellControl(self._toolbar, self._stack) self._debugmode = 0 self._dbs = DebugStack(self._toolbar) # self._toolbar.addWidget(self._shellButton) self._toolbar.addSeparator() # self._toolbar.addWidget(self._dbc) -> delayed, see addContextMenu() self._interpreterhelp = InterpreterHelper(self) # widget layout layout = QtWidgets.QVBoxLayout() layout.setSpacing(0) # set margins margin = pyzo.config.view.widgetMargin layout.setContentsMargins(margin, margin, margin, margin) layout.addWidget(self._toolbar) layout.addWidget(self._stack, 0) layout.addWidget(self._interpreterhelp, 0) self.setLayout(layout) # make callbacks self._stack.currentChanged.connect(self.onCurrentChanged) self.showInterpreterHelper()
def contextMenuEvent(self, event): """ contextMenuEvent(event) Show the context menu. """ QtWidgets.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 __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)
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 pyzo.shells.currentShellChanged.connect(self.onCurrentShellChanged) pyzo.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 = pyzo.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 = pyzo.shells.getCurrentShell() if not shell: self._variables = [] self.haveNewData.emit() def onCurrentShellStateChanged(self): """ onCurrentShellStateChanged() Do a request for information! """ shell = pyzo.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): QtWidgets.QWidget.__init__(self, parent) # Make sure there is a configuration entry for this tool # The pyzo tool manager makes sure that there is an entry in # config.tools before the tool is instantiated. toolId = self.__class__.__name__.lower() self._config = pyzo.config.tools[toolId] if not hasattr(self._config, 'hideTypes'): self._config.hideTypes = [] # <kludge 2> # configuring the typeTranslation dictionary if not hasattr(self._config, 'typeTranslation'): # to prevent the exception to be raised, one could init to : # {"method": "function", "function": "function", "type": "type", "private": "private", "module": "module"} self._config.typeTranslation = {} # Defaults self._config.typeTranslation['method'] = 'function' self._config.typeTranslation['builtin_function_or_method'] = 'function' # <kludge 2> # Create tool button self._up = QtWidgets.QToolButton(self) style = QtWidgets.qApp.style() self._up.setIcon(style.standardIcon(style.SP_ArrowLeft)) self._up.setIconSize(QtCore.QSize(16, 16)) # Create "path" line edit self._line = QtWidgets.QLineEdit(self) self._line.setReadOnly(True) self._line.setStyleSheet("QLineEdit { background:#ddd; }") self._line.setFocusPolicy(QtCore.Qt.NoFocus) # Create options menu self._options = QtWidgets.QToolButton(self) self._options.setIcon(pyzo.icons.filter) self._options.setIconSize(QtCore.QSize(16, 16)) self._options.setPopupMode(self._options.InstantPopup) self._options.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon) # self._options._menu = QtWidgets.QMenu() self._options.setMenu(self._options._menu) self.onOptionsPress() # create menu now # Create tree self._tree = WorkspaceTree(self) # Create message for when tree is empty self._initText = QtWidgets.QLabel( pyzo.translate( "pyzoWorkspace", """Lists the variables in the current shell's namespace. Currently, there are none. Some of them may be hidden because of the filters you configured.""" ), self) self._initText.setVisible(False) self._initText.setWordWrap(True) # Set layout layout = QtWidgets.QHBoxLayout() layout.addWidget(self._up, 0) layout.addWidget(self._line, 1) layout.addWidget(self._options, 0) # mainLayout = QtWidgets.QVBoxLayout(self) mainLayout.addLayout(layout, 0) mainLayout.addWidget(self._initText, 1) mainLayout.addWidget(self._tree, 2) mainLayout.setSpacing(2) # set margins margin = pyzo.config.view.widgetMargin mainLayout.setContentsMargins(margin, margin, margin, margin) self.setLayout(mainLayout) # Bind events self._up.pressed.connect(self._tree._proxy.goUp) self._options.pressed.connect(self.onOptionsPress) self._options._menu.triggered.connect(self.onOptionMenuTiggered)
def __init__(self, parent): QtWidgets.QWidget.__init__(self, parent) # Create text field, checkbox, and button self._text = QtWidgets.QLineEdit(self) self._printBut = QtWidgets.QPushButton("Print", self) style = QtWidgets.qApp.style() self._backBut = QtWidgets.QToolButton(self) self._backBut.setIcon(style.standardIcon(style.SP_ArrowLeft)) self._backBut.setIconSize(QtCore.QSize(16, 16)) self._backBut.setPopupMode(self._backBut.DelayedPopup) self._backBut.setMenu( PyzoInteractiveHelpHistoryMenu("Backward menu", self, False)) self._forwBut = QtWidgets.QToolButton(self) self._forwBut.setIcon(style.standardIcon(style.SP_ArrowRight)) self._forwBut.setIconSize(QtCore.QSize(16, 16)) self._forwBut.setPopupMode(self._forwBut.DelayedPopup) self._forwBut.setMenu( PyzoInteractiveHelpHistoryMenu("Forward menu", self, True)) # Create options button self._options = QtWidgets.QToolButton(self) self._options.setIcon(pyzo.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 = QtWidgets.QMenu() self._options.setMenu(self._options._menu) # Create browser self._browser = QtWidgets.QTextBrowser(self) self._browser_text = initText self._browser.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self._browser.customContextMenuRequested.connect(self.showMenu) # Create two sizers self._sizer1 = QtWidgets.QVBoxLayout(self) self._sizer2 = QtWidgets.QHBoxLayout() # Put the elements together self._sizer2.addWidget(self._backBut, 1) self._sizer2.addWidget(self._forwBut, 2) self._sizer2.addWidget(self._text, 4) self._sizer2.addWidget(self._printBut, 0) self._sizer2.addWidget(self._options, 3) # self._sizer1.addLayout(self._sizer2, 0) self._sizer1.addWidget(self._browser, 1) # self._sizer1.setSpacing(2) # set margins margin = pyzo.config.view.widgetMargin self._sizer1.setContentsMargins(margin, margin, margin, margin) self.setLayout(self._sizer1) # Set config toolId = self.__class__.__name__.lower() self._config = config = pyzo.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._backBut.clicked.connect(self.goBack) self._forwBut.clicked.connect(self.goForward) # self._options.pressed.connect(self.onOptionsPress) self._options._menu.triggered.connect(self.onOptionMenuTiggered) # Start self._history = [] self._histindex = 0 self.setText() # Set default text self.onOptionsPress() # Fill menu
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 CompactTabBar(QtWidgets.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): QtWidgets.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 = QtWidgets.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(QtWidgets.QTabBar.tabText(self, i)) tabData = TabData(name) QtWidgets.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 mousePressEvent(self, event): if event.button() == QtCore.Qt.MiddleButton: i = self.tabAt(event.pos()) if i >= 0: self.parent().tabCloseRequested.emit(i) return super().mousePressEvent(event) 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): QtWidgets.QTabBar.tabInserted(self, i) # Is called when a tab is inserted # Get given name and store name = str(QtWidgets.QTabBar.tabText(self, i)) tabData = TabData(name) QtWidgets.QTabBar.setTabData(self, i, tabData) # Update self.alignTabs() def tabRemoved(self, i): QtWidgets.QTabBar.tabRemoved(self, i) # Update self.alignTabs() def resizeEvent(self, event): QtWidgets.QTabBar.resizeEvent(self, event) self.alignTabs() def showEvent(self, event): QtWidgets.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 QtWidgets.QTabBar.setTabText(self, i, name) # Done return itemReduced
def mimeData(self, indexes): mimeData = QtCore.QMimeData() mimeData.setData("text/plain", self.plainText(indexes)) return mimeData
class PyzoEditor(BaseTextCtrl): # called when dirty changed or filename changed, etc somethingChanged = QtCore.Signal() def __init__(self, parent, **kwds): super().__init__(parent, showLineNumbers=True, **kwds) # Init filename and name self._filename = '' self._name = '<TMP>' # View settings self.setShowWhitespace(pyzo.config.view.showWhitespace) #TODO: self.setViewWrapSymbols(view.showWrapSymbols) self.setShowLineEndings(pyzo.config.view.showLineEndings) self.setShowIndentationGuides(pyzo.config.view.showIndentationGuides) # self.setWrap(bool(pyzo.config.view.wrap)) self.setHighlightCurrentLine(pyzo.config.view.highlightCurrentLine) self.setLongLineIndicatorPosition(pyzo.config.view.edgeColumn) self.setHighlightMatchingBracket( pyzo.config.view.highlightMatchingBracket) #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 = pyzo.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))) ## Properties @property def name(self): return self._name @property def filename(self): return self._filename @property def lineEndings(self): """ Line-endings style of this file. Setter accepts machine-readable (e.g. '\r') and human-readable (e.g. 'CR') input """ return self._lineEndings @lineEndings.setter def lineEndings(self, value): if value in ('\r', '\n', '\r\n'): self._lineEndings = value return try: self._lineEndings = {'CR': '\r', 'LF': '\n', 'CRLF': '\r\n'}[value] except KeyError: raise ValueError('Invalid line endings style %r' % value) @property def lineEndingsHumanReadable(self): """ Current line-endings style, human readable (e.g. 'CR') """ return {'\r': 'CR', '\n': 'LF', '\r\n': 'CRLF'}[self.lineEndings] @property def encoding(self): """ Encoding used to convert the text of this file to bytes. """ return self._encoding @encoding.setter def encoding(self, value): # Test given value, correct name if it exists try: c = codecs.lookup(value) value = c.name except Exception: value = codecs.lookup('UTF-8').name # Store self._encoding = value ## def justifyText(self): """ Overloaded version of justifyText to make it use our configurable justificationwidth. """ super().justifyText(pyzo.config.settings.justificationWidth) def showRunCursor(self, cursor): """ Momentarily highlight a piece of code to show that this is being executed """ extraSelection = QtWidgets.QTextEdit.ExtraSelection() extraSelection.cursor = cursor extraSelection.format.setBackground(QtCore.Qt.gray) self.setExtraSelections([extraSelection]) self._showRunCursorTimer.singleShot( 200, lambda: self.setExtraSelections([])) def id(self): """ Get an id of this editor. This is the filename, or for tmp files, the name. """ if self._filename: return self._filename else: return self._name def focusInEvent(self, event): """ Test whether the file has been changed 'behind our back' """ # Act normally to the focus event BaseTextCtrl.focusInEvent(self, event) # Test file change self.testWhetherFileWasChanged() def testWhetherFileWasChanged(self): """ testWhetherFileWasChanged() Test to see whether the file was changed outside our backs, and let the user decide what to do. Returns True if it was changed. """ # get the path path = self._filename if not os.path.isfile(path): # file is deleted from the outside return # test the modification time... mtime = os.path.getmtime(path) if mtime != self._modifyTime: # ask user dlg = QtWidgets.QMessageBox(self) dlg.setWindowTitle('File was changed') dlg.setText("File has been modified outside of the editor:\n" + self._filename) dlg.setInformativeText("Do you want to reload?") t = dlg.addButton("Reload", QtWidgets.QMessageBox.AcceptRole) #0 dlg.addButton("Keep this version", QtWidgets.QMessageBox.RejectRole) #1 dlg.setDefaultButton(t) # whatever the result, we will reset the modified time self._modifyTime = os.path.getmtime(path) # get result and act result = dlg.exec_() if result == QtWidgets.QMessageBox.AcceptRole: self.reload() else: pass # when cancelled or explicitly said, do nothing # Return that indeed the file was changes return True def _onModificationChanged(self, changed): """Handler for the modificationChanged signal. Emit somethingChanged for the editorStack to update the modification notice.""" self.somethingChanged.emit() def _onModified(self): pyzo.parser.parseThis(self) def dragMoveEvent(self, event): """ Otherwise cursor can get stuck. https://bitbucket.org/iep-project/iep/issue/252 https://qt-project.org/forums/viewthread/3180 """ if event.mimeData().hasUrls(): event.acceptProposedAction() else: BaseTextCtrl.dropEvent(self, event) def dropEvent(self, event): """ Drop files in the list. """ if event.mimeData().hasUrls(): # file: let the editorstack do the work. pyzo.editors.dropEvent(event) else: # text: act normal BaseTextCtrl.dropEvent(self, event) def showEvent(self, event=None): """ Capture show event to change title. """ # Act normally if event: BaseTextCtrl.showEvent(self, event) # Make parser update pyzo.parser.parseThis(self) def setTitleInMainWindow(self): """ set the title text in the main window to show filename. """ # compose title name, path = self._name, self._filename if path: pyzo.main.setMainTitle(path) else: pyzo.main.setMainTitle(name) def save(self, filename=None): """ Save the file. No checking is done. """ # get filename if filename is None: filename = self._filename if not filename: raise ValueError("No filename specified, and no filename known.") # Test whether it was changed without us knowing. If so, dont save now. if self.testWhetherFileWasChanged(): return # Get text and remember where we are text = self.toPlainText() cursor = self.textCursor() linenr = cursor.blockNumber() + 1 index = cursor.positionInBlock() scroll = self.verticalScrollBar().value() # Convert line endings (optionally remove trailing whitespace if pyzo.config.settings.removeTrailingWhitespaceWhenSaving: lines = [line.rstrip() for line in text.split('\n')] if lines[-1]: lines.append('') # Ensure the file ends in an empty line text = self.lineEndings.join(lines) self.setPlainText(text) # Go back to where we were cursor = self.textCursor() cursor.movePosition(cursor.Start) # move to begin of the document cursor.movePosition(cursor.NextBlock, n=linenr - 1) # n blocks down index = min(index, cursor.block().length() - 1) cursor.movePosition(cursor.Right, n=index) # n chars right self.setTextCursor(cursor) self.verticalScrollBar().setValue(scroll) else: text = text.replace('\n', self.lineEndings) # Make bytes bb = text.encode(self.encoding) # Store f = open(filename, 'wb') try: f.write(bb) finally: f.close() # Update stats self._filename = normalizePath(filename) self._name = os.path.split(self._filename)[1] self.document().setModified(False) self._modifyTime = os.path.getmtime(self._filename) # update title (in case of a rename) self.setTitleInMainWindow() # allow item to update its texts (no need: onModifiedChanged does this) #self.somethingChanged.emit() def reload(self): """ Reload text using the self._filename. We do not have a load method; we first try to load the file and only when we succeed create an editor to show it in... This method is only for reloading in case the file was changed outside of the editor. """ # We can only load if the filename is known if not self._filename: return filename = self._filename # Remember where we are cursor = self.textCursor() linenr = cursor.blockNumber() + 1 # Load file (as bytes) with open(filename, 'rb') as f: bb = f.read() # Convert to text text = bb.decode('UTF-8') # Process line endings (before setting the text) self.lineEndings = determineLineEnding(text) # Set text self.setPlainText(text) self.document().setModified(False) # Go where we were (approximately) self.gotoLine(linenr) def deleteLines(self): cursor = self.textCursor() # Find start and end of selection start = cursor.selectionStart() end = cursor.selectionEnd() # Expand selection: from start of first block to start of next block cursor.setPosition(start) cursor.movePosition(cursor.StartOfBlock) cursor.setPosition(end, cursor.KeepAnchor) cursor.movePosition(cursor.NextBlock, cursor.KeepAnchor) cursor.removeSelectedText() def duplicateLines(self): cursor = self.textCursor() # Find start and end of selection start = cursor.selectionStart() end = cursor.selectionEnd() # Expand selection: from start of first block to start of next block cursor.setPosition(start) cursor.movePosition(cursor.StartOfBlock) cursor.setPosition(end, cursor.KeepAnchor) cursor.movePosition(cursor.NextBlock, cursor.KeepAnchor) text = cursor.selectedText() cursor.setPosition(start) cursor.movePosition(cursor.StartOfBlock) cursor.insertText(text) def commentCode(self): """ Comment the lines that are currently selected """ indents = [] def getIndent(cursor): text = cursor.block().text().rstrip() if text: indents.append(len(text) - len(text.lstrip())) def commentBlock(cursor): cursor.setPosition(cursor.block().position() + minindent) cursor.insertText('# ') self.doForSelectedBlocks(getIndent) minindent = min(indents) if indents else 0 self.doForSelectedBlocks(commentBlock) def uncommentCode(self): """ Uncomment the lines that are currently selected """ #TODO: this should not be applied to lines that are part of a multi-line string #Define the uncomment function to be applied to all blocks def uncommentBlock(cursor): """ Find the first # on the line; if there is just whitespace before it, remove the # and if it is followed by a space remove the space, too """ text = cursor.block().text() commentStart = text.find('#') if commentStart == -1: return #No comment on this line if text[:commentStart].strip() != '': return #Text before the # #Move the cursor to the beginning of the comment cursor.setPosition(cursor.block().position() + commentStart) cursor.deleteChar() if text[commentStart:].startswith('# '): cursor.deleteChar() #Apply this function to all blocks self.doForSelectedBlocks(uncommentBlock) def gotoDef(self): """ Goto the definition for the word under the cursor """ # Get name of object to go to cursor = self.textCursor() if not cursor.hasSelection(): cursor.select(cursor.WordUnderCursor) word = cursor.selection().toPlainText() # Send the open command to the shell s = pyzo.shells.getCurrentShell() if s is not None: if word and word.isidentifier(): s.executeCommand('open %s\n' % word) else: s.write('Invalid identifier %r\n' % word) ## Introspection processing methods def processCallTip(self, cto): """ Processes a calltip request using a CallTipObject instance. """ # Try using buffer first if cto.tryUsingBuffer(): return # Try obtaining calltip from the source sig = pyzo.parser.getFictiveSignature(cto.name, self, True) if sig: # Done cto.finish(sig) else: # Try the shell shell = pyzo.shells.getCurrentShell() if shell: shell.processCallTip(cto) def processAutoComp(self, aco): """ Processes an autocomp request using an AutoCompObject instance. """ # Try using buffer first if aco.tryUsingBuffer(): return # Init name to poll by remote process (can be changed!) nameForShell = aco.name # Get normal fictive namespace fictiveNS = pyzo.parser.getFictiveNameSpace(self) fictiveNS = set(fictiveNS) # Add names if not aco.name: # "root" names aco.addNames(fictiveNS) # imports importNames, importLines = pyzo.parser.getFictiveImports(self) aco.addNames(importNames) else: # Prepare list of class names to check out classNames = [aco.name] handleSelf = True # Unroll supers while classNames: className = classNames.pop(0) if not className: continue if handleSelf or (className in fictiveNS): # Only the self list (only first iter) fictiveClass = pyzo.parser.getFictiveClass( className, self, handleSelf) handleSelf = False if fictiveClass: aco.addNames(fictiveClass.members) classNames.extend(fictiveClass.supers) else: nameForShell = className break # If there's a shell, let it finish the autocompletion shell = pyzo.shells.getCurrentShell() if shell: aco.name = nameForShell # might be the same or a base class shell.processAutoComp(aco) else: # Otherwise we finish it ourselves aco.finish()
class CommandHistory(QtCore.QObject): """Keep track of a (global) history of commands. This kinda assumes Python commands, but should be easy enough to generalize. """ max_commands = 2000 command_added = QtCore.Signal(str) command_removed = QtCore.Signal(int) commands_reset = QtCore.Signal() def __init__(self, fname): super().__init__() self._commands = [] self._fname = fname self._last_date = datetime.date(2000, 1, 1) self._load() def _load(self): """Load commands from file.""" if not self._fname: return assert not self._commands try: filename = os.path.join(pyzo.appDataDir, self._fname) if not os.path.isfile(filename): with open(filename, "wb"): pass # Load lines and add to commands lines = open(filename, "r", encoding="utf-8").read().splitlines() self._commands.extend( [line.rstrip() for line in lines[-self.max_commands :]] ) # Resolve last date for c in reversed(lines): if c.startswith("# ==== "): try: c = c.split("====")[1].strip() self._last_date = datetime.datetime.strptime( c, "%Y-%m-%d" ).date() break except Exception: pass except Exception as e: print("An error occurred while loading the history: " + str(e)) def save(self): """Save the commands to disk.""" if not self._fname: return filename = os.path.join(pyzo.appDataDir, self._fname) try: with open(filename, "wt", encoding="utf-8") as f: f.write("\n".join(self._commands)) except Exception: print("Could not save command history") def get_commands(self): """Get a list of all commands (latest last).""" return self._commands.copy() def append(self, command): """Add a command to the list.""" command = command.rstrip() if not command: return # Add date? today = datetime.date.today() if today > self._last_date: self._last_date = today self._commands.append("# ==== " + today.strftime("%Y-%m-%d")) self.command_added.emit(self._commands[-1]) # Clear it try: index = self._commands.index(command) except ValueError: pass else: self._commands.pop(index) self.command_removed.emit(index) # Append self._commands.append(command) self.command_added.emit(self._commands[-1]) # Reset? if len(self._commands) > self.max_commands: self._commands[: self.max_commands // 2] = [] self.commands_reset.emit() def pop(self, index): """Remove a command by index.""" self._commands.pop(index) self.command_removed.emit(index) def find_starting_with(self, firstpart, n=1): """Find the nth (1-based) command that starts with firstpart, or None.""" count = 0 for c in reversed(self._commands): if c.startswith(firstpart): count += 1 if count >= n: return c return None def find_all(self, needle): """Find all commands that contain the given text. In order of being used. """ commands = [] for c in reversed(self._commands): if needle in c: commands.append(c) return commands
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(pyzo.pyzoDir, 'tools') toolDir2 = os.path.join(pyzo.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('pyzoFileBrowser.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('pyzo.tools.' + 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('pyzo.tools.' + 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, splitWith=None): """ 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(pyzo.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(pyzo.config.tools, toolId): pyzo.config.tools[toolId] = ssdf.new() # Create dock widget and add in the main window dock = ToolDockWidget(pyzo.main, self) dock.setTool(toolId, name, toolClass) if splitWith and splitWith in self._activeTools: otherDock = self._activeTools[splitWith] pyzo.main.splitDockWidget(otherDock, dock, QtCore.Qt.Horizontal) else: pyzo.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 postEventWithCallback(self, callback, *args): self.queue.put((callback, args)) QtWidgets.qApp.postEvent(self, QtCore.QEvent(QtCore.QEvent.User))
def __init__(self, parent): QtWidgets.QFrame.__init__(self, parent) # Init config toolId = self.__class__.__name__.lower() self._config = pyzo.config.tools[toolId] if not hasattr(self._config, 'zoomFactor'): self._config.zoomFactor = 1.0 if not hasattr(self._config, 'bookMarks'): self._config.bookMarks = [] for item in default_bookmarks: if item not in self._config.bookMarks: self._config.bookMarks.append(item) # Get style object (for icons) style = QtWidgets.QApplication.style() # Create some buttons self._back = QtWidgets.QToolButton(self) self._back.setIcon(style.standardIcon(style.SP_ArrowBack)) self._back.setIconSize(QtCore.QSize(16, 16)) # self._forward = QtWidgets.QToolButton(self) self._forward.setIcon(style.standardIcon(style.SP_ArrowForward)) self._forward.setIconSize(QtCore.QSize(16, 16)) # Create address bar #self._address = QtWidgets.QLineEdit(self) self._address = QtWidgets.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 if imported_qtwebkit: self._view = QtWebKit.QWebView(self) else: 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 = QtWidgets.QVBoxLayout(self) self._sizer2 = QtWidgets.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 Installer(QtWidgets.QDialog): lineFromStdOut = QtCore.Signal(str) def __init__(self, parent=None): QtWidgets.QDialog.__init__(self, parent) self.setWindowTitle('Install miniconda') self.setModal(True) self.resize(500, 500) text = translate('bootstrapconda', 'This will download and install miniconda on your computer.') self._label = QtWidgets.QLabel(text, self) self._scipystack = QtWidgets.QCheckBox(translate('bootstrapconda', 'Also install scientific packages'), self) self._scipystack.setChecked(True) self._path = QtWidgets.QLineEdit(default_conda_dir, self) self._progress = QtWidgets.QProgressBar(self) self._outputLine = QtWidgets.QLabel(self) self._output = QtWidgets.QPlainTextEdit(self) self._output.setReadOnly(True) self._button = QtWidgets.QPushButton('Install', self) self._outputLine.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Fixed) vbox = QtWidgets.QVBoxLayout(self) self.setLayout(vbox) vbox.addWidget(self._label, 0) vbox.addWidget(self._path, 0) vbox.addWidget(self._scipystack, 0) vbox.addWidget(self._progress, 0) vbox.addWidget(self._outputLine, 0) vbox.addWidget(self._output, 1) vbox.addWidget(self._button, 0) self._button.clicked.connect(self.go) self.addOutput(translate('bootstrapconda', 'Waiting to start installation.\n')) self._progress.setVisible(False) self.lineFromStdOut.connect(self.setStatus) def setStatus(self, line): self._outputLine.setText(line) def addOutput(self, text): #self._output.setPlainText(self._output.toPlainText() + '\n' + text) cursor = self._output.textCursor() cursor.movePosition(cursor.End, cursor.MoveAnchor) cursor.insertText(text) cursor.movePosition(cursor.End, cursor.MoveAnchor) self._output.setTextCursor(cursor) self._output.ensureCursorVisible() def addStatus(self, line): self.addOutput('\n' + line) self.setStatus(line) def go(self): # Check if we can install try: self._conda_dir = self._path.text() if not os.path.isabs(self._conda_dir): raise ValueError('Given installation path must be absolute.') if os.path.exists(self._conda_dir): raise ValueError('The given installation path already exists.') except Exception as err: self.addOutput('\nCould not install:\n' + str(err)) return ok = False try: # Disable user input, get ready for installation self._progress.setVisible(True) self._button.clicked.disconnect() self._button.setEnabled(False) self._scipystack.setEnabled(False) self._path.setEnabled(False) if not os.path.exists(self._conda_dir): self.addStatus('Downloading installer ... ') self._progress.setMaximum(100) self.download() self.addStatus('Done downloading installer.') self.make_done() self.addStatus('Installing (this can take a minute) ... ') self._progress.setMaximum(0) ret = self.install() self.addStatus(('Failed' if ret else 'Done') + ' installing.') self.make_done() self.post_install() if self._scipystack.isChecked(): self.addStatus('Installing scientific packages ... ') self._progress.setMaximum(0) ret = self.install_scipy() self.addStatus('Done installing scientific packages') self.make_done() self.addStatus('Verifying ... ') self._progress.setMaximum(100) ret = self.verify() if ret: self.addOutput('Error\n' + ret) self.addStatus('Verification Failed!') else: self.addOutput('Done verifying') self.addStatus('Ready to go!') self.make_done() ok = True except Exception as err: self.addStatus('Installation failed ...') self.addOutput('\n\nException!\n' + str(err)) if not ok: self.addOutput('\n\nWe recommend installing miniconda or anaconda, ') self.addOutput('and making Pyzo aware if it via the shell configuration.') else: self.addOutput('\n\nYou can install additional packages by running "conda install" in the shell.') # Wrap up, allow user to exit self._progress.hide() self._button.setEnabled(True) self._button.setText('Close') self._button.clicked.connect(self.close) def make_done(self): self._progress.setMaximum(100) self._progress.setValue(100) etime = time.time() + 0.2 while time.time() < etime: time.sleep(0.01) QtWidgets.qApp.processEvents() def download(self): # Installer already downloaded? if os.path.isfile(miniconda_path): self.addOutput('Already downloaded.') return # os.remove(miniconda_path) # Get url key key = '' if sys.platform.startswith('win'): key = 'win' elif sys.platform.startswith('darwin'): key = 'osx' elif sys.platform.startswith('linux'): key = 'linux' key += '64' if is_64bit() else '32' # Get url if key not in links: raise RuntimeError('Cannot download miniconda for this platform.') url = base_url + links[key] _fetch_file(url, miniconda_path, self._progress) def install(self): dest = self._conda_dir # Clear dir assert not os.path.isdir(dest), 'Miniconda dir already exists' assert ' ' not in dest, 'miniconda dest path must not contain spaces' if sys.platform.startswith('win'): return self._run_process([miniconda_path, '/S', '/D=%s' % dest]) else: os.chmod(miniconda_path, os.stat(miniconda_path).st_mode | stat.S_IEXEC) return self._run_process([miniconda_path, '-b', '-p', dest]) def post_install(self): exe = py_exe(self._conda_dir) # Add Pyzo channel cmd = [exe, '-m', 'conda', 'config', '--system', '--add', 'channels', 'pyzo'] subprocess.check_call(cmd, shell=sys.platform.startswith('win')) self.addStatus('Added Pyzo channel to conda env') # Add to pyzo shell config first = None if pyzo.config.shellConfigs2 and pyzo.config.shellConfigs2[0]['exe'] == exe: pass else: s = pyzo.ssdf.new() s.name = 'Py3-conda' s.exe = exe s.gui='PyQt4' pyzo.config.shellConfigs2.insert(0, s) pyzo.saveConfig() self.addStatus('Prepended new env to Pyzo shell configs.') def install_scipy(self): packages = ['numpy', 'scipy', 'pandas', 'matplotlib', 'sympy', #'scikit-image', 'scikit-learn', 'pyopengl', # 'visvis', 'imageio', 'tornado', 'pyqt', #'ipython', 'jupyter', #'requests', 'pygments','pytest', ] exe = py_exe(self._conda_dir) cmd = [exe, '-m', 'conda', 'install', '--yes'] + packages return self._run_process(cmd) def _run_process(self, cmd): """ Run command in a separate process, catch stdout, show lines in the output label. On fail, show all output in output text. """ p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=sys.platform.startswith('win')) catcher = StreamCatcher(p.stdout, self.lineFromStdOut) while p.poll() is None: time.sleep(0.01) QtWidgets.qApp.processEvents() catcher.join() if p.poll(): self.addOutput(catcher.output()) return p.poll() def verify(self): self._progress.setValue(1) if not os.path.isdir(self._conda_dir): return 'Conda dir not created.' self._progress.setValue(11) exe = py_exe(self._conda_dir) if not os.path.isfile(exe): return 'Conda dir does not have Python exe' self._progress.setValue(21) try: ver = subprocess.check_output([exe, '-c', 'import sys; print(sys.version)']) except Exception as err: return 'Error getting Python version: ' + str(err) self._progress.setValue(31) if ver.decode() < '3.4': return 'Expected Python version 3.4 or higher' self._progress.setValue(41) try: ver = subprocess.check_output([exe, '-c', 'import conda; print(conda.__version__)']) except Exception as err: return 'Error calling Python exe: ' + str(err) self._progress.setValue(51) if ver.decode() < '3.16': return 'Expected Conda version 3.16 or higher' # Smooth toward 100% for i in range(self._progress.value(), 100, 5): time.sleep(0.05) self._progress.setValue(i) QtWidgets.qApp.processEvents()
class WebView(QtWidgets.QTextBrowser): """ Inherit the webview class to implement zooming using the mouse wheel. """ loadStarted = QtCore.Signal() loadFinished = QtCore.Signal(bool) def __init__(self, parent): QtWidgets.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 & QtWidgets.qApp.keyboardModifiers(): self.parent().wheelEvent(event) else: QtWidgets.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